From e017da8f2c5e7707b46440f04eeb6bf5127e7a18 Mon Sep 17 00:00:00 2001 From: yxxhero Date: Sun, 25 Jan 2026 15:06:50 +0800 Subject: [PATCH] Refactor kubedog tracking to use helm.TemplateRelease for better resource detection Changes: - Replace helm.Template with helm.TemplateRelease to get release manifest - Parse manifest to detect all resources (Deployment, StatefulSet, DaemonSet, Job, Pod, ReplicaSet) - Track all detected resources with kubedog instead of hardcoded deployment name - Add parseResourceKindAndName helper to extract resource type and name - Add isTrackableResourceKind helper to filter supported resource types - Remove assumption that resource name equals release name This approach is more elegant and follows helmfile conventions by using helm.TemplateRelease instead of manual manifest parsing. Resolves: #660 Signed-off-by: yxxhero --- cmd/apply.go | 5 +- cmd/sync.go | 3 + go.mod | 12 ++ go.sum | 25 +++- pkg/app/app_sync_test.go | 1 - pkg/app/app_test.go | 15 +++ pkg/app/config.go | 3 + pkg/config/apply.go | 21 +++ pkg/config/sync.go | 21 +++ pkg/kubedog/options.go | 68 ++++++++++ pkg/kubedog/tracker.go | 206 +++++++++++++++++++++++++++++ pkg/kubedog/tracker_test.go | 81 ++++++++++++ pkg/state/helmx.go | 257 ++++++++++++++++++++++++++++++++++++ pkg/state/state.go | 23 +++- 14 files changed, 733 insertions(+), 8 deletions(-) create mode 100644 pkg/kubedog/options.go create mode 100644 pkg/kubedog/tracker.go create mode 100644 pkg/kubedog/tracker_test.go diff --git a/cmd/apply.go b/cmd/apply.go index c47ce83d..1d5cf198 100644 --- a/cmd/apply.go +++ b/cmd/apply.go @@ -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 } diff --git a/cmd/sync.go b/cmd/sync.go index 6cc31975..151aefda 100644 --- a/cmd/sync.go +++ b/cmd/sync.go @@ -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 } diff --git a/go.mod b/go.mod index 90725ac2..cc30bef0 100644 --- a/go.mod +++ b/go.mod @@ -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 @@ -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 diff --git a/go.sum b/go.sum index fe099225..79e11df9 100644 --- a/go.sum +++ b/go.sum @@ -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= diff --git a/pkg/app/app_sync_test.go b/pkg/app/app_sync_test.go index 948e8c06..200e8f5c 100644 --- a/pkg/app/app_sync_test.go +++ b/pkg/app/app_sync_test.go @@ -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, diff --git a/pkg/app/app_test.go b/pkg/app/app_test.go index fce32b84..7f829b4d 100644 --- a/pkg/app/app_test.go +++ b/pkg/app/app_test.go @@ -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 diff --git a/pkg/app/config.go b/pkg/app/config.go index ea22c3d5..75711957 100644 --- a/pkg/app/config.go +++ b/pkg/app/config.go @@ -119,6 +119,9 @@ type SyncConfigProvider interface { IncludeTransitiveNeeds() bool SyncReleaseLabels() bool + TrackMode() string + TrackTimeout() int + TrackLogs() bool DAGConfig diff --git a/pkg/config/apply.go b/pkg/config/apply.go index cfdd3451..f7ad2d6d 100644 --- a/pkg/config/apply.go +++ b/pkg/config/apply.go @@ -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 +} diff --git a/pkg/config/sync.go b/pkg/config/sync.go index b59701f1..f4ba77a6 100644 --- a/pkg/config/sync.go +++ b/pkg/config/sync.go @@ -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 +} diff --git a/pkg/kubedog/options.go b/pkg/kubedog/options.go new file mode 100644 index 00000000..0812f171 --- /dev/null +++ b/pkg/kubedog/options.go @@ -0,0 +1,68 @@ +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 +} + +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 +} diff --git a/pkg/kubedog/tracker.go b/pkg/kubedog/tracker.go new file mode 100644 index 00000000..12754bc5 --- /dev/null +++ b/pkg/kubedog/tracker.go @@ -0,0 +1,206 @@ +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 + } + + t.logger.Infof("Tracking %d resources with kubedog", len(resources)) + + specs := multitrack.MultitrackSpecs{} + + for _, res := range resources { + 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) Close() error { + return nil +} diff --git a/pkg/kubedog/tracker_test.go b/pkg/kubedog/tracker_test.go new file mode 100644 index 00000000..b272c8ce --- /dev/null +++ b/pkg/kubedog/tracker_test.go @@ -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) +} diff --git a/pkg/state/helmx.go b/pkg/state/helmx.go index 1513c617..d6c68e61 100644 --- a/pkg/state/helmx.go +++ b/pkg/state/helmx.go @@ -1,17 +1,20 @@ 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/helmexec" + "github.com/helmfile/helmfile/pkg/kubedog" "github.com/helmfile/helmfile/pkg/remote" ) @@ -165,6 +168,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") @@ -176,7 +183,21 @@ func (st *HelmState) appendWaitForJobsFlags(flags []string, release *ReleaseSpec return flags } +func (st *HelmState) shouldUseKubeDog(release *ReleaseSpec, ops *SyncOpts) bool { + if release.TrackMode != "" && release.TrackMode == "kubedog" { + return true + } + if release.TrackMode == "" && st.HelmDefaults.TrackMode == "kubedog" { + return true + } + return false +} + 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,239 @@ 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 {func (st *HelmState) trackWithKubeDog(ctx context.Context, release *ReleaseSpec, helm helmexec.Interface) error { + timeout := 5 * time.Minute + trackLogs := false + + if release.TrackTimeout != nil { + timeout = time.Duration(*release.TrackTimeout) * time.Second + } + + if release.TrackLogs != nil && *release.TrackLogs { + trackLogs = true + } + + st.logger.Infof("Tracking release %s resources with kubedog", release.Name) + + trackOpts := &kubedog.TrackOptions{ + Timeout: timeout, + Logs: trackLogs, + LogsSince: 10 * time.Minute, + Namespace: release.Namespace, + KubeContext: st.HelmDefaults.KubeContext, + Kubeconfig: "", + } + + tracker, err := kubedog.NewTracker(&kubedog.TrackerConfig{ + Logger: st.logger, + Namespace: release.Namespace, + KubeContext: st.HelmDefaults.KubeContext, + Kubeconfig: "", + 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(ctx context.Context, release *ReleaseSpec, helm helmexec.Interface) ([]*kubedog.ResourceSpec, error) { + st.logger.Debugf("Getting resources for release %s", release.Name) + + values := "" + if release.Values != nil { + for _, v := range release.Values { + values = fmt.Sprintf("%s --set-json=%s", values, v) + } + } + + flags := []string{ + "--show-only-manifest", + } + if release.KubeContext != "" { + flags = append(flags, "--kube-context", release.KubeContext) + } + + manifest, err := helm.TemplateRelease(release.Name, release.ChartPathOrName(), append(flags, values...)) + if err != nil { + return nil, fmt.Errorf("failed to get release manifest: %w", err) + } + + st.logger.Debugf("Got release manifest for %s", release.Name) + + lines := strings.Split(manifest, "\n") +") + var resources []*kubedog.ResourceSpec + + for _, line := range lines { + line = strings.TrimSpace(line) + if line == "" || strings.HasPrefix(line, "#") || strings.HasPrefix(line, "---") { + continue + } + + kind, name, err := st.parseResourceKindAndName(line) + if err != nil { + st.logger.Debugf("Could not parse resource line: %s: %v", line, err) + continue + } + + if kind != "" && name != "" && st.isTrackableResourceKind(kind) { + resources = append(resources, &kubedog.ResourceSpec{ + Name: name, + Namespace: release.Namespace, + Kind: kind, + }) + st.logger.Debugf("Found trackable resource: %s/%s (kind: %s)", release.Namespace, name, kind) + } + } + + if len(resources) == 0 { + st.logger.Debugf("No trackable resources found in manifest") + } + + return resources, nil +} + +func (st *HelmState) parseResourceKindAndName(line string) (string, string, error) { + parts := strings.Fields(line) + if len(parts) < 3 { + return "", "", nil + } + + kind := strings.TrimSpace(parts[0]) + name := strings.TrimSpace(parts[1]) + + return kind, name, nil +} + +func (st *HelmState) isTrackableResourceKind(kind string) bool { + trackableKinds := map[string]bool{ + "Deployment": true, + "StatefulSet": true, + "DaemonSet": true, + "Job": true, + "Pod": true, + "ReplicaSet": true, + } + + return trackableKinds[kind] +} + + +func (st *HelmState) getReleaseResources(ctx context.Context, release *ReleaseSpec, helm helmexec.Interface) ([]*kubedog.ResourceSpec, error) { + st.logger.Debugf("Getting resources for release %s", release.Name) + + values := "" + if release.Values != nil { + for _, v := range release.Values { + values = fmt.Sprintf("%s --set-json=%s", values, v) + } + } + + flags := []string{ + "--show-only-manifest", + } + if release.KubeContext != "" { + flags = append(flags, "--kube-context", release.KubeContext) + } + + manifest, err := helm.TemplateRelease(release.Name, release.ChartPathOrName(), append(flags, values...)) + if err != nil { + return nil, fmt.Errorf("failed to get release manifest: %w", err) + } + + st.logger.Debugf("Got release manifest for %s", release.Name) + + lines := strings.Split(manifest, "\n") + var resources []*kubedog.ResourceSpec + + for _, line := range lines { + line = strings.TrimSpace(line) + if line == "" || strings.HasPrefix(line, "#") || strings.HasPrefix(line, "---") { + continue + } + + kind, name, err := st.parseResourceKindAndName(line) + if err != nil { + st.logger.Debugf("Could not parse resource line: %s: %v", line, err) + continue + } + + if kind != "" && name != "" && st.isTrackableResourceKind(kind) { + resources = append(resources, &kubedog.ResourceSpec{ + Name: name, + Namespace: release.Namespace, + Kind: kind, + }) + st.logger.Debugf("Found trackable resource: %s/%s (kind: %s)", release.Namespace, name, kind) + } + } + + if len(resources) == 0 { + st.logger.Debugf("No trackable resources found in manifest") + } + + return resources, nil +} + + + if release.TrackLogs != nil && *release.TrackLogs { + trackLogs = true + } + + trackOpts := &kubedog.TrackOptions{ + Timeout: timeout, + Logs: trackLogs, + LogsSince: 10 * time.Minute, + Namespace: release.Namespace, + KubeContext: st.HelmDefaults.KubeContext, + Kubeconfig: "", + } + + tracker, err := kubedog.NewTracker(&kubedog.TrackerConfig{ + Logger: st.logger, + Namespace: release.Namespace, + KubeContext: st.HelmDefaults.KubeContext, + Kubeconfig: "", + TrackOptions: trackOpts, + }) + if err != nil { + return fmt.Errorf("failed to create kubedog tracker: %w", err) + } + defer tracker.Close() + + st.logger.Infof("Tracking release %s with kubedog", release.Name) + + resources := []*kubedog.ResourceSpec{ + { + Name: release.Name, + Namespace: release.Namespace, + Kind: "deployment", + }, + } + + if err := tracker.TrackResources(ctx, resources); err != nil { + return fmt.Errorf("kubedog tracking failed for release %s: %w", release.Name, err) + } + + return nil +} diff --git a/pkg/state/state.go b/pkg/state/state.go index ae699477..f21ee8ec 100644 --- a/pkg/state/state.go +++ b/pkg/state/state.go @@ -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 @@ -440,8 +442,14 @@ 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"` } func (r *Inherits) UnmarshalYAML(unmarshal func(any) error) error { @@ -1102,6 +1110,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 +4681,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 +4716,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()