diff --git a/README.md b/README.md index 286ad6fe..6b744f0a 100644 --- a/README.md +++ b/README.md @@ -328,6 +328,85 @@ your repository users won't have to install this plugin. To do this, you need your charts to have relative URLs in the index. See [Relative chart URLs](#relative-chart-urls). +
+Example of setting up a public repo using Virtual hosting of buckets + +1. Create S3 bucket named `example-bucket` in EU (Frankfurt) `eu-central-1` region. + +2. Go to "Permissions", edit Bucket Policy: + + ``` + { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": "*", + "Action": [ + "s3:ListBucket", + "s3:GetObject" + ], + "Resource": [ + "arn:aws:s3:::example-bucket", + "arn:aws:s3:::example-bucket/*" + ] + } + ] + } + ``` + +3. Initialize repository: + + ``` + $ helm s3 init s3://example-bucket + Initialized empty repository at s3://example-bucket + ``` + +4. Add repository: + + ``` + $ helm repo add example-bucket s3://example-bucket + "example-bucket" has been added to your repositories + ``` + +5. Create demo chart: + + ``` + $ helm create petstore + Creating petstore + + $ helm package petstore --version 1.0.0 + Successfully packaged chart and saved it to: petstore-1.0.0.tgz + ``` + +6. Push chart: + + ``` + $ helm s3 push ./petstore-1.0.0.tgz --relative + Successfully uploaded the chart to the repository. + ``` + +7. The bucket is public and chart repo is set up. Now users can use the repo + without the need to install helm-s3 plugin. + + Add HTTP repo: + + ``` + $ helm repo add example-bucket-http https://example-bucket.s3.eu-central-1.amazonaws.com/ + "example-bucket-http" has been added to your repositories + ``` + + Search and download charts: + + ``` + $ helm search repo example-bucket-http + NAME CHART VERSION APP VERSION DESCRIPTION + example-bucket-http/petstore 1.0.0 1.16.0 A Helm chart for Kubernetes + + $ helm pull example-bucket-http/petstore --version 1.0.0 + ``` +
+ ### ACLs In use cases where you share a repo across multiple AWS accounts, you may want diff --git a/cmd/helm-s3/push.go b/cmd/helm-s3/push.go index 977f735f..0cfb257a 100644 --- a/cmd/helm-s3/push.go +++ b/cmd/helm-s3/push.go @@ -199,11 +199,15 @@ func (act *pushAction) run(ctx context.Context) error { if err := idx.UnmarshalBinary(b); err != nil { return errors.WithMessage(err, "load index from downloaded file") } + baseURL := repoEntry.URL() if act.relative { baseURL = "" } - if err := idx.AddOrReplace(chart.Metadata().Value(), fname, baseURL, hash); err != nil { + + filename := escapeIfRelative(fname, act.relative) + + if err := idx.AddOrReplace(chart.Metadata().Value(), filename, baseURL, hash); err != nil { return errors.WithMessage(err, "add/replace chart in the index") } idx.SortEntries() diff --git a/cmd/helm-s3/reindex.go b/cmd/helm-s3/reindex.go index 8f33b4eb..a6543ca0 100644 --- a/cmd/helm-s3/reindex.go +++ b/cmd/helm-s3/reindex.go @@ -89,7 +89,10 @@ func (act *reindexAction) run(ctx context.Context) error { if act.relative { baseURL = "" } - if err := idx.Add(item.Meta.Value(), item.Filename, baseURL, item.Hash); err != nil { + + filename := escapeIfRelative(item.Filename, act.relative) + + if err := idx.Add(item.Meta.Value(), filename, baseURL, item.Hash); err != nil { act.printer.PrintErrf("[ERROR] failed to add chart to the index: %s", err) } } diff --git a/cmd/helm-s3/util.go b/cmd/helm-s3/util.go index 782c1957..a9810bbe 100644 --- a/cmd/helm-s3/util.go +++ b/cmd/helm-s3/util.go @@ -1,6 +1,27 @@ package main +import "github.com/hypnoglow/helm-s3/internal/awsutil" + type printer interface { Printf(format string, v ...interface{}) PrintErrf(format string, i ...interface{}) } + +// escapeIfRelative escapes chart filename if it is indexed as relative. +// +// Note: we escape filename only if 'relative' is set for a few reasons: +// - Full URLs don't need to be escaped because with full URLs in the index +// the charts can be downloaded only with this plugin; +// - Even if we escape the filename here, the code in Index.Add and +// Index.AddOrReplace (in particular, the call to urlutil.URLJoin) will +// break the URL, e.g. the escaped filename "petstore-1.0.0%2B102.tgz" +// with the "s3://example-bucket" baseURL will become +// "s3://example-bucket/petstore-1.0.0%252B102.tgz". +// So if we ever decide to escape, we need to fix this. +func escapeIfRelative(filename string, relative bool) string { + if !relative { + return filename + } + + return awsutil.EscapePath(filename) +} diff --git a/internal/awsutil/url.go b/internal/awsutil/url.go new file mode 100644 index 00000000..9811713c --- /dev/null +++ b/internal/awsutil/url.go @@ -0,0 +1,10 @@ +package awsutil + +import "github.com/aws/aws-sdk-go/private/protocol/rest" + +// EscapePath escapes URL path according to AWS escaping rules. +// +// This func can be used to escape S3 object keys for HTTP access. +func EscapePath(path string) string { + return rest.EscapePath(path, true) +}