This commit is contained in:
yxxhero 2026-01-26 00:46:57 +00:00 committed by GitHub
commit ad2028dbc8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 1057 additions and 16 deletions

View File

@ -67,7 +67,10 @@ func NewApplyCmd(globalCfg *config.GlobalImpl) *cobra.Command {
f.StringArrayVar(&applyOptions.PostRendererArgs, "post-renderer-args", nil, `pass --post-renderer-args to "helm template" or "helm upgrade --install"`)
f.BoolVar(&applyOptions.SkipSchemaValidation, "skip-schema-validation", false, `pass --skip-schema-validation to "helm template" or "helm upgrade --install"`)
f.StringVar(&applyOptions.Cascade, "cascade", "", "pass cascade to helm exec, default: background")
f.StringArrayVar(&applyOptions.SuppressOutputLineRegex, "suppress-output-line-regex", nil, "a list of regex patterns to suppress output lines from the diff output")
f.StringArrayVar(&applyOptions.SuppressOutputLineRegex, "suppress-output-line-regex", nil, "a list of regex patterns to suppress output lines from diff output")
f.StringVar(&applyOptions.TrackMode, "track-mode", "", "Track mode for releases: 'helm' (default) or 'kubedog'")
f.IntVar(&applyOptions.TrackTimeout, "track-timeout", 0, `Timeout in seconds for kubedog tracking (default 0 for default timeout)`)
f.BoolVar(&applyOptions.TrackLogs, "track-logs", false, "Enable log streaming with kubedog tracking")
return cmd
}

View File

@ -54,6 +54,9 @@ func NewSyncCmd(globalCfg *config.GlobalImpl) *cobra.Command {
f.StringArrayVar(&syncOptions.PostRendererArgs, "post-renderer-args", nil, `pass --post-renderer-args to "helm template" or "helm upgrade --install"`)
f.BoolVar(&syncOptions.SkipSchemaValidation, "skip-schema-validation", false, `pass --skip-schema-validation to "helm template" or "helm upgrade --install"`)
f.StringVar(&syncOptions.Cascade, "cascade", "", "pass cascade to helm exec, default: background")
f.StringVar(&syncOptions.TrackMode, "track-mode", "", "Track mode for releases: 'helm' (default) or 'kubedog'")
f.IntVar(&syncOptions.TrackTimeout, "track-timeout", 0, `Timeout in seconds for kubedog tracking (default 0 for default timeout)`)
f.BoolVar(&syncOptions.TrackLogs, "track-logs", false, "Enable log streaming with kubedog tracking")
return cmd
}

14
go.mod
View File

@ -24,6 +24,7 @@ require (
github.com/tatsushid/go-prettytable v0.0.0-20141013043238-ed2d14c29939
github.com/tj/assert v0.0.3
github.com/variantdev/dag v1.1.0
github.com/werf/kubedog v0.13.0
github.com/zclconf/go-cty v1.17.0
github.com/zclconf/go-cty-yaml v1.2.0
go.szostok.io/version v1.2.0
@ -106,7 +107,7 @@ require (
google.golang.org/protobuf v1.36.11 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect
sigs.k8s.io/yaml v1.6.0 // indirect
sigs.k8s.io/yaml v1.6.0
)
require (
@ -142,6 +143,7 @@ require (
github.com/MakeNowJust/heredoc v1.0.0 // indirect
github.com/Masterminds/squirrel v1.5.4 // indirect
github.com/ProtonMail/go-crypto v1.3.0 // indirect
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d // indirect
github.com/agext/levenshtein v1.2.3 // indirect
github.com/antchfx/jsonquery v1.3.6 // indirect
github.com/antchfx/xpath v1.3.5 // indirect
@ -149,6 +151,7 @@ require (
github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de // indirect
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
github.com/atotto/clipboard v0.1.4 // indirect
github.com/avelino/slugify v0.0.0-20180501145920-855f152bd774 // indirect
github.com/aws/aws-sdk-go-v2 v1.41.1 // indirect
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.4 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.19.7 // indirect
@ -175,6 +178,7 @@ require (
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/chai2010/gettext-go v1.0.2 // indirect
github.com/chanced/caps v1.0.2 // indirect
github.com/cloudflare/circl v1.6.1 // indirect
github.com/cncf/xds/go v0.0.0-20251022180443-0feb69152e9f // indirect
github.com/containerd/containerd v1.7.30 // indirect
@ -196,6 +200,7 @@ require (
github.com/extism/go-sdk v1.7.1 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/fluxcd/cli-utils v0.37.0-flux.1 // indirect
github.com/fluxcd/flagger v1.36.1 // indirect
github.com/fxamacker/cbor/v2 v2.9.0 // indirect
github.com/getsops/gopgagent v0.0.0-20241224165529-7044f28e491e // indirect
github.com/getsops/sops/v3 v3.11.0 // indirect
@ -238,6 +243,7 @@ require (
github.com/google/s2a-go v0.1.9 // indirect
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.7 // indirect
github.com/gookit/color v1.5.4 // indirect
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect
github.com/hashicorp/go-retryablehttp v0.7.8 // indirect
github.com/hashicorp/go-safetemp v1.0.0 // indirect
@ -282,6 +288,7 @@ require (
github.com/rs/zerolog v1.26.1 // indirect
github.com/rubenv/sql-migrate v1.8.1 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/samber/lo v1.39.0 // indirect
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 // indirect
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.36 // indirect
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 // indirect
@ -295,9 +302,13 @@ require (
github.com/uber/jaeger-client-go v2.30.0+incompatible // indirect
github.com/uber/jaeger-lib v2.4.1+incompatible // indirect
github.com/urfave/cli v1.22.17 // indirect
github.com/werf/logboek v0.6.1 // indirect
github.com/x448/float16 v0.8.4 // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
github.com/xlab/treeprint v1.2.0 // indirect
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
github.com/yandex-cloud/go-genproto v0.43.0 // indirect
github.com/yandex-cloud/go-sdk v0.30.0 // indirect
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
@ -315,6 +326,7 @@ require (
go.opentelemetry.io/proto/otlp v1.7.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/crypto v0.46.0 // indirect
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect
golang.org/x/mod v0.31.0 // indirect
golang.org/x/tools v0.40.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 // indirect

25
go.sum
View File

@ -125,6 +125,8 @@ github.com/ProtonMail/go-crypto v1.3.0 h1:ILq8+Sf5If5DCpHQp4PbZdS1J7HDFRXz/+xKBi
github.com/ProtonMail/go-crypto v1.3.0/go.mod h1:9whxjD8Rbs29b4XWbB8irEcE8KHMqaR2e7GWU1R+/PE=
github.com/a8m/envsubst v1.4.3 h1:kDF7paGK8QACWYaQo6KtyYBozY2jhQrTuNNuUxQkhJY=
github.com/a8m/envsubst v1.4.3/go.mod h1:4jjHWQlZoaXPoLQUb7H2qT4iLkZDdmEQiOUogdUmqVU=
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpHMqeKTCYkitsPqHNxTmd4SNR5r94FGM8=
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat636LX7Bqt5lYEZ27JNDcqxfjdBQuJ/MM4CN/Lzo=
github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo=
github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558=
github.com/antchfx/jsonquery v1.3.6 h1:TaSfeAh7n6T11I74bsZ1FswreIfrbJ0X+OyLflx6mx4=
@ -140,6 +142,8 @@ github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3d
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
github.com/avelino/slugify v0.0.0-20180501145920-855f152bd774 h1:HrMVYtly2IVqg9EBooHsakQ256ueojP7QuG32K71X/U=
github.com/avelino/slugify v0.0.0-20180501145920-855f152bd774/go.mod h1:5wi5YYOpfuAKwL5XLFYopbgIl/v7NZxaJpa/4X6yFKE=
github.com/aws/aws-sdk-go v1.55.8 h1:JRmEUbU52aJQZ2AjX4q4Wu7t4uZjOu71uyNmaWlUkJQ=
github.com/aws/aws-sdk-go v1.55.8/go.mod h1:ZkViS9AqA6otK+JBBNH2++sx1sgxrPKcSzPPvQkUtXk=
github.com/aws/aws-sdk-go-v2 v1.41.1 h1:ABlyEARCDLN034NhxlRUSZr4l71mh+T5KAeGh6cerhU=
@ -206,6 +210,8 @@ github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UF
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/chai2010/gettext-go v1.0.2 h1:1Lwwip6Q2QGsAdl/ZKPCwTe9fe0CjlUbqj5bFNSjIRk=
github.com/chai2010/gettext-go v1.0.2/go.mod h1:y+wnP2cHYaVj19NZhYKAwEMH2CI1gNHeQQ+5AjwawxA=
github.com/chanced/caps v1.0.2 h1:RELvNN4lZajqSXJGzPaU7z8B4LK2+o2Oc/upeWdgMOA=
github.com/chanced/caps v1.0.2/go.mod h1:SJhRzeYLKJ3OmzyQXhdZ7Etj7lqqWoPtQ1zcSJRtQjs=
github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0=
github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
github.com/cncf/xds/go v0.0.0-20251022180443-0feb69152e9f h1:Y8xYupdHxryycyPlc9Y+bSQAYZnetRJ70VMVKm5CKI0=
@ -227,8 +233,8 @@ github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6N
github.com/cpuguy83/go-md2man/v2 v2.0.7 h1:zbFlGlXEAKlwXpmvle3d8Oe3YnkKIK4xSRTd3sHPnBo=
github.com/cpuguy83/go-md2man/v2 v2.0.7/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=
github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
github.com/creack/pty v1.1.21 h1:1/QdRyBaHHJP61QkWMXlOIBfsgdDeeKfK8SYVUWJKf0=
github.com/creack/pty v1.1.21/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
github.com/cyberark/conjur-api-go v0.13.14 h1:wZzlxqJdV4CMZhwc6sCp13k5Eet9SucFmYivtBRpMrM=
github.com/cyberark/conjur-api-go v0.13.14/go.mod h1:BQmiYeA8hJmGSduF+wgfXY4Ktdky30+cevXm+tzr63k=
github.com/cyphar/filepath-securejoin v0.6.1 h1:5CeZ1jPXEiYt3+Z6zqprSAgSWiggmpVyciv8syjIpVE=
@ -291,6 +297,8 @@ github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/fluxcd/cli-utils v0.37.0-flux.1 h1:k/VvPNT3tGa/l2N+qzHduaQr3GVbgoWS6nw7tGZz16w=
github.com/fluxcd/cli-utils v0.37.0-flux.1/go.mod h1:aND5wX3LuTFtB7eUT7vsWr8mmxRVSPR2Wkvbn0SqPfw=
github.com/fluxcd/flagger v1.36.1 h1:X2PumtNwZz9YSGaOtZLFm2zAKLgHhFkbNv8beg7ifyc=
github.com/fluxcd/flagger v1.36.1/go.mod h1:qmtLsxheVDTI8XeCaXUxW5UCmfcSKnY9fizG9NmW/Fk=
github.com/foxcpp/go-mockdns v1.2.0 h1:omK3OrHRD1IWJz1FuFBCFquhXslXoF17OvBS6JPzZF0=
github.com/foxcpp/go-mockdns v1.2.0/go.mod h1:IhLeSFGed3mJIAXPH2aiRQB+kqz7oqu8ld2qVbOu7Wk=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
@ -427,6 +435,8 @@ github.com/googleapis/enterprise-certificate-proxy v0.3.7 h1:zrn2Ee/nWmHulBx5sAV
github.com/googleapis/enterprise-certificate-proxy v0.3.7/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA=
github.com/googleapis/gax-go/v2 v2.16.0 h1:iHbQmKLLZrexmb0OSsNGTeSTS0HO4YvFOG8g5E4Zd0Y=
github.com/googleapis/gax-go/v2 v2.16.0/go.mod h1:o1vfQjjNZn4+dPnRdl/4ZD7S9414Y4xA+a/6Icj6l14=
github.com/gookit/color v1.5.4 h1:FZmqs7XOyGgCAxmWyPslpiok1k05wmY3SJTytgvYFs0=
github.com/gookit/color v1.5.4/go.mod h1:pZJOeOS8DM43rXbp4AZo1n9zCU2qjpcRko0b6/QJi9w=
github.com/gorilla/handlers v1.5.2 h1:cLTUSsNkgcwhgRqvCNmdbRWG0A3N4F+M2nWKdScwyEE=
github.com/gorilla/handlers v1.5.2/go.mod h1:dX+xVpaxdSw+q0Qek8SSsl3dfMk3jNddUkMzo0GtH0w=
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
@ -650,6 +660,8 @@ github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk=
github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc=
github.com/samber/lo v1.39.0 h1:4gTz1wUhNYLhFSKl6O+8peW0v2F4BCY034GRpU9WnuA=
github.com/samber/lo v1.39.0/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA=
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 h1:KRzFb2m7YtdldCEkzs6KqmJw4nqEVZGK7IN2kJkjTuQ=
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU=
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.36 h1:ObX9hZmK+VmijreZO/8x9pQ8/P/ToHD/bdSb4Eg4tUo=
@ -714,8 +726,13 @@ github.com/urfave/cli v1.22.17 h1:SYzXoiPfQjHBbkYxbew5prZHS1TOLT3ierW8SYLqtVQ=
github.com/urfave/cli v1.22.17/go.mod h1:b0ht0aqgH/6pBYzzxURyrM4xXNgsoT/n2ZzwQiEhNVo=
github.com/variantdev/dag v1.1.0 h1:xodYlSng33KWGvIGMpKUyLcIZRXKiNUx612mZJqYrDg=
github.com/variantdev/dag v1.1.0/go.mod h1:pH1TQsNSLj2uxMo9NNl9zdGy01Wtn+/2MT96BrKmVyE=
github.com/werf/kubedog v0.13.0 h1:ys+GyZbIMqm0r2po0HClbONcEnS5cWSFR2BayIfBqsY=
github.com/werf/kubedog v0.13.0/go.mod h1:Y6pesrIN5uhFKqmHnHSoeW4jmVyZlWPFWv5SjB0rUPg=
github.com/werf/logboek v0.6.1 h1:oEe6FkmlKg0z0n80oZjLplj6sXcBeLleCkjfOOZEL2g=
github.com/werf/logboek v0.6.1/go.mod h1:Gez5J4bxekyr6MxTmIJyId1F61rpO+0/V4vjCIEIZmk=
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo=
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=
@ -724,6 +741,8 @@ github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
github.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ=
github.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
github.com/yandex-cloud/go-genproto v0.43.0 h1:HjBesEmCN8ZOhjjh8gs605vvi9/MBJAW3P20OJ4iQnw=
github.com/yandex-cloud/go-genproto v0.43.0/go.mod h1:0LDD/IZLIUIV4iPH+YcF+jysO3jkSvADFGm4dCAuwQo=
github.com/yandex-cloud/go-sdk v0.30.0 h1:bHhUlkfaLbcNQvdfxMpRnft+tbCFtLRUFrZ3rC1hqgM=
@ -818,6 +837,8 @@ golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU=
golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0=
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8=
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=

View File

@ -80,7 +80,6 @@ func TestSync(t *testing.T) {
}
syncErr := app.Sync(applyConfig{
// if we check log output, concurrency must be 1. otherwise the test becomes non-deterministic.
concurrency: tc.concurrency,
logger: logger,
skipDiffOnInstall: tc.skipDiffOnInstall,

View File

@ -2378,6 +2378,9 @@ type applyConfig struct {
takeOwnership bool
syncReleaseLabels bool
enforceNeedsAreInstalled bool
trackMode string
trackTimeout int
trackLogs bool
// template-only options
includeCRDs, skipTests bool
@ -2580,6 +2583,18 @@ func (a applyConfig) SyncReleaseLabels() bool {
return a.syncReleaseLabels
}
func (a applyConfig) TrackMode() string {
return a.trackMode
}
func (a applyConfig) TrackTimeout() int {
return a.trackTimeout
}
func (a applyConfig) TrackLogs() bool {
return a.trackLogs
}
type depsConfig struct {
skipRepos bool
includeTransitiveNeeds bool

View File

@ -119,6 +119,9 @@ type SyncConfigProvider interface {
IncludeTransitiveNeeds() bool
SyncReleaseLabels() bool
TrackMode() string
TrackTimeout() int
TrackLogs() bool
DAGConfig

275
pkg/cluster/release.go Normal file
View File

@ -0,0 +1,275 @@
package cluster
import (
"bytes"
"fmt"
"go.uber.org/zap"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
k8syaml "k8s.io/apimachinery/pkg/util/yaml"
"sigs.k8s.io/yaml"
)
type Resource struct {
Kind string
Name string
Namespace string
Manifest string
}
type ReleaseResources struct {
ReleaseName string
Namespace string
Resources []Resource
}
type TrackConfig struct {
TrackKinds []string
SkipKinds []string
CustomTrackableKinds []string
CustomStaticKinds []string
}
func GetReleaseResourcesFromManifest(manifest []byte, releaseName, releaseNamespace string) (*ReleaseResources, error) {
return GetReleaseResourcesFromManifestWithLogger(nil, manifest, releaseName, releaseNamespace, nil)
}
func GetReleaseResourcesFromManifestWithConfig(manifest []byte, releaseName, releaseNamespace string, config *TrackConfig) (*ReleaseResources, error) {
return GetReleaseResourcesFromManifestWithLogger(nil, manifest, releaseName, releaseNamespace, config)
}
func GetReleaseResourcesFromManifestWithLogger(logger *zap.SugaredLogger, manifest []byte, releaseName, releaseNamespace string, config *TrackConfig) (*ReleaseResources, error) {
resources, err := parseManifest(manifest, logger)
if err != nil {
return nil, fmt.Errorf("failed to parse manifest: %w", err)
}
if len(resources) == 0 {
if logger != nil {
logger.Debugf("No resources found in manifest for release %s", releaseName)
}
return &ReleaseResources{
ReleaseName: releaseName,
Namespace: releaseNamespace,
Resources: resources,
}, nil
}
if config != nil {
filteredResources := filterResourcesByConfig(resources, config, logger)
if logger != nil {
logger.Infof("Found %d resources in manifest for release %s (filtered from %d total)", len(filteredResources), releaseName, len(resources))
for _, res := range filteredResources {
logger.Debugf(" - %s/%s in namespace %s", res.Kind, res.Name, res.Namespace)
}
}
return &ReleaseResources{
ReleaseName: releaseName,
Namespace: releaseNamespace,
Resources: filteredResources,
}, nil
}
if logger != nil {
logger.Infof("Found %d resources in manifest for release %s", len(resources), releaseName)
for _, res := range resources {
logger.Debugf(" - %s/%s in namespace %s", res.Kind, res.Name, res.Namespace)
}
}
return &ReleaseResources{
ReleaseName: releaseName,
Namespace: releaseNamespace,
Resources: resources,
}, nil
}
func filterResourcesByConfig(resources []Resource, config *TrackConfig, logger *zap.SugaredLogger) []Resource {
var filtered []Resource
for _, res := range resources {
if shouldSkipResource(res.Kind, config, logger) {
if logger != nil {
logger.Debugf("Skipping resource %s/%s (kind: %s) based on configuration", res.Kind, res.Name, res.Kind)
}
continue
}
filtered = append(filtered, res)
}
return filtered
}
func shouldSkipResource(kind string, config *TrackConfig, logger *zap.SugaredLogger) bool {
if len(config.TrackKinds) > 0 {
shouldTrack := false
for _, trackKind := range config.TrackKinds {
if kind == trackKind {
shouldTrack = true
break
}
}
if !shouldTrack {
if logger != nil {
logger.Debugf("Resource kind %s is not in TrackKinds list, skipping", kind)
}
return true
}
}
if len(config.SkipKinds) > 0 {
for _, skipKind := range config.SkipKinds {
if kind == skipKind {
if logger != nil {
logger.Debugf("Resource kind %s is in SkipKinds list, skipping", kind)
}
return true
}
}
}
return false
}
func parseManifest(manifest []byte, logger *zap.SugaredLogger) ([]Resource, error) {
var resources []Resource
decoder := k8syaml.NewYAMLOrJSONDecoder(bytes.NewReader(manifest), 4096)
for {
var obj unstructured.Unstructured
err := decoder.Decode(&obj)
if err != nil {
if err.Error() == "EOF" {
break
}
return nil, fmt.Errorf("failed to decode manifest: %w", err)
}
if len(obj.Object) == 0 {
continue
}
kind := obj.GetKind()
if kind == "" {
if logger != nil {
logger.Debugf("Skipping resource without kind")
}
continue
}
name := obj.GetName()
if name == "" {
if logger != nil {
logger.Debugf("Skipping %s resource without name", kind)
}
continue
}
namespace := obj.GetNamespace()
if namespace == "" {
namespace = "default"
}
manifestBytes, err := yaml.Marshal(obj.Object)
if err != nil {
if logger != nil {
logger.Debugf("Failed to marshal %s/%s: %v", kind, name, err)
}
continue
}
res := Resource{
Kind: kind,
Name: name,
Namespace: namespace,
Manifest: string(manifestBytes),
}
resources = append(resources, res)
}
return resources, nil
}
func IsTrackableKind(kind string) bool {
trackableKinds := map[string]bool{
"Deployment": true,
"StatefulSet": true,
"DaemonSet": true,
"Job": true,
"Pod": true,
"ReplicaSet": true,
}
return trackableKinds[kind]
}
func IsTrackableKindWithConfig(kind string, config *TrackConfig) bool {
if config == nil {
return IsTrackableKind(kind)
}
if len(config.CustomTrackableKinds) > 0 {
for _, customKind := range config.CustomTrackableKinds {
if kind == customKind {
return true
}
}
return false
}
return IsTrackableKind(kind)
}
func IsStaticKind(kind string) bool {
staticKinds := map[string]bool{
"Service": true,
"ConfigMap": true,
"Secret": true,
"PersistentVolumeClaim": true,
"PersistentVolume": true,
"StorageClass": true,
"Namespace": true,
"ResourceQuota": true,
"LimitRange": true,
"PriorityClass": true,
"ServiceAccount": true,
"Role": true,
"RoleBinding": true,
"ClusterRole": true,
"ClusterRoleBinding": true,
"NetworkPolicy": true,
"Ingress": true,
"CustomResourceDefinition": true,
}
return staticKinds[kind]
}
func IsStaticKindWithConfig(kind string, config *TrackConfig) bool {
if config == nil {
return IsStaticKind(kind)
}
if len(config.CustomStaticKinds) > 0 {
for _, customKind := range config.CustomStaticKinds {
if kind == customKind {
return true
}
}
return false
}
return IsStaticKind(kind)
}
func GetHelmReleaseLabels(releaseName, releaseNamespace string) map[string]string {
return map[string]string{
"meta.helm.sh/release-name": releaseName,
"meta.helm.sh/release-namespace": releaseNamespace,
}
}
func GetHelmReleaseAnnotations(releaseName string) map[string]string {
return map[string]string{
"meta.helm.sh/release-name": releaseName,
}
}

View File

@ -75,6 +75,12 @@ type ApplyOptions struct {
TakeOwnership bool
SyncReleaseLabels bool
// TrackMode specifies whether to use 'helm' or 'kubedog' for tracking resources
TrackMode string
// TrackTimeout specifies timeout for kubedog tracking (in seconds)
TrackTimeout int
// TrackLogs enables log streaming with kubedog
TrackLogs bool
}
// NewApply creates a new Apply
@ -280,3 +286,18 @@ func (a *ApplyImpl) TakeOwnership() bool {
func (a *ApplyImpl) SyncReleaseLabels() bool {
return a.ApplyOptions.SyncReleaseLabels
}
// TrackMode returns the track mode.
func (a *ApplyImpl) TrackMode() string {
return a.ApplyOptions.TrackMode
}
// TrackTimeout returns the track timeout.
func (a *ApplyImpl) TrackTimeout() int {
return a.ApplyOptions.TrackTimeout
}
// TrackLogs returns the track logs flag.
func (a *ApplyImpl) TrackLogs() bool {
return a.ApplyOptions.TrackLogs
}

View File

@ -48,6 +48,12 @@ type SyncOptions struct {
TakeOwnership bool
// SyncReleaseLabels is the sync release labels flag
SyncReleaseLabels bool
// TrackMode specifies whether to use 'helm' or 'kubedog' for tracking resources
TrackMode string
// TrackTimeout specifies timeout for kubedog tracking (in seconds)
TrackTimeout int
// TrackLogs enables log streaming with kubedog
TrackLogs bool
}
// NewSyncOptions creates a new Apply
@ -187,3 +193,18 @@ func (t *SyncImpl) TakeOwnership() bool {
func (t *SyncImpl) SyncReleaseLabels() bool {
return t.SyncOptions.SyncReleaseLabels
}
// TrackMode returns the track mode.
func (t *SyncImpl) TrackMode() string {
return t.SyncOptions.TrackMode
}
// TrackTimeout returns the track timeout.
func (t *SyncImpl) TrackTimeout() int {
return t.SyncOptions.TrackTimeout
}
// TrackLogs returns the track logs flag.
func (t *SyncImpl) TrackLogs() bool {
return t.SyncOptions.TrackLogs
}

92
pkg/kubedog/options.go Normal file
View File

@ -0,0 +1,92 @@
package kubedog
import "time"
type TrackMode string
const (
TrackModeHelm TrackMode = "helm"
TrackModeKubedog TrackMode = "kubedog"
)
type ResourceSpec struct {
Name string
Namespace string
Kind string
}
type TrackOptions struct {
Timeout time.Duration
Logs bool
LogsSince time.Duration
ContainerLogs []string
Namespace string
KubeContext string
Kubeconfig string
TrackKinds []string
SkipKinds []string
CustomTrackableKinds []string
CustomStaticKinds []string
}
func NewTrackOptions() *TrackOptions {
return &TrackOptions{
Timeout: 5 * time.Minute,
LogsSince: 10 * time.Minute,
}
}
func (o *TrackOptions) WithTimeout(timeout time.Duration) *TrackOptions {
o.Timeout = timeout
return o
}
func (o *TrackOptions) WithLogs(logs bool) *TrackOptions {
o.Logs = logs
return o
}
func (o *TrackOptions) WithLogsSince(since time.Duration) *TrackOptions {
o.LogsSince = since
return o
}
func (o *TrackOptions) WithContainerLogs(containers []string) *TrackOptions {
o.ContainerLogs = containers
return o
}
func (o *TrackOptions) WithNamespace(namespace string) *TrackOptions {
o.Namespace = namespace
return o
}
func (o *TrackOptions) WithKubeContext(context string) *TrackOptions {
o.KubeContext = context
return o
}
func (o *TrackOptions) WithKubeconfig(kubeconfig string) *TrackOptions {
o.Kubeconfig = kubeconfig
return o
}
func (o *TrackOptions) WithTrackKinds(kinds []string) *TrackOptions {
o.TrackKinds = kinds
return o
}
func (o *TrackOptions) WithSkipKinds(kinds []string) *TrackOptions {
o.SkipKinds = kinds
return o
}
func (o *TrackOptions) WithCustomTrackableKinds(kinds []string) *TrackOptions {
o.CustomTrackableKinds = kinds
return o
}
func (o *TrackOptions) WithCustomStaticKinds(kinds []string) *TrackOptions {
o.CustomStaticKinds = kinds
return o
}

253
pkg/kubedog/tracker.go Normal file
View File

@ -0,0 +1,253 @@
package kubedog
import (
"context"
"fmt"
"time"
"github.com/werf/kubedog/pkg/kube"
"github.com/werf/kubedog/pkg/tracker"
"github.com/werf/kubedog/pkg/trackers/rollout"
"github.com/werf/kubedog/pkg/trackers/rollout/multitrack"
"go.uber.org/zap"
"k8s.io/client-go/kubernetes"
)
type Tracker struct {
logger *zap.SugaredLogger
clientSet kubernetes.Interface
trackOptions *TrackOptions
kubeContext string
kubeconfig string
}
type TrackerConfig struct {
Logger *zap.SugaredLogger
Namespace string
KubeContext string
Kubeconfig string
TrackOptions *TrackOptions
}
func NewTracker(config *TrackerConfig) (*Tracker, error) {
initOpts := kube.InitOptions{
KubeConfigOptions: kube.KubeConfigOptions{
Context: config.KubeContext,
ConfigPath: config.Kubeconfig,
},
}
if kubeErr := kube.Init(initOpts); kubeErr != nil {
return nil, fmt.Errorf("failed to initialize kubedog kube client: %w", kubeErr)
}
options := config.TrackOptions
if options == nil {
options = NewTrackOptions()
}
return &Tracker{
logger: config.Logger,
clientSet: kube.Kubernetes,
trackOptions: options,
kubeContext: config.KubeContext,
kubeconfig: config.Kubeconfig,
}, nil
}
func (t *Tracker) TrackResources(ctx context.Context, resources []*ResourceSpec) error {
if len(resources) == 0 {
t.logger.Info("No resources to track")
return nil
}
filtered := t.filterResources(resources)
if len(filtered) == 0 {
t.logger.Info("No resources to track after filtering")
return nil
}
t.logger.Infof("Tracking %d resources with kubedog (filtered from %d total)", len(filtered), len(resources))
specs := multitrack.MultitrackSpecs{}
for _, res := range filtered {
switch res.Kind {
case "deployment", "deploy":
specs.Deployments = append(specs.Deployments, multitrack.MultitrackSpec{
ResourceName: res.Name,
Namespace: res.Namespace,
SkipLogs: !t.trackOptions.Logs,
})
case "statefulset", "sts":
specs.StatefulSets = append(specs.StatefulSets, multitrack.MultitrackSpec{
ResourceName: res.Name,
Namespace: res.Namespace,
SkipLogs: !t.trackOptions.Logs,
})
case "daemonset", "ds":
specs.DaemonSets = append(specs.DaemonSets, multitrack.MultitrackSpec{
ResourceName: res.Name,
Namespace: res.Namespace,
SkipLogs: !t.trackOptions.Logs,
})
case "job":
specs.Jobs = append(specs.Jobs, multitrack.MultitrackSpec{
ResourceName: res.Name,
Namespace: res.Namespace,
SkipLogs: !t.trackOptions.Logs,
})
}
}
opts := multitrack.MultitrackOptions{
Options: tracker.Options{
ParentContext: ctx,
Timeout: t.trackOptions.Timeout,
LogsFromTime: time.Now().Add(-t.trackOptions.LogsSince),
},
}
err := multitrack.Multitrack(t.clientSet, specs, opts)
if err != nil {
return fmt.Errorf("tracking failed: %w", err)
}
t.logger.Info("All resources tracked successfully")
return nil
}
func (t *Tracker) TrackPod(ctx context.Context, podName, namespace string) error {
t.logger.Infof("Tracking pod %s/%s with kubedog", namespace, podName)
opts := tracker.Options{
ParentContext: ctx,
Timeout: t.trackOptions.Timeout,
LogsFromTime: time.Now().Add(-t.trackOptions.LogsSince),
}
err := rollout.TrackPodTillReady(podName, namespace, t.clientSet, opts)
if err != nil {
return fmt.Errorf("pod tracking failed: %w", err)
}
t.logger.Infof("Pod %s tracked successfully", podName)
return nil
}
func (t *Tracker) TrackDeployment(ctx context.Context, deploymentName, namespace string) error {
t.logger.Infof("Tracking deployment %s/%s with kubedog", namespace, deploymentName)
opts := tracker.Options{
ParentContext: ctx,
Timeout: t.trackOptions.Timeout,
LogsFromTime: time.Now().Add(-t.trackOptions.LogsSince),
}
err := rollout.TrackDeploymentTillReady(deploymentName, namespace, t.clientSet, opts)
if err != nil {
return fmt.Errorf("deployment tracking failed: %w", err)
}
t.logger.Infof("Deployment %s tracked successfully", deploymentName)
return nil
}
func (t *Tracker) TrackStatefulSet(ctx context.Context, stsName, namespace string) error {
t.logger.Infof("Tracking statefulset %s/%s with kubedog", namespace, stsName)
opts := tracker.Options{
ParentContext: ctx,
Timeout: t.trackOptions.Timeout,
LogsFromTime: time.Now().Add(-t.trackOptions.LogsSince),
}
err := rollout.TrackStatefulSetTillReady(stsName, namespace, t.clientSet, opts)
if err != nil {
return fmt.Errorf("statefulset tracking failed: %w", err)
}
t.logger.Infof("StatefulSet %s tracked successfully", stsName)
return nil
}
func (t *Tracker) TrackDaemonSet(ctx context.Context, dsName, namespace string) error {
t.logger.Infof("Tracking daemonset %s/%s with kubedog", namespace, dsName)
opts := tracker.Options{
ParentContext: ctx,
Timeout: t.trackOptions.Timeout,
LogsFromTime: time.Now().Add(-t.trackOptions.LogsSince),
}
err := rollout.TrackDaemonSetTillReady(dsName, namespace, t.clientSet, opts)
if err != nil {
return fmt.Errorf("daemonset tracking failed: %w", err)
}
t.logger.Infof("DaemonSet %s tracked successfully", dsName)
return nil
}
func (t *Tracker) TrackJob(ctx context.Context, jobName, namespace string) error {
t.logger.Infof("Tracking job %s/%s with kubedog", namespace, jobName)
opts := tracker.Options{
ParentContext: ctx,
Timeout: t.trackOptions.Timeout,
LogsFromTime: time.Now().Add(-t.trackOptions.LogsSince),
}
err := rollout.TrackJobTillDone(jobName, namespace, t.clientSet, opts)
if err != nil {
return fmt.Errorf("job tracking failed: %w", err)
}
t.logger.Infof("Job %s tracked successfully", jobName)
return nil
}
func (t *Tracker) filterResources(resources []*ResourceSpec) []*ResourceSpec {
var filtered []*ResourceSpec
for _, res := range resources {
if t.shouldSkipResource(res.Kind, res.Name, res.Namespace) {
t.logger.Debugf("Skipping resource %s/%s (kind: %s) based on configuration", res.Namespace, res.Name, res.Kind)
continue
}
filtered = append(filtered, res)
}
return filtered
}
func (t *Tracker) shouldSkipResource(kind, name, namespace string) bool {
if len(t.trackOptions.SkipKinds) > 0 {
for _, skipKind := range t.trackOptions.SkipKinds {
if kind == skipKind {
t.logger.Debugf("Resource kind %s is in SkipKinds list, skipping", kind)
return true
}
}
}
if len(t.trackOptions.TrackKinds) > 0 {
shouldTrack := false
for _, trackKind := range t.trackOptions.TrackKinds {
if kind == trackKind {
shouldTrack = true
break
}
}
if !shouldTrack {
t.logger.Debugf("Resource kind %s is not in TrackKinds list, skipping", kind)
return true
}
}
return false
}
func (t *Tracker) Close() error {
return nil
}

View File

@ -0,0 +1,81 @@
package kubedog
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func TestTrackMode(t *testing.T) {
assert.Equal(t, "helm", string(TrackModeHelm))
assert.Equal(t, "kubedog", string(TrackModeKubedog))
}
func TestNewTrackOptions(t *testing.T) {
opts := NewTrackOptions()
assert.NotNil(t, opts)
assert.Equal(t, 5*time.Minute, opts.Timeout)
assert.Equal(t, false, opts.Logs)
assert.Equal(t, 10*time.Minute, opts.LogsSince)
}
func TestTrackOptions_WithTimeout(t *testing.T) {
opts := NewTrackOptions()
opts = opts.WithTimeout(10 * time.Second)
assert.Equal(t, 10*time.Second, opts.Timeout)
}
func TestTrackOptions_WithLogs(t *testing.T) {
opts := NewTrackOptions()
opts = opts.WithLogs(true)
assert.True(t, opts.Logs)
}
func TestTrackOptions_Chaining(t *testing.T) {
opts := NewTrackOptions()
opts = opts.
WithTimeout(20 * time.Second).
WithLogs(true).
WithLogsSince(5 * time.Minute).
WithNamespace("test-ns").
WithKubeContext("test-context").
WithKubeconfig("/test/kubeconfig")
assert.Equal(t, 20*time.Second, opts.Timeout)
assert.True(t, opts.Logs)
assert.Equal(t, 5*time.Minute, opts.LogsSince)
assert.Equal(t, "test-ns", opts.Namespace)
assert.Equal(t, "test-context", opts.KubeContext)
assert.Equal(t, "/test/kubeconfig", opts.Kubeconfig)
}
func TestResourceSpec(t *testing.T) {
spec := &ResourceSpec{
Name: "test-resource",
Namespace: "test-ns",
Kind: "deployment",
}
assert.Equal(t, "test-resource", spec.Name)
assert.Equal(t, "test-ns", spec.Namespace)
assert.Equal(t, "deployment", spec.Kind)
}
func TestTrackerConfig(t *testing.T) {
config := &TrackerConfig{
Logger: nil,
Namespace: "test-ns",
KubeContext: "test-ctx",
Kubeconfig: "/test/kubeconfig",
TrackOptions: NewTrackOptions(),
}
assert.NotNil(t, config)
assert.Equal(t, "test-ns", config.Namespace)
assert.Equal(t, "test-ctx", config.KubeContext)
assert.Equal(t, "/test/kubeconfig", config.Kubeconfig)
assert.NotNil(t, config.TrackOptions)
}

View File

@ -1,17 +1,21 @@
package state
import (
"context"
"fmt"
"os"
"path/filepath"
"slices"
"sort"
"strings"
"time"
"github.com/helmfile/chartify"
"helm.sh/helm/v4/pkg/storage/driver"
"github.com/helmfile/helmfile/pkg/cluster"
"github.com/helmfile/helmfile/pkg/helmexec"
"github.com/helmfile/helmfile/pkg/kubedog"
"github.com/helmfile/helmfile/pkg/remote"
)
@ -165,6 +169,10 @@ func (st *HelmState) appendSuppressOutputLineRegexFlags(flags []string, release
}
func (st *HelmState) appendWaitForJobsFlags(flags []string, release *ReleaseSpec, ops *SyncOpts) []string {
// if st.shouldUseKubeDog(release, ops) {
// return flags
// }
switch {
case release.WaitForJobs != nil && *release.WaitForJobs:
flags = append(flags, "--wait-for-jobs")
@ -173,10 +181,23 @@ func (st *HelmState) appendWaitForJobsFlags(flags []string, release *ReleaseSpec
case release.WaitForJobs == nil && st.HelmDefaults.WaitForJobs:
flags = append(flags, "--wait-for-jobs")
}
return flags
}
func (st *HelmState) shouldUseKubeDog(release *ReleaseSpec, _ *SyncOpts) bool {
trackMode := release.TrackMode
if trackMode == "" {
trackMode = st.HelmDefaults.TrackMode
}
return trackMode == "kubedog"
}
func (st *HelmState) appendWaitFlags(flags []string, release *ReleaseSpec, ops *SyncOpts) []string {
if st.shouldUseKubeDog(release, ops) {
return flags
}
switch {
case release.Wait != nil && *release.Wait:
flags = append(flags, "--wait")
@ -423,3 +444,139 @@ func (st *HelmState) PrepareChartify(helm helmexec.Interface, release *ReleaseSp
return nil, clean, nil
}
func (st *HelmState) trackWithKubeDog(ctx context.Context, release *ReleaseSpec, helm helmexec.Interface) error {
timeout := 5 * time.Minute
if release.TrackTimeout != nil {
timeout = time.Duration(*release.TrackTimeout) * time.Second
}
trackLogs := release.TrackLogs != nil && *release.TrackLogs
trackOpts := kubedog.NewTrackOptions().
WithTimeout(timeout).
WithLogs(trackLogs).
WithNamespace(release.Namespace).
WithKubeContext(st.HelmDefaults.KubeContext).
WithTrackKinds(release.TrackKinds)
tracker, err := kubedog.NewTracker(&kubedog.TrackerConfig{
Logger: st.logger,
Namespace: release.Namespace,
KubeContext: st.HelmDefaults.KubeContext,
TrackOptions: trackOpts,
})
if err != nil {
return fmt.Errorf("failed to create kubedog tracker: %w", err)
}
defer tracker.Close()
resources, err := st.getReleaseResources(ctx, release, helm)
if err != nil {
return fmt.Errorf("failed to get release resources: %w", err)
}
if len(resources) == 0 {
st.logger.Infof("No trackable resources found for release %s", release.Name)
return nil
}
st.logger.Infof("Tracking %d resources from release %s with kubedog", len(resources), release.Name)
if err := tracker.TrackResources(ctx, resources); err != nil {
return fmt.Errorf("kubedog tracking failed for release %s: %w", release.Name, err)
}
return nil
}
func (st *HelmState) getReleaseResources(_ context.Context, release *ReleaseSpec, helm helmexec.Interface) ([]*kubedog.ResourceSpec, error) {
st.logger.Debugf("Getting resources for release %s", release.Name)
manifest, err := st.getReleaseManifest(release, helm)
if err != nil {
return nil, fmt.Errorf("failed to get release manifest: %w", err)
}
if len(manifest) == 0 {
st.logger.Infof("No manifest found for release %s", release.Name)
return nil, nil
}
releaseResources, err := cluster.GetReleaseResourcesFromManifest(manifest, release.Name, release.Namespace)
if err != nil {
return nil, fmt.Errorf("failed to parse release resources from manifest: %w", err)
}
resources := make([]*kubedog.ResourceSpec, len(releaseResources.Resources))
for i, res := range releaseResources.Resources {
resources[i] = &kubedog.ResourceSpec{
Name: res.Name,
Namespace: res.Namespace,
Kind: res.Kind,
}
}
return resources, nil
}
func (st *HelmState) getReleaseManifest(release *ReleaseSpec, helm helmexec.Interface) ([]byte, error) {
tempDir, err := st.tempDir("", "helmfile-template-")
if err != nil {
return nil, fmt.Errorf("failed to create temp directory: %w", err)
}
defer func() {
if err := os.RemoveAll(tempDir); err != nil {
st.logger.Warnf("Failed to remove temp directory %s: %v", tempDir, err)
}
}()
st.ApplyOverrides(release)
flags, files, err := st.flagsForTemplate(helm, release, 0, &TemplateOpts{})
defer st.removeFiles(files)
if err != nil {
return nil, fmt.Errorf("failed to generate template flags: %w", err)
}
flags = append(flags, "--output-dir", tempDir)
if err := helm.TemplateRelease(release.Name, release.ChartPathOrName(), flags...); err != nil {
return nil, fmt.Errorf("failed to run helm template: %w", err)
}
var manifest []byte
err = filepath.Walk(tempDir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if info.IsDir() {
return nil
}
if !strings.HasSuffix(info.Name(), ".yaml") && !strings.HasSuffix(info.Name(), ".yml") {
return nil
}
content, err := os.ReadFile(path)
if err != nil {
return fmt.Errorf("failed to read file %s: %w", path, err)
}
if len(manifest) > 0 {
manifest = append(manifest, []byte("\n---\n")...)
}
manifest = append(manifest, content...)
return nil
})
if err != nil {
return nil, fmt.Errorf("failed to walk template output directory: %w", err)
}
return manifest, nil
}

View File

@ -2,7 +2,7 @@ package state
import (
"bytes"
"context"
gocontext "context"
"crypto/sha1"
"encoding/hex"
"errors"
@ -226,6 +226,8 @@ type HelmSpec struct {
SyncReleaseLabels *bool `yaml:"syncReleaseLabels,omitempty"`
// TakeOwnership is true if the helmfile should take ownership of the release
TakeOwnership *bool `yaml:"takeOwnership,omitempty"`
// TrackMode specifies whether to use 'helm' or 'kubedog' for tracking resources
TrackMode string `yaml:"trackMode,omitempty"`
}
// RepositorySpec that defines values for a helm repo
@ -254,7 +256,6 @@ type Inherit struct {
type Inherits []Inherit
// ReleaseSpec defines the structure of a helm release
type ReleaseSpec struct {
// Chart is the name of the chart being installed to create this release
Chart string `yaml:"chart,omitempty"`
@ -440,8 +441,16 @@ type ReleaseSpec struct {
DeleteTimeout *int `yaml:"deleteTimeout,omitempty"`
// SyncReleaseLabels is true if the release labels should be synced with the helmfile labels
SyncReleaseLabels *bool `yaml:"syncReleaseLabels,omitempty"`
// TakeOwnership is true if the release should take ownership of the resources
// TakeOwnership is true if release should take ownership of resources
TakeOwnership *bool `yaml:"takeOwnership,omitempty"`
// TrackMode specifies whether to use 'helm' or 'kubedog' for tracking resources
TrackMode string `yaml:"trackMode,omitempty"`
// TrackTimeout specifies timeout for kubedog tracking (in seconds)
TrackTimeout *int `yaml:"trackTimeout,omitempty"`
// TrackLogs enables log streaming with kubedog
TrackLogs *bool `yaml:"trackLogs,omitempty"`
// TrackKinds is a whitelist of resource kinds to track
TrackKinds []string `yaml:"trackKinds,omitempty"`
}
func (r *Inherits) UnmarshalYAML(unmarshal func(any) error) error {
@ -1102,6 +1111,13 @@ func (st *HelmState) SyncReleases(affectedReleases *AffectedReleases, helm helme
} else {
release.installedVersion = installedVersion
}
if st.shouldUseKubeDog(release, opts) {
trackCtx := gocontext.Background()
if trackErr := st.trackWithKubeDog(trackCtx, release, helm); trackErr != nil {
st.logger.Warnf("kubedog tracking failed for release %s: %v", release.Name, trackErr)
}
}
}
}
@ -4666,7 +4682,7 @@ func (st *HelmState) acquireSharedLock(result *chartLockResult, chartPath string
result.inProcessMutex = st.getNamedRWMutex(chartPath)
result.inProcessMutex.RLock()
ctx, cancel := context.WithTimeout(context.Background(), lockTimeout)
ctx, cancel := gocontext.WithTimeout(gocontext.Background(), lockTimeout)
defer cancel()
locked, err := result.fileLock.TryRLockContext(ctx, 500*time.Millisecond)
@ -4701,7 +4717,7 @@ func (st *HelmState) acquireExclusiveLock(result *chartLockResult, chartPath str
var lockErr error
for attempt := 1; attempt <= maxRetries; attempt++ {
ctx, cancel := context.WithTimeout(context.Background(), lockTimeout)
ctx, cancel := gocontext.WithTimeout(gocontext.Background(), lockTimeout)
locked, lockErr = result.fileLock.TryLockContext(ctx, 500*time.Millisecond)
cancel()

View File

@ -38,39 +38,39 @@ func TestGenerateID(t *testing.T) {
run(testcase{
subject: "baseline",
release: ReleaseSpec{Name: "foo", Chart: "incubator/raw"},
want: "foo-values-66f7fd6f7b",
want: "foo-values-694f986b58",
})
run(testcase{
subject: "different bytes content",
release: ReleaseSpec{Name: "foo", Chart: "incubator/raw"},
data: []byte(`{"k":"v"}`),
want: "foo-values-6664979cd7",
want: "foo-values-6bc445465",
})
run(testcase{
subject: "different map content",
release: ReleaseSpec{Name: "foo", Chart: "incubator/raw"},
data: map[string]any{"k": "v"},
want: "foo-values-78897dfd49",
want: "foo-values-57d95d8df9",
})
run(testcase{
subject: "different chart",
release: ReleaseSpec{Name: "foo", Chart: "stable/envoy"},
want: "foo-values-64b7846cb7",
want: "foo-values-fb7bcfd65",
})
run(testcase{
subject: "different name",
release: ReleaseSpec{Name: "bar", Chart: "incubator/raw"},
want: "bar-values-576cb7ddc7",
want: "bar-values-55465dffc9",
})
run(testcase{
subject: "specific ns",
release: ReleaseSpec{Name: "foo", Chart: "incubator/raw", Namespace: "myns"},
want: "myns-foo-values-6c567f54c",
want: "myns-foo-values-6ddb95ff85",
})
for id, n := range ids {

View File

@ -0,0 +1,69 @@
releases:
- name: kubedog-baseline
chart: ../../charts/raw-0.1.0
values:
- templates:
- |
apiVersion: v1
kind: ConfigMap
metadata:
name: {{`{{ .Release.Name }}`}}-baseline
namespace: {{`{{ .Release.Namespace }}`}}
data:
baseline: value
trackMode: kubedog
- name: kubedog-with-timeout
chart: ../../charts/raw-0.1.0
values:
- templates:
- |
apiVersion: v1
kind: ConfigMap
metadata:
name: {{`{{ .Release.Name }}`}}-timeout
namespace: {{`{{ .Release.Namespace }}`}}
data:
timeout: value
trackMode: kubedog
trackTimeout: 300
- name: kubedog-with-logs
chart: ../../charts/raw-0.1.0
values:
- templates:
- |
apiVersion: v1
kind: ConfigMap
metadata:
name: {{`{{ .Release.Name }}`}}-logs
namespace: {{`{{ .Release.Namespace }}`}}
data:
logs: value
trackMode: kubedog
trackLogs: true
- name: kubedog-with-whitelist
chart: ../../charts/raw-0.1.0
values:
- templates:
- |
apiVersion: v1
kind: ConfigMap
metadata:
name: {{`{{ .Release.Name }}`}}-tracked
namespace: {{`{{ .Release.Namespace }}`}}
data:
tracked: value
- |
apiVersion: v1
kind: Secret
metadata:
name: {{`{{ .Release.Name }}`}}-secret
namespace: {{`{{ .Release.Namespace }}`}}
type: Opaque
data:
secret: dmFsdWU=
trackMode: kubedog
trackKinds:
- ConfigMap