diff --git a/Makefile b/Makefile index 7b74f894d..84dfef4f8 100644 --- a/Makefile +++ b/Makefile @@ -197,7 +197,7 @@ goreleaser: .state/goreleaser curl -sL https://git.io/goreleaser | bash -s -- --snapshot --rm-dist --config .goreleaser.unstable.yml run: bin/ship - ./bin/ship --log-level=debug --studio-file=./app.yml + ./bin/ship --log-level=debug --runbook=./app.yml # this should really be in a different repo build_yoonit_docker_image: @@ -213,6 +213,6 @@ pkg/lifeycle/daemon/ui.bindatafs.go: $(UI) embed-ui: pkg/lifeycle/daemon/ui.bindatafs.go -build-ui: +build-ui: cd web; yarn install --force cd web; `yarn bin`/webpack --config webpack.config.js --env ship --mode production diff --git a/hack/docs/Makefile b/hack/docs/Makefile index db298030f..dbb2b65f6 100644 --- a/hack/docs/Makefile +++ b/hack/docs/Makefile @@ -59,4 +59,4 @@ lint: `yarn bin`/replicated-lint validate -f ../../e2e-retraced/ship-retraced.yml --excludeDefaults --schema $(SCHEMA_JSON) ship: - ../../bin/ship --studio-file ../../e2e-retraced/ship-retraced.yml --log-level debug + ../../bin/ship --runbook ../../e2e-retraced/ship-retraced.yml --log-level debug diff --git a/integration/base/README.md b/integration/base/README.md index fd80dacbe..5b152f78b 100644 --- a/integration/base/README.md +++ b/integration/base/README.md @@ -30,12 +30,12 @@ desired customer ID/installation ID/release semver, a folder 'input' containing containing the expected output of running ship with that state file, release yaml, and customer ID/installation ID/release semver. -Each integration test is run twice - once in studio (or 'local yaml') mode and once in online mode. +Each integration test is run twice - once in runbook (or 'local yaml') mode and once in online mode. Both runs are headless and use the Cobra API to simulate running Ship from the CLI. -The studio mode run will use the release yaml located at `input/.ship/release.yml` and the state file located at `input/.ship/state.json`. +The runbook mode run will use the release yaml located at `input/.ship/release.yml` and the state file located at `input/.ship/state.json`. The online mode run will use the state file at `input/.ship/state.json` but will get the release yaml from the graphql api using the provided customer ID, installation ID and release semver. Files are produced in a temporary directory created within the integration test directory. -The contents of this directory is then diffed with the contents of `expected/`. +The contents of this directory is then diffed with the contents of `expected/`. File names and contents must match. To add a new test, create a release that should demonstrate the desired behavior in the integration test staging account. diff --git a/integration/base/helm-nginx/metadata.yaml b/integration/base/helm-nginx/metadata.yaml index 4752f4b5c..1c1450c02 100644 --- a/integration/base/helm-nginx/metadata.yaml +++ b/integration/base/helm-nginx/metadata.yaml @@ -1,6 +1,6 @@ customer_id: "-Am-_6i5pw0u4AbspOwKN4lZUCn49u_G" installation_id: "WAtB86RsD8koFOgnAEX63TU0mafWsqAj" release_version: "0.0.2" -studio_channel_name: "integration-test-helm" +set_channel_name: "integration-test-helm" skip_cleanup: false diff --git a/integration/base/integration_test.go b/integration/base/integration_test.go index a9e456436..eb1921271 100644 --- a/integration/base/integration_test.go +++ b/integration/base/integration_test.go @@ -18,12 +18,12 @@ import ( ) type TestMetadata struct { - CustomerID string `yaml:"customer_id"` - InstallationID string `yaml:"installation_id"` - ReleaseVersion string `yaml:"release_version"` - StudioChannelName string `yaml:"studio_channel_name"` - Flavor string `yaml:"flavor"` - DisableOnline bool `yaml:"disable_online"` + CustomerID string `yaml:"customer_id"` + InstallationID string `yaml:"installation_id"` + ReleaseVersion string `yaml:"release_version"` + SetChannelName string `yaml:"set_channel_name"` + Flavor string `yaml:"flavor"` + DisableOnline bool `yaml:"disable_online"` //debugging SkipCleanup bool `yaml:"skip_cleanup"` @@ -85,9 +85,9 @@ var _ = Describe("basic", func() { cmd.SetArgs([]string{ "app", "--headless", - fmt.Sprintf("--studio-file=%s", path.Join(testInputPath, ".ship/release.yml")), + fmt.Sprintf("--runbook=%s", path.Join(testInputPath, ".ship/release.yml")), fmt.Sprintf("--state-file=%s", path.Join(testInputPath, ".ship/state.json")), - fmt.Sprintf("--studio-channel-name=%s", testMetadata.StudioChannelName), + fmt.Sprintf("--set-channel-name=%s", testMetadata.SetChannelName), fmt.Sprintf("--release-semver=%s", testMetadata.ReleaseVersion), "--log-level=off", "--terraform-yes", diff --git a/pkg/cli/app.go b/pkg/cli/app.go index 6cc0400bc..702a6c052 100644 --- a/pkg/cli/app.go +++ b/pkg/cli/app.go @@ -9,6 +9,10 @@ import ( "github.com/spf13/viper" ) +const ( + developerFlagUsage = "Useful for debugging your specs on the command line, without having to make round trips to the server" +) + func App() *cobra.Command { cmd := &cobra.Command{ Use: "app", @@ -30,10 +34,18 @@ func App() *cobra.Command { cmd.Flags().String("release-semver", "", "specific release version to pin installation to. Requires channel-id") cmd.Flags().Bool("terraform-yes", false, "Automatically answer \"yes\" to all terraform prompts") - // optional, devloper-tools - cmd.Flags().String("studio-file", "", "Useful for debugging your specs on the command line, without having to make round trips to the server") - cmd.Flags().String("studio-channel-name", "", "Useful for debugging your specs on the command line, without having to make round trips to the server") - cmd.Flags().String("studio-channel-icon", "", "Useful for debugging your specs on the command line, without having to make round trips to the server") + // optional developer flags + cmd.Flags().String("runbook", "", developerFlagUsage) + cmd.Flags().String("set-channel-name", "", developerFlagUsage) + cmd.Flags().String("set-channel-icon", "", developerFlagUsage) + + // Deprecated developer flags + cmd.Flags().String("studio-file", "", developerFlagUsage) + cmd.Flags().MarkDeprecated("studio-file", "please upgrade to the --runbook flag") + cmd.Flags().String("studio-channel-name", "", developerFlagUsage) + cmd.Flags().MarkDeprecated("studio-channel-name", "please upgrade to the --set-channel-name flag") + cmd.Flags().String("studio-channel-icon", "", developerFlagUsage) + cmd.Flags().MarkDeprecated("studio-channel-icon", "please upgrade to the --set-channel-icon flag") viper.BindPFlags(cmd.Flags()) viper.AutomaticEnv() diff --git a/pkg/helpers/flags/flags.go b/pkg/helpers/flags/flags.go new file mode 100644 index 000000000..5beadfd01 --- /dev/null +++ b/pkg/helpers/flags/flags.go @@ -0,0 +1,13 @@ +package flags + +import ( + "github.com/spf13/viper" +) + +func GetCurrentOrDeprecatedString(v *viper.Viper, currentKey string, deprecatedKey string) string { + currentKeyValue := v.GetString(currentKey) + if currentKeyValue == "" { + return v.GetString(deprecatedKey) + } + return currentKeyValue +} diff --git a/pkg/helpers/flags/flags_test.go b/pkg/helpers/flags/flags_test.go new file mode 100644 index 000000000..828ecdb1c --- /dev/null +++ b/pkg/helpers/flags/flags_test.go @@ -0,0 +1,65 @@ +package flags + +import ( + "testing" + + "github.com/spf13/viper" +) + +func Test_GetCurrentOrDeprecatedString(t *testing.T) { + currentStringViperExample := viper.New() + currentStringViperExample.Set("currentFlag", "123") + + deprecatedStringViperExample := viper.New() + deprecatedStringViperExample.Set("deprecatedFlag", "456") + + currentAndDeprecatedStringViperExample := viper.New() + currentAndDeprecatedStringViperExample.Set("currentFlag", "123") + currentAndDeprecatedStringViperExample.Set("deprecatedFlag", "456") + + type args struct { + v *viper.Viper + currentKey string + deprecatedKey string + } + tests := []struct { + name string + args args + want string + }{ + { + name: "Current", + args: args{ + v: currentStringViperExample, + currentKey: "currentFlag", + deprecatedKey: "deprecatedFlag", + }, + want: "123", + }, + { + name: "Deprecated", + args: args{ + v: deprecatedStringViperExample, + currentKey: "currentFlag", + deprecatedKey: "deprecatedFlag", + }, + want: "456", + }, + { + name: "Current and deprecated favors current", + args: args{ + v: currentAndDeprecatedStringViperExample, + currentKey: "currentFlag", + deprecatedKey: "deprecatedFlag", + }, + want: "123", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := GetCurrentOrDeprecatedString(tt.args.v, tt.args.currentKey, tt.args.deprecatedKey); got != tt.want { + t.Errorf("getCurrentOrDeprecatedFlagString() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/pkg/ship/ship.go b/pkg/ship/ship.go index bf78f2070..4ce31ad41 100644 --- a/pkg/ship/ship.go +++ b/pkg/ship/ship.go @@ -6,6 +6,8 @@ import ( "os" "time" + "github.com/replicatedhq/ship/pkg/helpers/flags" + "os/signal" "syscall" @@ -38,12 +40,12 @@ type Ship struct { InstallationID string PlanOnly bool - Daemon daemon.Daemon - Resolver *specs.Resolver - StudioFile string - Client *specs.GraphQLClient - UI cli.Ui - State *state.Manager + Daemon daemon.Daemon + Resolver *specs.Resolver + Runbook string + Client *specs.GraphQLClient + UI cli.Ui + State *state.Manager KustomizeRaw string Runner *lifecycle.Runner @@ -69,7 +71,7 @@ func NewShip( ReleaseSemver: v.GetString("release-semver"), ChannelID: v.GetString("channel-id"), InstallationID: v.GetString("installation-id"), - StudioFile: v.GetString("studio-file"), + Runbook: flags.GetCurrentOrDeprecatedString(v, "runbook", "studio-file"), KustomizeRaw: v.GetString("raw"), @@ -120,19 +122,19 @@ func (s *Ship) Execute(ctx context.Context) error { "customer-id", s.CustomerID, "installation-id", s.InstallationID, "plan_only", s.PlanOnly, - "studio-file", s.StudioFile, + "runbook", s.Runbook, "api-port", s.APIPort, "headless", s.Headless, ) debug.Log("phase", "validate-inputs") - if s.CustomerID == "" && s.StudioFile == "" && s.KustomizeRaw == "" { + if s.CustomerID == "" && s.Runbook == "" && s.KustomizeRaw == "" { debug.Log("phase", "validate-inputs", "error", "missing customer ID") return errors.New("missing parameter customer-id, Please provide your license key or customer ID") } - if s.InstallationID == "" && s.StudioFile == "" && s.KustomizeRaw == "" { + if s.InstallationID == "" && s.Runbook == "" && s.KustomizeRaw == "" { debug.Log("phase", "validate-inputs", "error", "missing installation ID") return errors.New("missing parameter installation-id, Please provide your license key or customer ID") } @@ -147,11 +149,11 @@ func (s *Ship) Execute(ctx context.Context) error { ChannelID: s.ChannelID, InstallationID: s.InstallationID, } - cloudOrStudioRelease, err := s.Resolver.ResolveRelease(ctx, *selector) + cloudOrRunbookRelease, err := s.Resolver.ResolveRelease(ctx, *selector) if err != nil { return errors.Wrap(err, "resolve specs") } - release = cloudOrStudioRelease + release = cloudOrRunbookRelease return s.execute(ctx, release, selector, false) } diff --git a/pkg/specs/resolver.go b/pkg/specs/resolver.go index f12d993c8..3fc08ad09 100644 --- a/pkg/specs/resolver.go +++ b/pkg/specs/resolver.go @@ -9,6 +9,7 @@ import ( "github.com/go-kit/kit/log/level" "github.com/pkg/errors" "github.com/replicatedhq/ship/pkg/api" + "github.com/replicatedhq/ship/pkg/helpers/flags" "github.com/replicatedhq/ship/pkg/state" "github.com/spf13/afero" "github.com/spf13/viper" @@ -32,16 +33,16 @@ type Selector struct { // A Resolver resolves specs type Resolver struct { - Logger log.Logger - Client *GraphQLClient - GithubClient *GithubClient - StateManager *state.Manager - FS afero.Afero - StudioFile string - StudioChannelName string - StudioReleaseSemver string - StudioChannelIcon string - HelmChartGitPath string + Logger log.Logger + Client *GraphQLClient + GithubClient *GithubClient + StateManager *state.Manager + FS afero.Afero + Runbook string + SetChannelName string + RunbookReleaseSemver string + SetChannelIcon string + HelmChartGitPath string } // NewResolver builds a resolver from a Viper instance @@ -54,21 +55,21 @@ func NewResolver( stateManager *state.Manager, ) *Resolver { return &Resolver{ - Logger: logger, - Client: graphql, - GithubClient: githubClient, - StateManager: stateManager, - FS: fs, - StudioFile: v.GetString("studio-file"), - StudioChannelName: v.GetString("studio-channel-name"), - StudioChannelIcon: v.GetString("studio-channel-icon"), - StudioReleaseSemver: v.GetString("release-semver"), - HelmChartGitPath: v.GetString("chart"), + Logger: logger, + Client: graphql, + GithubClient: githubClient, + StateManager: stateManager, + FS: fs, + Runbook: flags.GetCurrentOrDeprecatedString(v, "runbook", "studio-file"), + SetChannelName: flags.GetCurrentOrDeprecatedString(v, "set-channel-name", "studio-channel-name"), + SetChannelIcon: flags.GetCurrentOrDeprecatedString(v, "set-channel-icon", "studio-channel-icon"), + RunbookReleaseSemver: v.GetString("release-semver"), + HelmChartGitPath: v.GetString("chart"), } } // ResolveRelease uses the passed config options to get specs from pg.replicated.com or -// from a local studio-file if so configured +// from a local runbook if so configured func (r *Resolver) ResolveRelease(ctx context.Context, selector Selector) (*api.Release, error) { var specYAML []byte var err error @@ -76,10 +77,10 @@ func (r *Resolver) ResolveRelease(ctx context.Context, selector Selector) (*api. debug := level.Debug(log.With(r.Logger, "method", "ResolveRelease")) - if r.StudioFile != "" { - release, err = r.resolveStudioRelease() + if r.Runbook != "" { + release, err = r.resolveRunbookRelease() if err != nil { - return nil, errors.Wrapf(err, "resolve studio spec from %s", r.StudioFile) + return nil, errors.Wrapf(err, "resolve runbook from %s", r.Runbook) } } else { release, err = r.resolveCloudRelease(selector.CustomerID, selector.InstallationID, selector.ReleaseSemver) @@ -105,26 +106,26 @@ func (r *Resolver) ResolveRelease(ctx context.Context, selector Selector) (*api. return result, nil } -func (r *Resolver) resolveStudioRelease() (*ShipRelease, error) { - debug := level.Debug(log.With(r.Logger, "method", "resolveStudioSpec")) - debug.Log("phase", "load-specs", "from", "studio-file", "file", r.StudioFile) +func (r *Resolver) resolveRunbookRelease() (*ShipRelease, error) { + debug := level.Debug(log.With(r.Logger, "method", "resolveRunbookRelease")) + debug.Log("phase", "load-specs", "from", "runbook", "file", r.Runbook) - specYAML, err := r.StateManager.FS.ReadFile(r.StudioFile) + specYAML, err := r.StateManager.FS.ReadFile(r.Runbook) if err != nil { - return nil, errors.Wrapf(err, "read specs from %s", r.StudioFile) + return nil, errors.Wrapf(err, "read specs from %s", r.Runbook) } - debug.Log("phase", "load-specs", "from", "studio-file", "file", r.StudioFile, "spec", specYAML) + debug.Log("phase", "load-specs", "from", "runbook", "file", r.Runbook, "spec", specYAML) if err := r.persistSpec(specYAML); err != nil { return nil, errors.Wrapf(err, "serialize last-used YAML to disk") } - debug.Log("phase", "write-yaml", "from", r.StudioFile, "write-location", ReleasePath) + debug.Log("phase", "write-yaml", "from", r.Runbook, "write-location", ReleasePath) return &ShipRelease{ Spec: string(specYAML), - ChannelName: r.StudioChannelName, - ChannelIcon: r.StudioChannelIcon, - Semver: r.StudioReleaseSemver, + ChannelName: r.SetChannelName, + ChannelIcon: r.SetChannelIcon, + Semver: r.RunbookReleaseSemver, }, nil } @@ -159,7 +160,7 @@ func (r *Resolver) persistSpec(specYAML []byte) error { } func (r *Resolver) RegisterInstall(ctx context.Context, selector Selector, release *api.Release) error { - if r.StudioFile != "" { + if r.Runbook != "" { return nil }