From 5af2bf676988122d0486b855dcc044332960d429 Mon Sep 17 00:00:00 2001 From: yxxhero Date: Fri, 29 Sep 2023 09:04:59 +0800 Subject: [PATCH] feat: add labels for k8s resources Signed-off-by: yxxhero --- cmd/apply.go | 1 + cmd/sync.go | 1 + docs/index.md | 5 ++ go.mod | 6 ++ go.sum | 22 ++++++ pkg/app/app.go | 8 ++- pkg/app/app_list_test.go | 56 +++++++-------- pkg/app/app_test.go | 15 ++-- pkg/app/config.go | 5 ++ pkg/config/apply.go | 6 ++ pkg/config/sync.go | 6 ++ pkg/state/helmx.go | 37 ++++++++++ pkg/state/helmx_test.go | 51 ++++++++++++++ pkg/state/selector_test.go | 16 ++--- pkg/state/state.go | 69 +++++++++++-------- pkg/state/temp_test.go | 12 ++-- .../output.yaml | 9 +++ .../output.yaml | 6 +- 18 files changed, 251 insertions(+), 80 deletions(-) diff --git a/cmd/apply.go b/cmd/apply.go index 8cbe4ad9..6b5a23f4 100644 --- a/cmd/apply.go +++ b/cmd/apply.go @@ -56,6 +56,7 @@ func NewApplyCmd(globalCfg *config.GlobalImpl) *cobra.Command { f.BoolVar(&applyOptions.NoHooks, "no-hooks", false, "do not diff changes made by hooks.") f.BoolVar(&applyOptions.HideNotes, "hide-notes", false, "add --hide-notes flag to helm") f.BoolVar(&applyOptions.TakeOwnership, "take-ownership", false, "add --take-ownership flag to helm") + f.BoolVar(&applyOptions.SyncReleaseLabels, "sync-release-labels", false, "sync release labels to the target release") f.BoolVar(&applyOptions.SuppressDiff, "suppress-diff", false, "suppress diff in the output. Usable in new installs") f.BoolVar(&applyOptions.Wait, "wait", false, `Override helmDefaults.wait setting "helm upgrade --install --wait"`) f.BoolVar(&applyOptions.WaitForJobs, "wait-for-jobs", false, `Override helmDefaults.waitForJobs setting "helm upgrade --install --wait-for-jobs"`) diff --git a/cmd/sync.go b/cmd/sync.go index 418bf32c..1d8ed45f 100644 --- a/cmd/sync.go +++ b/cmd/sync.go @@ -43,6 +43,7 @@ func NewSyncCmd(globalCfg *config.GlobalImpl) *cobra.Command { f.BoolVar(&syncOptions.IncludeTransitiveNeeds, "include-transitive-needs", false, `like --include-needs, but also includes transitive needs (needs of needs). Does nothing when --selector/-l flag is not provided. Overrides exclusions of other selectors and conditions.`) f.BoolVar(&syncOptions.HideNotes, "hide-notes", false, "add --hide-notes flag to helm") f.BoolVar(&syncOptions.TakeOwnership, "take-ownership", false, `add --take-ownership flag to helm`) + f.BoolVar(&syncOptions.SyncReleaseLabels, "sync-release-labels", false, "sync release labels to the target release") f.BoolVar(&syncOptions.Wait, "wait", false, `Override helmDefaults.wait setting "helm upgrade --install --wait"`) f.BoolVar(&syncOptions.WaitForJobs, "wait-for-jobs", false, `Override helmDefaults.waitForJobs setting "helm upgrade --install --wait-for-jobs"`) f.BoolVar(&syncOptions.ReuseValues, "reuse-values", false, `Override helmDefaults.reuseValues "helm upgrade --install --reuse-values"`) diff --git a/docs/index.md b/docs/index.md index 46d574d7..0f4a12de 100644 --- a/docs/index.md +++ b/docs/index.md @@ -240,6 +240,9 @@ helmDefaults: # suppressOutputLineRegex is a list of regex patterns to suppress output lines from helm diff (default []), available in helmfile v0.162.0 suppressOutputLineRegex: - "version" + # syncReleaseLabels is a list of labels to be added to the release when syncing. + syncReleaseLabels: false + # these labels will be applied to all releases in a Helmfile. Useful in templating if you have a helmfile per environment or customer and don't want to copy the same label to each release commonLabels: @@ -369,6 +372,8 @@ releases: # suppressOutputLineRegex is a list of regex patterns to suppress output lines from helm diff (default []), available in helmfile v0.162.0 suppressOutputLineRegex: - "version" + # syncReleaseLabels is a list of labels to be added to the release when syncing. + syncReleaseLabels: false # Local chart example diff --git a/go.mod b/go.mod index 299319c5..dee386b7 100644 --- a/go.mod +++ b/go.mod @@ -138,6 +138,7 @@ require ( github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.48.1 // indirect github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.48.1 // indirect github.com/MakeNowJust/heredoc v1.0.0 // indirect + github.com/Masterminds/squirrel v1.5.4 // indirect github.com/ProtonMail/go-crypto v1.1.3 // indirect github.com/agext/levenshtein v1.2.3 // indirect github.com/antchfx/jsonquery v1.3.6 // indirect @@ -202,6 +203,7 @@ require ( github.com/getsops/gopgagent v0.0.0-20240527072608-0c14999532fe // indirect github.com/getsops/sops/v3 v3.9.2 // indirect github.com/go-errors/errors v1.4.2 // indirect + github.com/go-gorp/gorp/v3 v3.1.0 // indirect github.com/go-jose/go-jose/v4 v4.0.5 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect @@ -237,10 +239,13 @@ require ( github.com/hokaccha/go-prettyjson v0.0.0-20211117102719-0474bc63780f // indirect github.com/ianlancetaylor/demangle v0.0.0-20240805132620-81f5be970eca // indirect github.com/itchyny/timefmt-go v0.1.6 // indirect + github.com/jmoiron/sqlx v1.4.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect github.com/kylelemons/godebug v1.1.0 // indirect + github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect + github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/mailru/easyjson v0.9.0 // indirect @@ -269,6 +274,7 @@ require ( github.com/prometheus/common v0.55.0 // indirect github.com/prometheus/procfs v0.15.1 // indirect github.com/rivo/uniseg v0.4.7 // indirect + github.com/rubenv/sql-migrate v1.7.1 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 // indirect github.com/tetratelabs/wabin v0.0.0-20230304001439-f6f874872834 // indirect diff --git a/go.sum b/go.sum index b58744d7..3af272f4 100644 --- a/go.sum +++ b/go.sum @@ -627,6 +627,8 @@ dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= filippo.io/age v1.2.1 h1:X0TZjehAZylOIj4DubWYU1vWQxv9bJpo+Uu2/LGhi1o= filippo.io/age v1.2.1/go.mod h1:JL9ew2lTN+Pyft4RiNGguFfOpewKwSHm5ayKD/A4004= +filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= gioui.org v0.0.0-20210308172011-57750fc8a0a6/go.mod h1:RSH6KIUZ0p2xy5zHDxgAM4zumjgTw83q2ge/PI+yyw8= git.sr.ht/~sbinet/gg v0.3.1/go.mod h1:KGYtlADtqsqANL9ueOFkWymvzUvLMQllU5Ixo+8v3pc= github.com/1Password/connect-sdk-go v1.5.3 h1:KyjJ+kCKj6BwB2Y8tPM1Ixg5uIS6HsB0uWA8U38p/Uk= @@ -684,6 +686,8 @@ github.com/AzureAD/microsoft-authentication-library-for-go v1.3.3/go.mod h1:wP83 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU= +github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU= github.com/DopplerHQ/cli v0.5.11-0.20230908185655-7aef4713e1a4 h1:s7/zwMi5w+KnlumDVbX1+P6mNAk5o7Wvx0VmvrQ7Bm0= github.com/DopplerHQ/cli v0.5.11-0.20230908185655-7aef4713e1a4/go.mod h1:ipnA9Lpn5YM+FDSQZ7VWNjcuVurchInoGKm+v7O0sGs= github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.25.0 h1:3c8yed4lgqTt+oTQ+JNMDo+F4xprBf+O/il4ZC0nRLw= @@ -705,6 +709,8 @@ github.com/Masterminds/semver/v3 v3.3.1 h1:QtNSWtVZ3nBfk8mAOu/B6v7FMJ+NHTIgUPi7r github.com/Masterminds/semver/v3 v3.3.1/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= github.com/Masterminds/sprig/v3 v3.3.0 h1:mQh0Yrg1XPo6vjYXgtf5OtijNAKJRNcTdOOGZe3tPhs= github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSCzdgBfDb35Lz0= +github.com/Masterminds/squirrel v1.5.4 h1:uUcX/aBc8O7Fg9kaISIUsHXdKuqehiXAMQTYX8afzqM= +github.com/Masterminds/squirrel v1.5.4/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/Microsoft/hcsshim v0.11.7 h1:vl/nj3Bar/CvJSYo7gIQPyRWc9f3c6IeSNavBTSZNZQ= @@ -964,6 +970,8 @@ github.com/go-fonts/stix v0.1.0/go.mod h1:w/c1f0ldAUlJmLBvlbkvVXLAD+tAMqobIIQpmn github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gorp/gorp/v3 v3.1.0 h1:ItKF/Vbuj31dmV4jxA1qblpSwkl9g1typ24xoe70IGs= +github.com/go-gorp/gorp/v3 v3.1.0/go.mod h1:dLEjIyyRNiXvNZ8PSmzpt1GsWAUK8kjVhEpjH8TixEw= github.com/go-jose/go-jose/v4 v4.0.5 h1:M6T8+mKZl/+fNNuFHvGIzDz7BTLQPIounk/b9dw3AaE= github.com/go-jose/go-jose/v4 v4.0.5/go.mod h1:s3P1lRrkT8igV8D9OjyL4WRyHvjB6a4JSllnOrmmBOA= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= @@ -998,6 +1006,8 @@ github.com/go-openapi/validate v0.24.0 h1:LdfDKwNbpB6Vn40xhTdNZAnfLECL81w+VX3Bum github.com/go-openapi/validate v0.24.0/go.mod h1:iyeX1sEufmv3nPbBdX3ieNviWnOZaJ1+zquzJEf2BAQ= github.com/go-pdf/fpdf v0.5.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhOh5M= github.com/go-pdf/fpdf v0.6.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhOh5M= +github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= +github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= @@ -1238,6 +1248,8 @@ github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9Y github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= +github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o= +github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= @@ -1275,6 +1287,10 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq6+3iTQz8KNCLtVX6idSoTLdUw= +github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o= +github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk= +github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0= @@ -1300,6 +1316,8 @@ github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRC github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-sqlite3 v1.14.14/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= +github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= +github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI= @@ -1392,6 +1410,8 @@ github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/poy/onpar v1.1.2 h1:QaNrNiZx0+Nar5dLgTVp5mXkyoVFIbepjyEoGSnhbAY= +github.com/poy/onpar v1.1.2/go.mod h1:6X8FLNoxyr9kkmnlqpK6LSoiOtrO6MICtWwEuWkLjzg= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= @@ -1426,6 +1446,8 @@ github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTE github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/rubenv/sql-migrate v1.7.1 h1:f/o0WgfO/GqNuVg+6801K/KW3WdDSupzSjDYODmiUq4= +github.com/rubenv/sql-migrate v1.7.1/go.mod h1:Ob2Psprc0/3ggbM6wCzyYVFFuc6FyZrb2AS+ezLDFb4= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58/go.mod h1:6lfFZQK844Gfx8o5WFuvpxWRwnSoipWe/p622j1v06w= diff --git a/pkg/app/app.go b/pkg/app/app.go index 6529291d..65751681 100644 --- a/pkg/app/app.go +++ b/pkg/app/app.go @@ -1106,13 +1106,17 @@ func (a *App) visitStatesWithSelectorsAndRemoteSupport(fileOrDir string, converg } } - // pre-overrides HelmState + // pre-handles HelmState fHelmStatsWithOverrides := func(st *state.HelmState) (bool, []error) { var err error + // override release settings st.Releases, err = st.GetReleasesWithOverrides() if err != nil { return false, []error{err} } + + // override release labels + st.Releases = st.GetReleasesWithLabels() return f(st) } @@ -1502,6 +1506,7 @@ Do you really want to apply? SyncArgs: c.SyncArgs(), HideNotes: c.HideNotes(), TakeOwnership: c.TakeOwnership(), + SyncReleaseLabels: c.SyncReleaseLabels(), } return subst.SyncReleases(&affectedReleases, helm, c.Values(), c.Concurrency(), syncOpts) })) @@ -1900,6 +1905,7 @@ Do you really want to sync? HideNotes: c.HideNotes(), TakeOwnership: c.TakeOwnership(), SkipSchemaValidation: c.SkipSchemaValidation(), + SyncReleaseLabels: c.SyncReleaseLabels(), } return subst.SyncReleases(&affectedReleases, helm, c.Values(), c.Concurrency(), opts) })) diff --git a/pkg/app/app_list_test.go b/pkg/app/app_list_test.go index 69bbc754..d7775a83 100644 --- a/pkg/app/app_list_test.go +++ b/pkg/app/app_list_test.go @@ -159,17 +159,17 @@ releases: t.Run("default environment includes all releases", func(t *testing.T) { check(t, testcase{ environment: "default", - expected: `NAME NAMESPACE ENABLED INSTALLED LABELS CHART VERSION -logging kube-system true true incubator/raw -kubernetes-external-secrets kube-system true true incubator/raw -external-secrets default true true app:test incubator/raw -my-release default true true app:test incubator/raw -disabled kube-system true false incubator/raw -test2 true true incubator/raw -test3 true true incubator/raw -cache my-app true true app:test bitnami/redis 17.0.7 -database my-app true true bitnami/postgres 11.6.22 -global kube-system true true incubator/raw + expected: `NAME NAMESPACE ENABLED INSTALLED LABELS CHART VERSION +logging kube-system true true chart:raw,name:logging,namespace:kube-system incubator/raw +kubernetes-external-secrets kube-system true true chart:raw,name:kubernetes-external-secrets,namespace:kube-system incubator/raw +external-secrets default true true app:test,chart:raw,name:external-secrets,namespace:default incubator/raw +my-release default true true app:test,chart:raw,name:my-release,namespace:default incubator/raw +disabled kube-system true false chart:raw,name:disabled,namespace:kube-system incubator/raw +test2 true true chart:raw,name:test2,namespace: incubator/raw +test3 true true chart:raw,name:test3,namespace: incubator/raw +cache my-app true true app:test,chart:redis,name:cache,namespace:my-app bitnami/redis 17.0.7 +database my-app true true chart:postgres,name:database,namespace:my-app bitnami/postgres 11.6.22 +global kube-system true true chart:raw,name:global,namespace:kube-system incubator/raw `, }, cfg) }) @@ -185,9 +185,9 @@ global kube-system true true incubator/raw check(t, testcase{ environment: "development", selectors: []string{"app=test"}, - expected: `NAME NAMESPACE ENABLED INSTALLED LABELS CHART VERSION -external-secrets default true true app:test incubator/raw -my-release default true true app:test incubator/raw + expected: `NAME NAMESPACE ENABLED INSTALLED LABELS CHART VERSION +external-secrets default true true app:test,chart:raw,name:external-secrets,namespace:default incubator/raw +my-release default true true app:test,chart:raw,name:my-release,namespace:default incubator/raw `, }, cfg) }) @@ -195,9 +195,9 @@ my-release default true true app:test incubator/raw t.Run("filters releases for environment used in one file only", func(t *testing.T) { check(t, testcase{ environment: "test", - expected: `NAME NAMESPACE ENABLED INSTALLED LABELS CHART VERSION -cache my-app true true app:test bitnami/redis 17.0.7 -database my-app true true bitnami/postgres 11.6.22 + expected: `NAME NAMESPACE ENABLED INSTALLED LABELS CHART VERSION +cache my-app true true app:test,chart:redis,name:cache,namespace:my-app bitnami/redis 17.0.7 +database my-app true true chart:postgres,name:database,namespace:my-app bitnami/postgres 11.6.22 `, }, cfg) }) @@ -206,16 +206,16 @@ database my-app true true bitnami/postgres 11.6.22 check(t, testcase{ environment: "shared", // 'global' release has no environments, so is still excluded - expected: `NAME NAMESPACE ENABLED INSTALLED LABELS CHART VERSION -logging kube-system true true incubator/raw -kubernetes-external-secrets kube-system true true incubator/raw -external-secrets default true true app:test incubator/raw -my-release default true true app:test incubator/raw -disabled kube-system true false incubator/raw -test2 true true incubator/raw -test3 true true incubator/raw -cache my-app true true app:test bitnami/redis 17.0.7 -database my-app true true bitnami/postgres 11.6.22 + expected: `NAME NAMESPACE ENABLED INSTALLED LABELS CHART VERSION +logging kube-system true true chart:raw,name:logging,namespace:kube-system incubator/raw +kubernetes-external-secrets kube-system true true chart:raw,name:kubernetes-external-secrets,namespace:kube-system incubator/raw +external-secrets default true true app:test,chart:raw,name:external-secrets,namespace:default incubator/raw +my-release default true true app:test,chart:raw,name:my-release,namespace:default incubator/raw +disabled kube-system true false chart:raw,name:disabled,namespace:kube-system incubator/raw +test2 true true chart:raw,name:test2,namespace: incubator/raw +test3 true true chart:raw,name:test3,namespace: incubator/raw +cache my-app true true app:test,chart:redis,name:cache,namespace:my-app bitnami/redis 17.0.7 +database my-app true true chart:postgres,name:database,namespace:my-app bitnami/postgres 11.6.22 `, }, cfg) }) @@ -285,7 +285,7 @@ releases: }) assert.NoError(t, err) - expected := `[{"name":"myrelease1","namespace":"testNamespace","enabled":true,"installed":false,"labels":"id:myrelease1","chart":"mychart1","version":""},{"name":"myrelease2","namespace":"testNamespace","enabled":false,"installed":true,"labels":"","chart":"mychart1","version":""},{"name":"myrelease3","namespace":"testNamespace","enabled":true,"installed":true,"labels":"","chart":"mychart1","version":""},{"name":"myrelease4","namespace":"testNamespace","enabled":true,"installed":true,"labels":"id:myrelease1","chart":"mychart1","version":""}] + expected := `[{"name":"myrelease1","namespace":"testNamespace","enabled":true,"installed":false,"labels":"chart:mychart1,id:myrelease1,name:myrelease1,namespace:testNamespace","chart":"mychart1","version":""},{"name":"myrelease2","namespace":"testNamespace","enabled":false,"installed":true,"labels":"chart:mychart1,name:myrelease2,namespace:testNamespace","chart":"mychart1","version":""},{"name":"myrelease3","namespace":"testNamespace","enabled":true,"installed":true,"labels":"chart:mychart1,name:myrelease3,namespace:testNamespace","chart":"mychart1","version":""},{"name":"myrelease4","namespace":"testNamespace","enabled":true,"installed":true,"labels":"chart:mychart1,id:myrelease1,name:myrelease4,namespace:testNamespace","chart":"mychart1","version":""}] ` assert.Equal(t, expected, out) } diff --git a/pkg/app/app_test.go b/pkg/app/app_test.go index 66eb8829..6ac78a78 100644 --- a/pkg/app/app_test.go +++ b/pkg/app/app_test.go @@ -2259,6 +2259,7 @@ type applyConfig struct { showOnly []string hideNotes bool takeOwnership bool + syncReleaseLabels bool // template-only options includeCRDs, skipTests bool @@ -2453,6 +2454,10 @@ func (a applyConfig) TakeOwnership() bool { return a.takeOwnership } +func (a applyConfig) SyncReleaseLabels() bool { + return a.syncReleaseLabels +} + type depsConfig struct { skipRepos bool includeTransitiveNeeds bool @@ -3977,11 +3982,11 @@ releases: }) assert.NoError(t, err) - expected := `NAME NAMESPACE ENABLED INSTALLED LABELS CHART VERSION -myrelease1 testNamespace true false common:label,id:myrelease1 mychart1 -myrelease2 testNamespace false true common:label mychart1 -myrelease3 testNamespace true true mychart1 -myrelease4 testNamespace true true id:myrelease1 mychart1 + expected := `NAME NAMESPACE ENABLED INSTALLED LABELS CHART VERSION +myrelease1 testNamespace true false chart:mychart1,common:label,id:myrelease1,name:myrelease1,namespace:testNamespace mychart1 +myrelease2 testNamespace false true chart:mychart1,common:label,name:myrelease2,namespace:testNamespace mychart1 +myrelease3 testNamespace true true chart:mychart1,name:myrelease3,namespace:testNamespace mychart1 +myrelease4 testNamespace true true chart:mychart1,id:myrelease1,name:myrelease4,namespace:testNamespace mychart1 ` assert.Equal(t, expected, out) diff --git a/pkg/app/config.go b/pkg/app/config.go index 9cacaffb..34dff76d 100644 --- a/pkg/app/config.go +++ b/pkg/app/config.go @@ -80,6 +80,8 @@ type ApplyConfigProvider interface { DiffArgs() string SyncArgs() string + SyncReleaseLabels() bool + DAGConfig concurrencyConfig @@ -112,6 +114,9 @@ type SyncConfigProvider interface { SkipNeeds() bool IncludeNeeds() bool IncludeTransitiveNeeds() bool + + SyncReleaseLabels() bool + DAGConfig concurrencyConfig diff --git a/pkg/config/apply.go b/pkg/config/apply.go index 837359b9..7cfc6d3c 100644 --- a/pkg/config/apply.go +++ b/pkg/config/apply.go @@ -71,6 +71,8 @@ type ApplyOptions struct { // TakeOwnership is true if the ownership should be taken TakeOwnership bool + + SyncReleaseLabels bool } // NewApply creates a new Apply @@ -266,3 +268,7 @@ func (a *ApplyImpl) HideNotes() bool { func (a *ApplyImpl) TakeOwnership() bool { return a.ApplyOptions.TakeOwnership } + +func (a *ApplyImpl) SyncReleaseLabels() bool { + return a.ApplyOptions.SyncReleaseLabels +} diff --git a/pkg/config/sync.go b/pkg/config/sync.go index 033f82ed..71d38034 100644 --- a/pkg/config/sync.go +++ b/pkg/config/sync.go @@ -42,6 +42,8 @@ type SyncOptions struct { HideNotes bool // TakeOwnership is the take ownership flag TakeOwnership bool + // SyncReleaseLabels is the sync release labels flag + SyncReleaseLabels bool } // NewSyncOptions creates a new Apply @@ -167,3 +169,7 @@ func (t *SyncImpl) HideNotes() bool { func (t *SyncImpl) TakeOwnership() bool { return t.SyncOptions.TakeOwnership } + +func (t *SyncImpl) SyncReleaseLabels() bool { + return t.SyncOptions.SyncReleaseLabels +} diff --git a/pkg/state/helmx.go b/pkg/state/helmx.go index 2988a605..00d29cac 100644 --- a/pkg/state/helmx.go +++ b/pkg/state/helmx.go @@ -4,9 +4,13 @@ import ( "fmt" "os" "path/filepath" + "slices" + "sort" "strconv" + "strings" "github.com/helmfile/chartify" + "helm.sh/helm/v3/pkg/storage/driver" "github.com/helmfile/helmfile/pkg/helmexec" "github.com/helmfile/helmfile/pkg/remote" @@ -26,6 +30,39 @@ func (st *HelmState) appendHelmXFlags(flags []string, release *ReleaseSpec) []st return flags } +func formatLabels(labels map[string]string) string { + var labelsList, keys []string + for k := range labels { + if k == "" || slices.Contains(driver.GetSystemLabels(), k) { + continue + } + + keys = append(keys, k) + } + sort.Strings(keys) + + if len(keys) == 0 { + return "" + } + + for _, k := range keys { + val := labels[k] + labelsList = append(labelsList, fmt.Sprintf("%s=%s", k, val)) + } + return strings.Join(labelsList, ",") +} + +// append labels flags to helm flags, starting from helm v3.13.0 +func (st *HelmState) appendLabelsFlags(flags []string, helm helmexec.Interface, release *ReleaseSpec, syncReleaseLabels bool) []string { + if helm.IsVersionAtLeast("3.13.0") && (syncReleaseLabels || release.SyncReleaseLabels) { + labels := formatLabels(release.Labels) + if labels != "" { + flags = append(flags, "--labels", labels) + } + } + return flags +} + // append post-renderer flags to helm flags func (st *HelmState) appendPostRenderFlags(flags []string, release *ReleaseSpec, postRenderer string) []string { switch { diff --git a/pkg/state/helmx_test.go b/pkg/state/helmx_test.go index d60cd225..76f4300d 100644 --- a/pkg/state/helmx_test.go +++ b/pkg/state/helmx_test.go @@ -431,3 +431,54 @@ func TestAppendTakeOwnershipFlags(t *testing.T) { }) } } + +func TestFormatLabels(t *testing.T) { + tests := []struct { + name string + labels map[string]string + want string + }{ + { + name: "empty labels", + labels: map[string]string{}, + want: "", + }, + { + name: "single label", + labels: map[string]string{"foo": "bar"}, + want: "foo=bar", + }, + { + name: "multiple labels", + labels: map[string]string{"foo": "bar", "baz": "qux"}, + want: "baz=qux,foo=bar", + }, + { + name: "multiple labels with empty value", + labels: map[string]string{"foo": "bar", "baz": "qux", "quux": ""}, + want: "baz=qux,foo=bar,quux=", + }, + { + name: "multiple labels with empty key", + labels: map[string]string{"foo": "bar", "baz": "qux", "": "quux"}, + want: "baz=qux,foo=bar", + }, + { + name: "empty label value", + labels: map[string]string{"foo": ""}, + want: "foo=", + }, + { + name: "empty label key", + labels: map[string]string{"": "bar"}, + want: "", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := formatLabels(tt.labels) + require.Equal(t, tt.want, got, "formatLabels() = %v, want %v", got, tt.want) + }) + } +} diff --git a/pkg/state/selector_test.go b/pkg/state/selector_test.go index 787a79c3..e955aed4 100644 --- a/pkg/state/selector_test.go +++ b/pkg/state/selector_test.go @@ -108,14 +108,14 @@ func TestSelectReleasesWithOverrides(t *testing.T) { }.MustLoadState(t, "/helmfile.yaml", "default") for _, tc := range testcases { + var err error state.Selectors = tc.selector - releases, err := state.GetReleasesWithOverrides() - state.Releases = releases - + state.Releases, err = state.GetReleasesWithOverrides() if err != nil { t.Fatalf("%s %s: %v", tc.selector, tc.subject, err) } + state.Releases = state.GetReleasesWithLabels() rs, err := state.GetSelectedReleases(false) if err != nil { @@ -144,13 +144,13 @@ func TestSelectReleasesWithOverridesWithIncludedTransitives(t *testing.T) { testcases := []testcase{ { - subject: "include transitives", + subject: "include transitives is false", selector: []string{"name=serviceA"}, want: []string{"serviceA"}, includeTransitiveNeeds: false, }, { - subject: "include transitives", + subject: "include transitives is true", selector: []string{"name=serviceA"}, want: []string{"serviceA", "serviceB", "serviceC"}, includeTransitiveNeeds: true, @@ -184,13 +184,13 @@ func TestSelectReleasesWithOverridesWithIncludedTransitives(t *testing.T) { }.MustLoadState(t, "/helmfile.yaml", "default") for _, tc := range testcases { + var err error state.Selectors = tc.selector - releases, err := state.GetReleasesWithOverrides() + state.Releases, err = state.GetReleasesWithOverrides() if err != nil { t.Fatalf("%s %s: %v", tc.selector, tc.subject, err) } - - state.Releases = releases + state.Releases = state.GetReleasesWithLabels() rs, err := state.GetSelectedReleases(tc.includeTransitiveNeeds) if err != nil { diff --git a/pkg/state/state.go b/pkg/state/state.go index 681c3122..8d090723 100644 --- a/pkg/state/state.go +++ b/pkg/state/state.go @@ -7,7 +7,6 @@ import ( "errors" "fmt" "io" - "maps" "net/url" "os" "path/filepath" @@ -208,6 +207,8 @@ type HelmSpec struct { DeleteWait bool `yaml:"deleteWait"` // Timeout is the time in seconds to wait for helmfile delete command (default 300) DeleteTimeout int `yaml:"deleteTimeout"` + // SyncReleaseLabels is true if the release labels should be synced with the helmfile labels + SyncReleaseLabels bool `yaml:"syncReleaseLabels"` } // RepositorySpec that defines values for a helm repo @@ -408,6 +409,8 @@ type ReleaseSpec struct { DeleteWait *bool `yaml:"deleteWait,omitempty"` // Timeout is the time in seconds to wait for helmfile delete command (default 300) DeleteTimeout *int `yaml:"deleteTimeout,omitempty"` + // SyncReleaseLabels is true if the release labels should be synced with the helmfile labels + SyncReleaseLabels bool `yaml:"syncReleaseLabels"` } func (r *Inherits) UnmarshalYAML(unmarshal func(any) error) error { @@ -783,6 +786,7 @@ type SyncOpts struct { Wait bool WaitRetries int WaitForJobs bool + SyncReleaseLabels bool ReuseValues bool ResetValues bool PostRenderer string @@ -2262,16 +2266,40 @@ func (st *HelmState) GetReleasesWithOverrides() ([]ReleaseSpec, error) { return rs, nil } +func (st *HelmState) GetReleasesWithLabels() []ReleaseSpec { + var rs []ReleaseSpec + for _, r := range st.Releases { + spec := r + labels := map[string]string{} + // apply common labels + for k, v := range st.CommonLabels { + labels[k] = v + } + for k, v := range spec.Labels { + labels[k] = v + } + // Let the release name, namespace, and chart be used as a tag + labels["name"] = r.Name + labels["namespace"] = r.Namespace + // Strip off just the last portion for the name stable/newrelic would give newrelic + chartSplit := strings.Split(r.Chart, "/") + labels["chart"] = chartSplit[len(chartSplit)-1] + spec.Labels = labels + rs = append(rs, spec) + } + return rs +} + func (st *HelmState) SelectReleases(includeTransitiveNeeds bool) ([]Release, error) { values := st.Values() - rs, err := markExcludedReleases(st.Releases, st.Selectors, st.CommonLabels, values, includeTransitiveNeeds) + rs, err := markExcludedReleases(st.Releases, st.Selectors, values, includeTransitiveNeeds) if err != nil { return nil, err } return rs, nil } -func markExcludedReleases(releases []ReleaseSpec, selectors []string, commonLabels map[string]string, values map[string]any, includeTransitiveNeeds bool) ([]Release, error) { +func markExcludedReleases(releases []ReleaseSpec, selectors []string, values map[string]any, includeTransitiveNeeds bool) ([]Release, error) { var filteredReleases []Release filters := []ReleaseFilter{} for _, label := range selectors { @@ -2282,29 +2310,8 @@ func markExcludedReleases(releases []ReleaseSpec, selectors []string, commonLabe filters = append(filters, f) } for _, r := range releases { - orginReleaseLabel := maps.Clone(r.Labels) - if r.Labels == nil { - r.Labels = map[string]string{} - } else { - // Make a copy of the labels to avoid mutating the original - r.Labels = maps.Clone(r.Labels) - } - // Let the release name, namespace, and chart be used as a tag - r.Labels["name"] = r.Name - r.Labels["namespace"] = r.Namespace - // Strip off just the last portion for the name stable/newrelic would give newrelic - chartSplit := strings.Split(r.Chart, "/") - r.Labels["chart"] = chartSplit[len(chartSplit)-1] - // Merge CommonLabels into release labels - for k, v := range commonLabels { - r.Labels[k] = v - } - var filterMatch bool for _, f := range filters { - if r.Labels == nil { - r.Labels = map[string]string{} - } if f.Match(r) { filterMatch = true break @@ -2316,7 +2323,6 @@ func markExcludedReleases(releases []ReleaseSpec, selectors []string, commonLabe return nil, fmt.Errorf("failed to parse condition in release %s: %w", r.Name, err) } // reset the labels to the original - r.Labels = orginReleaseLabel res := Release{ ReleaseSpec: r, Filtered: (len(filters) > 0 && !filterMatch) || (!conditionMatch), @@ -2776,15 +2782,20 @@ func (st *HelmState) flagsForUpgrade(helm helmexec.Interface, release *ReleaseSp flags = append(flags, "--disable-openapi-validation") } + postRenderer := "" + syncReleaseLabels := false + if opt != nil { + postRenderer = opt.PostRenderer + syncReleaseLabels = opt.SyncReleaseLabels + } + flags = st.appendConnectionFlags(flags, release) flags = st.appendChartDownloadFlags(flags, release) flags = st.appendHelmXFlags(flags, release) - postRenderer := "" - if opt != nil { - postRenderer = opt.PostRenderer - } + flags = st.appendLabelsFlags(flags, helm, release, syncReleaseLabels) + flags = st.appendPostRenderFlags(flags, release, postRenderer) var postRendererArgs []string diff --git a/pkg/state/temp_test.go b/pkg/state/temp_test.go index 596d2dde..90eb5f45 100644 --- a/pkg/state/temp_test.go +++ b/pkg/state/temp_test.go @@ -38,39 +38,39 @@ func TestGenerateID(t *testing.T) { run(testcase{ subject: "baseline", release: ReleaseSpec{Name: "foo", Chart: "incubator/raw"}, - want: "foo-values-669d45cd7b", + want: "foo-values-6584bf5db7", }) run(testcase{ subject: "different bytes content", release: ReleaseSpec{Name: "foo", Chart: "incubator/raw"}, data: []byte(`{"k":"v"}`), - want: "foo-values-67d8c67fcf", + want: "foo-values-6b8c446b76", }) run(testcase{ subject: "different map content", release: ReleaseSpec{Name: "foo", Chart: "incubator/raw"}, data: map[string]any{"k": "v"}, - want: "foo-values-b9bc64677", + want: "foo-values-6b499b6fb6", }) run(testcase{ subject: "different chart", release: ReleaseSpec{Name: "foo", Chart: "stable/envoy"}, - want: "foo-values-585c4565f5", + want: "foo-values-775cbccfb", }) run(testcase{ subject: "different name", release: ReleaseSpec{Name: "bar", Chart: "incubator/raw"}, - want: "bar-values-c94846459", + want: "bar-values-849dcf78b4", }) run(testcase{ subject: "specific ns", release: ReleaseSpec{Name: "foo", Chart: "incubator/raw", Namespace: "myns"}, - want: "myns-foo-values-798d69477", + want: "myns-foo-values-d57499c58", }) for id, n := range ids { diff --git a/test/e2e/template/helmfile/testdata/snapshot/issue_2098_release_template_needs/output.yaml b/test/e2e/template/helmfile/testdata/snapshot/issue_2098_release_template_needs/output.yaml index fb2d2b51..c7d30230 100644 --- a/test/e2e/template/helmfile/testdata/snapshot/issue_2098_release_template_needs/output.yaml +++ b/test/e2e/template/helmfile/testdata/snapshot/issue_2098_release_template_needs/output.yaml @@ -15,7 +15,11 @@ releases: name: default-shared-resources namespace: default labels: + chart: util + name: default-shared-resources + namespace: default service: shared-resources + syncReleaseLabels: false - chart: aservo/util version: 0.0.1 needs: @@ -23,9 +27,14 @@ releases: name: default-release-resources namespace: default labels: + chart: util + name: default-release-resources + namespace: default service: release-resources + syncReleaseLabels: false templates: defaults: name: default-{{ .Release.Labels.service }} namespace: default + syncReleaseLabels: false renderedvalues: {} diff --git a/test/e2e/template/helmfile/testdata/snapshot/issue_493_template_yaml_anchors_merge/output.yaml b/test/e2e/template/helmfile/testdata/snapshot/issue_493_template_yaml_anchors_merge/output.yaml index d799a9aa..9c7b08f7 100644 --- a/test/e2e/template/helmfile/testdata/snapshot/issue_493_template_yaml_anchors_merge/output.yaml +++ b/test/e2e/template/helmfile/testdata/snapshot/issue_493_template_yaml_anchors_merge/output.yaml @@ -1,3 +1,3 @@ -NAME NAMESPACE ENABLED INSTALLED LABELS CHART VERSION -release1 myNamespace true true app:myapp,group:myGroup,project:myProject test -release2 myNamespace true true app:myapp,group:myGroup,project:myProject test +NAME NAMESPACE ENABLED INSTALLED LABELS CHART VERSION +release1 myNamespace true true app:myapp,chart:test,group:myGroup,name:release1,namespace:myNamespace,project:myProject test +release2 myNamespace true true app:myapp,chart:test,group:myGroup,name:release2,namespace:myNamespace,project:myProject test