Skip to content

Commit

Permalink
Merge pull request #207 from hypnoglow/ISSUE-73/index-force
Browse files Browse the repository at this point in the history
ISSUE-73 Add --force and --ignore-if-exists for init command
  • Loading branch information
hypnoglow committed Aug 30, 2022
2 parents e2394cf + aef6f3b commit e713c17
Show file tree
Hide file tree
Showing 10 changed files with 306 additions and 12 deletions.
4 changes: 2 additions & 2 deletions Makefile
Expand Up @@ -26,11 +26,11 @@ test-unit:

.PHONY: test-e2e
test-e2e:
go test -v ./tests/e2e/...
go test -v ./tests/e2e/... -run $(or $(RUN),.*)

.PHONY: test-e2e-local
test-e2e-local:
@./hack/test-e2e-local.sh
@./hack/test-e2e-local.sh $(RUN)

.PHONY: lint
lint:
Expand Down
95 changes: 92 additions & 3 deletions cmd/helm-s3/init.go
Expand Up @@ -2,6 +2,7 @@ package main

import (
"context"
"fmt"

"github.com/pkg/errors"
"github.com/spf13/cobra"
Expand All @@ -21,9 +22,11 @@ const initExample = ` helm s3 init s3://awesome-bucket/charts - inits chart rep

func newInitCommand(opts *options) *cobra.Command {
act := &initAction{
printer: nil,
acl: "",
uri: "",
printer: nil,
acl: "",
uri: "",
force: false,
ignoreIfExists: false,
}

cmd := &cobra.Command{
Expand All @@ -44,6 +47,10 @@ func newInitCommand(opts *options) *cobra.Command {
},
}

flags := cmd.Flags()
flags.BoolVar(&act.force, "force", act.force, "Replace the index file if it already exists.")
flags.BoolVar(&act.ignoreIfExists, "ignore-if-exists", act.ignoreIfExists, "If the index file already exists, exit normally and do not trigger an error.")

return cmd
}

Expand All @@ -57,9 +64,36 @@ type initAction struct {
// args

uri string

// flags

force bool
ignoreIfExists bool
}

func (act *initAction) run(ctx context.Context) error {
if act.force && act.ignoreIfExists {
act.printer.PrintErrf(
"The --force and --ignore-if-exists flags are mutually exclusive and cannot be specified together.\n",
)
return newSilentError()
}

repoEntry, ok, err := helmutil.LookupRepoEntryByURL(act.uri)
if err != nil {
return fmt.Errorf("lookup repo entry by url: %v", err)
}
if ok {
if act.ignoreIfExists {
return act.ignoreIfExistsError(repoEntry.Name())
}
if !act.force {
return act.alreadyExistsError(repoEntry.Name())
}

// fallthrough on --force
}

r, err := helmutil.NewIndex().Reader()
if err != nil {
return errors.WithMessage(err, "get index reader")
Expand All @@ -71,6 +105,21 @@ func (act *initAction) run(ctx context.Context) error {
}
storage := awss3.New(sess)

exists, err := storage.IndexExists(ctx, act.uri)
if err != nil {
return fmt.Errorf("check if index exists in the storage: %v", err)
}
if exists {
if act.ignoreIfExists {
return act.ignoreIfExistsInStorageError()
}
if !act.force {
return act.alreadyExistsInStorageError()
}

// fallthrough on --force
}

if err := storage.PutIndex(ctx, act.uri, act.acl, r); err != nil {
return errors.WithMessage(err, "upload index to s3")
}
Expand All @@ -83,3 +132,43 @@ func (act *initAction) run(ctx context.Context) error {
act.printer.Printf("Initialized empty repository at %s\n", act.uri)
return nil
}

func (act *initAction) ignoreIfExistsError(name string) error {
act.printer.Printf(
"The repository with the provided URI already exists under name %q, ignore init operation.\n",
name,
)
return nil
}

func (act *initAction) ignoreIfExistsInStorageError() error {
act.printer.Printf(
"The index file already exists under the provided URI, ignore init operation.\n",
)
return nil
}

func (act *initAction) alreadyExistsError(name string) error {
act.printer.PrintErrf(
"The repository with the provided URI already exists under name %[1]q, the index file and cannot be overwritten without an explicit intent.\n\n"+
"If you want to replace existing index file, use --force flag:\n\n"+
" helm s3 init --force %[2]s\n\n"+
"If you want to ignore this error, use --ignore-if-exists flag:\n\n"+
" helm s3 init --ignore-if-exists %[2]s\n\n",
name,
act.uri,
)
return newSilentError()
}

func (act *initAction) alreadyExistsInStorageError() error {
act.printer.PrintErrf(
"The index file already exists under the provided URI and cannot be overwritten without an explicit intent.\n\n"+
"If you want to replace existing index file, use --force flag:\n\n"+
" helm s3 init --force %[1]s\n\n"+
"If you want to ignore this error, use --ignore-if-exists flag:\n\n"+
" helm s3 init --ignore-if-exists %[1]s\n\n",
act.uri,
)
return newSilentError()
}
4 changes: 3 additions & 1 deletion cmd/helm-s3/push.go
Expand Up @@ -232,7 +232,9 @@ func (act *pushAction) run(ctx context.Context) error {
}

func (act *pushAction) ignoreIfExistsError() error {
act.printer.Printf("The chart already exists in the repository, keep existing chart and ignore push.")
act.printer.Printf(
"The chart already exists in the repository, keep existing chart and ignore push.\n",
)
return nil
}

Expand Down
3 changes: 2 additions & 1 deletion hack/test-e2e-local.sh
Expand Up @@ -12,6 +12,7 @@ export AWS_ENDPOINT=localhost:9000
export AWS_DISABLE_SSL=true

DOCKER_NAME='helm-s3-minio'
RUN="${1:-.*}"

cleanup() {
if docker container ls | grep -q "${DOCKER_NAME}$" ; then
Expand Down Expand Up @@ -50,7 +51,7 @@ go build -o bin/helm-s3 ./cmd/helm-s3

## Test

go test -v ./tests/e2e/...
go test -v ./tests/e2e/... -run "${RUN}"
if [ $? -eq 0 ] ; then
echo -e "\nAll tests passed!"
fi
14 changes: 13 additions & 1 deletion internal/awss3/storage.go
Expand Up @@ -276,7 +276,7 @@ func (s *Storage) PutIndex(ctx context.Context, uri string, acl string, r io.Rea
if strings.HasPrefix(uri, "index.yaml") {
return errors.New("uri must not contain \"index.yaml\" suffix, it appends automatically")
}
uri += "/index.yaml"
uri = helmutil.IndexFileURL(uri)

bucket, key, err := parseURI(uri)
if err != nil {
Expand All @@ -298,6 +298,18 @@ func (s *Storage) PutIndex(ctx context.Context, uri string, acl string, r io.Rea
return nil
}

// IndexExists returns true if index file exists in the storage for repository
// with the provided uri.
// uri must be in the form of s3 protocol: s3://bucket-name/key[...].
func (s *Storage) IndexExists(ctx context.Context, uri string) (bool, error) {
if strings.HasPrefix(uri, "index.yaml") {
return false, errors.New("uri must not contain \"index.yaml\" suffix, it appends automatically")
}
uri = helmutil.IndexFileURL(uri)

return s.Exists(ctx, uri)
}

// Delete deletes the object by uri.
// uri must be in the form of s3 protocol: s3://bucket-name/key[...].
func (s *Storage) Delete(ctx context.Context, uri string) error {
Expand Down
3 changes: 2 additions & 1 deletion internal/helmutil/helm.go
Expand Up @@ -13,7 +13,8 @@ func SetupHelm() {
setupHelm3()
}

func indexFile(repoURL string) string {
// IndexFileURL returns index file URL for the provided repository URL.
func IndexFileURL(repoURL string) string {
return strings.TrimSuffix(repoURL, "/") + "/index.yaml"
}

Expand Down
15 changes: 14 additions & 1 deletion internal/helmutil/repo_entry.go
@@ -1,13 +1,17 @@
package helmutil

type RepoEntry interface {
// Name returns repo name.
// Example: "my-charts".
Name() string

// URL returns repo URL.
// Examples:
// - https://kubernetes-charts.storage.googleapis.com/
// - s3://my-charts
URL() string

// IndexURI returns repo index file URL.
// IndexURL returns repo index file URL.
// Examples:
// - https://kubernetes-charts.storage.googleapis.com/index.yaml
// - s3://my-charts/index.yaml
Expand All @@ -27,3 +31,12 @@ func LookupRepoEntry(name string) (RepoEntry, error) {
}
return lookupV2(name)
}

// LookupRepoEntryByURL returns an entry from helm's repositories.yaml file by
// repo URL. If not found, returns false and <nil> error.
func LookupRepoEntryByURL(url string) (RepoEntry, bool, error) {
if IsHelm3() {
return lookupByURLV3(url)
}
return lookupByURLV2(url)
}
25 changes: 24 additions & 1 deletion internal/helmutil/repo_entry_v2.go
@@ -1,7 +1,9 @@
package helmutil

import (
"fmt"
"path/filepath"
"strings"

"github.com/pkg/errors"
"k8s.io/helm/pkg/repo"
Expand All @@ -12,12 +14,16 @@ type RepoEntryV2 struct {
entry *repo.Entry
}

func (r RepoEntryV2) Name() string {
return r.entry.Name
}

func (r RepoEntryV2) URL() string {
return r.entry.URL
}

func (r RepoEntryV2) IndexURL() string {
return indexFile(r.entry.URL)
return IndexFileURL(r.entry.URL)
}

func (r RepoEntryV2) CacheFile() string {
Expand All @@ -40,3 +46,20 @@ func lookupV2(name string) (RepoEntryV2, error) {

return RepoEntryV2{}, errors.Errorf("repo with name %s not found, try `helm repo add %s <uri>`", name, name)
}

func lookupByURLV2(url string) (RepoEntryV2, bool, error) {
repoFile, err := helm2LoadRepoFile(repoFilePathV2())
if err != nil {
return RepoEntryV2{}, false, fmt.Errorf("load repo file: %v", err)
}

url = strings.TrimSuffix(url, "/")
for _, entry := range repoFile.Repositories {
entryURL := strings.TrimSuffix(entry.URL, "/")
if url == entryURL {
return RepoEntryV2{entry: entry}, true, nil
}
}

return RepoEntryV2{}, false, nil
}
25 changes: 24 additions & 1 deletion internal/helmutil/repo_entry_v3.go
@@ -1,7 +1,9 @@
package helmutil

import (
"fmt"
"path/filepath"
"strings"

"github.com/pkg/errors"
"helm.sh/helm/v3/pkg/repo"
Expand All @@ -12,12 +14,16 @@ type RepoEntryV3 struct {
entry *repo.Entry
}

func (r RepoEntryV3) Name() string {
return r.entry.Name
}

func (r RepoEntryV3) URL() string {
return r.entry.URL
}

func (r RepoEntryV3) IndexURL() string {
return indexFile(r.entry.URL)
return IndexFileURL(r.entry.URL)
}

func (r RepoEntryV3) CacheFile() string {
Expand All @@ -37,3 +43,20 @@ func lookupV3(name string) (RepoEntryV3, error) {

return RepoEntryV3{entry: entry}, nil
}

func lookupByURLV3(url string) (RepoEntryV3, bool, error) {
repoFile, err := helm3LoadRepoFile(repoFilePathV3())
if err != nil {
return RepoEntryV3{}, false, fmt.Errorf("load repo file: %v", err)
}

url = strings.TrimSuffix(url, "/")
for _, entry := range repoFile.Repositories {
entryURL := strings.TrimSuffix(entry.URL, "/")
if url == entryURL {
return RepoEntryV3{entry: entry}, true, nil
}
}

return RepoEntryV3{}, false, nil
}

0 comments on commit e713c17

Please sign in to comment.