diff --git a/Gopkg.lock b/Gopkg.lock index 26be0b47..03803e95 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -59,8 +59,8 @@ "service/s3/s3manager", "service/sts" ] - revision = "00aba4fd6bb87ab518ee0857fea017804f40a6da" - version = "v1.12.58" + revision = "726cba4d63d34a7055809986b7942c3eae6c4eb2" + version = "v1.12.60" [[projects]] name = "github.com/ghodss/yaml" @@ -129,7 +129,7 @@ "openpgp/packet", "openpgp/s2k" ] - revision = "0fcca4842a8d74bfddc2c96a073bd2a4d2a7a2e8" + revision = "5f55bce93ad2c89f411e009659bb1fd83da36e7b" [[projects]] name = "gopkg.in/alecthomas/kingpin.v2" @@ -147,7 +147,7 @@ branch = "master" name = "k8s.io/apimachinery" packages = ["pkg/version"] - revision = "91d8586aac31d9086939d077ba556d2c7fb157b4" + revision = "c33db96a31b68b53ce6903caf3ba93571acb0e51" [[projects]] name = "k8s.io/client-go" @@ -178,6 +178,6 @@ [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "b92ce03447c6856aa3b4cf89c6a790f96f7857b0cd64e3450cd754e3b6da423d" + inputs-digest = "a56ef2157e9363aa359b5b89dd2f809e2c34df8fc2737ffea41eb0494368fd13" solver-name = "gps-cdcl" solver-version = 1 diff --git a/cmd/helms3/delete.go b/cmd/helms3/delete.go index 016ec986..ed5dcbc8 100644 --- a/cmd/helms3/delete.go +++ b/cmd/helms3/delete.go @@ -7,23 +7,28 @@ import ( "github.com/pkg/errors" "github.com/hypnoglow/helm-s3/pkg/awss3" + "github.com/hypnoglow/helm-s3/pkg/awsutil" "github.com/hypnoglow/helm-s3/pkg/helmutil" "github.com/hypnoglow/helm-s3/pkg/index" ) -func runDelete(name, version, repoName string) error { - repoEntry, err := helmutil.LookupRepoEntry(repoName) +type deleteAction struct { + name, version, repoName string +} + +func (act deleteAction) Run(ctx context.Context) error { + repoEntry, err := helmutil.LookupRepoEntry(act.repoName) if err != nil { return err } - storage := awss3.New() + sess, err := awsutil.Session() + if err != nil { + return err + } + storage := awss3.New(sess) // Fetch current index. - - ctx, cancel := context.WithTimeout(context.Background(), defaultTimeout) - defer cancel() - b, err := storage.FetchRaw(ctx, repoEntry.URL+"/index.yaml") if err != nil { return errors.WithMessage(err, "fetch current repo index") @@ -36,7 +41,7 @@ func runDelete(name, version, repoName string) error { // Update index. - chartVersion, err := idx.Delete(name, version) + chartVersion, err := idx.Delete(act.name, act.version) if err != nil { return err } @@ -53,9 +58,6 @@ func runDelete(name, version, repoName string) error { } uri := chartVersion.URLs[0] - ctx, cancel = context.WithTimeout(context.Background(), defaultTimeout*2) - defer cancel() - if err := storage.Delete(ctx, uri); err != nil { return errors.WithMessage(err, "delete chart file from s3") } diff --git a/cmd/helms3/init.go b/cmd/helms3/init.go index fab8002b..ca851376 100644 --- a/cmd/helms3/init.go +++ b/cmd/helms3/init.go @@ -6,21 +6,27 @@ import ( "github.com/pkg/errors" "github.com/hypnoglow/helm-s3/pkg/awss3" + "github.com/hypnoglow/helm-s3/pkg/awsutil" "github.com/hypnoglow/helm-s3/pkg/index" ) -func runInit(uri string) error { +type initAction struct { + uri string +} + +func (act initAction) Run(ctx context.Context) error { r, err := index.New().Reader() if err != nil { return errors.WithMessage(err, "get index reader") } - storage := awss3.New() - - ctx, cancel := context.WithTimeout(context.Background(), defaultTimeout) - defer cancel() + sess, err := awsutil.Session() + if err != nil { + return err + } + storage := awss3.New(sess) - if err := storage.PutIndex(ctx, uri, r); err != nil { + if err := storage.PutIndex(ctx, act.uri, r); err != nil { return errors.WithMessage(err, "upload index to s3") } diff --git a/cmd/helms3/main.go b/cmd/helms3/main.go index b25269ca..21d05786 100644 --- a/cmd/helms3/main.go +++ b/cmd/helms3/main.go @@ -1,6 +1,7 @@ package main import ( + "context" "fmt" "log" "os" @@ -20,12 +21,20 @@ const ( actionReindex = "reindex" actionDelete = "delete" - defaultTimeout = time.Second * 5 + defaultTimeout = time.Minute * 5 ) +// Action describes plugin action that can be run. +type Action interface { + Run(context.Context) error +} + func main() { if len(os.Args) == 5 { - if err := runProxy(os.Args[4]); err != nil { + cmd := proxyCmd{uri: os.Args[4]} + ctx, cancel := context.WithTimeout(context.Background(), defaultTimeout) + defer cancel() + if err := cmd.Run(ctx); err != nil { log.Fatal(err) } return @@ -69,35 +78,45 @@ func main() { os.Exit(0) } + var act Action switch action { case actionVersion: fmt.Print(version) return case actionInit: - if err := runInit(*initURI); err != nil { - log.Fatal(err) + act = initAction{ + uri: *initURI, } - fmt.Printf("Initialized empty repository at %s\n", *initURI) - return + defer fmt.Printf("Initialized empty repository at %s\n", *initURI) case actionPush: - if err := runPush(*pushChartPath, *pushTargetRepository); err != nil { - log.Fatal(err) + act = pushAction{ + chartPath: *pushChartPath, + repoName: *pushTargetRepository, } - return case actionReindex: fmt.Fprint(os.Stderr, "Warning: reindex feature is in beta. If you experience any issues,\nplease provide your feedback here: https://github.com/hypnoglow/helm-s3/issues/22\n\n") - if err := runReindex(*reindexTargetRepository); err != nil { - log.Fatal(err) + act = reindexAction{ + repoName: *reindexTargetRepository, } - fmt.Printf("Repository %s was successfully reindexed.\n", *reindexTargetRepository) + defer fmt.Printf("Repository %s was successfully reindexed.\n", *reindexTargetRepository) case actionDelete: - if err := runDelete(*deleteChartName, *deleteChartVersion, *deleteTargetRepository); err != nil { - log.Fatal(err) + act = deleteAction{ + name: *deleteChartName, + version: *deleteChartVersion, + repoName: *deleteTargetRepository, } + default: return } + + ctx, cancel := context.WithTimeout(context.Background(), defaultTimeout) + defer cancel() + + if err := act.Run(ctx); err != nil { + log.Fatal(err) + } } diff --git a/cmd/helms3/proxy.go b/cmd/helms3/proxy.go index 1b46bf4f..f71e5cf3 100644 --- a/cmd/helms3/proxy.go +++ b/cmd/helms3/proxy.go @@ -9,19 +9,24 @@ import ( "github.com/pkg/errors" "github.com/hypnoglow/helm-s3/pkg/awss3" + "github.com/hypnoglow/helm-s3/pkg/awsutil" ) -func runProxy(uri string) error { - - storage := awss3.New() +type proxyCmd struct { + uri string +} - ctx, cancel := context.WithTimeout(context.Background(), defaultTimeout) - defer cancel() +func (act proxyCmd) Run(ctx context.Context) error { + sess, err := awsutil.Session(awsutil.AssumeRoleTokenProvider(awsutil.StderrTokenProvider)) + if err != nil { + return err + } + storage := awss3.New(sess) - b, err := storage.FetchRaw(ctx, uri) + b, err := storage.FetchRaw(ctx, act.uri) if err != nil { - if strings.HasSuffix(uri, "index.yaml") && err == awss3.ErrObjectNotFound { - return fmt.Errorf("The index file does not exist by the path %s. If you haven't initialized the repository yet, try running \"helm s3 init %s\"", uri, path.Dir(uri)) + if strings.HasSuffix(act.uri, "index.yaml") && err == awss3.ErrObjectNotFound { + return fmt.Errorf("The index file does not exist by the path %s. If you haven't initialized the repository yet, try running \"helm s3 init %s\"", act.uri, path.Dir(act.uri)) } return errors.WithMessage(err, "fetch from s3") } diff --git a/cmd/helms3/push.go b/cmd/helms3/push.go index 70ce1b88..0f1adb3b 100644 --- a/cmd/helms3/push.go +++ b/cmd/helms3/push.go @@ -6,27 +6,30 @@ import ( "fmt" "os" "path/filepath" - "time" "github.com/pkg/errors" "k8s.io/helm/pkg/chartutil" "k8s.io/helm/pkg/provenance" "github.com/hypnoglow/helm-s3/pkg/awss3" + "github.com/hypnoglow/helm-s3/pkg/awsutil" "github.com/hypnoglow/helm-s3/pkg/helmutil" "github.com/hypnoglow/helm-s3/pkg/index" ) -const ( - pushCommandDefaultTimeout = time.Second * 15 -) +type pushAction struct { + chartPath string + repoName string +} -func runPush(chartPath string, repoName string) error { - // Just one big timeout for the whole operation. - ctx, cancel := context.WithTimeout(context.Background(), pushCommandDefaultTimeout) - defer cancel() +func (act pushAction) Run(ctx context.Context) error { + sess, err := awsutil.Session() + if err != nil { + return err + } + storage := awss3.New(sess) - fpath, err := filepath.Abs(chartPath) + fpath, err := filepath.Abs(act.chartPath) if err != nil { return errors.WithMessage(err, "get chart abs path") } @@ -38,8 +41,6 @@ func runPush(chartPath string, repoName string) error { return errors.Wrapf(err, "change dir to %s", dir) } - storage := awss3.New() - // Load chart, calculate required params like hash, // and upload the chart right away. @@ -48,7 +49,7 @@ func runPush(chartPath string, repoName string) error { return fmt.Errorf("file %s is not a helm chart archive", fname) } - repoEntry, err := helmutil.LookupRepoEntry(repoName) + repoEntry, err := helmutil.LookupRepoEntry(act.repoName) if err != nil { return err } diff --git a/cmd/helms3/reindex.go b/cmd/helms3/reindex.go index 4d6b3ef7..335659f6 100644 --- a/cmd/helms3/reindex.go +++ b/cmd/helms3/reindex.go @@ -2,30 +2,30 @@ package main import ( "context" - "time" "github.com/pkg/errors" "github.com/hypnoglow/helm-s3/pkg/awss3" + "github.com/hypnoglow/helm-s3/pkg/awsutil" "github.com/hypnoglow/helm-s3/pkg/helmutil" "github.com/hypnoglow/helm-s3/pkg/index" ) -const ( - reindexCommandDefaultTimeout = time.Second * 15 -) - -func runReindex(repoName string) error { - // Just one big timeout for the whole operation. - ctx, cancel := context.WithTimeout(context.Background(), reindexCommandDefaultTimeout) - defer cancel() +type reindexAction struct { + repoName string +} - repoEntry, err := helmutil.LookupRepoEntry(repoName) +func (act reindexAction) Run(ctx context.Context) error { + repoEntry, err := helmutil.LookupRepoEntry(act.repoName) if err != nil { return err } - storage := awss3.New() + sess, err := awsutil.Session() + if err != nil { + return err + } + storage := awss3.New(sess) items, errs := storage.Traverse(ctx, repoEntry.URL) diff --git a/pkg/awss3/storage.go b/pkg/awss3/storage.go index 9ae334b1..62f4043b 100644 --- a/pkg/awss3/storage.go +++ b/pkg/awss3/storage.go @@ -18,8 +18,6 @@ import ( "k8s.io/helm/pkg/chartutil" "k8s.io/helm/pkg/proto/hapi/chart" "k8s.io/helm/pkg/provenance" - - "github.com/hypnoglow/helm-s3/pkg/awsutil" ) var ( @@ -31,8 +29,8 @@ var ( ) // New returns a new Storage. -func New() *Storage { - return &Storage{} +func New(session *session.Session) *Storage { + return &Storage{session: session} } // Storage provides an interface to work with AWS S3 objects by s3 protocol. @@ -55,11 +53,6 @@ func (s *Storage) traverse(ctx context.Context, repoURI string, items chan<- Cha defer close(items) defer close(errs) - if err := s.initSession(); err != nil { - errs <- err - return - } - bucket, key, err := parseURI(repoURI) if err != nil { errs <- err @@ -171,10 +164,6 @@ type ChartInfo struct { // FetchRaw downloads the object from URI and returns it in the form of byte slice. // uri must be in the form of s3 protocol: s3://bucket-name/key[...]. func (s *Storage) FetchRaw(ctx context.Context, uri string) ([]byte, error) { - if err := s.initSession(); err != nil { - return nil, err - } - bucket, key, err := parseURI(uri) if err != nil { return nil, err @@ -206,10 +195,6 @@ func (s *Storage) FetchRaw(ctx context.Context, uri string) ([]byte, error) { // PutChart puts the chart file to the storage. // uri must be in the form of s3 protocol: s3://bucket-name/key[...]. func (s *Storage) PutChart(ctx context.Context, uri string, r io.Reader, chartMeta, chartDigest string) (string, error) { - if err := s.initSession(); err != nil { - return "", err - } - bucket, key, err := parseURI(uri) if err != nil { return "", err @@ -241,10 +226,6 @@ func (s *Storage) PutIndex(ctx context.Context, uri string, r io.Reader) error { } uri += "/index.yaml" - if err := s.initSession(); err != nil { - return err - } - bucket, key, err := parseURI(uri) if err != nil { return err @@ -267,10 +248,6 @@ func (s *Storage) PutIndex(ctx context.Context, uri string, r io.Reader) error { // 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 { - if err := s.initSession(); err != nil { - return err - } - bucket, key, err := parseURI(uri) if err != nil { return err @@ -290,15 +267,6 @@ func (s *Storage) Delete(ctx context.Context, uri string) error { return nil } -func (s *Storage) initSession() (err error) { - if s.session != nil { - return nil - } - - s.session, err = awsutil.Session() - return errors.Wrap(err, "init aws session") -} - // parseURI returns bucket and key from URIs like: // - s3://bucket-name/dir // - s3://bucket-name/dir/file.ext diff --git a/pkg/awsutil/session.go b/pkg/awsutil/session.go index 832319eb..fcf6e094 100644 --- a/pkg/awsutil/session.go +++ b/pkg/awsutil/session.go @@ -2,7 +2,6 @@ package awsutil import ( "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/credentials/stscreds" "github.com/aws/aws-sdk-go/aws/session" ) @@ -14,15 +13,31 @@ var ( awsEndpoint = "" ) +// SessionOption is an option for session. +type SessionOption func(*session.Options) + +// AssumeRoleTokenProvider is an option for setting custom assume role token provider. +func AssumeRoleTokenProvider(provider func() (string, error)) SessionOption { + return func(options *session.Options) { + options.AssumeRoleTokenProvider = provider + } +} + // Session returns an AWS session as described http://docs.aws.amazon.com/sdk-for-go/v1/developer-guide/configuring-sdk.html -func Session() (*session.Session, error) { - return session.NewSessionWithOptions(session.Options{ +func Session(opts ...SessionOption) (*session.Session, error) { + so := session.Options{ Config: aws.Config{ DisableSSL: aws.Bool(awsDisableSSL == "true"), S3ForcePathStyle: aws.Bool(true), Endpoint: aws.String(awsEndpoint), }, SharedConfigState: session.SharedConfigEnable, - AssumeRoleTokenProvider: stscreds.StdinTokenProvider, - }) + AssumeRoleTokenProvider: StderrTokenProvider, + } + + for _, opt := range opts { + opt(&so) + } + + return session.NewSessionWithOptions(so) } diff --git a/pkg/awsutil/token_provider.go b/pkg/awsutil/token_provider.go new file mode 100644 index 00000000..3efc097d --- /dev/null +++ b/pkg/awsutil/token_provider.go @@ -0,0 +1,15 @@ +package awsutil + +import ( + "fmt" + "os" +) + +// StderrTokenProvider implements token provider for AWS SDK. +func StderrTokenProvider() (string, error) { + var v string + fmt.Fprintf(os.Stderr, "Assume Role MFA token code: ") + _, err := fmt.Fscanln(os.Stderr, &v) + + return v, err +}