Skip to content

Commit 7b26130

Browse files
barkbaynaemonothbkrkr
authored
Helm release tool: Add an option to force upload (#8771) (#8772)
* Helm release tool: Add an option to force upload * Read FORCE in hack/helm/release/trigger-helm-release.sh --------- Co-authored-by: Michael Montgomery <mmontg1@gmail.com> Co-authored-by: Thibault Richard <thbkrkr@users.noreply.github.com>
1 parent bba019e commit 7b26130

File tree

6 files changed

+95
-5
lines changed

6 files changed

+95
-5
lines changed

.buildkite/pipeline-release-helm.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
env:
22
HELM_DRY_RUN: ${HELM_DRY_RUN:-true}
3+
HELM_FORCE: ${HELM_FORCE:-false}
34

45
steps:
56

hack/helm/release/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ Flags:
1313
--charts-dir string Directory which contains Helm charts to release (env: HELM_CHARTS_DIR) (default "./deploy")
1414
--credentials-file string Path to GCS credentials JSON file (env: HELM_CREDENTIALS_FILE) (default "/tmp/credentials.json")
1515
-d, --dry-run Do not upload files to bucket, or update Helm index (env: HELM_DRY_RUN) (default true)
16+
-f, --force Force an upload for non dev/snapshot (env: HELM_FORCE) (default false)
1617
--enable-vault Read 'credentials-file' from Vault (requires VAULT_ADDR and VAULT_TOKEN) (env: HELM_ENABLE_VAULT) (default true)
1718
--env string Environment in which to release Helm charts ('dev' or 'prod') (env: HELM_ENV) (default "dev")
1819
-h, --help help for release

hack/helm/release/cmd/root.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ const (
2222
chartsDirFlag = "charts-dir"
2323
credentialsFileFlag = "credentials-file"
2424
dryRunFlag = "dry-run"
25+
forceFlag = "force"
2526
keepTmpDirFlag = "keep-tmp-dir"
2627
envFlag = "env"
2728
enableVaultFlag = "enable-vault"
@@ -67,6 +68,7 @@ func releaseCmd() *cobra.Command {
6768
ChartsRepoURL: chartsRepoURL,
6869
CredentialsFilePath: viper.GetString(credentialsFileFlag),
6970
DryRun: viper.GetBool(dryRunFlag),
71+
Force: viper.GetBool(forceFlag),
7072
KeepTmpDir: viper.GetBool(keepTmpDirFlag),
7173
})
7274
},
@@ -82,6 +84,14 @@ func releaseCmd() *cobra.Command {
8284
)
8385
_ = viper.BindPFlag(dryRunFlag, flags.Lookup(dryRunFlag))
8486

87+
flags.BoolP(
88+
forceFlag,
89+
"f",
90+
false,
91+
"Upload artifacts even if they already exist (env: HELM_FORCE)",
92+
)
93+
_ = viper.BindPFlag(forceFlag, flags.Lookup(forceFlag))
94+
8595
flags.BoolP(
8696
keepTmpDirFlag,
8797
"k",

hack/helm/release/internal/helm/helm.go

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ type ReleaseConfig struct {
4141
CredentialsFilePath string
4242
// DryRun determines whether to run the release without making any changes to the GCS bucket or the Helm repository index file.
4343
DryRun bool
44+
// Force determines if uploading charts should overwrite existing charts in the GCS bucket even if they are not SNAPSHOT versions.
45+
Force bool
4446
// KeepTmpDir determines whether the temporary directory should be kept or not
4547
KeepTmpDir bool
4648
}
@@ -198,7 +200,8 @@ func copyChartToGCSBucket(ctx context.Context, conf ReleaseConfig, chart chart,
198200
// specify that the object must not exist for non-SNAPSHOT chart when publishing to prod Helm repo
199201
isNonSnapshot := !strings.HasSuffix(chart.Version, "-SNAPSHOT")
200202
isProdHelmRepo := !strings.HasSuffix(conf.Bucket, "-dev")
201-
if isNonSnapshot && isProdHelmRepo {
203+
shouldNotOverwrite := shouldNotOverwrite(isNonSnapshot, isProdHelmRepo, conf.Force)
204+
if shouldNotOverwrite {
202205
chartArchiveObj = chartArchiveObj.If(storage.Conditions{DoesNotExist: true})
203206
}
204207

@@ -215,7 +218,7 @@ func copyChartToGCSBucket(ctx context.Context, conf ReleaseConfig, chart chart,
215218
if err := chartArchiveWriter.Close(); err != nil {
216219
switch errType := err.(type) {
217220
case *googleapi.Error:
218-
if errType.Code == http.StatusPreconditionFailed && isNonSnapshot && isProdHelmRepo {
221+
if errType.Code == http.StatusPreconditionFailed && shouldNotOverwrite {
219222
return fmt.Errorf("file %s already exists in remote bucket; manually remove for this operation to succeed", chartPackagePath)
220223
}
221224
return fmt.Errorf("while writing data to bucket: %w", err)
@@ -227,6 +230,14 @@ func copyChartToGCSBucket(ctx context.Context, conf ReleaseConfig, chart chart,
227230
return nil
228231
}
229232

233+
// shouldNotOverwrite determines if a chart should not be overwritten in the bucket.
234+
func shouldNotOverwrite(isNonSnapshot, isProdHelmRepo, force bool) bool {
235+
if force {
236+
return false
237+
}
238+
return isNonSnapshot && isProdHelmRepo
239+
}
240+
230241
// updateIndex updates the Helm repo index by merging the existing index in the bucket
231242
// with a new version created with the released charts. A 'GenerationMatch' precondition
232243
// is used when writing to avoid a race condition with another concurrent write.

hack/helm/release/internal/helm/helm_test.go

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,68 @@ import (
1414
"github.com/google/go-cmp/cmp/cmpopts"
1515
)
1616

17+
func TestShouldNotOverwrite(t *testing.T) {
18+
tests := []struct {
19+
name string
20+
isNonSnapshot bool
21+
isProdHelmRepo bool
22+
force bool
23+
want bool
24+
}{
25+
{
26+
name: "Non-snapshot, prod repo, no force",
27+
isNonSnapshot: true,
28+
isProdHelmRepo: true,
29+
force: false,
30+
want: true,
31+
},
32+
{
33+
name: "Non-snapshot, prod repo, force true",
34+
isNonSnapshot: true,
35+
isProdHelmRepo: true,
36+
force: true,
37+
want: false,
38+
},
39+
{
40+
name: "Snapshot, prod repo, no force",
41+
isNonSnapshot: false,
42+
isProdHelmRepo: true,
43+
force: false,
44+
want: false,
45+
},
46+
{
47+
name: "Non-snapshot, dev repo, no force",
48+
isNonSnapshot: true,
49+
isProdHelmRepo: false,
50+
force: false,
51+
want: false,
52+
},
53+
{
54+
name: "Snapshot, dev repo, no force",
55+
isNonSnapshot: false,
56+
isProdHelmRepo: false,
57+
force: false,
58+
want: false,
59+
},
60+
{
61+
name: "Non-snapshot, dev repo, force true",
62+
isNonSnapshot: true,
63+
isProdHelmRepo: false,
64+
force: true,
65+
want: false,
66+
},
67+
}
68+
69+
for _, tt := range tests {
70+
t.Run(tt.name, func(t *testing.T) {
71+
got := shouldNotOverwrite(tt.isNonSnapshot, tt.isProdHelmRepo, tt.force)
72+
if got != tt.want {
73+
t.Errorf("shouldNotOverwrite() = %v, want %v", got, tt.want)
74+
}
75+
})
76+
}
77+
}
78+
1779
func Test_readCharts(t *testing.T) {
1880
tests := []struct {
1981
name string

hack/helm/release/trigger-helm-release.sh

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,17 @@
77
# Script to call the Buildkite API to trigger the release of the ECK Helm charts.
88
#
99
# Usage: BK_TOKEN=$(jq .graphql_token ~/.buildkite/config.json -r) \
10-
# BRANCH=2.8 DRY_RUN=true \
11-
# ./trigger-bk-release.sh SCOPE
10+
# BRANCH=2.8 DRY_RUN=true [FORCE=true] \
11+
# ./trigger-helm-release.sh SCOPE
1212
#
1313
# Required environment variables:
1414
# BK_TOKEN
1515
# BRANCH
1616
# DRY_RUN
1717
#
18+
# Optional environment variable:
19+
# FORCE (default: false)
20+
#
1821
# Argument:
1922
# SCOPE to select which charts to release ("all", "eck-operator" or "eck-stack")
2023
#
@@ -24,6 +27,7 @@ set -eu
2427
: "$BK_TOKEN"
2528
: "$BRANCH"
2629
: "$DRY_RUN"
30+
FORCE=${FORCE:-false}
2731

2832
# properties required to test PRs:
2933
# "pull_request_base_branch": "main",
@@ -40,7 +44,8 @@ main() {
4044
"branch": "'"$BRANCH"'",
4145
"message": "release '"$scope"' helm charts",
4246
"env": {
43-
"HELM_DRY_RUN": "'"$DRY_RUN"'"
47+
"HELM_DRY_RUN": "'"$DRY_RUN"'",
48+
"HELM_FORCE": "'"$FORCE"'"
4449
}
4550
}'
4651
}

0 commit comments

Comments
 (0)