diff --git a/.gitignore b/.gitignore index 6c1b763..bab6306 100644 --- a/.gitignore +++ b/.gitignore @@ -42,4 +42,5 @@ mage_output_file.go .DS_Store bosun -!bosun/ \ No newline at end of file +!bosun/ + diff --git a/.goreleaser.yml b/.goreleaser.yml index 34860d8..26c8020 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -1,9 +1,11 @@ -# This is an example goreleaser.yaml file with some sane defaults. + # Make sure to check the documentation at http://goreleaser.com before: hooks: # you may remove this if you don't use vgo - go mod download +release: + prerelease: auto builds: - env: - CGO_ENABLED=0 @@ -29,5 +31,5 @@ changelog: sort: asc filters: exclude: - - '^docs:' - - '^test:' + - '^docs' + - '^test' diff --git a/bosun.yaml b/bosun.yaml index 4715d7a..05cdf05 100644 --- a/bosun.yaml +++ b/bosun.yaml @@ -6,7 +6,7 @@ appRefs: {} apps: - name: bosun repo: naveego/bosun - version: 0.8.5 + version: 1.0.0-beta images: [] scripts: - name: publish @@ -46,3 +46,4 @@ apps: authSource: admin databaseFile: test/mongo/db.yaml rebuildDb: false +repos: [] diff --git a/cmd/app.go b/cmd/app.go index 9f02947..90759d0 100644 --- a/cmd/app.go +++ b/cmd/app.go @@ -16,12 +16,11 @@ package cmd import ( "fmt" - "github.com/aryann/difflib" "github.com/cheynewallace/tabby" - "github.com/fatih/color" "github.com/manifoldco/promptui" "github.com/naveego/bosun/pkg" "github.com/naveego/bosun/pkg/bosun" + "github.com/naveego/bosun/pkg/filter" "github.com/naveego/bosun/pkg/git" "github.com/pkg/errors" "github.com/schollz/progressbar" @@ -29,33 +28,32 @@ import ( "github.com/spf13/viper" "io/ioutil" "os" - "os/signal" "regexp" "strings" "sync" - "time" ) const ( ArgSvcToggleLocalhost = "localhost" ArgSvcToggleMinikube = "minikube" - ArgAppAll = "all" - ArgAppLabels = "labels" + ArgFilteringAll = "all" + ArgFilteringLabels = "labels" ArgAppListDiff = "diff" ArgAppListSkipActual = "skip-actual" - ArgAppDeploySet = "set" + ArgAppValueSet = "value-sets" + ArgAppSet = "set" ArgAppDeployDeps = "deploy-deps" ArgAppDeletePurge = "purge" ArgAppCloneDir = "dir" - ArgInclude = "include" - ArgExclude = "exclude" + ArgFilteringInclude = "include" + ArgFilteringExclude = "exclude" ) func init() { - appCmd.PersistentFlags().BoolP(ArgAppAll, "a", false, "Apply to all known microservices.") - appCmd.PersistentFlags().StringSliceP(ArgAppLabels, "i", []string{}, "Apply to microservices with the provided labels.") - appCmd.PersistentFlags().StringSlice(ArgInclude, []string{}, `Only include apps which match the provided selectors. --include trumps --exclude.".`) - appCmd.PersistentFlags().StringSlice(ArgExclude, []string{}, `Don't include apps which match the provided selectors.".`) + appCmd.PersistentFlags().BoolP(ArgFilteringAll, "a", false, "Apply to all known microservices.") + appCmd.PersistentFlags().StringSliceP(ArgFilteringLabels, "i", []string{}, "Apply to microservices with the provided labels.") + appCmd.PersistentFlags().StringSlice(ArgFilteringInclude, []string{}, `Only include apps which match the provided selectors. --include trumps --exclude.".`) + appCmd.PersistentFlags().StringSlice(ArgFilteringExclude, []string{}, `Don't include apps which match the provided selectors.".`) appCmd.AddCommand(appToggleCmd) appToggleCmd.Flags().Bool(ArgSvcToggleLocalhost, false, "Run service at localhost.") @@ -81,7 +79,7 @@ func init() { var appCmd = &cobra.Command{ Use: "app", Aliases: []string{"apps", "a"}, - Short: "AppRepo commands", + Short: "App commands", } var _ = addCommand(appCmd, configImportCmd) @@ -142,7 +140,7 @@ var appBumpCmd = addCommand(appCmd, &cobra.Command{ } if wantsTag { - _, err = g.Exec("tag", app.Version) + _, err = g.Exec("tag", app.Version.String()) if err != nil { return err } @@ -159,7 +157,7 @@ const ( ) // appBump is the implementation of appBumpCmd -func appBump(b *bosun.Bosun, app *bosun.AppRepo, bump string) error { +func appBump(b *bosun.Bosun, app *bosun.App, bump string) error { ctx := b.NewContext() err := app.BumpVersion(ctx, bump) @@ -167,9 +165,9 @@ func appBump(b *bosun.Bosun, app *bosun.AppRepo, bump string) error { return err } - err = app.Fragment.Save() + err = app.Parent.Save() if err == nil { - pkg.Log.Infof("Updated %q to version %s and saved in %q", app.Name, app.Version, app.Fragment.FromPath) + pkg.Log.Infof("Updated %q to version %s and saved in %q", app.Name, app.Version, app.Parent.FromPath) } return err } @@ -184,7 +182,7 @@ The current domain and the minikube IP are used to populate the output. To updat RunE: func(cmd *cobra.Command, args []string) error { b := mustGetBosun() - apps := mustGetAppRepos(b, args) + apps := mustGetAppsIncludeCurrent(b, args) env := b.GetCurrentEnvironment() ip := pkg.NewCommand("minikube", "ip").MustOut() @@ -250,7 +248,7 @@ var appRemoveHostsCmd = addCommand(appCmd, &cobra.Command{ RunE: func(cmd *cobra.Command, args []string) error { b := mustGetBosun() - apps := mustGetAppRepos(b, args) + apps := mustGetAppsIncludeCurrent(b, args) env := b.GetCurrentEnvironment() toRemove := map[string]bool{} @@ -327,34 +325,43 @@ var appAcceptActualCmd = &cobra.Command{ b := mustGetBosun() - apps, err := getAppRepos(b, args) + apps, err := getAppsIncludeCurrent(b, args) if err != nil { return err } p := progressbar.New(len(apps)) + ctx := b.NewContext() + deploySettings := bosun.DeploySettings{ + Environment: ctx.Env, + } for _, app := range apps { if !app.HasChart() { continue } - ctx := b.NewContext().WithAppRepo(app) - appRelease, err := bosun.NewAppReleaseFromRepo(ctx, app) + + ctx = ctx.WithApp(app) + appManifest, err := app.GetManifest(ctx) + if err != nil { + return errors.Wrapf(err, "get manifest for %q", app.Name) + } + appDeploy, err := bosun.NewAppDeploy(ctx, deploySettings, appManifest) if err != nil { - ctx.Log.WithError(err).Error("Error creating app release for current state analysis.") + ctx.Log.WithError(err).Error("Error creating app deploy for current state analysis.") continue } - ctx = ctx.WithAppRelease(appRelease) + ctx = ctx.WithAppDeploy(appDeploy) log := ctx.Log log.Debug("Getting actual state...") - err = appRelease.LoadActualState(ctx, false) + err = appDeploy.LoadActualState(ctx, false) p.Add(1) if err != nil { log.WithError(err).Error("Could not get actual state.") return err } - b.SetDesiredState(app.Name, appRelease.ActualState) + b.SetDesiredState(app.Name, appDeploy.ActualState) log.Debug("Updated.") } @@ -372,10 +379,10 @@ var appStatusCmd = &cobra.Command{ viper.BindPFlags(cmd.Flags()) b := mustGetBosun() - env := b.GetCurrentEnvironment() - - apps, err := getAppReposOpt(b, args, getAppReposOptions{ifNoMatchGetAll: true}) + f := getFilterParams(b, args) + chain := f.Chain().Then().Including(filter.FilterMatchAll()) + apps, err := f.GetAppsChain(chain) if err != nil { return err } @@ -385,7 +392,7 @@ var appStatusCmd = &cobra.Command{ diff := viper.GetBool(ArgAppListDiff) skipActual := viper.GetBool(ArgAppListSkipActual) - appReleases, err := getAppReleasesFromApps(b, apps) + appReleases, err := getAppDeploysFromApps(b, apps) if err != nil { return err } @@ -400,7 +407,7 @@ var appStatusCmd = &cobra.Command{ appRelease := appReleases[i] go func() { defer wg.Done() - ctx := b.NewContext().WithAppRelease(appRelease) + ctx := b.NewContext().WithAppDeploy(appRelease) err := appRelease.LoadActualState(ctx, false) if err != nil { ctx.Log.WithError(err).Fatal() @@ -446,7 +453,7 @@ var appStatusCmd = &cobra.Command{ fmtDesiredActual(desired.Status, actual.Status), routing, fmtTableEntry(diffStatus), - fmtTableEntry(fmt.Sprintf("%#v", m.AppRepo.AppLabels))) + fmtTableEntry(fmt.Sprintf("%#v", m.AppConfig.Labels))) } t.Print() @@ -489,11 +496,11 @@ var appToggleCmd = &cobra.Command{ return errors.New("Environment must be set to 'red' to toggle services.") } - repos, err := getAppRepos(b, args) + repos, err := getAppsIncludeCurrent(b, args) if err != nil { return err } - apps, err := getAppReleasesFromApps(b, repos) + apps, err := getAppDeploysFromApps(b, repos) if err != nil { return err } @@ -502,7 +509,7 @@ var appToggleCmd = &cobra.Command{ for _, app := range apps { - ctx = ctx.WithAppRelease(app) + ctx = ctx.WithAppDeploy(app) wantsLocalhost := viper.GetBool(ArgSvcToggleLocalhost) wantsMinikube := viper.GetBool(ArgSvcToggleMinikube) if wantsLocalhost { @@ -562,31 +569,7 @@ var appDeployCmd = addCommand(appCmd, &cobra.Command{ RunE: func(cmd *cobra.Command, args []string) error { viper.BindPFlags(cmd.Flags()) - valueOverrides := map[string]string{} - for _, set := range viper.GetStringSlice(ArgAppDeploySet) { - - segs := strings.Split(set, "=") - if len(segs) != 2 { - return errors.Errorf("invalid set (should be key=value): %q", set) - } - valueOverrides[segs[0]] = segs[1] - } - - ipp := strings.ToUpper(viper.GetString(AppDeployPullPolicy)) - if strings.HasPrefix(ipp, "A") { - ipp = "Always" - } else if strings.HasPrefix(ipp, "I") { - ipp = "IfNotPresent" - } - if ipp != "" { - valueOverrides["imagePullPolicy"] = ipp - } - - valueOverrides["tag"] = viper.GetString(AppDeployTag) - - b := mustGetBosun(bosun.Parameters{ - ValueOverrides: valueOverrides, - }) + b := mustGetBosun() if err := b.ConfirmEnvironment(); err != nil { return err @@ -594,41 +577,53 @@ var appDeployCmd = addCommand(appCmd, &cobra.Command{ ctx := b.NewContext() - apps, err := getAppReposOpt(b, args, getAppReposOptions{}) - if err != nil { - return err - } + apps := mustGetAppsIncludeCurrent(b, args) - ctx.Log.Debugf("AppReleaseConfigs: \n%s\n", MustYaml(apps)) + // fmt.Println("App Configs:") + // for _, app := range apps { + // fmt.Println(MustYaml(app.AppConfig)) + // } - ctx.Log.Debug("Creating transient release...") - rc := &bosun.ReleaseConfig{ - Name: time.Now().Format(time.RFC3339), - } - r, err := bosun.NewRelease(ctx, rc) + valueSets, err := getValueSetSlice(b, b.GetCurrentEnvironment()) if err != nil { return err } - - r.Transient = true + includeDeps := viper.GetBool(ArgAppDeployDeps) + deploySettings := bosun.DeploySettings{ + Environment: ctx.Env, + ValueSets: valueSets, + UseLocalContent: true, + IgnoreDependencies: !includeDeps, + Apps: map[string]*bosun.App{}, + } for _, app := range apps { ctx.Log.WithField("app", app.Name).Debug("Including in release.") - err = r.IncludeApp(ctx, app) - if err != nil { - return errors.Errorf("error including app %q in release: %s", app.Name, err) + deploySettings.Apps[app.Name] = app + if includeDeps { + ctx.Log.Debug("Including dependencies of all apps...") + deps, err := b.GetAppDependencies(app.Name) + if err != nil { + return errors.Wrapf(err, "getting deps for %q", app.Name) + } + for _, depName := range deps { + if _, ok := deploySettings.Apps[depName]; !ok { + + depApp, err := b.GetApp(depName) + if err != nil { + return errors.Wrapf(err, "getting app for dep %q", app.Name) + } + deploySettings.Apps[depApp.Name] = depApp + } + } } } - - if viper.GetBool(ArgAppDeployDeps) { - ctx.Log.Debug("Including dependencies of all apps...") - err = r.IncludeDependencies(ctx) - if err != nil { - return errors.Wrap(err, "include dependencies") - } + r, err := bosun.NewDeploy(ctx, deploySettings) + if err != nil { + return err } - ctx.Log.Debugf("Created transient release to define deploy: \n%s\n", r.Name) + ctx.Log.Debugf("Created deploy") err = r.Deploy(ctx) @@ -641,17 +636,11 @@ var appDeployCmd = addCommand(appCmd, &cobra.Command{ return err }, }, func(cmd *cobra.Command) { - cmd.Flags().StringP(AppDeployPullPolicy, "p", "", "Set the imagePullPolicy in the chart. (A = Always, I = IfNotPresent)") - cmd.Flags().StringP(AppDeployTag, "t", "latest", "Set the tag used in the chart.") cmd.Flags().Bool(ArgAppDeployDeps, false, "Also deploy all dependencies of the requested apps.") - cmd.Flags().StringSlice(ArgAppDeploySet, []string{}, "Additional values to pass to helm for this deploy.") + cmd.Flags().StringSliceP(ArgAppValueSet, "v", []string{}, "Additional value sets to include in this deploy.") + cmd.Flags().StringSliceP(ArgAppSet, "s", []string{}, "Value overrides to set in this deploy, as key=value pairs.") }) -const ( - AppDeployPullPolicy = "pull-policy" - AppDeployTag = "tag" -) - var appRecycleCmd = addCommand(appCmd, &cobra.Command{ Use: "recycle [name] [name...]", Short: "Recycles the requested app(s) by deleting their pods.", @@ -670,18 +659,18 @@ var appRecycleCmd = addCommand(appCmd, &cobra.Command{ return err } - releases := mustGetAppReleases(b, args) + releases := getFilterParams(b, args).IncludeCurrent().MustGetAppDeploys() pullLatest := viper.GetBool(ArgAppRecyclePullLatest) for _, appRelease := range releases { - ctx := ctx.WithAppRelease(appRelease) + ctx := ctx.WithAppDeploy(appRelease) if env.IsLocal && pullLatest { ctx.Log.Info("Pulling latest version of image(s) on minikube...") - for _, imageName := range appRelease.ImageNames { - image := fmt.Sprintf("%s:latest", imageName) - err := pkg.NewCommand("sh", "-c", fmt.Sprintf("eval $(minikube docker-env); docker pull %s", image)).RunE() + for _, image := range appRelease.AppConfig.GetImages() { + imageName := image.GetFullNameWithTag("latest") + err := pkg.NewCommand("sh", "-c", fmt.Sprintf("eval $(minikube docker-env); docker pull %s", imageName)).RunE() if err != nil { return err } @@ -717,7 +706,7 @@ var appDeleteCmd = &cobra.Command{ return err } - appReleases := mustGetAppReleases(b, args) + appReleases := getFilterParams(b, args).IncludeCurrent().MustGetAppDeploys() ctx := b.NewContext() @@ -750,68 +739,70 @@ var appRunCmd = &cobra.Command{ SilenceErrors: true, RunE: func(cmd *cobra.Command, args []string) error { - viper.BindPFlags(cmd.Flags()) - - b := mustGetBosun() - c := b.GetCurrentEnvironment() - - if err := b.ConfirmEnvironment(); err != nil { - return err - } - - if c.Name != "red" { - return errors.New("Environment must be set to 'red' to run apps.") - } - - app := mustGetApp(b, args) - - run, err := app.GetRunCommand() - if err != nil { - return err - } - - ctx := b.NewContext() - - appRelease, err := bosun.NewAppReleaseFromRepo(ctx, app) - if err != nil { - return err - } - - appRelease.DesiredState.Routing = bosun.RoutingLocalhost - appRelease.DesiredState.Status = bosun.StatusDeployed - b.SetDesiredState(app.Name, appRelease.DesiredState) - err = appRelease.Reconcile(ctx) - if err != nil { - return err - } - - err = b.Save() - - done := make(chan struct{}) - s := make(chan os.Signal) - signal.Notify(s, os.Kill, os.Interrupt) - log := pkg.Log.WithField("cmd", run.Args) - - go func() { - log.Info("Running child process.") - err = run.Run() - close(done) - }() - - select { - case <-done: - case <-s: - log.Info("Killing child process.") - run.Process.Signal(os.Interrupt) - } - select { - case <-done: - case <-time.After(3 * time.Second): - log.Warn("Child process did not exit when signalled.") - run.Process.Kill() - } - - return err + return errors.New("this needs to be refactored to use new deploy paradigm") + // + // viper.BindPFlags(cmd.Flags()) + // + // b := mustGetBosun() + // c := b.GetCurrentEnvironment() + // + // if err := b.ConfirmEnvironment(); err != nil { + // return err + // } + // + // if c.Name != "red" { + // return errors.New("Environment must be set to 'red' to run apps.") + // } + // + // app := mustGetApp(b, args) + // + // run, err := app.GetRunCommand() + // if err != nil { + // return err + // } + // + // ctx := b.NewContext() + // + // appRelease, err := bosun.NewAppReleaseFromApp(ctx, app) + // if err != nil { + // return err + // } + // + // appRelease.DesiredState.Routing = bosun.RoutingLocalhost + // appRelease.DesiredState.Status = bosun.StatusDeployed + // b.SetDesiredState(app.Name, appRelease.DesiredState) + // err = appRelease.Reconcile(ctx) + // if err != nil { + // return err + // } + // + // err = b.Save() + // + // done := make(chan struct{}) + // s := make(chan os.Signal) + // signal.Notify(s, os.Kill, os.Interrupt) + // log := pkg.Log.WithField("cmd", run.Args) + // + // go func() { + // log.Info("Running child process.") + // err = run.Run() + // close(done) + // }() + // + // select { + // case <-done: + // case <-s: + // log.Info("Killing child process.") + // run.Process.Signal(os.Interrupt) + // } + // select { + // case <-done: + // case <-time.After(3 * time.Second): + // log.Warn("Child process did not exit when signalled.") + // run.Process.Kill() + // } + // + // return err }, } @@ -874,7 +865,7 @@ var appBuildImageCmd = addCommand( RunE: func(cmd *cobra.Command, args []string) error { b := mustGetBosun() app := mustGetApp(b, args) - ctx := b.NewContext().WithAppRepo(app) + ctx := b.NewContext().WithApp(app) err := app.BuildImages(ctx) return err }, @@ -883,7 +874,7 @@ var appBuildImageCmd = addCommand( var appPullCmd = addCommand( appCmd, &cobra.Command{ - Use: "pull [app]", + Use: "pull [app] [app...]", Short: "Pulls the repo for the app.", Long: "If app is not provided, the current directory is used.", SilenceUsage: true, @@ -891,26 +882,31 @@ var appPullCmd = addCommand( RunE: func(cmd *cobra.Command, args []string) error { b := mustGetBosun() ctx := b.NewContext() - apps, err := getAppRepos(b, args) + apps, err := getAppsIncludeCurrent(b, args) if err != nil { return err } - repos := map[string]*bosun.AppRepo{} + repos := map[string]*bosun.Repo{} for _, app := range apps { - repos[app.Repo] = app + if app.Repo == nil { + ctx.Log.Errorf("no repo identified for app %q", app.Name) + } + repos[app.RepoName] = app.Repo } - for _, app := range repos { - log := ctx.Log.WithField("repo", app.Repo) + var lastFailure error + for _, repo := range repos { + log := ctx.Log.WithField("repo", repo.Name) log.Info("Pulling...") - err := app.PullRepo(ctx) + err = repo.Pull(ctx) if err != nil { + lastFailure = err log.WithError(err).Error("Error pulling.") } else { log.Info("Pulled.") } } - return err + return lastFailure }, }) @@ -930,7 +926,7 @@ var appScriptCmd = addCommand(appCmd, &cobra.Command{ return err } - var app *bosun.AppRepo + var app *bosun.App var scriptName string switch len(args) { case 1: @@ -957,18 +953,12 @@ var appScriptCmd = addCommand(appCmd, &cobra.Command{ return errors.Errorf("no script named %q in app %q\navailable scripts:\n-%s", scriptName, app.Name, strings.Join(scriptNames, "\n-")) } - ctx := b.NewContext().WithDir(app.FromPath) - - appRelease, err := bosun.NewAppReleaseFromRepo(ctx, app) - if err != nil { - return err - } - - values, err := appRelease.GetReleaseValues(ctx) + ctx := b.NewContext() + values, err := getResolvedValuesFromApp(b, app) if err != nil { return err } - ctx = ctx.WithReleaseValues(values) + ctx = ctx.WithPersistableValues(values) err = script.Execute(ctx, scriptStepsSlice...) @@ -994,7 +984,7 @@ var appActionCmd = addCommand(appCmd, &cobra.Command{ return err } - var app *bosun.AppRepo + var app *bosun.App var actionName string switch len(args) { case 1: @@ -1024,16 +1014,11 @@ var appActionCmd = addCommand(appCmd, &cobra.Command{ ctx := b.NewContext() - appRelease, err := bosun.NewAppReleaseFromRepo(ctx, app) + values, err := getResolvedValuesFromApp(b, app) if err != nil { return err } - - values, err := appRelease.GetReleaseValues(ctx) - if err != nil { - return err - } - ctx = ctx.WithReleaseValues(values) + ctx = ctx.WithPersistableValues(values) err = action.Execute(ctx) @@ -1083,159 +1068,34 @@ var appCloneCmd = addCommand( b = mustGetBosun() } - apps, err := getAppRepos(b, args) + apps, err := getAppsIncludeCurrent(b, args) if err != nil { return err } ctx := b.NewContext() + var lastErr error for _, app := range apps { log := ctx.Log.WithField("app", app.Name).WithField("repo", app.Repo) if app.IsRepoCloned() { - pkg.Log.Infof("AppRepo already cloned to %q", app.FromPath) + pkg.Log.Infof("App already cloned to %q", app.FromPath) continue } log.Info("Cloning...") - err := app.CloneRepo(ctx, dir) + err = app.Repo.Clone(ctx, dir) if err != nil { + lastErr = err log.WithError(err).Error("Error cloning.") } else { log.Info("Cloned.") } } - return err + return lastErr }, }, func(cmd *cobra.Command) { cmd.Flags().String(ArgAppCloneDir, "", "The directory to clone into.") }) - -var appDiffCmd = addCommand( - appCmd, - &cobra.Command{ - Use: "diff {app} [release/]{env} [release]/{env}", - Short: "Reports the differences between the values for an app in two scenarios.", - Long: `If the release part of the scenario is not provided, a transient release will be created and used instead.`, - Example: `This command will show the differences between the values deployed -to the blue environment in release 2.4.2 and the current values for the -green environment: - -diff go-between 2.4.2/blue green -`, - Args: cobra.ExactArgs(3), - SilenceUsage: true, - SilenceErrors: true, - RunE: func(cmd *cobra.Command, args []string) error { - b := mustGetBosun() - app := mustGetApp(b, []string{args[0]}) - - env1 := args[1] - env2 := args[2] - - getValuesForEnv := func(scenario string) (string, error) { - - segs := strings.Split(scenario, "/") - var releaseName, envName string - var appRelease *bosun.AppRelease - switch len(segs) { - case 1: - envName = segs[0] - case 2: - releaseName = segs[0] - envName = segs[1] - default: - return "", errors.Errorf("invalid scenario %q", scenario) - } - - env, err := b.GetEnvironment(envName) - if err != nil { - return "", errors.Wrap(err, "environment") - } - ctx := b.NewContext().WithEnv(env) - - if releaseName != "" { - releaseConfig, err := b.GetReleaseConfig(releaseName) - release, err := bosun.NewRelease(ctx, releaseConfig) - if err != nil { - return "", err - } - appReleaseConfig, ok := release.AppReleaseConfigs[app.Name] - if !ok { - return "", errors.Errorf("no app named %q in release %q", app.Name, releaseName) - } - ctx = ctx.WithRelease(release) - appRelease, err = bosun.NewAppRelease(ctx, appReleaseConfig) - if err != nil { - return "", err - } - } else { - rc := &bosun.ReleaseConfig{ - Name: time.Now().Format(time.RFC3339), - } - r, err := bosun.NewRelease(ctx, rc) - if err != nil { - return "", err - } - r.Transient = true - ctx = ctx.WithRelease(r) - config, err := app.GetAppReleaseConfig(ctx) - if err != nil { - return "", errors.Wrap(err, "make app release config") - } - - appRelease, err = bosun.NewAppRelease(ctx, config) - if err != nil { - return "", errors.Wrap(err, "make app release") - } - } - - values, err := appRelease.GetReleaseValues(ctx) - if err != nil { - return "", errors.Wrap(err, "get release values") - } - - valueYaml, err := values.Values.YAML() - if err != nil { - return "", errors.Wrap(err, "get release values yaml") - } - - return valueYaml, nil - } - - env1yaml, err := getValuesForEnv(env1) - if err != nil { - return errors.Errorf("error for env1 %q: %s", env1, err) - } - - env2yaml, err := getValuesForEnv(env2) - if err != nil { - return errors.Errorf("error for env2 %q: %s", env2, err) - } - - env1lines := strings.Split(env1yaml, "\n") - env2lines := strings.Split(env2yaml, "\n") - diffs := difflib.Diff(env1lines, env2lines) - - for _, diff := range diffs { - fmt.Println(renderDiff(diff)) - } - - return nil - - }, - }) - -func renderDiff(diff difflib.DiffRecord) string { - switch diff.Delta { - case difflib.Common: - return fmt.Sprintf(" %s", diff.Payload) - case difflib.LeftOnly: - return color.RedString("- %s", diff.Payload) - case difflib.RightOnly: - return color.GreenString("+ %s", diff.Payload) - } - panic(fmt.Sprintf("invalid delta %v", diff.Delta)) -} diff --git a/cmd/app_list.go b/cmd/app_list.go index c1a88ae..8ce9b6f 100644 --- a/cmd/app_list.go +++ b/cmd/app_list.go @@ -5,7 +5,8 @@ import ( "github.com/kyokomi/emoji" "github.com/spf13/cobra" "github.com/spf13/viper" - "strings" + "os" + "path/filepath" ) var appListCmd = addCommand(appCmd, &cobra.Command{ @@ -15,45 +16,43 @@ var appListCmd = addCommand(appCmd, &cobra.Command{ SilenceUsage: true, RunE: func(cmd *cobra.Command, args []string) error { viper.BindPFlags(cmd.Flags()) - viper.SetDefault(ArgAppAll, true) + viper.SetDefault(ArgFilteringAll, true) b := mustGetBosun() - apps, err := getAppRepos(b, args) - if err != nil { - return err - } - gitRoots := b.GetGitRoots() - var trimGitRoot = func(p string) string { - for _, gitRoot := range gitRoots { - p = strings.Replace(p, gitRoot, "$GITROOT", -1) - } - return p - } + apps := getFilterParams(b, args).GetApps() + + wd, _ := os.Getwd() + + ctx := b.NewContext() t := tabby.New() - t.AddHeader("APP", "CLONED", "VERSION", "PATH or REPO", "BRANCH", "IMPORTED BY") + t.AddHeader("APP", "CLONED", "VERSION", "REPO", "PATH", "BRANCH") for _, app := range apps { - var isCloned, pathrepo, branch, version, importedBy string + var isCloned, repo, path, branch, version string + repo = app.RepoName if app.IsRepoCloned() { - isCloned = emoji.Sprint( ":heavy_check_mark:") - pathrepo = trimGitRoot(app.FromPath) + isCloned = emoji.Sprint(":heavy_check_mark:") if app.BranchForRelease { - branch = app.GetBranch() + branch = app.GetBranchName().String() } else { branch = "" } - version = app.Version + version = app.Version.String() } else { isCloned = emoji.Sprint(" :x:") - pathrepo = app.Repo branch = "" - version = app.Version - importedBy = trimGitRoot(app.FromPath) + version = app.Version.String() } - t.AddLine(app.Name, isCloned, version, pathrepo, branch, importedBy) + if app.IsFromManifest { + manifest, _ := app.GetManifest(ctx) + path, _ = filepath.Rel(wd, manifest.AppConfig.FromPath) + } else { + path, _ = filepath.Rel(wd, app.AppConfig.FromPath) + } + t.AddLine(app.Name, isCloned, version, repo, path, branch) } t.Print() @@ -69,13 +68,10 @@ var appListActionsCmd = addCommand(appListCmd, &cobra.Command{ SilenceUsage: true, RunE: func(cmd *cobra.Command, args []string) error { viper.BindPFlags(cmd.Flags()) - viper.SetDefault(ArgAppAll, true) + viper.SetDefault(ArgFilteringAll, true) b := mustGetBosun() - apps, err := getAppReposOpt(b, args, getAppReposOptions{ifNoFiltersGetCurrent: true}) - if err != nil { - return err - } + apps := getFilterParams(b, args).GetApps() t := tabby.New() t.AddHeader("APP", "ACTION", "WHEN", "WHERE", "DESCRIPTION") diff --git a/cmd/cobra_helpers.go b/cmd/cobra_helpers.go new file mode 100644 index 0000000..a739dd0 --- /dev/null +++ b/cmd/cobra_helpers.go @@ -0,0 +1,99 @@ +package cmd + +import "github.com/spf13/cobra" + +func addCommand(parent *cobra.Command, child *cobra.Command, flags ...func(cmd *cobra.Command)) *cobra.Command { + for _, fn := range flags { + fn(child) + } + child = TraverseRunHooks(child) + parent.AddCommand(child) + + return child +} + +func withFilteringFlags(cmd *cobra.Command) { + cmd.Flags().StringSlice(ArgFilteringLabels, []string{}, "Will include any items where a label with that key is present.") + cmd.Flags().StringSlice(ArgFilteringInclude, []string{}, "Will include items with labels matching filter (like x==y or x?=prefix-.*).") + cmd.Flags().StringSlice(ArgFilteringExclude, []string{}, "Will exclude items with labels matching filter (like x==y or x?=prefix-.*).") + cmd.Flags().Bool(ArgFilteringAll, false, "Will include all items.") +} + +func withValueSetFlags(cmd *cobra.Command) { + cmd.Flags().StringSliceP(ArgAppValueSet, "v", []string{}, "Additional value sets to include in this deploy.") + cmd.Flags().StringSliceP(ArgAppSet, "s", []string{}, "Value overrides to set in this deploy, as path.to.key=value pairs.") +} + +// TraverseRunHooks modifies c's PersistentPreRun* and PersistentPostRun* +// functions (when present) so that they will search c's command chain and +// invoke the corresponding hook of the first parent that provides a hook. +// When used on every command in the chain the invocation of hooks will be +// propagated up the chain to the root command. +// +// In the case of PersistentPreRun* hooks the parent hook is invoked before the +// child hook. In the case of PersistentPostRun* the child hook is invoked +// first. +// +// Use it in place of &cobra.Command{}, e.g. +// command := TraverseRunHooks(&cobra.Command{ +// PersistentPreRun: func ..., +// }) +func TraverseRunHooks(c *cobra.Command) *cobra.Command { + preRunE := c.PersistentPreRunE + preRun := c.PersistentPreRun + if preRunE != nil || preRun != nil { + c.PersistentPreRun = nil + c.PersistentPreRunE = func(cmd *cobra.Command, args []string) error { + for p := c.Parent(); p != nil; p = p.Parent() { + if p.PersistentPreRunE != nil { + if err := p.PersistentPreRunE(cmd, args); err != nil { + return err + } + break + } else if p.PersistentPreRun != nil { + p.PersistentPreRun(cmd, args) + break + } + } + + if preRunE != nil { + return preRunE(cmd, args) + } + + preRun(cmd, args) + + return nil + } + } + + postRunE := c.PersistentPostRunE + postRun := c.PersistentPostRun + if postRunE != nil || postRun != nil { + c.PersistentPostRun = nil + c.PersistentPostRunE = func(cmd *cobra.Command, args []string) error { + if postRunE != nil { + if err := postRunE(cmd, args); err != nil { + return err + } + } else if postRun != nil { + postRun(cmd, args) + } + + for p := c.Parent(); p != nil; p = p.Parent() { + if p.PersistentPostRunE != nil { + if err := p.PersistentPostRunE(cmd, args); err != nil { + return err + } + break + } else if p.PersistentPostRun != nil { + p.PersistentPostRun(cmd, args) + break + } + } + + return nil + } + } + + return c +} diff --git a/cmd/docs.go b/cmd/docs.go index 1e1b525..0d72326 100644 --- a/cmd/docs.go +++ b/cmd/docs.go @@ -15,7 +15,10 @@ package cmd import ( + "fmt" "github.com/spf13/cobra" + "github.com/spf13/cobra/doc" + // "github.com/spf13/cobra/doc" "os" ) @@ -25,26 +28,26 @@ func init() { } var docsCmd = &cobra.Command{ - Use: "docs", - ArgAliases: []string{"doc"}, - Short: "Completion and documentation generators.", + Use: "docs", + ArgAliases: []string{"doc"}, + Short: "Completion and documentation generators.", } -// -// var _ = addCommand(docsCmd, &cobra.Command{ -// Use: "markdown [dir]", -// Short: "Output documentation in markdown. Output dir defaults to ./docs", -// RunE: func(cmd *cobra.Command, args []string) error{ -// dir := "./docs" -// if len(args) > 0 { -// dir = args[0] -// } -// err := doc.GenMarkdownTree(rootCmd, dir) -// if err != nil { -// fmt.Printf("Output to %q.\n", dir) -// } -// return err -// }, -// }) + +var _ = addCommand(docsCmd, &cobra.Command{ + Use: "markdown [dir]", + Short: "Output documentation in markdown. Output dir defaults to ./docs", + RunE: func(cmd *cobra.Command, args []string) error { + dir := "./docs" + if len(args) > 0 { + dir = args[0] + } + err := doc.GenMarkdownTree(rootCmd, dir) + if err != nil { + fmt.Printf("Output to %q.\n", dir) + } + return err + }, +}) var _ = addCommand(docsCmd, &cobra.Command{ Use: "bash", diff --git a/cmd/edit.go b/cmd/edit.go index d29d0ac..1cfd3b2 100644 --- a/cmd/edit.go +++ b/cmd/edit.go @@ -47,63 +47,67 @@ var editCmd = addCommand(rootCmd, &cobra.Command{ targetPath = b.GetWorkspace().Path } - editor, ok := os.LookupEnv("EDITOR") - if !ok { - return errors.New("EDITOR environment variable is not set") - } - - currentBytes, err := ioutil.ReadFile(targetPath) - if err != nil{ - return err - } - - stat, err := os.Stat(targetPath) - if err != nil { - return errors.Wrap(err, "stat target file") - } - - tmp, err := ioutil.TempFile(os.TempDir(), "bosun-*.yaml") - if err != nil { - return errors.Wrap(err, "temp file") - } - - _, err = io.Copy(tmp, bytes.NewReader(currentBytes)) - if err != nil { - return errors.Wrap(err, "copy to temp file") - } - err = tmp.Close() - if err != nil { - return errors.Wrap(err, "close temp file") - } - - editorCmd := exec.Command("sh", "-c", fmt.Sprintf("%s %s", editor, tmp.Name())) - - editorCmd.Stderr = os.Stderr - editorCmd.Stdout = os.Stdout - editorCmd.Stdin = os.Stdin - - err = editorCmd.Run() - if err != nil { - return errors.Errorf("editor command %s failed: %s", editor, err) - } - - updatedBytes, err := ioutil.ReadFile(tmp.Name()) - if err != nil { - return errors.Wrap(err, "read updated file") - } + return Edit(targetPath) + }, +}) - if bytes.Equal(currentBytes, updatedBytes) { - pkg.Log.Info("No changes detected.") - return nil - } +func Edit(targetPath string) error { + editor, ok := os.LookupEnv("EDITOR") + if !ok { + return errors.New("EDITOR environment variable is not set") + } + + currentBytes, err := ioutil.ReadFile(targetPath) + if err != nil { + return err + } + + stat, err := os.Stat(targetPath) + if err != nil { + return errors.Wrap(err, "stat target file") + } + + tmp, err := ioutil.TempFile(os.TempDir(), "bosun-*.yaml") + if err != nil { + return errors.Wrap(err, "temp file") + } + + _, err = io.Copy(tmp, bytes.NewReader(currentBytes)) + if err != nil { + return errors.Wrap(err, "copy to temp file") + } + err = tmp.Close() + if err != nil { + return errors.Wrap(err, "close temp file") + } + + editorCmd := exec.Command("sh", "-c", fmt.Sprintf("%s %s", editor, tmp.Name())) + + editorCmd.Stderr = os.Stderr + editorCmd.Stdout = os.Stdout + editorCmd.Stdin = os.Stdin + + err = editorCmd.Run() + if err != nil { + return errors.Errorf("editor command %s failed: %s", editor, err) + } + + updatedBytes, err := ioutil.ReadFile(tmp.Name()) + if err != nil { + return errors.Wrap(err, "read updated file") + } + + if bytes.Equal(currentBytes, updatedBytes) { + pkg.Log.Info("No changes detected.") + return nil + } - pkg.Log.WithField("path", targetPath).Info("Updating file.") + pkg.Log.WithField("path", targetPath).Info("Updating file.") - err = ioutil.WriteFile(targetPath, updatedBytes, stat.Mode()) - if err != nil { - return errors.Wrap(err, "write updated file") - } + err = ioutil.WriteFile(targetPath, updatedBytes, stat.Mode()) + if err != nil { + return errors.Wrap(err, "write updated file") + } - return nil - }, -}) + return nil +} diff --git a/cmd/env.go b/cmd/env.go index b8251fa..e91a04c 100644 --- a/cmd/env.go +++ b/cmd/env.go @@ -17,10 +17,12 @@ package cmd import ( "errors" "fmt" + "github.com/fatih/color" "github.com/naveego/bosun/pkg" "github.com/naveego/bosun/pkg/bosun" "github.com/spf13/cobra" "github.com/spf13/viper" + "gopkg.in/yaml.v2" "io/ioutil" "os" "path/filepath" @@ -184,3 +186,63 @@ var envGetOrCreateCert = addCommand(envCmd, &cobra.Command{ return nil }, }) + +var _ = addCommand(envCmd, &cobra.Command{ + Use: "show [name]", + Args: cobra.MaximumNArgs(1), + Aliases: []string{"dump"}, + Short: "Shows the current environment with its valueSets.", + RunE: func(cmd *cobra.Command, args []string) error { + b := mustGetBosun() + var env *bosun.EnvironmentConfig + var err error + if len(args) == 1 { + env, err = b.GetEnvironment(args[0]) + if err != nil { + return err + } + } else { + env = b.GetCurrentEnvironment() + } + + y, err := yaml.Marshal(env) + if err != nil { + return err + } + + valueSets, err := b.GetValueSetsForEnv(env) + if err != nil { + return err + } + + fmt.Println(string(y)) + for _, vs := range valueSets { + y, err = yaml.Marshal(vs) + if err != nil { + return err + } + fmt.Println("---") + fmt.Println(string(y)) + } + + return nil + }, +}) + +var _ = addCommand(envCmd, &cobra.Command{ + Use: "value-sets", + Short: "Lists known value-sets.", + RunE: func(cmd *cobra.Command, args []string) error { + b := mustGetBosun() + for _, vs := range b.GetValueSets() { + color.Blue("%s ", vs.Name) + color.White("(from %s):\n", vs.FromPath) + y, err := yaml.Marshal(vs) + if err != nil { + return err + } + fmt.Println(string(y)) + } + return nil + }, +}) diff --git a/cmd/git.go b/cmd/git.go index 9841734..67abb37 100644 --- a/cmd/git.go +++ b/cmd/git.go @@ -315,13 +315,13 @@ var gitAcceptPullRequestCmd = addCommand(gitCmd, &cobra.Command{ ecmd.VersionBump = args[1] b := mustGetBosun() - app, err := getAppOpt(b, nil, getAppReposOptions{ifNoFiltersGetCurrent: true}) + app, err := getFilterParams(b, args).IncludeCurrent().GetApp() if err != nil { return errors.Wrap(err, "could not get app to version") } - ecmd.AppsToVersion = []*bosun.AppRepo{app} + ecmd.AppsToVersion = []*bosun.App{app} } return ecmd.Execute() @@ -337,7 +337,7 @@ type GitAcceptPRCommand struct { RepoDirectory string // if true, will skip merging the base branch back into the pr branch before merging into the target. DoNotMergeBaseIntoBranch bool - AppsToVersion []*bosun.AppRepo + AppsToVersion []*bosun.App VersionBump string } @@ -437,7 +437,7 @@ func (c GitAcceptPRCommand) Execute() error { return err } - finalVersion = app.Version + finalVersion = app.Version.String() } out, err := g.Exec("add", ".") diff --git a/cmd/helpers.go b/cmd/helpers.go index 2c199e3..8199f2b 100644 --- a/cmd/helpers.go +++ b/cmd/helpers.go @@ -7,10 +7,11 @@ import ( "github.com/manifoldco/promptui" "github.com/naveego/bosun/pkg" "github.com/naveego/bosun/pkg/bosun" + "github.com/naveego/bosun/pkg/filter" + "github.com/naveego/bosun/pkg/util" "github.com/olekukonko/tablewriter" "github.com/pkg/errors" "github.com/sirupsen/logrus" - "github.com/spf13/cobra" "github.com/spf13/viper" "gopkg.in/yaml.v2" "log" @@ -28,15 +29,6 @@ const ( OutputYaml = "yaml" ) -func addCommand(parent *cobra.Command, child *cobra.Command, flags ...func(cmd *cobra.Command)) *cobra.Command { - for _, fn := range flags { - fn(child) - } - parent.AddCommand(child) - - return child -} - func mustGetBosun(optionalParams ...bosun.Parameters) *bosun.Bosun { b, err := getBosun(optionalParams...) if err != nil { @@ -46,7 +38,7 @@ func mustGetBosun(optionalParams ...bosun.Parameters) *bosun.Bosun { envFromEnv := os.Getenv(bosun.EnvEnvironment) envFromConfig := b.GetCurrentEnvironment().Name if envFromConfig != envFromEnv { - colorError.Printf("Bosun config indicates environment should be %[1]q, but the environment var %[2]s is %[3]q. You may want to run $(bosun env %[1]s)", + _, _ = colorError.Printf("Bosun config indicates environment should be %[1]q, but the environment var %[2]s is %[3]q. You may want to run $(bosun env %[1]s)", envFromConfig, bosun.EnvEnvironment, envFromEnv) @@ -55,37 +47,44 @@ func mustGetBosun(optionalParams ...bosun.Parameters) *bosun.Bosun { return b } -func mustGetCurrentRelease(b *bosun.Bosun) *bosun.Release { - r, err := b.GetCurrentRelease() +func mustGetCurrentRelease(b *bosun.Bosun) *bosun.ReleaseManifest { + r, err := b.GetCurrentReleaseManifest(true) if err != nil { log.Fatal(err) } - whitelist := viper.GetStringSlice(ArgInclude) - if len(whitelist) > 0 { - toReleaseSet := map[string]bool{} - for _, r := range whitelist { - toReleaseSet[r] = true - } - for k, app := range r.AppReleases { - if !toReleaseSet[k] { - pkg.Log.Warnf("Skipping %q because it was not listed in the --%s flag.", k, ArgInclude) - app.DesiredState.Status = bosun.StatusUnchanged - app.Excluded = true - } - } - } - - blacklist := viper.GetStringSlice(ArgExclude) - for _, name := range blacklist { - pkg.Log.Warnf("Skipping %q because it was excluded by the --%s flag.", name, ArgExclude) - if app, ok := r.AppReleases[name]; ok { - app.DesiredState.Status = bosun.StatusUnchanged - app.Excluded = true - } - } - return r + + // r, err := b.GetCurrentRelease() + // if err != nil { + // log.Fatal(err) + // } + // + // whitelist := viper.GetStringSlice(ArgFilteringInclude) + // if len(whitelist) > 0 { + // toReleaseSet := map[string]bool{} + // for _, r := range whitelist { + // toReleaseSet[r] = true + // } + // for k, app := range r.AppReleases { + // if !toReleaseSet[k] { + // pkg.Log.Warnf("Skipping %q because it was not listed in the --%s flag.", k, ArgFilteringInclude) + // app.DesiredState.Status = bosun.StatusUnchanged + // app.Excluded = true + // } + // } + // } + // + // blacklist := viper.GetStringSlice(ArgFilteringExclude) + // for _, name := range blacklist { + // pkg.Log.Warnf("Skipping %q because it was excluded by the --%s flag.", name, ArgFilteringExclude) + // if app, ok := r.AppReleases[name]; ok { + // app.DesiredState.Status = bosun.StatusUnchanged + // app.Excluded = true + // } + // } + // + // return r } func getBosun(optionalParams ...bosun.Parameters) (*bosun.Bosun, error) { @@ -120,33 +119,9 @@ func getBosun(optionalParams ...bosun.Parameters) (*bosun.Bosun, error) { return bosun.New(params, config) } -func getAppOpt(b *bosun.Bosun, names []string, options getAppReposOptions) (*bosun.AppRepo, error) { - apps, err := getAppReposOpt(b, names, options) - if err != nil { - return nil, err - } - if len(apps) == 0 { - return nil, errors.Errorf("no apps matched %v", names) - } - if len(apps) > 1 { - if len(names) > 0 { - return nil, errors.Errorf("%d apps match %v", len(apps), names) - } - return nil, errors.Errorf("%d apps found, please provide a filter", len(apps)) - } - return apps[0], nil -} - -func mustGetAppOpt(b *bosun.Bosun, names []string, options getAppReposOptions) *bosun.AppRepo { - app, err := getAppOpt(b, names, options) - if err != nil { - log.Fatal(err) - } - return app -} - -func mustGetApp(b *bosun.Bosun, names []string) *bosun.AppRepo { - return mustGetAppOpt(b, names, getAppReposOptions{ifNoMatchGetCurrent: true}) +func mustGetApp(b *bosun.Bosun, names []string) *bosun.App { + f := getFilterParams(b, names).IncludeCurrent() + return f.MustGetApp() } func MustYaml(i interface{}) string { @@ -157,17 +132,38 @@ func MustYaml(i interface{}) string { return string(b) } -func getAppReleasesFromApps(b *bosun.Bosun, repos []*bosun.AppRepo) ([]*bosun.AppRelease, error) { - var appReleases []*bosun.AppRelease +func getAppDeploysFromApps(b *bosun.Bosun, repos []*bosun.App) ([]*bosun.AppDeploy, error) { + var appReleases []*bosun.AppDeploy - for _, appRepo := range repos { - if !appRepo.HasChart() { + for _, app := range repos { + if !app.HasChart() { continue } ctx := b.NewContext() - appRelease, err := bosun.NewAppReleaseFromRepo(ctx, appRepo) + ctx.Log.Debug("Creating transient release...") + valueSetNames := util.ConcatStrings(ctx.Env.ValueSets, viper.GetStringSlice(ArgAppValueSet)) + valueSets, err := b.GetValueSetSlice(valueSetNames) + if err != nil { + return nil, err + } + + includeDeps := viper.GetBool(ArgAppDeployDeps) + deploySettings := bosun.DeploySettings{ + Environment: ctx.Env, + ValueSets: valueSets, + UseLocalContent: true, + IgnoreDependencies: !includeDeps, + Apps: map[string]*bosun.App{}, + } + + manifest, err := app.GetManifest(ctx) if err != nil { - return nil, errors.Errorf("error creating release for repo %q: %s", appRepo.Name, err) + return nil, err + } + + appRelease, err := bosun.NewAppDeploy(ctx, deploySettings, manifest) + if err != nil { + return nil, errors.Errorf("error creating release for repo %q: %s", app.Name, err) } appReleases = append(appReleases, appRelease) } @@ -175,99 +171,143 @@ func getAppReleasesFromApps(b *bosun.Bosun, repos []*bosun.AppRepo) ([]*bosun.Ap return appReleases, nil } -func mustGetAppRepos(b *bosun.Bosun, names []string) []*bosun.AppRepo { - repos, err := getAppRepos(b, names) - if err != nil { - log.Fatal(err) - } - if len(repos) == 0 { - color.Red("No apps found (provided names: %v).", names) - } - return repos +type FilterParams struct { + b *bosun.Bosun + Names []string + All bool + Include []string + Exclude []string + Labels []string } -func mustGetAppReleases(b *bosun.Bosun, names []string) []*bosun.AppRelease { - repos, err := getAppRepos(b, names) - if err != nil { - log.Fatal(err) - } - releases, err := getAppReleasesFromApps(b, repos) - if err != nil { - log.Fatal(err) - } - return releases +func (f FilterParams) IsEmpty() bool { + return len(f.Names) == 0 && len(f.Include) == 0 && len(f.Exclude) == 0 && len(f.Labels) == 0 } -type getAppReposOptions struct { - ifNoFiltersGetAll bool - ifNoFiltersGetCurrent bool - ifNoMatchGetAll bool - ifNoMatchGetCurrent bool -} +func getFilterParams(b *bosun.Bosun, names []string) FilterParams { + p := FilterParams{ + b: b, + Names: names, + All: viper.GetBool(ArgFilteringAll), + } + p.Labels = viper.GetStringSlice(ArgFilteringLabels) + p.Include = viper.GetStringSlice(ArgFilteringInclude) + p.Exclude = viper.GetStringSlice(ArgFilteringExclude) -// gets one or more apps matching names, or if names -// are valid file paths, imports the file at that path. -// if names is empty, tries to find a apps starting -// from the current directory -func getAppReposOpt(b *bosun.Bosun, names []string, opt getAppReposOptions) ([]*bosun.AppRepo, error) { + return p +} - apps := b.GetAppsSortedByName() +// ApplyToDeploySettings will set a filter on the deploy settings if +// the filter is not empty. +func (f FilterParams) ApplyToDeploySettings(d *bosun.DeploySettings) { + if !f.IsEmpty() { + chain := f.Chain() + d.Filter = &chain + } +} - for i := range names { - name := names[i] - if strings.HasSuffix(name, "yaml") { - if _, err := os.Stat(name); err == nil { - name, _ = filepath.Abs(name) - } +func (f FilterParams) IncludeCurrent() FilterParams { + if f.IsEmpty() { + app, err := getCurrentApp(f.b) + if err == nil && app != nil { + f.Names = []string{app.Name} } - names[i] = name } + return f +} - includeFilters := getIncludeFilters(names) - excludeFilters := getExcludeFilters() +func (f FilterParams) Chain() filter.Chain { + var include []filter.Filter + var exclude []filter.Filter - if opt.ifNoFiltersGetAll && len(includeFilters) == 0 && len(excludeFilters) == 0 { - return apps, nil + if viper.GetBool(ArgFilteringAll) { + include = append(include, filter.FilterMatchAll()) + } else if len(f.Names) > 0 { + for _, name := range f.Names { + include = append(include, filter.MustParse(bosun.LabelName, "==", name)) + } + } else { + labels := append(viper.GetStringSlice(ArgFilteringLabels), viper.GetStringSlice(ArgFilteringInclude)...) + for _, label := range labels { + include = append(include, filter.MustParse(label)) + } } - if opt.ifNoFiltersGetCurrent && len(includeFilters) == 0 && len(excludeFilters) == 0 { - app, err := getCurrentApp(b) - if err != nil { - return nil, errors.Wrap(err, "no filters provided but current directory is not associated with an app") + if len(f.Exclude) > 0 { + for _, label := range f.Exclude { + exclude = append(exclude, filter.MustParse(label)) } - return []*bosun.AppRepo{app}, nil } - filtered := bosun.ApplyFilter(apps, true, includeFilters).(bosun.ReposSortedByName) - filtered = bosun.ApplyFilter(filtered, false, excludeFilters).(bosun.ReposSortedByName) + chain := filter.Try().Including(include...).Excluding(exclude...) + return chain +} - if len(filtered) > 0 { - return filtered, nil +func (f FilterParams) MustGetApp() *bosun.App { + app, err := f.GetApp() + if err != nil { + panic(err) } + return app +} - if opt.ifNoMatchGetAll { - return apps, nil +func (f FilterParams) GetApp() (*bosun.App, error) { + apps := f.b.GetAppsSortedByName() + + result, err := f.Chain().ToGetExactly(1).FromErr(apps) + if err != nil { + return nil, err } - var err error + return result.([]*bosun.App)[0], nil +} - if opt.ifNoMatchGetCurrent { - var app *bosun.AppRepo - app, err = getCurrentApp(b) - if err != nil { - return nil, err - } - apps = append(apps, app) - return apps, nil +func (f FilterParams) GetApps() []*bosun.App { + apps := f.b.GetAppsSortedByName() + + result := f.Chain().From(apps) + + return result.([]*bosun.App) +} + +func (f FilterParams) GetAppsChain(chain filter.Chain) ([]*bosun.App, error) { + apps := f.b.GetAppsSortedByName() + + result, err := chain.FromErr(apps) + + return result.([]*bosun.App), err +} + +func mustGetAppsIncludeCurrent(b *bosun.Bosun, names []string) []*bosun.App { + repos, err := getAppsIncludeCurrent(b, names) + if err != nil { + log.Fatal(err) } + if len(repos) == 0 { + color.Red("No apps found (provided names: %v).", names) + } + return repos +} - return nil, errors.New("no apps matched") +func (f FilterParams) GetAppDeploys() ([]*bosun.AppDeploy, error) { + apps := f.GetApps() + + releases, err := getAppDeploysFromApps(f.b, apps) + return releases, err +} + +func (f FilterParams) MustGetAppDeploys() []*bosun.AppDeploy { + appReleases, err := f.GetAppDeploys() + if err != nil { + log.Fatal(err) + } + return appReleases } -func getCurrentApp(b *bosun.Bosun) (*bosun.AppRepo, error) { +func getCurrentApp(b *bosun.Bosun) (*bosun.App, error) { var bosunFile string var err error - var app *bosun.AppRepo + var app *bosun.App wd, _ := os.Getwd() bosunFile, err = findFileInDirOrAncestors(wd, "bosun.yaml") @@ -283,7 +323,7 @@ func getCurrentApp(b *bosun.Bosun) (*bosun.AppRepo, error) { bosunFileDir := filepath.Dir(bosunFile) var appsUnderDirNames []string - var appsUnderDir []*bosun.AppRepo + var appsUnderDir []*bosun.App for _, app = range b.GetApps() { if app.IsRepoCloned() && strings.HasPrefix(app.FromPath, bosunFileDir) { appsUnderDirNames = append(appsUnderDirNames, app.Name) @@ -292,6 +332,10 @@ func getCurrentApp(b *bosun.Bosun) (*bosun.AppRepo, error) { } sort.Strings(appsUnderDirNames) + if len(appsUnderDir) == 0 { + return nil, errors.Errorf("no apps found under current path %s", wd) + } + if len(appsUnderDir) == 1 { return appsUnderDir[0], nil } @@ -313,33 +357,24 @@ func getCurrentApp(b *bosun.Bosun) (*bosun.AppRepo, error) { // are valid file paths, imports the file at that path. // if names is empty, tries to find a apps starting // from the current directory -func getAppRepos(b *bosun.Bosun, names []string) ([]*bosun.AppRepo, error) { - return getAppReposOpt(b, names, getAppReposOptions{ifNoMatchGetCurrent: true}) +func getAppsIncludeCurrent(b *bosun.Bosun, names []string) ([]*bosun.App, error) { + f := getFilterParams(b, names).IncludeCurrent() + return f.GetApps(), nil } -func getIncludeFilters(names []string) []bosun.Filter { - if viper.GetBool(ArgAppAll) { - return bosun.FilterMatchAll() - } - - out := bosun.FiltersFromNames(names...) - - labels := viper.GetStringSlice(ArgAppLabels) - if len(labels) > 0 { - out = append(out, bosun.FiltersFromAppLabels(labels...)...) - } - - conditions := viper.GetStringSlice(ArgInclude) - if len(conditions) > 0 { - out = append(out, bosun.FiltersFromArgs(conditions...)...) +// gets all known apps, without attempting to discover new ones +func mustGetKnownApps(b *bosun.Bosun, names []string) []*bosun.App { + apps, err := getKnownApps(b, names) + if err != nil { + log.Fatal(err) } - - return out + return apps } -func getExcludeFilters() []bosun.Filter { - conditions := viper.GetStringSlice(ArgExclude) - return bosun.FiltersFromArgs(conditions...) +// gets all known apps +func getKnownApps(b *bosun.Bosun, names []string) ([]*bosun.App, error) { + f := getFilterParams(b, names) + return f.GetApps(), nil } func checkExecutableDependency(exe string) { @@ -515,52 +550,6 @@ var ( colorOK = color.New(color.FgGreen, color.Bold) ) -func filterApps(apps []*bosun.AppRepo) []*bosun.AppRepo { - var out []*bosun.AppRepo - for _, app := range apps { - if passesConditions(app) { - out = append(out, app) - } - } - if len(apps) > 0 && len(out) == 0 && len(viper.GetStringSlice(ArgInclude)) > 0 { - color.Yellow("All apps excluded by conditions.") - os.Exit(0) - } - return out -} - -func passesConditions(app *bosun.AppRepo) bool { - conditions := viper.GetStringSlice(ArgInclude) - if len(conditions) == 0 { - return true - } - - for _, cs := range conditions { - - segs := strings.Split(cs, "=") - if len(segs) != 2 { - check(errors.Errorf("invalid condition %q (should be x=y)", cs)) - } - kind, arg := segs[0], segs[1] - - switch kind { - case "branch": - re, err := regexp.Compile(arg) - check(errors.Wrapf(err, "branch must be regex (was %q)", arg)) - branch := app.GetBranch() - if !re.MatchString(branch) { - color.Yellow("Skipping command for app %s because it did not match condition %q", app.Name, cs) - return false - } - return true - default: - check(errors.Errorf("invalid condition %q (should be branch=y)", cs)) - } - } - - return true -} - func printOutput(out interface{}, columns ...string) error { format := viper.GetString(ArgGlobalOutput) @@ -579,53 +568,67 @@ func printOutput(out interface{}, columns ...string) error { enc := yaml.NewEncoder(os.Stdout) return enc.Encode(out) case "t": - segs := strings.Split(format, "=") - if len(segs) > 1 { - columns = strings.Split(segs[1], ",") - } - j, err := json.Marshal(out) - if err != nil { - return err - } - var mapSlice []map[string]json.RawMessage - err = json.Unmarshal(j, &mapSlice) - if err != nil { - return errors.Wrapf(err, "only slices of structs or maps can be rendered as a table, but got %T", out) - } - if len(mapSlice) == 0 { - return nil - } - first := mapSlice[0] + var header []string + var rows [][]string - var keys []string - if len(columns) > 0 { - keys = columns - } else { - for k := range first { - keys = append(keys, k) + switch t := out.(type) { + case util.Tabler: + header = t.Headers() + rows = t.Rows() + default: + segs := strings.Split(format, "=") + if len(segs) > 1 { + columns = strings.Split(segs[1], ",") + } + j, err := json.Marshal(out) + if err != nil { + return err + } + var mapSlice []map[string]json.RawMessage + err = json.Unmarshal(j, &mapSlice) + if err != nil { + return errors.Wrapf(err, "only slices of structs or maps can be rendered as a table, but got %T", out) + } + if len(mapSlice) == 0 { + return nil + } + + first := mapSlice[0] + + var keys []string + if len(columns) > 0 { + keys = columns + } else { + for k := range first { + keys = append(keys, k) + } + sort.Strings(keys) + } + for _, k := range keys { + header = append(header, k) + } + for _, m := range mapSlice { + var values []string + + for _, k := range keys { + if v, ok := m[k]; ok && len(v) > 0 { + values = append(values, strings.Trim(string(v), `"`)) + } else { + values = append(values, "") + } + } + rows = append(rows, values) } - sort.Strings(keys) - } - var header []string - for _, k := range keys { - header = append(header, k) } + table := tablewriter.NewWriter(os.Stdout) table.SetHeader(header) table.SetBorders(tablewriter.Border{Left: true, Top: false, Right: true, Bottom: false}) table.SetCenterSeparator("|") - for _, m := range mapSlice { - var values []string - for _, k := range keys { - if v, ok := m[k]; ok && len(v) > 0 { - values = append(values, strings.Trim(string(v), `"`)) - } else { - values = append(values, "") - } - } - table.Append(values) + for _, row := range rows { + table.Append(row) } table.Render() @@ -636,3 +639,60 @@ func printOutput(out interface{}, columns ...string) error { } } + +func getResolvedValuesFromApp(b *bosun.Bosun, app *bosun.App) (*bosun.PersistableValues, error) { + ctx := b.NewContext().WithDir(app.FromPath) + + appManifest, err := app.GetManifest(ctx) + if err != nil { + return nil, err + } + return getResolvedValuesFromAppManifest(b, appManifest) +} + +func getResolvedValuesFromAppManifest(b *bosun.Bosun, appManifest *bosun.AppManifest) (*bosun.PersistableValues, error) { + + ctx := b.NewContext() + + appDeploy, err := bosun.NewAppDeploy(ctx, bosun.DeploySettings{}, appManifest) + if err != nil { + return nil, err + } + + values, err := appDeploy.GetResolvedValues(ctx) + if err != nil { + return nil, err + } + + return values, nil +} + +// getValueSetSlice gets the value sets for the provided environment +// and for any additional value sets specified using --value-sets, +// and creates an additional valueSet from any --set parameters. +func getValueSetSlice(b *bosun.Bosun, env *bosun.EnvironmentConfig) ([]bosun.ValueSet, error) { + valueSetNames := util.ConcatStrings(env.ValueSets, viper.GetStringSlice(ArgAppValueSet)) + valueSets, err := b.GetValueSetSlice(valueSetNames) + if err != nil { + return nil, err + } + valueOverrides := map[string]string{} + for _, set := range viper.GetStringSlice(ArgAppSet) { + segs := strings.Split(set, "=") + if len(segs) != 2 { + return nil, errors.Errorf("invalid set (should be key=value): %q", set) + } + valueOverrides[segs[0]] = segs[1] + } + if len(valueOverrides) > 0 { + overrideValueSet := bosun.ValueSet{ + Dynamic: map[string]*bosun.CommandValue{}, + } + for k, v := range valueOverrides { + overrideValueSet.Dynamic[k] = &bosun.CommandValue{Value: v} + } + valueSets = append(valueSets, overrideValueSet) + } + + return valueSets, err +} diff --git a/cmd/meta.go b/cmd/meta.go index 4eee1eb..bc041ce 100644 --- a/cmd/meta.go +++ b/cmd/meta.go @@ -16,55 +16,54 @@ package cmd import ( "context" - "encoding/json" "fmt" - "github.com/coreos/go-semver/semver" "github.com/google/go-github/v20/github" + "github.com/hashicorp/go-getter" + "github.com/naveego/bosun/pkg" + "github.com/naveego/bosun/pkg/semver" "github.com/pkg/errors" + "github.com/spf13/cobra" + "github.com/spf13/viper" + "gopkg.in/inconshreveable/go-update.v0" "io/ioutil" "os" "path/filepath" - "gopkg.in/inconshreveable/go-update.v0" "runtime" - - "github.com/naveego/bosun/pkg" - "github.com/hashicorp/go-getter" - "github.com/spf13/cobra" "strings" "time" ) var metaCmd = addCommand(rootCmd, &cobra.Command{ - Use: "meta", - Short: "Commands for managing bosun itself.", - + Use: "meta", + Short: "Commands for managing bosun itself.", }) var metaVersionCmd = addCommand(metaCmd, &cobra.Command{ Use: "version", Short: "Shows bosun version", Run: func(cmd *cobra.Command, args []string) { - fmt.Printf(`Version: %s\n -Timestamp: %s\n -Commit: %s\n + fmt.Printf(`Version: %s +Timestamp: %s +Commit: %s `, Version, Timestamp, Commit) }, }) var metaUpgradeCmd = addCommand(metaCmd, &cobra.Command{ - Use:"upgrade", - Short:"Upgrades bosun if a newer release is available", - SilenceUsage:true, + Use: "upgrade", + Short: "Upgrades bosun if a newer release is available", + SilenceUsage: true, RunE: func(cmd *cobra.Command, args []string) error { client := mustGetGithubClient() ctx, _ := context.WithTimeout(context.Background(), 5*time.Second) -var err error + var err error if Version == "" { - Version, err = pkg.NewCommand("bosun", "app", "version", "bosun").RunOut() - if err != nil { - return errors.Wrap(err, "could not get version") + confirmed := confirm("You are using a locally built version of bosun, are you sure you want to upgrade?") + if !confirmed { + return nil } + Version = "0.0.0-local" } currentVersion, err := semver.NewVersion(Version) @@ -75,13 +74,18 @@ var err error } var release *github.RepositoryRelease var upgradeAvailable bool + includePreRelease := viper.GetBool(ArgMetaUpgradePreRelease) for _, release = range releases { + if !includePreRelease && release.GetPrerelease() { + continue + } tag := release.GetTagName() tagVersion, err := semver.NewVersion(strings.TrimLeft(tag, "v")) - if err != nil{ + if err != nil { continue } - if currentVersion.LessThan(*tagVersion){ + + if currentVersion.LessThan(tagVersion) { upgradeAvailable = true break } @@ -94,62 +98,135 @@ var err error pkg.Log.Infof("Found upgrade: %s", release.GetTagName()) + err = downloadOtherVersion(release) + if err != nil { + return err + } + + fmt.Println("Upgrade completed.") - expectedAssetName := fmt.Sprintf("bosun_%s_%s_%s.tar.gz", release.GetTagName(), runtime.GOOS, runtime.GOARCH) - var foundAsset bool - var asset github.ReleaseAsset - for _, asset = range release.Assets { - name := asset.GetName() - if name == expectedAssetName { - foundAsset = true - break + return nil + }, +}, func(cmd *cobra.Command) { + cmd.Flags().BoolP(ArgMetaUpgradePreRelease, "p", false, "Upgrade to pre-release version.") +}) + +var metaDowngradeCmd = addCommand(metaCmd, &cobra.Command{ + Use: "downgrade", + Short: "Downgrades bosun to a previous release.", + SilenceUsage: true, + RunE: func(cmd *cobra.Command, args []string) error { + + client := mustGetGithubClient() + ctx, _ := context.WithTimeout(context.Background(), 5*time.Second) + var err error + if Version == "" { + confirmed := confirm("You are using a locally built version of bosun, are you sure you want to upgrade?") + if !confirmed { + return nil } + Version = "0.0.0-local" } - if !foundAsset { - return errors.Errorf("could not find an asset with name %q", expectedAssetName) - } - - j, _ := json.MarshalIndent(asset, "", " ") - fmt.Println(string(j)) + currentVersion, err := semver.NewVersion(Version) - tempDir, err := ioutil.TempDir(os.TempDir(), "bosun-upgrade") + releases, _, err := client.Repositories.ListReleases(ctx, "naveego", "bosun", nil) if err != nil { return err } - defer os.RemoveAll(tempDir) - - downloadURL := asset.GetBrowserDownloadURL() - pkg.Log.Infof("Found upgrade asset, will download from %q to %q", downloadURL, tempDir) - + var release *github.RepositoryRelease + var downgradeAvailable bool + includePreRelease := viper.GetBool(ArgMetaUpgradePreRelease) + for _, release = range releases { + if !includePreRelease && release.GetPrerelease() { + continue + } + tag := release.GetTagName() + tagVersion, err := semver.NewVersion(strings.TrimLeft(tag, "v")) + if err != nil { + continue + } - err = getter.Get(tempDir, "http::"+downloadURL) - if err != nil { - return errors.Errorf("error downloading from %q: %s", downloadURL, err) + if tagVersion.LessThan(currentVersion) { + downgradeAvailable = true + break + } } - executable, err := os.Executable() - if err != nil { - return errors.WithStack(err) + if !downgradeAvailable { + fmt.Printf("Current version (%s) is the oldest.\n", Version) + return nil } - newVersion := filepath.Join(tempDir, filepath.Base(executable)) + pkg.Log.Infof("Found downgrade: %s", release.GetTagName()) - err, errRecover := update.New().FromFile(newVersion) + err = downloadOtherVersion(release) if err != nil { return err } - if errRecover != nil { - return errRecover - } fmt.Println("Upgrade completed.") return nil }, +}, func(cmd *cobra.Command) { + cmd.Flags().BoolP(ArgMetaUpgradePreRelease, "p", false, "Upgrade to pre-release version.") }) -func init(){ - rootCmd.AddCommand(metaUpgradeCmd) +func downloadOtherVersion(release *github.RepositoryRelease) error { + expectedAssetName := fmt.Sprintf("bosun_%s_%s_%s.tar.gz", release.GetTagName(), runtime.GOOS, runtime.GOARCH) + var foundAsset bool + var asset github.ReleaseAsset + for _, asset = range release.Assets { + name := asset.GetName() + if name == expectedAssetName { + foundAsset = true + break + } + } + if !foundAsset { + return errors.Errorf("could not find an asset with name %q", expectedAssetName) + } + + // j, _ := json.MarshalIndent(asset, "", " ") + // fmt.Println(string(j)) + + tempDir, err := ioutil.TempDir(os.TempDir(), "bosun-upgrade") + if err != nil { + return err + } + defer os.RemoveAll(tempDir) + + downloadURL := asset.GetBrowserDownloadURL() + pkg.Log.Infof("Found upgrade asset, will download from %q to %q", downloadURL, tempDir) + + err = getter.Get(tempDir, "http::"+downloadURL) + if err != nil { + return errors.Errorf("error downloading from %q: %s", downloadURL, err) + } + + executable, err := os.Executable() + if err != nil { + return errors.WithStack(err) + } + + newVersion := filepath.Join(tempDir, filepath.Base(executable)) + + err, errRecover := update.New().FromFile(newVersion) + if err != nil { + return err + } + if errRecover != nil { + return errRecover + } + + return nil } +const ( + ArgMetaUpgradePreRelease = "pre-release" +) + +func init() { + rootCmd.AddCommand(metaUpgradeCmd) +} diff --git a/cmd/platform.go b/cmd/platform.go new file mode 100644 index 0000000..78f45e2 --- /dev/null +++ b/cmd/platform.go @@ -0,0 +1,148 @@ +// Copyright © 2018 NAME HERE +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cmd + +import ( + "fmt" + "github.com/naveego/bosun/pkg/bosun" + "github.com/spf13/cobra" + "gopkg.in/yaml.v2" +) + +func init() { + +} + +var platformCmd = addCommand(rootCmd, &cobra.Command{ + Use: "platform", + Args: cobra.NoArgs, + Short: "Contains platform related sub-commands.", +}) + +var _ = addCommand(platformCmd, &cobra.Command{ + Use: "list", + Short: "Lists platforms.", + RunE: func(cmd *cobra.Command, args []string) error { + b := mustGetBosun() + platforms, err := b.GetPlatforms() + if err != nil { + return err + } + for _, e := range platforms { + fmt.Println(e.Name) + } + return nil + }, +}) + +var _ = addCommand(platformCmd, &cobra.Command{ + Use: "use [name]", + Args: cobra.ExactArgs(1), + Short: "Sets the platform.", + RunE: func(cmd *cobra.Command, args []string) error { + b := mustGetBosun() + err := b.UsePlatform(args[0]) + if err != nil { + err = b.Save() + } + return err + }, +}) + +var _ = addCommand(platformCmd, &cobra.Command{ + Use: "pull [names...]", + Short: "Pulls the latest code, and updates the `latest` release.", + RunE: func(cmd *cobra.Command, args []string) error { + b := mustGetBosun() + p, err := b.GetCurrentPlatform() + if err != nil { + return err + } + + ctx := b.NewContext() + apps := mustGetKnownApps(b, args) + + for _, app := range apps { + ctx = ctx.WithApp(app) + ctx.Log.Debug("Refreshing...") + + if !app.IsRepoCloned() { + ctx.Log.Warn("App is not cloned, refresh will be incomplete.") + continue + } + + err = p.RefreshApp(ctx, app.Name) + if err != nil { + ctx.Log.WithError(err).Warn("Could not refresh.") + } + } + + err = p.Save(ctx) + + return err + }, +}, withFilteringFlags) + +var _ = addCommand(platformCmd, &cobra.Command{ + Use: "include [appNames...]", + Short: "Adds an app from the workspace to the platform.", + RunE: func(cmd *cobra.Command, args []string) error { + b := mustGetBosun() + p, err := b.GetCurrentPlatform() + if err != nil { + return err + } + ctx := b.NewContext() + apps := mustGetKnownApps(b, args) + for _, app := range apps { + err = p.IncludeApp(ctx, app.Name) + if err != nil { + return err + } + } + + err = p.Save(ctx) + return err + }, +}, withFilteringFlags) + +var _ = addCommand(platformCmd, &cobra.Command{ + Use: "show [name]", + Args: cobra.MaximumNArgs(1), + Aliases: []string{"dump"}, + Short: "Shows the named platform, or the current platform if no name provided.", + RunE: func(cmd *cobra.Command, args []string) error { + b := mustGetBosun() + var platform *bosun.Platform + var err error + if len(args) == 1 { + platform, err = b.GetPlatform(args[0]) + } else { + platform, err = b.GetCurrentPlatform() + } + + if err != nil { + return err + } + var y []byte + y, err = yaml.Marshal(platform) + if err != nil { + return err + } + + fmt.Println(string(y)) + return nil + }, +}) diff --git a/cmd/release.go b/cmd/release.go index 938bba1..a9a0b32 100644 --- a/cmd/release.go +++ b/cmd/release.go @@ -2,189 +2,298 @@ package cmd import ( "fmt" - "github.com/cheynewallace/tabby" + "github.com/aryann/difflib" "github.com/fatih/color" - "github.com/naveego/bosun/pkg" "github.com/naveego/bosun/pkg/bosun" - "github.com/naveego/bosun/pkg/git" + "github.com/naveego/bosun/pkg/filter" "github.com/naveego/bosun/pkg/util" + "github.com/olekukonko/tablewriter" "github.com/pkg/errors" "github.com/spf13/cobra" "github.com/spf13/viper" + "github.com/vbauerster/mpb/v4" + "github.com/vbauerster/mpb/v4/decor" "gopkg.in/yaml.v2" - "regexp" + "log" + "os" "strings" "sync" + "time" ) -func init() { - - releaseAddCmd.Flags().BoolP(ArgAppAll, "a", false, "Apply to all known microservices.") - releaseAddCmd.Flags().StringSliceP(ArgAppLabels, "i", []string{}, "Apply to microservices with the provided labels.") - - releaseCmd.AddCommand(releaseUseCmd) - - releaseCmd.AddCommand(releaseAddCmd) - - releaseCmd.PersistentFlags().StringSlice(ArgInclude, []string{}, `Only include apps which match the provided selectors. --include trumps --exclude.".`) - releaseCmd.PersistentFlags().StringSlice(ArgExclude, []string{}, `Don't include apps which match the provided selectors.".`) - rootCmd.AddCommand(releaseCmd) +func runParentPersistentPreRunE(cmd *cobra.Command, args []string) error { + parent := cmd.Parent() + for parent != nil { + if parent.PersistentPreRunE != nil { + err := parent.PersistentPreRunE(cmd, args) + if err != nil { + return errors.Wrapf(err, "parent.PersistentPreRunE (%s)", parent.Name()) + } + } + parent = parent.Parent() + } + return nil +} +func runParentPersistentPostRunE(cmd *cobra.Command, args []string) error { + parent := cmd.Parent() + for parent != nil { + if parent.PersistentPreRunE != nil { + err := parent.PersistentPostRunE(cmd, args) + if err != nil { + return errors.Wrapf(err, "parent.PersistentPreRunE (%s)", parent.Name()) + } + } + parent = parent.Parent() + } + return nil } // releaseCmd represents the release command -var releaseCmd = &cobra.Command{ +var releaseCmd = addCommand(rootCmd, &cobra.Command{ Use: "release", Aliases: []string{"rel", "r"}, - Short: "ReleaseConfig commands.", -} + Short: "Contains sub-commands for releases.", + PersistentPreRunE: func(cmd *cobra.Command, args []string) error { + + releaseOverride := viper.GetString(ArgReleaseName) + if releaseOverride != "" { + b := mustGetBosun() + currentReleaseMetadata, err := b.GetCurrentReleaseMetadata() + if err == nil { + originalCurrentRelease = ¤tReleaseMetadata.Name + } + err = b.UseRelease(releaseOverride) + if err != nil { + return errors.Wrap(err, "setting release override") + } + err = b.Save() + if err != nil { + return errors.Wrap(err, "saving release override") + } + b.NewContext().Log.Infof("Using release %q for this command (original release was %q).", releaseOverride, currentReleaseMetadata.Name) + } + return nil + }, + PersistentPostRunE: func(cmd *cobra.Command, args []string) error { + if originalCurrentRelease != nil { + b := mustGetBosun() + err := b.UseRelease(*originalCurrentRelease) + if err != nil { + return errors.Wrap(err, "resetting current release") + } + err = b.Save() + if err != nil { + return errors.Wrap(err, "saving release reset") + } + b.NewContext().Log.Infof("Reset release to %q.", *originalCurrentRelease) + } + return nil + }, +}, func(cmd *cobra.Command) { + cmd.PersistentFlags().StringP(ArgReleaseName, "r", "", "The release to use for this command (overrides current release set with `release use {name}`).") +}) + +var originalCurrentRelease *string + +const ( + ArgReleaseName = "release" +) var releaseListCmd = addCommand(releaseCmd, &cobra.Command{ Use: "list", Aliases: []string{"ls"}, Short: "Lists known releases.", - Run: func(cmd *cobra.Command, args []string) { + RunE: func(cmd *cobra.Command, args []string) error { b := mustGetBosun() - current, _ := b.GetCurrentRelease() - t := tabby.New() - t.AddHeader("RELEASE", "VERSION", "PATH") - releases := b.GetReleaseConfigs() - for _, release := range releases { + t := tablewriter.NewWriter(os.Stdout) + t.SetCenterSeparator("") + t.SetColumnSeparator("") + t.SetHeader([]string{"", "RELEASE", "VERSION", "PATH"}) + platform, err := b.GetCurrentPlatform() + if err != nil { + return err + } + + current, err := b.GetCurrentReleaseMetadata() + + for _, release := range platform.GetReleaseMetadataSortedByVersion(true, true) { name := release.Name + currentMark := "" if current != nil && release.Name == current.Name { - name = fmt.Sprintf("* %s", name) + currentMark = "*" + name = color.GreenString("%s", name) } - t.AddLine(name, release.Version, release.FromPath) + + t.Append([]string{currentMark, name, release.Version.String(), release.Description}) } - t.Print() + + t.Render() if current == nil { color.Red("No current release selected (use `bosun release use {name}` to select one).") } else { color.White("(* indicates currently active release)") } + return nil }, }) -var releaseShowCmd = addCommand(releaseCmd, &cobra.Command{ - Use: "list-apps", - Aliases: []string{"la"}, - Short: "Lists the apps in the current release.", - Run: func(cmd *cobra.Command, args []string) { - b := mustGetBosun() - r := mustGetCurrentRelease(b) - - switch viper.GetString(ArgGlobalOutput) { - case OutputYaml: - fmt.Println(MustYaml(r)) - default: +var releaseReplanCmd = addCommand(releaseCmd, &cobra.Command{ + Use: "replan", + Short: "Returns the release to the planning stage.", + RunE: func(cmd *cobra.Command, args []string) error { + b, p := getReleaseCmdDeps() + rm, err := b.GetCurrentReleaseMetadata() + if err != nil { + return err + } + ctx := b.NewContext() + _, err = p.RePlanRelease(ctx, rm) + if err != nil { + return err + } - t := tabby.New() - t.AddHeader("APP", "VERSION", "REPO") - for _, app := range r.AppReleases.GetAppsSortedByName() { - t.AddLine(app.Name, app.Version, app.Repo) - } - t.Print() + err = p.Save(ctx) - } + return err }, }) -var releaseDiffCmd = addCommand(releaseCmd, &cobra.Command{ - Use: "diff", - Short: "Reports on the changes deploying the release will inflict on the current environment.", +var releaseShowCmd = addCommand(releaseCmd, &cobra.Command{ + Use: "show", + Aliases: []string{"dump"}, + Short: "Lists the apps in the current release.", RunE: func(cmd *cobra.Command, args []string) error { b := mustGetBosun() - r := mustGetCurrentRelease(b) - if len(viper.GetStringSlice(ArgAppLabels)) == 0 && len(args) == 0 { - viper.Set(ArgAppAll, true) - } - - apps, err := getAppRepos(b, args) + rm, err := b.GetCurrentReleaseManifest(false) if err != nil { return err } - requestedApps := map[string]bool{} - for _, app := range apps { - requestedApps[app.Name] = true - } - - total := len(requestedApps) - complete := 0 - - appReleases := r.AppReleases - wg := new(sync.WaitGroup) - wg.Add(len(appReleases)) - for _, appRelease := range appReleases { - if !requestedApps[appRelease.Name] { - continue - } - go func(appRelease *bosun.AppRelease) { - defer wg.Done() - ctx := b.NewContext().WithAppRelease(appRelease) - values, err := appRelease.GetReleaseValues(ctx) - if err != nil { - ctx.Log.WithError(err).Error("Could not create values map for app release.") - return - } + err = printOutput(rm) + return err + }, +}) - ctx = ctx.WithReleaseValues(values) - err = appRelease.LoadActualState(ctx, true) - if err != nil { - ctx.Log.WithError(err).Error("Could not load actual state.") - return - } - complete += 1 - color.White("Loaded %s (%d/%d)", appRelease.Name, complete, total) - wg.Done() - }(appRelease) +var releaseDotCmd = addCommand(releaseCmd, &cobra.Command{ + Use: "dot", + Short: "Prints a dot diagram of the release.", + RunE: func(cmd *cobra.Command, args []string) error { + b := mustGetBosun() + rm, err := b.GetCurrentReleaseManifest(true) + if err != nil { + return err } - wg.Wait() - for _, appRelease := range appReleases { - color.Blue("%s\n", appRelease.Name) - if appRelease.ActualState.Diff == "" { - color.White("No diff detected.") - } else { - color.Yellow("Diff:\n") - fmt.Println(appRelease.ActualState.Diff) - fmt.Println() - } - } + out := rm.ExportDiagram() + fmt.Println(out) + return nil + }, +}) - color.Blue("SUMMARY:\n") - for _, appRelease := range appReleases { - color.Blue(appRelease.Name) - if appRelease.ActualState.Diff == "" { - color.White("No diff detected.\n") - } else { - fmt.Println("Has changes (see above).") - } - } +func getReleaseCmdDeps() (*bosun.Bosun, *bosun.Platform) { + b := mustGetBosun() + p, err := b.GetCurrentPlatform() + if err != nil { + log.Fatal(err) + } + return b, p +} +var releaseImpactCmd = addCommand(releaseCmd, &cobra.Command{ + Use: "impact", + Short: "Reports on the changes deploying the release will inflict on the current environment.", + RunE: func(cmd *cobra.Command, args []string) error { + // TODO: Re-implement release impact command + return errors.New("needs to be re-implemented after release manifest refactor") + // b, p := getReleaseCmdDeps() + // + // if len(viper.GetStringSlice(ArgFilteringLabels)) == 0 && len(args) == 0 { + // viper.Set(ArgFilteringAll, true) + // } + // + // apps, err := getAppsIncludeCurrent(b, args) + // if err != nil { + // return err + // } + // requestedApps := map[string]bool{} + // for _, app := range apps { + // requestedApps[app.Name] = true + // } + // + // total := len(requestedApps) + // complete := 0 + // + // appReleases := r.AppReleases + // wg := new(sync.WaitGroup) + // wg.Add(len(appReleases)) + // for _, appRelease := range appReleases { + // if !requestedApps[appRelease.Name] { + // continue + // } + // go func(appRelease *bosun.AppDeploy) { + // defer wg.Done() + // + // ctx := b.NewContext().WithAppDeploy(appRelease) + // values, err := appRelease.GetResolvedValues(ctx) + // if err != nil { + // ctx.Log.WithError(err).Error("Could not create values map for app release.") + // return + // } + // + // ctx = ctx.WithPersistableValues(values) + // err = appRelease.LoadActualState(ctx, true) + // if err != nil { + // ctx.Log.WithError(err).Error("Could not load actual state.") + // return + // } + // complete += 1 + // color.White("Loaded %s (%d/%d)", appRelease.Name, complete, total) + // wg.Done() + // }(appRelease) + // } + // wg.Wait() + // + // for _, appRelease := range appReleases { + // color.Blue("%s\n", appRelease.Name) + // if appRelease.ActualState.Diff == "" { + // color.White("No diff detected.") + // } else { + // color.Yellow("Diff:\n") + // fmt.Println(appRelease.ActualState.Diff) + // fmt.Println() + // } + // } + // + // color.Blue("SUMMARY:\n") + // for _, appRelease := range appReleases { + // color.Blue(appRelease.Name) + // if appRelease.ActualState.Diff == "" { + // color.White("No diff detected.\n") + // } else { + // fmt.Println("Has changes (see above).") + // } + // } + // return nil }, -}) +}, withFilteringFlags) var releaseShowValuesCmd = addCommand(releaseCmd, &cobra.Command{ Use: "show-values {app}", Args: cobra.ExactArgs(1), Short: "Shows the values which will be used for a release.", RunE: func(cmd *cobra.Command, args []string) error { - b := mustGetBosun() - r := mustGetCurrentRelease(b) + b, _ := getReleaseCmdDeps() + releaseManifest := mustGetCurrentRelease(b) - appRelease := r.AppReleases[args[0]] - if appRelease == nil { + appManifest := releaseManifest.AppManifests[args[0]] + if appManifest == nil { return errors.Errorf("app %q not in this release", args[0]) } - ctx := b.NewContext() - values, err := appRelease.GetReleaseValues(ctx) - if err != nil { - return err - } + values, err := getResolvedValuesFromAppManifest(b, appManifest) yml, err := yaml.Marshal(values) if err != nil { @@ -197,244 +306,146 @@ var releaseShowValuesCmd = addCommand(releaseCmd, &cobra.Command{ }, }) -var releaseUseCmd = &cobra.Command{ +var releaseUseCmd = addCommand(releaseCmd, &cobra.Command{ Use: "use {name}", Args: cobra.ExactArgs(1), Short: "Sets the release which release commands will work against.", RunE: func(cmd *cobra.Command, args []string) error { b := mustGetBosun() - err := b.UseRelease(args[0]) - if err != nil { - return err - } - if err == nil { - err = b.Save() - } - return err - }, -} -var releaseCreateCmd = addCommand(releaseCmd, &cobra.Command{ - Use: "create {name} {path}", - Args: cobra.ExactArgs(2), - Short: "Creates a new release.", - Long: `The name will be used to refer to the release. -The release file will be stored at the path. - -The --patch flag changes the behavior of "bosun release add {app}". -If the --patch flag is set when the release is created, the add command -will check check if the app was in a previous release with the same -major.minor version as this release. If such a branch is found, -the release branch will be created from the previous release branch, -rather than being created off of master. -`, - RunE: func(cmd *cobra.Command, args []string) error { - name, path := args[0], args[1] - - version := viper.GetString(ArgReleaseCreateVersion) - if version == "" { - semverRaw := regexp.MustCompile(`[^\.0-9]`).ReplaceAllString(name, "") - semverSegs := strings.Split(semverRaw, ".") - if len(semverSegs) < 3 { - semverSegs = append(semverSegs, "0") - } - version = strings.Join(semverSegs, ".") - } - - c := bosun.File{ - FromPath: path, - Releases: []*bosun.ReleaseConfig{ - &bosun.ReleaseConfig{ - Name: name, - Version: version, - IsPatch: viper.GetBool(ArgReleaseCreatePatch), - }, - }, - } - - err := c.Save() + err := b.UseRelease(args[0]) if err != nil { return err } - b := mustGetBosun() - - err = b.UseRelease(name) - - if err != nil { - // release path is not already imported - b.AddImport(path) - err = b.Save() - if err != nil { - return err - } - b = mustGetBosun() - err = b.UseRelease(name) - if err != nil { - // this shouldn't happen... - return err - } - } - err = b.Save() - return err }, -}, func(cmd *cobra.Command) { - cmd.Flags().Bool(ArgReleaseCreatePatch, false, "Set if this is a patch release.") - cmd.Flags().String(ArgReleaseCreateVersion, "", "Version of this release (will attempt to derive from name if not provided).") }) -const ( - ArgReleaseCreateVersion = "version" - ArgReleaseCreatePatch = "patch" - ArgReleaseCreateParentVersion = "parent-version" -) +var releaseDeleteCmd = addCommand(releaseCmd, &cobra.Command{ + Use: "delete [name]", + Args: cobra.ExactArgs(1), + Short: "Deletes a release.", -var releaseAddCmd = &cobra.Command{ - Use: "add [names...]", - Short: "Adds one or more apps to a release.", - Long: "Provide app names or use labels.", RunE: func(cmd *cobra.Command, args []string) error { - viper.BindPFlags(cmd.Flags()) - b := mustGetBosun() - release := mustGetCurrentRelease(b) - - apps, err := getAppRepos(b, args) - if err != nil { - return err - } - - ctx := b.NewContext().WithRelease(release) - - for _, app := range apps { - - delete(release.Exclude, app.Name) - _, ok := release.AppReleaseConfigs[app.Name] - if ok { - pkg.Log.Warnf("Overwriting existing app %q.", app.Name) - } else { - ctx.Log.Infof("Adding app %q", app.Name) - } - - release.AppReleaseConfigs[app.Name], err = app.GetAppReleaseConfig(ctx) - - if err != nil { - return errors.Errorf("could not make release for app %q: %s", app.Name, err) - } - } - - err = release.IncludeDependencies(ctx) - if err != nil { - return err - } - - err = release.Parent.Save() - return err + b, p := getReleaseCmdDeps() + ctx := b.NewContext() + return p.DeleteRelease(ctx, args[0]) }, -} +}) + +// +// var releaseExcludeCmd = addCommand(releaseCmd, &cobra.Command{ +// Use: "exclude [names...]", +// Short: "Excludes and removes one or more apps from a release.", +// Long: "Provide app names or use labels. The matched apps will be removed " + +// "from the release and will not be re-added even if apps which depend on " + +// "them are added or synced. If the app is explicitly added it will be " + +// "removed from the exclude list.", +// RunE: func(cmd *cobra.Command, args []string) error { +// viper.BindPFlags(cmd.Flags()) +// b := mustGetBosun() +// release := mustGetCurrentRelease(b) +// +// apps, err := getAppsIncludeCurrent(b, args) +// if err != nil { +// return err +// } +// +// for _, app := range apps { +// delete(release.AppReleaseConfigs, app.Name) +// release.Exclude[app.Name] = true +// } +// +// err = release.Parent.Save() +// return err +// }, +// }, withFilteringFlags) -var releaseRemoveCmd = addCommand(releaseCmd, &cobra.Command{ - Use: "remove [names...]", - Short: "Removes one or more apps from a release.", - Long: "Provide app names or use labels.", +var releaseValidateCmd = addCommand(releaseCmd, &cobra.Command{ + Use: "validate [names...]", + Short: "Validates the release.", + Long: "Validation checks that all apps (or the named apps) in the current release have a published chart and docker image.", + SilenceErrors: true, + SilenceUsage: true, RunE: func(cmd *cobra.Command, args []string) error { viper.BindPFlags(cmd.Flags()) b := mustGetBosun() release := mustGetCurrentRelease(b) - apps, err := getAppRepos(b, args) + valueSets, err := getValueSetSlice(b, b.GetCurrentEnvironment()) if err != nil { return err } + ctx := b.NewContext() - for _, app := range apps { - delete(release.AppReleaseConfigs, app.Name) + deploySettings := bosun.DeploySettings{ + Environment: ctx.Env, + ValueSets: valueSets, + Manifest: release, } - err = release.Parent.Save() - return err - }, -}) + getFilterParams(b, args).ApplyToDeploySettings(&deploySettings) -var releaseExcludeCmd = addCommand(releaseCmd, &cobra.Command{ - Use: "exclude [names...]", - Short: "Excludes and removes one or more apps from a release.", - Long: "Provide app names or use labels. The matched apps will be removed " + - "from the release and will not be re-added even if apps which depend on " + - "them are added or synced. If the app is explicitly added it will be " + - "removed from the exclude list.", - RunE: func(cmd *cobra.Command, args []string) error { - viper.BindPFlags(cmd.Flags()) - b := mustGetBosun() - release := mustGetCurrentRelease(b) - - apps, err := getAppRepos(b, args) + deploy, err := bosun.NewDeploy(ctx, deploySettings) if err != nil { return err } - for _, app := range apps { - delete(release.AppReleaseConfigs, app.Name) - release.Exclude[app.Name] = true - } - - err = release.Parent.Save() - return err + return validateDeploy(b, ctx, deploy) }, -}) +}, + withFilteringFlags) -var releaseValidateCmd = addCommand(releaseCmd, &cobra.Command{ - Use: "validate", - Short: "Validates the release.", - Long: "Validation checks that all apps in this release have a published chart and docker image for this release.", - SilenceErrors: true, - SilenceUsage: true, - RunE: func(cmd *cobra.Command, args []string) error { - viper.BindPFlags(cmd.Flags()) - b := mustGetBosun() - release := mustGetCurrentRelease(b) - - ctx := b.NewContext() - - return validateRelease(b, ctx, release) - }, -}) +func validateDeploy(b *bosun.Bosun, ctx bosun.BosunContext, release *bosun.Deploy) error { -func validateRelease(b *bosun.Bosun, ctx bosun.BosunContext, release *bosun.Release) error { - w := new(strings.Builder) hasErrors := false - apps := release.AppReleases.GetAppsSortedByName() - p := util.NewProgressBar(len(apps)) + apps := release.AppDeploys - for _, app := range apps { + var wg sync.WaitGroup + // pass &wg (optional), so p will wait for it eventually + p := mpb.New(mpb.WithWaitGroup(&wg)) + + errs := map[string][]error{} + start := time.Now() + for i := range apps { + app := apps[i] if app.Excluded { continue } + wg.Add(1) + bar := p.AddBar(100, mpb.PrependDecorators(decor.Name(app.Name)), + mpb.AppendDecorators(decor.OnComplete(decor.EwmaETA(decor.ET_STYLE_GO, 60), "done"))) - p.Add(0, app.Name) - - errs := app.Validate(ctx) + go func() { + err := app.Validate(ctx) + if err != nil { + errs[app.Name] = err + } + bar.IncrBy(100, time.Since(start)) + wg.Done() + }() - p.Add(1, app.Name) + } + p.Wait() - colorHeader.Fprintf(w, "%s ", app.Name) + for _, app := range apps { + errl := errs[app.Name] + fmt.Printf("%s: ", app.Name) - if len(errs) == 0 { - colorOK.Fprintf(w, "OK\n") + if len(errl) == 0 { + _, _ = colorOK.Println("OK") } else { - fmt.Fprintln(w) + _, _ = colorError.Println("Failed") for _, err := range errs { hasErrors = true - colorError.Fprintf(w, " - %s\n", err) + _, _ = colorError.Printf(" - %s\n", err) } } } - fmt.Println() - fmt.Println(w.String()) - if hasErrors { return errors.New("Some apps are invalid.") } @@ -442,88 +453,89 @@ func validateRelease(b *bosun.Bosun, ctx bosun.BosunContext, release *bosun.Rele return nil } -var releaseSyncCmd = addCommand(releaseCmd, &cobra.Command{ - Use: "sync", - Short: "Pulls the latest commits for every app in the release, then updates the values in the release entry.", - SilenceErrors: true, - SilenceUsage: true, - RunE: func(cmd *cobra.Command, args []string) error { - viper.BindPFlags(cmd.Flags()) - b := mustGetBosun() - release := mustGetCurrentRelease(b) - ctx := b.NewContext() - - appReleases := mustGetAppReleases(b, args) - - err := processAppReleases(b, ctx, appReleases, func(appRelease *bosun.AppRelease) error { - ctx = ctx.WithAppRelease(appRelease) - if appRelease.AppRepo == nil { - ctx.Log.Warn("AppRepo not found.") - } - - repo := appRelease.AppRepo - if !repo.BranchForRelease { - return nil - } - - if err := repo.FetchRepo(ctx); err != nil { - return errors.Wrap(err, "fetch") - } - - g, _ := git.NewGitWrapper(repo.FromPath) - - commits, err := g.Log("--oneline", fmt.Sprintf("%s..origin/%s", appRelease.Commit, appRelease.Branch)) - if err != nil { - return errors.Wrap(err, "check for missed commits") - } - if len(commits) == 0 { - return nil - } - - ctx.Log.Warn("Release branch has had commits since app was added to release. Will attempt to merge before updating release.") - - currentBranch := appRelease.AppRepo.GetBranch() - if currentBranch != appRelease.Branch { - dirtiness, err := g.Exec("status", "--porcelain") - if err != nil { - return errors.Wrap(err, "check if branch is dirty") - } - if len(dirtiness) > 0 { - return errors.New("app is on branch %q, not release branch %q, and has dirty files, so we can't switch to the release branch") - } - ctx.Log.Warnf("Checking out branch %s") - _, err = g.Exec("checkout", appRelease.Branch) - if err != nil { - return errors.Wrap(err, "check out release branch") - } - - _, err = g.Exec("merge", fmt.Sprintf("origin/%s", appRelease.Branch)) - if err != nil { - return errors.Wrap(err, "merge release branch") - } - } - - err = release.IncludeApp(ctx, appRelease.AppRepo) - if err != nil { - return errors.Wrap(err, "update failed") - } - - return nil - }) - - if err != nil { - return err - } - - err = release.Parent.Save() - - return err - }, -}) - -func processAppReleases(b *bosun.Bosun, ctx bosun.BosunContext, appReleases []*bosun.AppRelease, fn func(a *bosun.AppRelease) error) error { - - var included []*bosun.AppRelease +// +// var releaseSyncCmd = addCommand(releaseCmd, &cobra.Command{ +// Use: "sync", +// Short: "Pulls the latest commits for every app in the release, then updates the values in the release entry.", +// SilenceErrors: true, +// SilenceUsage: true, +// RunE: func(cmd *cobra.Command, args []string) error { +// viper.BindPFlags(cmd.Flags()) +// b := mustGetBosun() +// release := mustGetCurrentRelease(b) +// ctx := b.NewContext() +// +// appReleases := getFilterParams(b, args).MustGetAppDeploys() +// +// err := processAppReleases(b, ctx, appReleases, func(appRelease *bosun.AppDeploy) error { +// ctx = ctx.WithAppDeploy(appRelease) +// if appRelease.App == nil { +// ctx.Log.Warn("App not found.") +// } +// +// repo := appRelease.App +// if !repo.BranchForRelease { +// return nil +// } +// +// if err := repo.Repo.Fetch(ctx); err != nil { +// return errors.Wrap(err, "fetch") +// } +// +// g, _ := git.NewGitWrapper(repo.FromPath) +// +// commits, err := g.Log("--oneline", fmt.Sprintf("%s..origin/%s", appRelease.Commit, appRelease.Branch)) +// if err != nil { +// return errors.Wrap(err, "check for missed commits") +// } +// if len(commits) == 0 { +// return nil +// } +// +// ctx.Log.Warn("Deploy branch has had commits since app was added to release. Will attempt to merge before updating release.") +// +// currentBranch := appRelease.App.GetBranchName() +// if currentBranch != appRelease.Branch { +// dirtiness, err := g.Exec("status", "--porcelain") +// if err != nil { +// return errors.Wrap(err, "check if branch is dirty") +// } +// if len(dirtiness) > 0 { +// return errors.New("app is on branch %q, not release branch %q, and has dirty files, so we can't switch to the release branch") +// } +// ctx.Log.Warnf("Checking out branch %s") +// _, err = g.Exec("checkout", appRelease.Branch.String()) +// if err != nil { +// return errors.Wrap(err, "check out release branch") +// } +// +// _, err = g.Exec("merge", fmt.Sprintf("origin/%s", appRelease.Branch)) +// if err != nil { +// return errors.Wrap(err, "merge release branch") +// } +// } +// +// err = release.MakeAppAvailable(ctx, appRelease.App) +// if err != nil { +// return errors.Wrap(err, "update failed") +// } +// +// return nil +// }) +// +// if err != nil { +// return err +// } +// +// err = release.Parent.Save() +// +// return err +// }, +// }) + +func processAppReleases(b *bosun.Bosun, ctx bosun.BosunContext, appReleases []*bosun.AppDeploy, fn func(a *bosun.AppDeploy) error) error { + + var included []*bosun.AppDeploy for _, ar := range appReleases { if !ar.Excluded { included = append(included, ar) @@ -555,12 +567,12 @@ var releaseTestCmd = addCommand(releaseCmd, &cobra.Command{ return err } - appReleases := mustGetAppReleases(b, args) + appReleases := getFilterParams(b, args).MustGetAppDeploys() for _, appRelease := range appReleases { - ctx = ctx.WithAppRelease(appRelease) - for _, action := range appRelease.Actions { + ctx = ctx.WithAppDeploy(appRelease) + for _, action := range appRelease.AppConfig.Actions { if action.Test != nil { err := action.Execute(ctx) if err != nil { @@ -572,7 +584,7 @@ var releaseTestCmd = addCommand(releaseCmd, &cobra.Command{ return nil }, -}) +}, withFilteringFlags) var releaseDeployCmd = addCommand(releaseCmd, &cobra.Command{ Use: "deploy", @@ -590,23 +602,43 @@ var releaseDeployCmd = addCommand(releaseCmd, &cobra.Command{ return err } + valueSets, err := getValueSetSlice(b, b.GetCurrentEnvironment()) + if err != nil { + return err + } + + deploySettings := bosun.DeploySettings{ + Environment: ctx.Env, + ValueSets: valueSets, + Manifest: release, + } + + getFilterParams(b, args).ApplyToDeploySettings(&deploySettings) + + deploy, err := bosun.NewDeploy(ctx, deploySettings) + if err != nil { + return err + } + if viper.GetBool(ArgReleaseSkipValidate) { ctx.Log.Warn("Validation disabled.") } else { ctx.Log.Info("Validating...") - err := validateRelease(b, ctx, release) + err := validateDeploy(b, ctx, deploy) if err != nil { return err } } - err := release.Deploy(ctx) + err = deploy.Deploy(ctx) return err }, }, func(cmd *cobra.Command) { cmd.Flags().Bool(ArgReleaseSkipValidate, false, "Skips running validation before deploying the release.") -}) +}, + withFilteringFlags, + withValueSetFlags) var releaseMergeCmd = addCommand(releaseCmd, &cobra.Command{ Use: "merge [apps...]", @@ -615,107 +647,270 @@ var releaseMergeCmd = addCommand(releaseCmd, &cobra.Command{ SilenceUsage: true, RunE: func(cmd *cobra.Command, args []string) error { viper.BindPFlags(cmd.Flags()) - b := mustGetBosun() - ctx := b.NewContext() + return errors.New("not implemented") + // b := mustGetBosun() + // ctx := b.NewContext() + + // force := viper.GetBool(ArgGlobalForce) + + // release, err := b.GetCurrentRelease() + // if err != nil { + // return err + // } + // appReleases := getFilterParams(b, args).MustGetAppDeploys() + // + // releaseBranch := fmt.Sprintf("release/%s", release.Name) + // + // repoDirs := make(map[string]string) + // + // for _, appRelease := range appReleases { + // appRepo := appRelease.App + // if !appRepo.IsRepoCloned() { + // ctx.Log.Error("Repo is not cloned, cannot merge.") + // continue + // } + // repoDir, err := git.GetRepoPath(appRepo.FromPath) + // if err != nil { + // ctx.Log.WithError(err).Error("Could not get git repository from path, cannot merge.") + // continue + // } + // repoDirs[repoDir] = appRelease.Version.String() + // } + // + // for repoDir, version := range repoDirs { + // + // ctx = ctx.WithDir(repoDir) + // + // g, _ := git.NewGitWrapper(repoDir) + // + // g.Pull() + // + // _, err := g.Exec("checkout", releaseBranch) + // if err != nil { + // return errors.Errorf("checkout %s: %s", repoDir, releaseBranch) + // } + // + // tagArgs := []string{"tag", fmt.Sprintf("%s-%s", version, release.Version)} + // if force { + // tagArgs = append(tagArgs, "--force") + // } + // + // _, err = g.Exec(tagArgs...) + // if err != nil { + // ctx.Log.WithError(err).Warn("Could not tag repo, skipping merge. Set --force flag to force tag.") + // continue + // } + // + // pushArgs := []string{"push", "--tags"} + // if force { + // pushArgs = append(pushArgs, "--force") + // } + // _, err = g.Exec(pushArgs...) + // if err != nil { + // return errors.Errorf("push tags: %s", err) + // } + // + // diff, err := g.Exec("log", "origin/master..origin/"+releaseBranch, "--oneline") + // if err != nil { + // return errors.Errorf("find diffs: %s", err) + // } + // + // if len(diff) > 0 { + // + // ctx.Log.Info("Deploy branch has diverged from master, will merge back...") + // + // ctx.Log.Info("Creating pull request.") + // prNumber, err := GitPullRequestCommand{ + // LocalRepoPath: repoDir, + // Base: "master", + // FromBranch: releaseBranch, + // }.Execute() + // if err != nil { + // ctx.Log.WithError(err).Error("Could not create pull request.") + // continue + // } + // + // ctx.Log.Info("Accepting pull request.") + // err = GitAcceptPRCommand{ + // PRNumber: prNumber, + // RepoDirectory: repoDir, + // DoNotMergeBaseIntoBranch: true, + // }.Execute() + // + // if err != nil { + // ctx.Log.WithError(err).Error("Could not accept pull request.") + // continue + // } + // + // ctx.Log.Info("Merged back to master.") + // } + // + // } - force := viper.GetBool(ArgGlobalForce) + return nil + }, +}, withFilteringFlags) - release, err := b.GetCurrentRelease() - if err != nil { - return err - } - appReleases := mustGetAppReleases(b, args) +const ArgReleaseSkipValidate = "skip-validation" - releaseBranch := fmt.Sprintf("release/%s", release.Name) +var releaseDiffCmd = addCommand( + releaseCmd, + &cobra.Command{ + Use: "diff {app} [release/]{env} [release]/{env}", + Short: "Reports the differences between the values for an app in two scenarios.", + Long: `If the release part of the scenario is not provided, a transient release will be created and used instead.`, + Example: `This command will show the differences between the values deployed +to the blue environment in release 2.4.2 and the current values for the +green environment: + +diff go-between 2.4.2/blue green +`, + Args: cobra.ExactArgs(3), + SilenceUsage: true, + SilenceErrors: true, + RunE: func(cmd *cobra.Command, args []string) error { + b := mustGetBosun() + app := mustGetApp(b, []string{args[0]}) - repoDirs := make(map[string]string) + env1 := args[1] + env2 := args[2] - for _, appRelease := range appReleases { - appRepo := appRelease.AppRepo - if !appRepo.IsRepoCloned() { - ctx.Log.Error("Repo is not cloned, cannot merge.") - continue - } - repoDir, err := git.GetRepoPath(appRepo.FromPath) + p, err := b.GetCurrentPlatform() if err != nil { - ctx.Log.WithError(err).Error("Could not get git repository from path, cannot merge.") - continue + return err } - repoDirs[repoDir] = appRelease.Version - } - for repoDir, version := range repoDirs { + getValuesForEnv := func(scenario string) (string, error) { + + segs := strings.Split(scenario, "/") + var releaseName, envName string + var appDeploy *bosun.AppDeploy + switch len(segs) { + case 1: + envName = segs[0] + case 2: + releaseName = segs[0] + envName = segs[1] + default: + return "", errors.Errorf("invalid scenario %q", scenario) + } - ctx = ctx.WithDir(repoDir) + env, err := b.GetEnvironment(envName) + if err != nil { + return "", errors.Wrap(err, "environment") + } + ctx := b.NewContext().WithEnv(env) - g, _ := git.NewGitWrapper(repoDir) + var ok bool + if releaseName != "" { + releaseManifest, err := p.GetReleaseManifestByName(releaseName, true) - g.Pull() + valueSets, err := getValueSetSlice(b, env) + if err != nil { + return "", err + } - _, err := g.Exec("checkout", releaseBranch) - if err != nil { - return errors.Errorf("checkout %s: %s", repoDir, releaseBranch) - } + deploySettings := bosun.DeploySettings{ + Environment: ctx.Env, + ValueSets: valueSets, + Manifest: releaseManifest, + } - tagArgs := []string{"tag", fmt.Sprintf("%s-%s", version, release.Version)} - if force { - tagArgs = append(tagArgs, "--force") - } + deploy, err := bosun.NewDeploy(ctx, deploySettings) + if err != nil { + return "", err + } - _, err = g.Exec(tagArgs...) - if err != nil { - ctx.Log.WithError(err).Warn("Could not tag repo, skipping merge. Set --force flag to force tag.") - continue - } + appDeploy, ok = deploy.AppDeploys[app.Name] + if !ok { + return "", errors.Errorf("no app named %q in release %q", app.Name, releaseName) + } - pushArgs := []string{"push", "--tags"} - if force { - pushArgs = append(pushArgs, "--force") - } - _, err = g.Exec(pushArgs...) - if err != nil { - return errors.Errorf("push tags: %s", err) - } + } else { + valueSets, err := getValueSetSlice(b, env) + if err != nil { + return "", err + } - diff, err := g.Exec("log", "origin/master..origin/"+releaseBranch, "--oneline") - if err != nil { - return errors.Errorf("find diffs: %s", err) - } + deploySettings := bosun.DeploySettings{ + Environment: ctx.Env, + ValueSets: valueSets, + Apps: map[string]*bosun.App{ + app.Name: app, + }, + } + + deploy, err := bosun.NewDeploy(ctx, deploySettings) + if err != nil { + return "", err + } - if len(diff) > 0 { + appDeploy, ok = deploy.AppDeploys[app.Name] + if !ok { + return "", errors.Errorf("no app named %q in release %q", app.Name, releaseName) + } - ctx.Log.Info("Release branch has diverged from master, will merge back...") + } - ctx.Log.Info("Creating pull request.") - prNumber, err := GitPullRequestCommand{ - LocalRepoPath: repoDir, - Base: "master", - FromBranch: releaseBranch, - }.Execute() + values, err := appDeploy.GetResolvedValues(ctx) if err != nil { - ctx.Log.WithError(err).Error("Could not create pull request.") - continue + return "", errors.Wrap(err, "get release values") } - ctx.Log.Info("Accepting pull request.") - err = GitAcceptPRCommand{ - PRNumber: prNumber, - RepoDirectory: repoDir, - DoNotMergeBaseIntoBranch: true, - }.Execute() - + valueYaml, err := values.Values.YAML() if err != nil { - ctx.Log.WithError(err).Error("Could not accept pull request.") - continue + return "", errors.Wrap(err, "get release values yaml") } - ctx.Log.Info("Merged back to master.") + return valueYaml, nil } - } + env1yaml, err := getValuesForEnv(env1) + if err != nil { + return errors.Errorf("error for env1 %q: %s", env1, err) + } - return nil - }, -}) + env2yaml, err := getValuesForEnv(env2) + if err != nil { + return errors.Errorf("error for env2 %q: %s", env2, err) + } -const ArgReleaseSkipValidate = "skip-validation" + env1lines := strings.Split(env1yaml, "\n") + env2lines := strings.Split(env2yaml, "\n") + diffs := difflib.Diff(env1lines, env2lines) + + for _, diff := range diffs { + fmt.Println(renderDiff(diff)) + } + + return nil + + }, + }) + +func diffStrings(a, b string) []difflib.DiffRecord { + left := strings.Split(a, "\n") + right := strings.Split(b, "\n") + return difflib.Diff(left, right) +} + +func renderDiff(diff difflib.DiffRecord) string { + switch diff.Delta { + case difflib.Common: + return fmt.Sprintf(" %s", diff.Payload) + case difflib.LeftOnly: + return color.RedString("- %s", diff.Payload) + case difflib.RightOnly: + return color.GreenString("+ %s", diff.Payload) + } + panic(fmt.Sprintf("invalid delta %v", diff.Delta)) +} + +func getDeployableApps(b *bosun.Bosun, args []string) ([]*bosun.App, error) { + fp := getFilterParams(b, args) + apps, err := fp.GetAppsChain(fp.Chain().Including(filter.MustParse(bosun.LabelDeployable))) + if err != nil { + return nil, err + } + return apps, nil +} diff --git a/cmd/release_plan.go b/cmd/release_plan.go new file mode 100644 index 0000000..ba48c3b --- /dev/null +++ b/cmd/release_plan.go @@ -0,0 +1,296 @@ +package cmd + +import ( + "fmt" + "github.com/aryann/difflib" + "github.com/fatih/color" + "github.com/manifoldco/promptui" + "github.com/naveego/bosun/pkg" + "github.com/naveego/bosun/pkg/bosun" + "github.com/naveego/bosun/pkg/filter" + "github.com/naveego/bosun/pkg/semver" + "github.com/naveego/bosun/pkg/util" + "github.com/pkg/errors" + "github.com/spf13/cobra" + "github.com/spf13/viper" + "strings" +) + +// releaseCmd represents the release command +var releasePlanCmd = addCommand(releaseCmd, &cobra.Command{ + Use: "plan", + Aliases: []string{"planning", "p"}, + Short: "Contains sub-commands for release planning.", + Run: func(cmd *cobra.Command, args []string) { + cmd.Help() + + _, p := getReleaseCmdDeps() + + fmt.Println() + if p.Plan == nil { + color.Red("There is no current plan.\n") + } else { + color.Blue("Currently planning %s.\n", p.Plan.ReleaseMetadata) + } + + }, +}) + +var releasePlanShowCmd = addCommand(releasePlanCmd, &cobra.Command{ + Use: "show", + Aliases: []string{"dump"}, + Short: "Shows the current release plan.", + SilenceUsage: true, + RunE: func(cmd *cobra.Command, args []string) error { + _, p := getReleaseCmdDeps() + + if p.Plan == nil { + return errors.New("no release plan active") + } + + err := printOutput(p.Plan) + return err + }, +}) + +var releasePlanEditCmd = addCommand(releasePlanCmd, &cobra.Command{ + Use: "edit", + Short: "Opens release plan in an editor.", + SilenceUsage: true, + RunE: func(cmd *cobra.Command, args []string) error { + _, p := getReleaseCmdDeps() + + if p.Plan == nil { + return errors.New("no release plan active") + } + + err := Edit(p.FromPath) + + return err + }, +}) + +var releasePlanStartCmd = addCommand(releasePlanCmd, &cobra.Command{ + Use: "start", + Aliases: []string{"create"}, + Short: "Begins planning a new release.", + RunE: func(cmd *cobra.Command, args []string) error { + b, p := getReleaseCmdDeps() + ctx := b.NewContext() + + var err error + var version semver.Version + versionString := viper.GetString(ArgReleasePlanStartVersion) + if versionString != "" { + version, err = semver.NewVersion(versionString) + if err != nil { + return errors.Errorf("invalid version: %s", err) + } + } + + settings := bosun.ReleasePlanSettings{ + Name: viper.GetString(ArgReleasePlanStartName), + Version: version, + Bump: viper.GetString(ArgReleasePlanStartBump), + BranchParent: viper.GetString(ArgReleasePlanStartPatchParent), + } + + _, err = p.CreateReleasePlan(ctx, settings) + if err != nil { + return err + } + + err = p.Save(ctx) + + return err + }, +}, func(cmd *cobra.Command) { + cmd.Flags().String(ArgReleasePlanStartName, "", "The name of the release (defaults to the version if not provided).") + cmd.Flags().String(ArgReleasePlanStartVersion, "", "The version of the release.") + cmd.Flags().String(ArgReleasePlanStartBump, "", "The version bump of the release.") + cmd.Flags().String(ArgReleasePlanStartPatchParent, "", "The release the plan will prefer to create branches from.") +}) + +const ( + ArgReleasePlanStartName = "name" + ArgReleasePlanStartVersion = "version" + ArgReleasePlanStartBump = "bump" + ArgReleasePlanStartPatchParent = "patch-parent" +) + +var releasePlanDiscardCmd = addCommand(releasePlanCmd, &cobra.Command{ + Use: "discard", + Args: cobra.NoArgs, + Short: "Discard the current release plan.", + RunE: func(cmd *cobra.Command, args []string) error { + b, p := getReleaseCmdDeps() + if pkg.RequestConfirmFromUser("Are you sure you want to discard the current release plan?") { + err := p.DiscardPlan(b.NewContext()) + return err + } + return nil + }, +}) + +var releasePlanCommitCmd = addCommand(releasePlanCmd, &cobra.Command{ + Use: "commit", + Args: cobra.NoArgs, + Short: "Commit the current release plan.", + RunE: func(cmd *cobra.Command, args []string) error { + b, p := getReleaseCmdDeps() + ctx := b.NewContext() + _, err := p.CommitPlan(ctx) + + if err != nil { + return err + } + + return p.Save(ctx) + }, +}) + +var releasePlanAppCmd = addCommand(releasePlanCmd, &cobra.Command{ + Use: "app", + Short: "Sets the disposition of an app in the release.", + Long: "Alternatively, you can edit the plan directly in the platform yaml file.", + RunE: func(cmd *cobra.Command, args []string) error { + viper.BindPFlags(cmd.Flags()) + + b, p := getReleaseCmdDeps() + + plan := p.Plan + if plan == nil { + return errors.New("no plan active") + } + + ctx := b.NewContext() + + var apps []*bosun.App + fp := getFilterParams(b, args) + if !fp.IsEmpty() { + apps, _ = fp.GetAppsChain(fp.Chain().Including(filter.MustParse(bosun.LabelDeployable))) + } + + appPlans := map[string]*bosun.AppPlan{} + + if len(apps) > 0 { + for _, app := range apps { + if intent, ok := plan.Apps[app.Name]; ok { + appPlans[app.Name] = intent + } + } + } else { + var appPlanList []*bosun.AppPlan + for _, name := range util.SortedKeys(plan.Apps) { + appPlan := plan.Apps[name] + appPlanList = append(appPlanList, appPlan) + } + + selectAppUI := promptui.Select{ + Label: "Select an app", + Items: appPlanList, + StartInSearchMode: true, + Templates: editStatusTemplates, + } + index, _, err := selectAppUI.Run() + if err != nil { + return err + } + + selectedAppPlan := appPlanList[index] + appPlans[selectedAppPlan.Name] = selectedAppPlan + } + + var err error + changes := map[string][]difflib.DiffRecord{} + for _, appPlan := range appPlans { + original := MustYaml(appPlan) + + deploySet := cmd.Flags().Changed(ArgReleaseSetStatusDeploy) + var deploy bool + if deploySet { + deploy = viper.GetBool(ArgReleaseSetStatusDeploy) + } else { + + deployUI := promptui.Prompt{ + Label: fmt.Sprintf("Do you want to deploy %q? [y/N] ", appPlan.Name), + } + + deployResult, err := deployUI.Run() + if err != nil { + return err + } + + deploy = strings.HasPrefix(strings.ToLower(deployResult), "y") + } + + appPlan.Deploy = deploy + + reason := viper.GetString(ArgReleaseSetStatusReason) + if reason == "" { + + reasonUI := promptui.Prompt{ + Label: fmt.Sprintf("Why do you want to make this decision for %s? ", appPlan.Name), + AllowEdit: true, + } + + reason, err = reasonUI.Run() + if err != nil { + return err + } + } + + bump := viper.GetString(ArgReleaseSetStatusBump) + if bump == "" { + bumpUI := promptui.Select{ + Label: fmt.Sprintf("What kind of version bump is appropriate for %q", appPlan.Name), + Items: []string{"none", "patch", "minor", "major"}, + } + _, bump, err = bumpUI.Run() + if err != nil { + return err + } + } + appPlan.Bump = bump + + updated := MustYaml(appPlan) + + changes[appPlan.Name] = diffStrings(original, updated) + } + + for name, diffs := range changes { + fmt.Printf("Changes to %q:\n", name) + for _, diff := range diffs { + if diff.Delta != difflib.Common { + fmt.Println(renderDiff(diff)) + } + } + } + + err = p.Save(ctx) + if err != nil { + return err + } + + return nil + }, +}, withFilteringFlags, + func(cmd *cobra.Command) { + cmd.Flags().Bool(ArgReleaseSetStatusDeploy, false, "Set to deploy matched apps.") + cmd.Flags().String(ArgReleaseSetStatusReason, "", "The reason to set for the status change for matched apps.") + cmd.Flags().String(ArgReleaseSetStatusBump, "", "The version bump to apply to upgrades among matched apps.") + }) + +const ( + ArgReleaseSetStatusDeploy = "deploy" + ArgReleaseSetStatusReason = "reason" + ArgReleaseSetStatusBump = "bump" +) + +var editStatusTemplates = &promptui.SelectTemplates{ + Label: "{{ . }}:", + Active: "> {{ .String | cyan }}", + Inactive: " {{ .String }}", + Selected: "> {{ .String }}", + Details: ``, +} diff --git a/cmd/repo.go b/cmd/repo.go new file mode 100644 index 0000000..700045e --- /dev/null +++ b/cmd/repo.go @@ -0,0 +1,242 @@ +// Copyright © 2018 NAME HERE +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cmd + +import ( + "fmt" + "github.com/manifoldco/promptui" + "github.com/naveego/bosun/pkg" + "github.com/naveego/bosun/pkg/bosun" + "github.com/naveego/bosun/pkg/filter" + "github.com/naveego/bosun/pkg/util" + "github.com/olekukonko/tablewriter" + "github.com/pkg/errors" + "github.com/spf13/cobra" + "github.com/spf13/viper" + "os" + "sort" + "strings" +) + +var repoCmd = addCommand(rootCmd, &cobra.Command{ + Use: "repo", + Short: "Contains sub-commands for interacting with repos. Has some overlap with the git sub-command.", + Long: `Most repo sub-commands take one or more optional name parameters. +If no name parameters are provided, the command will attempt to find a repo which +contains the current working path.`, + Args: cobra.NoArgs, +}) + +var _ = addCommand(repoCmd, &cobra.Command{ + Use: "list", + Aliases: []string{"ls"}, + Short: "Lists the known repos and their clone status.", + SilenceUsage: true, + RunE: func(cmd *cobra.Command, args []string) error { + b := mustGetBosun() + repos := getFilterParams(b, []string{}).Chain().Then().Including(filter.FilterMatchAll()).From(b.GetRepos()).([]*bosun.Repo) + + t := tablewriter.NewWriter(os.Stdout) + + t.SetHeader([]string{"Name", "Cloned", "Local Path", "Labels", "Apps"}) + t.SetReflowDuringAutoWrap(false) + t.SetAutoWrapText(false) + + for _, repo := range repos { + + var name, cloned, path, labels, apps string + + name = repo.Name + if repo.LocalRepo != nil { + cloned = "YES" + path = repo.LocalRepo.Path + } + var appNames []string + for _, app := range repo.Apps { + appNames = append(appNames, app.Name) + } + appNames = util.DistinctStrings(appNames) + sort.Strings(appNames) + apps = strings.Join(appNames, "\n") + + var labelKeys []string + for label := range repo.FilteringLabels { + labelKeys = append(labelKeys, label) + } + sort.Strings(labelKeys) + var labelsKVs []string + for _, label := range labelKeys { + if label != "" { + labelsKVs = append(labelsKVs, fmt.Sprintf("%s:%s", label, repo.FilteringLabels[label])) + } + } + labels = strings.Join(labelsKVs, "\n") + + t.Append([]string{name, cloned, path, labels, apps}) + } + + t.Render() + return nil + }, +}, withFilteringFlags) + +var _ = addCommand(repoCmd, &cobra.Command{ + Use: "path {name}", + Args: cobra.ExactArgs(1), + Short: "Outputs the path where the repo is cloned on the local system.", + RunE: func(cmd *cobra.Command, args []string) error { + b := mustGetBosun() + repo, err := b.GetRepo(args[0]) + if err != nil { + return err + } + if repo.LocalRepo == nil { + return errors.New("repo is not cloned") + } + + fmt.Println(repo.LocalRepo.Path) + return nil + }, +}) + +var _ = addCommand( + repoCmd, + &cobra.Command{ + Use: "clone {name} [name...]", + Args: cobra.MinimumNArgs(1), + Short: "Clones the named repo(s).", + Long: "Uses the first directory in `gitRoots` from the root config.", + RunE: func(cmd *cobra.Command, args []string) error { + b := mustGetBosun() + + dir := viper.GetString(ArgAppCloneDir) + roots := b.GetGitRoots() + var err error + if dir == "" { + if len(roots) == 0 { + p := promptui.Prompt{ + Label: "Provide git root (apps will be cloned to ./org/repo in the dir you specify)", + } + dir, err = p.Run() + if err != nil { + return err + } + } else { + dir = roots[0] + } + } + rootExists := false + for _, root := range roots { + if root == dir { + rootExists = true + break + } + } + if !rootExists { + b.AddGitRoot(dir) + err = b.Save() + if err != nil { + return err + } + b = mustGetBosun() + } + + repos, err := getFilterParams(b, args).Chain().ToGetAtLeast(1).FromErr(b.GetRepos()) + if err != nil { + return err + } + + ctx := b.NewContext() + for _, repo := range repos.([]*bosun.Repo) { + log := ctx.Log.WithField("repo", repo.Name) + + if repo.CheckCloned() == nil { + pkg.Log.Infof("Repo already cloned to %q", repo.LocalRepo.Path) + continue + } + log.Info("Cloning...") + + err = repo.Clone(ctx, dir) + if err != nil { + log.WithError(err).Error("Error cloning.") + } else { + log.Info("Cloned.") + } + } + + err = b.Save() + + return err + }, + }, + func(cmd *cobra.Command) { + cmd.Flags().String(ArgAppCloneDir, "", "The directory to clone into. (The repo will be cloned into `org/repo` in this directory.) ") + }, + withFilteringFlags, +) + +var _ = addCommand( + repoCmd, + &cobra.Command{ + Use: "pull [repo] [repo...]", + Short: "Pulls the repo(s).", + SilenceUsage: true, + SilenceErrors: true, + RunE: func(cmd *cobra.Command, args []string) error { + return forEachRepo(args, func(ctx bosun.BosunContext, repo *bosun.Repo) error { + ctx.Log.Info("Fetching...") + err := repo.Pull(ctx) + return err + }) + }, + }, withFilteringFlags) + +var _ = addCommand( + repoCmd, + &cobra.Command{ + Use: "fetch [repo] [repo...]", + Short: "Fetches the repo(s).", + SilenceUsage: true, + SilenceErrors: true, + RunE: func(cmd *cobra.Command, args []string) error { + return forEachRepo(args, func(ctx bosun.BosunContext, repo *bosun.Repo) error { + ctx.Log.Info("Pulling...") + err := repo.Fetch(ctx) + return err + }) + }, + }, withFilteringFlags) + +func forEachRepo(args []string, fn func(ctx bosun.BosunContext, repo *bosun.Repo) error) error { + b := mustGetBosun() + ctx := b.NewContext() + repos, err := getFilterParams(b, args).Chain().ToGetAtLeast(1).FromErr(b.GetRepos()) + if err != nil { + return err + } + + var errs error + for _, repo := range repos.([]*bosun.Repo) { + ctx.Log.Infof("Processing %q...", repo.Name) + err = fn(ctx, repo) + if err != nil { + errs = util.MultiErr(errs, err) + ctx.Log.WithError(err).Errorf("Error on repo %q", repo.Name) + } else { + ctx.Log.Infof("Completed %q.", repo.Name) + } + } + return errs +} diff --git a/cmd/root.go b/cmd/root.go index cde4d61..d18c5e5 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -17,7 +17,9 @@ package cmd import ( "fmt" "github.com/naveego/bosun/pkg" + "github.com/pkg/errors" "github.com/sirupsen/logrus" + "runtime/pprof" "strings" "os" @@ -36,7 +38,7 @@ var Timestamp string var Commit string // rootCmd represents the base command when called without any subcommands -var rootCmd = &cobra.Command{ +var rootCmd = TraverseRunHooks(&cobra.Command{ Use: "bosun", Short: "Devops tool.", SilenceErrors: true, @@ -73,14 +75,26 @@ building, deploying, or monitoring apps you may want to add them to this tool.`, cmd.SilenceUsage = true } - conditions := viper.GetStringSlice(ArgInclude) - if len(conditions) > 0 { - + if viper.GetBool(ArgGlobalProfile) { + profilePath := "./bosun.prof" + f, err := os.Create(profilePath) + if err != nil { + return errors.Wrap(err, "creating profiling file") + } + err = pprof.StartCPUProfile(f) + if err != nil { + return errors.Wrap(err, "starting profiling") + } } return nil }, -} + PersistentPostRun: func(cmd *cobra.Command, args []string) { + if viper.GetBool(ArgGlobalProfile) { + pprof.StopCPUProfile() + } + }, +}) // Execute adds all child commands to the root command and sets flags appropriately. // This is called by main.main(). It only needs to happen once to the rootCmd. @@ -90,7 +104,12 @@ func Execute() { case handledError: fmt.Println(e.Error()) default: - colorError.Fprintln(os.Stderr, err) + if viper.GetBool(ArgGlobalVerbose) { + colorError.Fprintf(os.Stderr, "%+v\n", err) + + } else { + colorError.Fprintln(os.Stderr, err) + } } os.Exit(1) @@ -98,16 +117,17 @@ func Execute() { } const ( - ArgGlobalVerbose = "verbose" - ArgGlobalDryRun = "dry-run" - ArgGlobalCluster = "cluster" - ArgGlobalDomain = "domain" - ArgGlobalValues = "values" - ArgBosunConfigFile = "config-file" + ArgGlobalVerbose = "verbose" + ArgGlobalDryRun = "dry-run" + ArgGlobalCluster = "cluster" + ArgGlobalDomain = "domain" + ArgGlobalValues = "values" + ArgBosunConfigFile = "config-file" ArgGlobalConfirmedEnv = "confirm-env" - ArgGlobalForce = "force" - ArgGlobalNoReport = "no-report" - ArgGlobalOutput = "output" + ArgGlobalForce = "force" + ArgGlobalNoReport = "no-report" + ArgGlobalOutput = "output" + ArgGlobalProfile = "profile" ) func init() { @@ -123,6 +143,8 @@ func init() { rootCmd.PersistentFlags().Bool(ArgGlobalNoReport, false, "Disable reporting of deploys to github.") rootCmd.PersistentFlags().String(ArgGlobalConfirmedEnv, "", "Set to confirm that the environment is correct when targeting a protected environment.") rootCmd.PersistentFlags().MarkHidden(ArgGlobalConfirmedEnv) + rootCmd.PersistentFlags().Bool(ArgGlobalProfile, false, "Dump profiling info.") + rootCmd.PersistentFlags().MarkHidden(ArgGlobalProfile) defaultCluster := "" defaultDomain := "" diff --git a/cmd/tools.go b/cmd/tools.go index b777991..f2c9427 100644 --- a/cmd/tools.go +++ b/cmd/tools.go @@ -83,7 +83,7 @@ var toolsInstallCmd = addCommand(toolsCmd, &cobra.Command{ RunE: func(cmd *cobra.Command, args []string) error { b := mustGetBosun() tools := b.GetTools() - var tool bosun.ToolDef + var tool *bosun.ToolDef var ok bool name := args[0] for _, tool = range tools { @@ -108,7 +108,3 @@ var toolsInstallCmd = addCommand(toolsCmd, &cobra.Command{ return err }, }) - -func init() { - rootCmd.AddCommand(metaUpgradeCmd) -} diff --git a/cmd/vault.go b/cmd/vault.go index f9cfb25..77c720c 100644 --- a/cmd/vault.go +++ b/cmd/vault.go @@ -90,7 +90,9 @@ Any values provided using --values will be in {{ .Values.xxx }} return nil } - err = vaultLayout.Apply(vaultClient) + key := strings.Join(args, "-") + force := viper.GetBool(ArgGlobalForce) + err = vaultLayout.Apply(key, force, vaultClient) return err }, @@ -229,7 +231,6 @@ var vaultJWTCmd = &cobra.Command{ return err } - role := viper.GetString(ArgVaultJWTRole) tenant := viper.GetString(ArgVaultJWTTenant) sub := viper.GetString(ArgVaultJWTSub) diff --git a/cmd/workspace.go b/cmd/workspace.go index 88aaf39..4ea52da 100644 --- a/cmd/workspace.go +++ b/cmd/workspace.go @@ -81,7 +81,7 @@ var configShowImportsCmd = addCommand(configShowCmd, &cobra.Command{ if !filepath.IsAbs(importPath) { importPath = filepath.Join(filepath.Dir(path), importPath) } - visit(importPath, depth+1, i + 1 >= len(file.Imports)) + visit(importPath, depth+1, i+1 >= len(file.Imports)) } } else { @@ -90,7 +90,7 @@ var configShowImportsCmd = addCommand(configShowCmd, &cobra.Command{ fmt.Println(c.Path) for i, path := range c.Imports { - visit(path, 0, i + 1 == len(c.Imports)) + visit(path, 0, i+1 == len(c.Imports)) } return nil @@ -98,9 +98,9 @@ var configShowImportsCmd = addCommand(configShowCmd, &cobra.Command{ }) var configGetCmd = addCommand(workspaceCmd, &cobra.Command{ - Use: "get {JSONPath}", - Args: cobra.ExactArgs(1), - Short: "Gets a value in the workspace config. Use a dotted path to reference the value.", + Use: "get {JSONPath}", + Args: cobra.ExactArgs(1), + Short: "Gets a value in the workspace config. Use a dotted path to reference the value.", RunE: func(cmd *cobra.Command, args []string) error { b := mustGetBosun() ws := b.GetWorkspace() @@ -124,9 +124,9 @@ var configGetCmd = addCommand(workspaceCmd, &cobra.Command{ }) var configSetImports = addCommand(workspaceCmd, &cobra.Command{ - Use: "set {path} {value}", - Args: cobra.ExactArgs(2), - Short: "Sets a value in the workspace config. Use a dotted path to reference the value.", + Use: "set {path} {value}", + Args: cobra.ExactArgs(2), + Short: "Sets a value in the workspace config. Use a dotted path to reference the value.", RunE: func(cmd *cobra.Command, args []string) error { b := mustGetBosun() err := b.SetInWorkspace(args[0], args[1]) @@ -148,7 +148,7 @@ var configDumpCmd = addCommand(workspaceCmd, &cobra.Command{ if err != nil { return err } - data, _ := yaml.Marshal(app.AppRepoConfig) + data, _ := yaml.Marshal(app.AppConfig) fmt.Println(string(data)) return nil } diff --git a/docs/bosun.md b/docs/bosun.md index 28a11e3..fd6fd82 100644 --- a/docs/bosun.md +++ b/docs/bosun.md @@ -10,20 +10,22 @@ building, deploying, or monitoring apps you may want to add them to this tool. ### Options ``` - --ci-mode Operate in CI mode, reporting deployments and builds to github. - --config-file string Config file for Bosun. (default "$HOME/.bosun/bosun.yaml") + --config-file string Config file for Bosun. You can also set BOSUN_CONFIG. (default "$HOME/.bosun/bosun.yaml") --dry-run Display rendered plans, but do not actually execute (not supported by all commands). --force Force the requested command to be executed even if heuristics indicate it should not be. -h, --help help for bosun + --no-report Disable reporting of deploys to github. + -o, --output table Output format. Options are table, `json`, or `yaml`. Only respected by a some commands. (default "yaml") --verbose Enable verbose logging. ``` ### SEE ALSO * [bosun app](bosun_app.md) - App commands -* [bosun config](bosun_config.md) - Root command for configuring bosun. * [bosun docker](bosun_docker.md) - Group of docker-related commands. * [bosun docs](bosun_docs.md) - Completion and documentation generators. +* [bosun e2e](bosun_e2e.md) - Contains sub-commands for running E2E tests. +* [bosun edit](bosun_edit.md) - Edits your root config, or the config of an app if provided. * [bosun env](bosun_env.md) - Sets the environment, and outputs a script which will set environment variables in the environment. Should be called using $() so that the shell will apply the script. * [bosun git](bosun_git.md) - Git commands. * [bosun graylog](bosun_graylog.md) - Group of graylog-related commands. @@ -31,9 +33,16 @@ building, deploying, or monitoring apps you may want to add them to this tool. * [bosun helmsman](bosun_helmsman.md) - Deploys a helmsman to a cluster. Supports --dry-run flag. * [bosun kube](bosun_kube.md) - Group of commands wrapping kubectl. * [bosun lpass](bosun_lpass.md) - Root command for LastPass commands. +* [bosun meta](bosun_meta.md) - Commands for managing bosun itself. * [bosun minikube](bosun_minikube.md) - Group of commands wrapping kubectl. -* [bosun release](bosun_release.md) - Release commands. +* [bosun mongo](bosun_mongo.md) - Commands for working with MongoDB. +* [bosun platform](bosun_platform.md) - Contains platform related sub-commands. +* [bosun release](bosun_release.md) - Contains sub-commands for releases. +* [bosun repo](bosun_repo.md) - Contains sub-commands for interacting with repos. Has some overlap with the git sub-command. * [bosun script](bosun_script.md) - Run a scripted sequence of commands. +* [bosun tools](bosun_tools.md) - Commands for listing and installing tools. +* [bosun upgrade](bosun_upgrade.md) - Upgrades bosun if a newer release is available * [bosun vault](bosun_vault.md) - Updates VaultClient using layout files. Supports --dry-run flag. +* [bosun workspace](bosun_workspace.md) - Workspace commands configure and manipulate the bindings between app repos and your local machine. -###### Auto generated by spf13/cobra on 27-Dec-2018 +###### Auto generated by spf13/cobra on 16-May-2019 diff --git a/docs/bosun_app.md b/docs/bosun_app.md index 645f2c1..ae5eca9 100644 --- a/docs/bosun_app.md +++ b/docs/bosun_app.md @@ -9,18 +9,21 @@ App commands ### Options ``` - -a, --all Apply to all known microservices. - -h, --help help for app - -i, --labels strings Apply to microservices with the provided labels. + -a, --all Apply to all known microservices. + --exclude strings Don't include apps which match the provided selectors.". + -h, --help help for app + --include strings Only include apps which match the provided selectors. --include trumps --exclude.". + -i, --labels strings Apply to microservices with the provided labels. ``` ### Options inherited from parent commands ``` - --ci-mode Operate in CI mode, reporting deployments and builds to github. - --config-file string Config file for Bosun. (default "$HOME/.bosun/bosun.yaml") + --config-file string Config file for Bosun. You can also set BOSUN_CONFIG. (default "$HOME/.bosun/bosun.yaml") --dry-run Display rendered plans, but do not actually execute (not supported by all commands). --force Force the requested command to be executed even if heuristics indicate it should not be. + --no-report Disable reporting of deploys to github. + -o, --output table Output format. Options are table, `json`, or `yaml`. Only respected by a some commands. (default "yaml") --verbose Enable verbose logging. ``` @@ -28,16 +31,26 @@ App commands * [bosun](bosun.md) - Devops tool. * [bosun app accept-actual](bosun_app_accept-actual.md) - Updates the desired state to match the actual state of the apps. +* [bosun app action](bosun_app_action.md) - Run an action associated with an app. +* [bosun app add-hosts](bosun_app_add-hosts.md) - Writes out what the hosts file apps to the hosts file would look like if the requested apps were bound to the minikube IP. +* [bosun app build-image](bosun_app_build-image.md) - Builds the image(s) for an app. +* [bosun app bump](bosun_app_bump.md) - Updates the version of an app. * [bosun app bump](bosun_app_bump.md) - Updates the version of an app. * [bosun app clone](bosun_app_clone.md) - Clones the repo for the named app(s). * [bosun app delete](bosun_app_delete.md) - Deletes the specified apps. * [bosun app deploy](bosun_app_deploy.md) - Deploys the requested app. -* [bosun app list](bosun_app_list.md) - Lists apps +* [bosun app import](bosun_app_import.md) - Includes the file in the user's bosun.yaml. If file is not provided, searches for a bosun.yaml file in this or a parent directory. +* [bosun app list](bosun_app_list.md) - Lists the static config of all known apps. * [bosun app publish-chart](bosun_app_publish-chart.md) - Publishes the chart for an app. +* [bosun app publish-image](bosun_app_publish-image.md) - Publishes the image for an app. * [bosun app pull](bosun_app_pull.md) - Pulls the repo for the app. +* [bosun app recycle](bosun_app_recycle.md) - Recycles the requested app(s) by deleting their pods. +* [bosun app remove-hosts](bosun_app_remove-hosts.md) - Removes apps with the current domain from the hosts file. +* [bosun app repo-path](bosun_app_repo-path.md) - Outputs the path where the app is cloned on the local system. * [bosun app run](bosun_app_run.md) - Configures an app to have traffic routed to localhost, then runs the apps's run command. -* [bosun app show](bosun_app_show.md) - Lists the static config of all known apps. +* [bosun app script](bosun_app_script.md) - Run a scripted sequence of commands. +* [bosun app status](bosun_app_status.md) - Lists apps * [bosun app toggle](bosun_app_toggle.md) - Toggles or sets where traffic for an app will be routed to. * [bosun app version](bosun_app_version.md) - Outputs the version of an app. -###### Auto generated by spf13/cobra on 27-Dec-2018 +###### Auto generated by spf13/cobra on 16-May-2019 diff --git a/docs/bosun_app_accept-actual.md b/docs/bosun_app_accept-actual.md index 1132e92..ada56d8 100644 --- a/docs/bosun_app_accept-actual.md +++ b/docs/bosun_app_accept-actual.md @@ -20,11 +20,14 @@ bosun app accept-actual [name...] [flags] ``` -a, --all Apply to all known microservices. - --ci-mode Operate in CI mode, reporting deployments and builds to github. - --config-file string Config file for Bosun. (default "$HOME/.bosun/bosun.yaml") + --config-file string Config file for Bosun. You can also set BOSUN_CONFIG. (default "$HOME/.bosun/bosun.yaml") --dry-run Display rendered plans, but do not actually execute (not supported by all commands). + --exclude strings Don't include apps which match the provided selectors.". --force Force the requested command to be executed even if heuristics indicate it should not be. + --include strings Only include apps which match the provided selectors. --include trumps --exclude.". -i, --labels strings Apply to microservices with the provided labels. + --no-report Disable reporting of deploys to github. + -o, --output table Output format. Options are table, `json`, or `yaml`. Only respected by a some commands. (default "yaml") --verbose Enable verbose logging. ``` @@ -32,4 +35,4 @@ bosun app accept-actual [name...] [flags] * [bosun app](bosun_app.md) - App commands -###### Auto generated by spf13/cobra on 27-Dec-2018 +###### Auto generated by spf13/cobra on 16-May-2019 diff --git a/docs/bosun_app_action.md b/docs/bosun_app_action.md new file mode 100644 index 0000000..13bf82b --- /dev/null +++ b/docs/bosun_app_action.md @@ -0,0 +1,38 @@ +## bosun app action + +Run an action associated with an app. + +### Synopsis + +If app is not provided, the current directory is used. + +``` +bosun app action [app] {name} [flags] +``` + +### Options + +``` + -h, --help help for action +``` + +### Options inherited from parent commands + +``` + -a, --all Apply to all known microservices. + --config-file string Config file for Bosun. You can also set BOSUN_CONFIG. (default "$HOME/.bosun/bosun.yaml") + --dry-run Display rendered plans, but do not actually execute (not supported by all commands). + --exclude strings Don't include apps which match the provided selectors.". + --force Force the requested command to be executed even if heuristics indicate it should not be. + --include strings Only include apps which match the provided selectors. --include trumps --exclude.". + -i, --labels strings Apply to microservices with the provided labels. + --no-report Disable reporting of deploys to github. + -o, --output table Output format. Options are table, `json`, or `yaml`. Only respected by a some commands. (default "yaml") + --verbose Enable verbose logging. +``` + +### SEE ALSO + +* [bosun app](bosun_app.md) - App commands + +###### Auto generated by spf13/cobra on 16-May-2019 diff --git a/docs/bosun_app_add-hosts.md b/docs/bosun_app_add-hosts.md new file mode 100644 index 0000000..cd47f02 --- /dev/null +++ b/docs/bosun_app_add-hosts.md @@ -0,0 +1,46 @@ +## bosun app add-hosts + +Writes out what the hosts file apps to the hosts file would look like if the requested apps were bound to the minikube IP. + +### Synopsis + +Writes out what the hosts file apps to the hosts file would look like if the requested apps were bound to the minikube IP. + +The current domain and the minikube IP are used to populate the output. To update the hosts file, pipe to sudo tee /etc/hosts. + +``` +bosun app add-hosts [name...] [flags] +``` + +### Examples + +``` +bosun apps add-hosts --all | sudo tee /etc/hosts +``` + +### Options + +``` + -h, --help help for add-hosts +``` + +### Options inherited from parent commands + +``` + -a, --all Apply to all known microservices. + --config-file string Config file for Bosun. You can also set BOSUN_CONFIG. (default "$HOME/.bosun/bosun.yaml") + --dry-run Display rendered plans, but do not actually execute (not supported by all commands). + --exclude strings Don't include apps which match the provided selectors.". + --force Force the requested command to be executed even if heuristics indicate it should not be. + --include strings Only include apps which match the provided selectors. --include trumps --exclude.". + -i, --labels strings Apply to microservices with the provided labels. + --no-report Disable reporting of deploys to github. + -o, --output table Output format. Options are table, `json`, or `yaml`. Only respected by a some commands. (default "yaml") + --verbose Enable verbose logging. +``` + +### SEE ALSO + +* [bosun app](bosun_app.md) - App commands + +###### Auto generated by spf13/cobra on 16-May-2019 diff --git a/docs/bosun_app_build-image.md b/docs/bosun_app_build-image.md new file mode 100644 index 0000000..aedff52 --- /dev/null +++ b/docs/bosun_app_build-image.md @@ -0,0 +1,38 @@ +## bosun app build-image + +Builds the image(s) for an app. + +### Synopsis + +If app is not provided, the current directory is used. The image(s) will be built with the "latest" tag. + +``` +bosun app build-image [app] [flags] +``` + +### Options + +``` + -h, --help help for build-image +``` + +### Options inherited from parent commands + +``` + -a, --all Apply to all known microservices. + --config-file string Config file for Bosun. You can also set BOSUN_CONFIG. (default "$HOME/.bosun/bosun.yaml") + --dry-run Display rendered plans, but do not actually execute (not supported by all commands). + --exclude strings Don't include apps which match the provided selectors.". + --force Force the requested command to be executed even if heuristics indicate it should not be. + --include strings Only include apps which match the provided selectors. --include trumps --exclude.". + -i, --labels strings Apply to microservices with the provided labels. + --no-report Disable reporting of deploys to github. + -o, --output table Output format. Options are table, `json`, or `yaml`. Only respected by a some commands. (default "yaml") + --verbose Enable verbose logging. +``` + +### SEE ALSO + +* [bosun app](bosun_app.md) - App commands + +###### Auto generated by spf13/cobra on 16-May-2019 diff --git a/docs/bosun_app_bump.md b/docs/bosun_app_bump.md index d3ef8f6..a01ae0f 100644 --- a/docs/bosun_app_bump.md +++ b/docs/bosun_app_bump.md @@ -14,17 +14,21 @@ bosun app bump {name} {major|minor|patch|major.minor.patch} [flags] ``` -h, --help help for bump + --tag Create and push a git tag for the version. ``` ### Options inherited from parent commands ``` -a, --all Apply to all known microservices. - --ci-mode Operate in CI mode, reporting deployments and builds to github. - --config-file string Config file for Bosun. (default "$HOME/.bosun/bosun.yaml") + --config-file string Config file for Bosun. You can also set BOSUN_CONFIG. (default "$HOME/.bosun/bosun.yaml") --dry-run Display rendered plans, but do not actually execute (not supported by all commands). + --exclude strings Don't include apps which match the provided selectors.". --force Force the requested command to be executed even if heuristics indicate it should not be. + --include strings Only include apps which match the provided selectors. --include trumps --exclude.". -i, --labels strings Apply to microservices with the provided labels. + --no-report Disable reporting of deploys to github. + -o, --output table Output format. Options are table, `json`, or `yaml`. Only respected by a some commands. (default "yaml") --verbose Enable verbose logging. ``` @@ -32,4 +36,4 @@ bosun app bump {name} {major|minor|patch|major.minor.patch} [flags] * [bosun app](bosun_app.md) - App commands -###### Auto generated by spf13/cobra on 27-Dec-2018 +###### Auto generated by spf13/cobra on 16-May-2019 diff --git a/docs/bosun_app_clone.md b/docs/bosun_app_clone.md index acf8fd8..7b16e24 100644 --- a/docs/bosun_app_clone.md +++ b/docs/bosun_app_clone.md @@ -21,11 +21,14 @@ bosun app clone [name] [name...] [flags] ``` -a, --all Apply to all known microservices. - --ci-mode Operate in CI mode, reporting deployments and builds to github. - --config-file string Config file for Bosun. (default "$HOME/.bosun/bosun.yaml") + --config-file string Config file for Bosun. You can also set BOSUN_CONFIG. (default "$HOME/.bosun/bosun.yaml") --dry-run Display rendered plans, but do not actually execute (not supported by all commands). + --exclude strings Don't include apps which match the provided selectors.". --force Force the requested command to be executed even if heuristics indicate it should not be. + --include strings Only include apps which match the provided selectors. --include trumps --exclude.". -i, --labels strings Apply to microservices with the provided labels. + --no-report Disable reporting of deploys to github. + -o, --output table Output format. Options are table, `json`, or `yaml`. Only respected by a some commands. (default "yaml") --verbose Enable verbose logging. ``` @@ -33,4 +36,4 @@ bosun app clone [name] [name...] [flags] * [bosun app](bosun_app.md) - App commands -###### Auto generated by spf13/cobra on 27-Dec-2018 +###### Auto generated by spf13/cobra on 16-May-2019 diff --git a/docs/bosun_app_delete.md b/docs/bosun_app_delete.md index d6bbf6f..aa47d23 100644 --- a/docs/bosun_app_delete.md +++ b/docs/bosun_app_delete.md @@ -21,11 +21,14 @@ bosun app delete [name] [name...] [flags] ``` -a, --all Apply to all known microservices. - --ci-mode Operate in CI mode, reporting deployments and builds to github. - --config-file string Config file for Bosun. (default "$HOME/.bosun/bosun.yaml") + --config-file string Config file for Bosun. You can also set BOSUN_CONFIG. (default "$HOME/.bosun/bosun.yaml") --dry-run Display rendered plans, but do not actually execute (not supported by all commands). + --exclude strings Don't include apps which match the provided selectors.". --force Force the requested command to be executed even if heuristics indicate it should not be. + --include strings Only include apps which match the provided selectors. --include trumps --exclude.". -i, --labels strings Apply to microservices with the provided labels. + --no-report Disable reporting of deploys to github. + -o, --output table Output format. Options are table, `json`, or `yaml`. Only respected by a some commands. (default "yaml") --verbose Enable verbose logging. ``` @@ -33,4 +36,4 @@ bosun app delete [name] [name...] [flags] * [bosun app](bosun_app.md) - App commands -###### Auto generated by spf13/cobra on 27-Dec-2018 +###### Auto generated by spf13/cobra on 16-May-2019 diff --git a/docs/bosun_app_deploy.md b/docs/bosun_app_deploy.md index 9563c23..960f9c9 100644 --- a/docs/bosun_app_deploy.md +++ b/docs/bosun_app_deploy.md @@ -13,20 +13,24 @@ bosun app deploy [name] [name...] [flags] ### Options ``` - --deploy-deps Also deploy all dependencies of the requested apps. - -h, --help help for deploy - --set strings Additional values to pass to helm for this deploy. + --deploy-deps Also deploy all dependencies of the requested apps. + -h, --help help for deploy + -s, --set strings Value overrides to set in this deploy, as key=value pairs. + -v, --value-sets strings Additional value sets to include in this deploy. ``` ### Options inherited from parent commands ``` -a, --all Apply to all known microservices. - --ci-mode Operate in CI mode, reporting deployments and builds to github. - --config-file string Config file for Bosun. (default "$HOME/.bosun/bosun.yaml") + --config-file string Config file for Bosun. You can also set BOSUN_CONFIG. (default "$HOME/.bosun/bosun.yaml") --dry-run Display rendered plans, but do not actually execute (not supported by all commands). + --exclude strings Don't include apps which match the provided selectors.". --force Force the requested command to be executed even if heuristics indicate it should not be. + --include strings Only include apps which match the provided selectors. --include trumps --exclude.". -i, --labels strings Apply to microservices with the provided labels. + --no-report Disable reporting of deploys to github. + -o, --output table Output format. Options are table, `json`, or `yaml`. Only respected by a some commands. (default "yaml") --verbose Enable verbose logging. ``` @@ -34,4 +38,4 @@ bosun app deploy [name] [name...] [flags] * [bosun app](bosun_app.md) - App commands -###### Auto generated by spf13/cobra on 27-Dec-2018 +###### Auto generated by spf13/cobra on 16-May-2019 diff --git a/docs/bosun_app_import.md b/docs/bosun_app_import.md new file mode 100644 index 0000000..b6b0e00 --- /dev/null +++ b/docs/bosun_app_import.md @@ -0,0 +1,38 @@ +## bosun app import + +Includes the file in the user's bosun.yaml. If file is not provided, searches for a bosun.yaml file in this or a parent directory. + +### Synopsis + +Includes the file in the user's bosun.yaml. If file is not provided, searches for a bosun.yaml file in this or a parent directory. + +``` +bosun app import [file] [flags] +``` + +### Options + +``` + -h, --help help for import +``` + +### Options inherited from parent commands + +``` + -a, --all Apply to all known microservices. + --config-file string Config file for Bosun. You can also set BOSUN_CONFIG. (default "$HOME/.bosun/bosun.yaml") + --dry-run Display rendered plans, but do not actually execute (not supported by all commands). + --exclude strings Don't include apps which match the provided selectors.". + --force Force the requested command to be executed even if heuristics indicate it should not be. + --include strings Only include apps which match the provided selectors. --include trumps --exclude.". + -i, --labels strings Apply to microservices with the provided labels. + --no-report Disable reporting of deploys to github. + -o, --output table Output format. Options are table, `json`, or `yaml`. Only respected by a some commands. (default "yaml") + --verbose Enable verbose logging. +``` + +### SEE ALSO + +* [bosun app](bosun_app.md) - App commands + +###### Auto generated by spf13/cobra on 16-May-2019 diff --git a/docs/bosun_app_list.md b/docs/bosun_app_list.md index aef3b9d..7e0befd 100644 --- a/docs/bosun_app_list.md +++ b/docs/bosun_app_list.md @@ -1,37 +1,39 @@ ## bosun app list -Lists apps +Lists the static config of all known apps. ### Synopsis -Lists apps +Lists the static config of all known apps. ``` -bosun app list [name...] [flags] +bosun app list [flags] ``` ### Options ``` - --diff Run diff on deployed charts. - -h, --help help for list - -s, --skip-actual Skip collection of actual state. + -h, --help help for list ``` ### Options inherited from parent commands ``` -a, --all Apply to all known microservices. - --ci-mode Operate in CI mode, reporting deployments and builds to github. - --config-file string Config file for Bosun. (default "$HOME/.bosun/bosun.yaml") + --config-file string Config file for Bosun. You can also set BOSUN_CONFIG. (default "$HOME/.bosun/bosun.yaml") --dry-run Display rendered plans, but do not actually execute (not supported by all commands). + --exclude strings Don't include apps which match the provided selectors.". --force Force the requested command to be executed even if heuristics indicate it should not be. + --include strings Only include apps which match the provided selectors. --include trumps --exclude.". -i, --labels strings Apply to microservices with the provided labels. + --no-report Disable reporting of deploys to github. + -o, --output table Output format. Options are table, `json`, or `yaml`. Only respected by a some commands. (default "yaml") --verbose Enable verbose logging. ``` ### SEE ALSO * [bosun app](bosun_app.md) - App commands +* [bosun app list actions](bosun_app_list_actions.md) - Lists the actions for an app. If no app is provided, lists all actions. -###### Auto generated by spf13/cobra on 27-Dec-2018 +###### Auto generated by spf13/cobra on 16-May-2019 diff --git a/docs/bosun_app_list_actions.md b/docs/bosun_app_list_actions.md new file mode 100644 index 0000000..d8b647d --- /dev/null +++ b/docs/bosun_app_list_actions.md @@ -0,0 +1,38 @@ +## bosun app list actions + +Lists the actions for an app. If no app is provided, lists all actions. + +### Synopsis + +Lists the actions for an app. If no app is provided, lists all actions. + +``` +bosun app list actions [app] [flags] +``` + +### Options + +``` + -h, --help help for actions +``` + +### Options inherited from parent commands + +``` + -a, --all Apply to all known microservices. + --config-file string Config file for Bosun. You can also set BOSUN_CONFIG. (default "$HOME/.bosun/bosun.yaml") + --dry-run Display rendered plans, but do not actually execute (not supported by all commands). + --exclude strings Don't include apps which match the provided selectors.". + --force Force the requested command to be executed even if heuristics indicate it should not be. + --include strings Only include apps which match the provided selectors. --include trumps --exclude.". + -i, --labels strings Apply to microservices with the provided labels. + --no-report Disable reporting of deploys to github. + -o, --output table Output format. Options are table, `json`, or `yaml`. Only respected by a some commands. (default "yaml") + --verbose Enable verbose logging. +``` + +### SEE ALSO + +* [bosun app list](bosun_app_list.md) - Lists the static config of all known apps. + +###### Auto generated by spf13/cobra on 16-May-2019 diff --git a/docs/bosun_app_publish-chart.md b/docs/bosun_app_publish-chart.md index 359bc29..9744e62 100644 --- a/docs/bosun_app_publish-chart.md +++ b/docs/bosun_app_publish-chart.md @@ -20,11 +20,14 @@ bosun app publish-chart [app] [flags] ``` -a, --all Apply to all known microservices. - --ci-mode Operate in CI mode, reporting deployments and builds to github. - --config-file string Config file for Bosun. (default "$HOME/.bosun/bosun.yaml") + --config-file string Config file for Bosun. You can also set BOSUN_CONFIG. (default "$HOME/.bosun/bosun.yaml") --dry-run Display rendered plans, but do not actually execute (not supported by all commands). + --exclude strings Don't include apps which match the provided selectors.". --force Force the requested command to be executed even if heuristics indicate it should not be. + --include strings Only include apps which match the provided selectors. --include trumps --exclude.". -i, --labels strings Apply to microservices with the provided labels. + --no-report Disable reporting of deploys to github. + -o, --output table Output format. Options are table, `json`, or `yaml`. Only respected by a some commands. (default "yaml") --verbose Enable verbose logging. ``` @@ -32,4 +35,4 @@ bosun app publish-chart [app] [flags] * [bosun app](bosun_app.md) - App commands -###### Auto generated by spf13/cobra on 27-Dec-2018 +###### Auto generated by spf13/cobra on 16-May-2019 diff --git a/docs/bosun_app_publish-image.md b/docs/bosun_app_publish-image.md new file mode 100644 index 0000000..df9727d --- /dev/null +++ b/docs/bosun_app_publish-image.md @@ -0,0 +1,42 @@ +## bosun app publish-image + +Publishes the image for an app. + +### Synopsis + +If app is not provided, the current directory is used. +The image will be published with the "latest" tag and with a tag for the current version. +If the current branch is a release branch, the image will also be published with a tag formatted +as "version-release". + + +``` +bosun app publish-image [app] [flags] +``` + +### Options + +``` + -h, --help help for publish-image +``` + +### Options inherited from parent commands + +``` + -a, --all Apply to all known microservices. + --config-file string Config file for Bosun. You can also set BOSUN_CONFIG. (default "$HOME/.bosun/bosun.yaml") + --dry-run Display rendered plans, but do not actually execute (not supported by all commands). + --exclude strings Don't include apps which match the provided selectors.". + --force Force the requested command to be executed even if heuristics indicate it should not be. + --include strings Only include apps which match the provided selectors. --include trumps --exclude.". + -i, --labels strings Apply to microservices with the provided labels. + --no-report Disable reporting of deploys to github. + -o, --output table Output format. Options are table, `json`, or `yaml`. Only respected by a some commands. (default "yaml") + --verbose Enable verbose logging. +``` + +### SEE ALSO + +* [bosun app](bosun_app.md) - App commands + +###### Auto generated by spf13/cobra on 16-May-2019 diff --git a/docs/bosun_app_pull.md b/docs/bosun_app_pull.md index 549164b..9fbd03f 100644 --- a/docs/bosun_app_pull.md +++ b/docs/bosun_app_pull.md @@ -7,7 +7,7 @@ Pulls the repo for the app. If app is not provided, the current directory is used. ``` -bosun app pull [app] [flags] +bosun app pull [app] [app...] [flags] ``` ### Options @@ -20,11 +20,14 @@ bosun app pull [app] [flags] ``` -a, --all Apply to all known microservices. - --ci-mode Operate in CI mode, reporting deployments and builds to github. - --config-file string Config file for Bosun. (default "$HOME/.bosun/bosun.yaml") + --config-file string Config file for Bosun. You can also set BOSUN_CONFIG. (default "$HOME/.bosun/bosun.yaml") --dry-run Display rendered plans, but do not actually execute (not supported by all commands). + --exclude strings Don't include apps which match the provided selectors.". --force Force the requested command to be executed even if heuristics indicate it should not be. + --include strings Only include apps which match the provided selectors. --include trumps --exclude.". -i, --labels strings Apply to microservices with the provided labels. + --no-report Disable reporting of deploys to github. + -o, --output table Output format. Options are table, `json`, or `yaml`. Only respected by a some commands. (default "yaml") --verbose Enable verbose logging. ``` @@ -32,4 +35,4 @@ bosun app pull [app] [flags] * [bosun app](bosun_app.md) - App commands -###### Auto generated by spf13/cobra on 27-Dec-2018 +###### Auto generated by spf13/cobra on 16-May-2019 diff --git a/docs/bosun_app_recycle.md b/docs/bosun_app_recycle.md new file mode 100644 index 0000000..a2b9ee8 --- /dev/null +++ b/docs/bosun_app_recycle.md @@ -0,0 +1,39 @@ +## bosun app recycle + +Recycles the requested app(s) by deleting their pods. + +### Synopsis + +If app is not specified, the first app in the nearest bosun.yaml file is used. + +``` +bosun app recycle [name] [name...] [flags] +``` + +### Options + +``` + -h, --help help for recycle + --pull-latest Pull the latest image before recycling (only works in minikube). +``` + +### Options inherited from parent commands + +``` + -a, --all Apply to all known microservices. + --config-file string Config file for Bosun. You can also set BOSUN_CONFIG. (default "$HOME/.bosun/bosun.yaml") + --dry-run Display rendered plans, but do not actually execute (not supported by all commands). + --exclude strings Don't include apps which match the provided selectors.". + --force Force the requested command to be executed even if heuristics indicate it should not be. + --include strings Only include apps which match the provided selectors. --include trumps --exclude.". + -i, --labels strings Apply to microservices with the provided labels. + --no-report Disable reporting of deploys to github. + -o, --output table Output format. Options are table, `json`, or `yaml`. Only respected by a some commands. (default "yaml") + --verbose Enable verbose logging. +``` + +### SEE ALSO + +* [bosun app](bosun_app.md) - App commands + +###### Auto generated by spf13/cobra on 16-May-2019 diff --git a/docs/bosun_app_remove-hosts.md b/docs/bosun_app_remove-hosts.md new file mode 100644 index 0000000..73d74ab --- /dev/null +++ b/docs/bosun_app_remove-hosts.md @@ -0,0 +1,38 @@ +## bosun app remove-hosts + +Removes apps with the current domain from the hosts file. + +### Synopsis + +Removes apps with the current domain from the hosts file. + +``` +bosun app remove-hosts [name...] [flags] +``` + +### Options + +``` + -h, --help help for remove-hosts +``` + +### Options inherited from parent commands + +``` + -a, --all Apply to all known microservices. + --config-file string Config file for Bosun. You can also set BOSUN_CONFIG. (default "$HOME/.bosun/bosun.yaml") + --dry-run Display rendered plans, but do not actually execute (not supported by all commands). + --exclude strings Don't include apps which match the provided selectors.". + --force Force the requested command to be executed even if heuristics indicate it should not be. + --include strings Only include apps which match the provided selectors. --include trumps --exclude.". + -i, --labels strings Apply to microservices with the provided labels. + --no-report Disable reporting of deploys to github. + -o, --output table Output format. Options are table, `json`, or `yaml`. Only respected by a some commands. (default "yaml") + --verbose Enable verbose logging. +``` + +### SEE ALSO + +* [bosun app](bosun_app.md) - App commands + +###### Auto generated by spf13/cobra on 16-May-2019 diff --git a/docs/bosun_app_repo-path.md b/docs/bosun_app_repo-path.md new file mode 100644 index 0000000..00a7986 --- /dev/null +++ b/docs/bosun_app_repo-path.md @@ -0,0 +1,38 @@ +## bosun app repo-path + +Outputs the path where the app is cloned on the local system. + +### Synopsis + +Outputs the path where the app is cloned on the local system. + +``` +bosun app repo-path [name] [flags] +``` + +### Options + +``` + -h, --help help for repo-path +``` + +### Options inherited from parent commands + +``` + -a, --all Apply to all known microservices. + --config-file string Config file for Bosun. You can also set BOSUN_CONFIG. (default "$HOME/.bosun/bosun.yaml") + --dry-run Display rendered plans, but do not actually execute (not supported by all commands). + --exclude strings Don't include apps which match the provided selectors.". + --force Force the requested command to be executed even if heuristics indicate it should not be. + --include strings Only include apps which match the provided selectors. --include trumps --exclude.". + -i, --labels strings Apply to microservices with the provided labels. + --no-report Disable reporting of deploys to github. + -o, --output table Output format. Options are table, `json`, or `yaml`. Only respected by a some commands. (default "yaml") + --verbose Enable verbose logging. +``` + +### SEE ALSO + +* [bosun app](bosun_app.md) - App commands + +###### Auto generated by spf13/cobra on 16-May-2019 diff --git a/docs/bosun_app_run.md b/docs/bosun_app_run.md index 8852362..9708f83 100644 --- a/docs/bosun_app_run.md +++ b/docs/bosun_app_run.md @@ -20,11 +20,14 @@ bosun app run [app] [flags] ``` -a, --all Apply to all known microservices. - --ci-mode Operate in CI mode, reporting deployments and builds to github. - --config-file string Config file for Bosun. (default "$HOME/.bosun/bosun.yaml") + --config-file string Config file for Bosun. You can also set BOSUN_CONFIG. (default "$HOME/.bosun/bosun.yaml") --dry-run Display rendered plans, but do not actually execute (not supported by all commands). + --exclude strings Don't include apps which match the provided selectors.". --force Force the requested command to be executed even if heuristics indicate it should not be. + --include strings Only include apps which match the provided selectors. --include trumps --exclude.". -i, --labels strings Apply to microservices with the provided labels. + --no-report Disable reporting of deploys to github. + -o, --output table Output format. Options are table, `json`, or `yaml`. Only respected by a some commands. (default "yaml") --verbose Enable verbose logging. ``` @@ -32,4 +35,4 @@ bosun app run [app] [flags] * [bosun app](bosun_app.md) - App commands -###### Auto generated by spf13/cobra on 27-Dec-2018 +###### Auto generated by spf13/cobra on 16-May-2019 diff --git a/docs/bosun_app_script.md b/docs/bosun_app_script.md new file mode 100644 index 0000000..e4473c6 --- /dev/null +++ b/docs/bosun_app_script.md @@ -0,0 +1,39 @@ +## bosun app script + +Run a scripted sequence of commands. + +### Synopsis + +If app is not provided, the current directory is used. + +``` +bosun app script [app] {name} [flags] +``` + +### Options + +``` + -h, --help help for script + --steps ints Steps to run (defaults to all steps) +``` + +### Options inherited from parent commands + +``` + -a, --all Apply to all known microservices. + --config-file string Config file for Bosun. You can also set BOSUN_CONFIG. (default "$HOME/.bosun/bosun.yaml") + --dry-run Display rendered plans, but do not actually execute (not supported by all commands). + --exclude strings Don't include apps which match the provided selectors.". + --force Force the requested command to be executed even if heuristics indicate it should not be. + --include strings Only include apps which match the provided selectors. --include trumps --exclude.". + -i, --labels strings Apply to microservices with the provided labels. + --no-report Disable reporting of deploys to github. + -o, --output table Output format. Options are table, `json`, or `yaml`. Only respected by a some commands. (default "yaml") + --verbose Enable verbose logging. +``` + +### SEE ALSO + +* [bosun app](bosun_app.md) - App commands + +###### Auto generated by spf13/cobra on 16-May-2019 diff --git a/docs/bosun_app_status.md b/docs/bosun_app_status.md new file mode 100644 index 0000000..e8b9021 --- /dev/null +++ b/docs/bosun_app_status.md @@ -0,0 +1,40 @@ +## bosun app status + +Lists apps + +### Synopsis + +Lists apps + +``` +bosun app status [name...] [flags] +``` + +### Options + +``` + --diff Run diff on deployed charts. + -h, --help help for status + -s, --skip-actual Skip collection of actual state. +``` + +### Options inherited from parent commands + +``` + -a, --all Apply to all known microservices. + --config-file string Config file for Bosun. You can also set BOSUN_CONFIG. (default "$HOME/.bosun/bosun.yaml") + --dry-run Display rendered plans, but do not actually execute (not supported by all commands). + --exclude strings Don't include apps which match the provided selectors.". + --force Force the requested command to be executed even if heuristics indicate it should not be. + --include strings Only include apps which match the provided selectors. --include trumps --exclude.". + -i, --labels strings Apply to microservices with the provided labels. + --no-report Disable reporting of deploys to github. + -o, --output table Output format. Options are table, `json`, or `yaml`. Only respected by a some commands. (default "yaml") + --verbose Enable verbose logging. +``` + +### SEE ALSO + +* [bosun app](bosun_app.md) - App commands + +###### Auto generated by spf13/cobra on 16-May-2019 diff --git a/docs/bosun_app_toggle.md b/docs/bosun_app_toggle.md index 5d706f2..dafa692 100644 --- a/docs/bosun_app_toggle.md +++ b/docs/bosun_app_toggle.md @@ -22,11 +22,14 @@ bosun app toggle [name] [name...] [flags] ``` -a, --all Apply to all known microservices. - --ci-mode Operate in CI mode, reporting deployments and builds to github. - --config-file string Config file for Bosun. (default "$HOME/.bosun/bosun.yaml") + --config-file string Config file for Bosun. You can also set BOSUN_CONFIG. (default "$HOME/.bosun/bosun.yaml") --dry-run Display rendered plans, but do not actually execute (not supported by all commands). + --exclude strings Don't include apps which match the provided selectors.". --force Force the requested command to be executed even if heuristics indicate it should not be. + --include strings Only include apps which match the provided selectors. --include trumps --exclude.". -i, --labels strings Apply to microservices with the provided labels. + --no-report Disable reporting of deploys to github. + -o, --output table Output format. Options are table, `json`, or `yaml`. Only respected by a some commands. (default "yaml") --verbose Enable verbose logging. ``` @@ -34,4 +37,4 @@ bosun app toggle [name] [name...] [flags] * [bosun app](bosun_app.md) - App commands -###### Auto generated by spf13/cobra on 27-Dec-2018 +###### Auto generated by spf13/cobra on 16-May-2019 diff --git a/docs/bosun_app_version.md b/docs/bosun_app_version.md index b7ba352..85e4603 100644 --- a/docs/bosun_app_version.md +++ b/docs/bosun_app_version.md @@ -20,11 +20,14 @@ bosun app version [name] [flags] ``` -a, --all Apply to all known microservices. - --ci-mode Operate in CI mode, reporting deployments and builds to github. - --config-file string Config file for Bosun. (default "$HOME/.bosun/bosun.yaml") + --config-file string Config file for Bosun. You can also set BOSUN_CONFIG. (default "$HOME/.bosun/bosun.yaml") --dry-run Display rendered plans, but do not actually execute (not supported by all commands). + --exclude strings Don't include apps which match the provided selectors.". --force Force the requested command to be executed even if heuristics indicate it should not be. + --include strings Only include apps which match the provided selectors. --include trumps --exclude.". -i, --labels strings Apply to microservices with the provided labels. + --no-report Disable reporting of deploys to github. + -o, --output table Output format. Options are table, `json`, or `yaml`. Only respected by a some commands. (default "yaml") --verbose Enable verbose logging. ``` @@ -32,4 +35,4 @@ bosun app version [name] [flags] * [bosun app](bosun_app.md) - App commands -###### Auto generated by spf13/cobra on 27-Dec-2018 +###### Auto generated by spf13/cobra on 16-May-2019 diff --git a/docs/bosun_docker.md b/docs/bosun_docker.md index 9abf48b..9e6a9c6 100644 --- a/docs/bosun_docker.md +++ b/docs/bosun_docker.md @@ -15,10 +15,11 @@ Group of docker-related commands. ### Options inherited from parent commands ``` - --ci-mode Operate in CI mode, reporting deployments and builds to github. - --config-file string Config file for Bosun. (default "$HOME/.bosun/bosun.yaml") + --config-file string Config file for Bosun. You can also set BOSUN_CONFIG. (default "$HOME/.bosun/bosun.yaml") --dry-run Display rendered plans, but do not actually execute (not supported by all commands). --force Force the requested command to be executed even if heuristics indicate it should not be. + --no-report Disable reporting of deploys to github. + -o, --output table Output format. Options are table, `json`, or `yaml`. Only respected by a some commands. (default "yaml") --verbose Enable verbose logging. ``` @@ -28,4 +29,4 @@ Group of docker-related commands. * [bosun docker choose-release-image](bosun_docker_choose-release-image.md) - Tags an image for release. * [bosun docker map-images](bosun_docker_map-images.md) - Retags a list of images -###### Auto generated by spf13/cobra on 27-Dec-2018 +###### Auto generated by spf13/cobra on 16-May-2019 diff --git a/docs/bosun_docker_choose-release-image.md b/docs/bosun_docker_choose-release-image.md index ef94341..8a13d62 100644 --- a/docs/bosun_docker_choose-release-image.md +++ b/docs/bosun_docker_choose-release-image.md @@ -20,10 +20,11 @@ bosun docker choose-release-image {service-name}:{version-tag} [flags] ### Options inherited from parent commands ``` - --ci-mode Operate in CI mode, reporting deployments and builds to github. - --config-file string Config file for Bosun. (default "$HOME/.bosun/bosun.yaml") + --config-file string Config file for Bosun. You can also set BOSUN_CONFIG. (default "$HOME/.bosun/bosun.yaml") --dry-run Display rendered plans, but do not actually execute (not supported by all commands). --force Force the requested command to be executed even if heuristics indicate it should not be. + --no-report Disable reporting of deploys to github. + -o, --output table Output format. Options are table, `json`, or `yaml`. Only respected by a some commands. (default "yaml") --verbose Enable verbose logging. ``` @@ -31,4 +32,4 @@ bosun docker choose-release-image {service-name}:{version-tag} [flags] * [bosun docker](bosun_docker.md) - Group of docker-related commands. -###### Auto generated by spf13/cobra on 27-Dec-2018 +###### Auto generated by spf13/cobra on 16-May-2019 diff --git a/docs/bosun_docker_map-images.md b/docs/bosun_docker_map-images.md index 35fe053..0715224 100644 --- a/docs/bosun_docker_map-images.md +++ b/docs/bosun_docker_map-images.md @@ -23,10 +23,11 @@ bosun docker map-images {map file} [flags] ### Options inherited from parent commands ``` - --ci-mode Operate in CI mode, reporting deployments and builds to github. - --config-file string Config file for Bosun. (default "$HOME/.bosun/bosun.yaml") + --config-file string Config file for Bosun. You can also set BOSUN_CONFIG. (default "$HOME/.bosun/bosun.yaml") --dry-run Display rendered plans, but do not actually execute (not supported by all commands). --force Force the requested command to be executed even if heuristics indicate it should not be. + --no-report Disable reporting of deploys to github. + -o, --output table Output format. Options are table, `json`, or `yaml`. Only respected by a some commands. (default "yaml") --verbose Enable verbose logging. ``` @@ -34,4 +35,4 @@ bosun docker map-images {map file} [flags] * [bosun docker](bosun_docker.md) - Group of docker-related commands. -###### Auto generated by spf13/cobra on 27-Dec-2018 +###### Auto generated by spf13/cobra on 16-May-2019 diff --git a/docs/bosun_docs.md b/docs/bosun_docs.md index b62d0dc..a00b686 100644 --- a/docs/bosun_docs.md +++ b/docs/bosun_docs.md @@ -15,10 +15,11 @@ Completion and documentation generators. ### Options inherited from parent commands ``` - --ci-mode Operate in CI mode, reporting deployments and builds to github. - --config-file string Config file for Bosun. (default "$HOME/.bosun/bosun.yaml") + --config-file string Config file for Bosun. You can also set BOSUN_CONFIG. (default "$HOME/.bosun/bosun.yaml") --dry-run Display rendered plans, but do not actually execute (not supported by all commands). --force Force the requested command to be executed even if heuristics indicate it should not be. + --no-report Disable reporting of deploys to github. + -o, --output table Output format. Options are table, `json`, or `yaml`. Only respected by a some commands. (default "yaml") --verbose Enable verbose logging. ``` @@ -29,4 +30,4 @@ Completion and documentation generators. * [bosun docs bash](bosun_docs_bash.md) - Completion generator for bash. * [bosun docs markdown](bosun_docs_markdown.md) - Output documentation in markdown. Output dir defaults to ./docs -###### Auto generated by spf13/cobra on 27-Dec-2018 +###### Auto generated by spf13/cobra on 16-May-2019 diff --git a/docs/bosun_docs_bash.md b/docs/bosun_docs_bash.md index eb2a4e3..75b03b0 100644 --- a/docs/bosun_docs_bash.md +++ b/docs/bosun_docs_bash.md @@ -19,10 +19,11 @@ bosun docs bash [flags] ### Options inherited from parent commands ``` - --ci-mode Operate in CI mode, reporting deployments and builds to github. - --config-file string Config file for Bosun. (default "$HOME/.bosun/bosun.yaml") + --config-file string Config file for Bosun. You can also set BOSUN_CONFIG. (default "$HOME/.bosun/bosun.yaml") --dry-run Display rendered plans, but do not actually execute (not supported by all commands). --force Force the requested command to be executed even if heuristics indicate it should not be. + --no-report Disable reporting of deploys to github. + -o, --output table Output format. Options are table, `json`, or `yaml`. Only respected by a some commands. (default "yaml") --verbose Enable verbose logging. ``` @@ -30,4 +31,4 @@ bosun docs bash [flags] * [bosun docs](bosun_docs.md) - Completion and documentation generators. -###### Auto generated by spf13/cobra on 27-Dec-2018 +###### Auto generated by spf13/cobra on 16-May-2019 diff --git a/docs/bosun_docs_markdown.md b/docs/bosun_docs_markdown.md index 3d16d2f..e890b10 100644 --- a/docs/bosun_docs_markdown.md +++ b/docs/bosun_docs_markdown.md @@ -19,10 +19,11 @@ bosun docs markdown [dir] [flags] ### Options inherited from parent commands ``` - --ci-mode Operate in CI mode, reporting deployments and builds to github. - --config-file string Config file for Bosun. (default "$HOME/.bosun/bosun.yaml") + --config-file string Config file for Bosun. You can also set BOSUN_CONFIG. (default "$HOME/.bosun/bosun.yaml") --dry-run Display rendered plans, but do not actually execute (not supported by all commands). --force Force the requested command to be executed even if heuristics indicate it should not be. + --no-report Disable reporting of deploys to github. + -o, --output table Output format. Options are table, `json`, or `yaml`. Only respected by a some commands. (default "yaml") --verbose Enable verbose logging. ``` @@ -30,4 +31,4 @@ bosun docs markdown [dir] [flags] * [bosun docs](bosun_docs.md) - Completion and documentation generators. -###### Auto generated by spf13/cobra on 27-Dec-2018 +###### Auto generated by spf13/cobra on 16-May-2019 diff --git a/docs/bosun_e2e.md b/docs/bosun_e2e.md new file mode 100644 index 0000000..ebc6e49 --- /dev/null +++ b/docs/bosun_e2e.md @@ -0,0 +1,32 @@ +## bosun e2e + +Contains sub-commands for running E2E tests. + +### Synopsis + +Contains sub-commands for running E2E tests. + +### Options + +``` + -h, --help help for e2e +``` + +### Options inherited from parent commands + +``` + --config-file string Config file for Bosun. You can also set BOSUN_CONFIG. (default "$HOME/.bosun/bosun.yaml") + --dry-run Display rendered plans, but do not actually execute (not supported by all commands). + --force Force the requested command to be executed even if heuristics indicate it should not be. + --no-report Disable reporting of deploys to github. + -o, --output table Output format. Options are table, `json`, or `yaml`. Only respected by a some commands. (default "yaml") + --verbose Enable verbose logging. +``` + +### SEE ALSO + +* [bosun](bosun.md) - Devops tool. +* [bosun e2e list](bosun_e2e_list.md) - Lists E2E test suites. +* [bosun e2e run](bosun_e2e_run.md) - Runs an E2E test suite. + +###### Auto generated by spf13/cobra on 16-May-2019 diff --git a/docs/bosun_e2e_list.md b/docs/bosun_e2e_list.md new file mode 100644 index 0000000..2514f6b --- /dev/null +++ b/docs/bosun_e2e_list.md @@ -0,0 +1,34 @@ +## bosun e2e list + +Lists E2E test suites. + +### Synopsis + +Lists E2E test suites. + +``` +bosun e2e list [flags] +``` + +### Options + +``` + -h, --help help for list +``` + +### Options inherited from parent commands + +``` + --config-file string Config file for Bosun. You can also set BOSUN_CONFIG. (default "$HOME/.bosun/bosun.yaml") + --dry-run Display rendered plans, but do not actually execute (not supported by all commands). + --force Force the requested command to be executed even if heuristics indicate it should not be. + --no-report Disable reporting of deploys to github. + -o, --output table Output format. Options are table, `json`, or `yaml`. Only respected by a some commands. (default "yaml") + --verbose Enable verbose logging. +``` + +### SEE ALSO + +* [bosun e2e](bosun_e2e.md) - Contains sub-commands for running E2E tests. + +###### Auto generated by spf13/cobra on 16-May-2019 diff --git a/docs/bosun_e2e_run.md b/docs/bosun_e2e_run.md new file mode 100644 index 0000000..96e448a --- /dev/null +++ b/docs/bosun_e2e_run.md @@ -0,0 +1,37 @@ +## bosun e2e run + +Runs an E2E test suite. + +### Synopsis + +Runs an E2E test suite. + +``` +bosun e2e run {suite} [flags] +``` + +### Options + +``` + -h, --help help for run + --skip-setup Skip setup scripts. + --skip-teardown Skip teardown scripts. + --tests strings Specific tests to run. +``` + +### Options inherited from parent commands + +``` + --config-file string Config file for Bosun. You can also set BOSUN_CONFIG. (default "$HOME/.bosun/bosun.yaml") + --dry-run Display rendered plans, but do not actually execute (not supported by all commands). + --force Force the requested command to be executed even if heuristics indicate it should not be. + --no-report Disable reporting of deploys to github. + -o, --output table Output format. Options are table, `json`, or `yaml`. Only respected by a some commands. (default "yaml") + --verbose Enable verbose logging. +``` + +### SEE ALSO + +* [bosun e2e](bosun_e2e.md) - Contains sub-commands for running E2E tests. + +###### Auto generated by spf13/cobra on 16-May-2019 diff --git a/docs/bosun_edit.md b/docs/bosun_edit.md new file mode 100644 index 0000000..1ae2e1d --- /dev/null +++ b/docs/bosun_edit.md @@ -0,0 +1,34 @@ +## bosun edit + +Edits your root config, or the config of an app if provided. + +### Synopsis + +Edits your root config, or the config of an app if provided. + +``` +bosun edit [app] [flags] +``` + +### Options + +``` + -h, --help help for edit +``` + +### Options inherited from parent commands + +``` + --config-file string Config file for Bosun. You can also set BOSUN_CONFIG. (default "$HOME/.bosun/bosun.yaml") + --dry-run Display rendered plans, but do not actually execute (not supported by all commands). + --force Force the requested command to be executed even if heuristics indicate it should not be. + --no-report Disable reporting of deploys to github. + -o, --output table Output format. Options are table, `json`, or `yaml`. Only respected by a some commands. (default "yaml") + --verbose Enable verbose logging. +``` + +### SEE ALSO + +* [bosun](bosun.md) - Devops tool. + +###### Auto generated by spf13/cobra on 16-May-2019 diff --git a/docs/bosun_env.md b/docs/bosun_env.md index ed9503f..3c0b871 100644 --- a/docs/bosun_env.md +++ b/docs/bosun_env.md @@ -4,10 +4,10 @@ Sets the environment, and outputs a script which will set environment variables ### Synopsis -Sets the environment, and outputs a script which will set environment variables in the environment. Should be called using $() so that the shell will apply the script. +The special environment name `current` will emit the script for the current environment without changing anything. ``` -bosun env {environment} [flags] +bosun env [environment] [flags] ``` ### Examples @@ -19,22 +19,28 @@ $(bosun env {env}) ### Options ``` - -h, --help help for env + --current Write script for setting current environment. + -h, --help help for env ``` ### Options inherited from parent commands ``` - --ci-mode Operate in CI mode, reporting deployments and builds to github. - --config-file string Config file for Bosun. (default "$HOME/.bosun/bosun.yaml") + --config-file string Config file for Bosun. You can also set BOSUN_CONFIG. (default "$HOME/.bosun/bosun.yaml") --dry-run Display rendered plans, but do not actually execute (not supported by all commands). --force Force the requested command to be executed even if heuristics indicate it should not be. + --no-report Disable reporting of deploys to github. + -o, --output table Output format. Options are table, `json`, or `yaml`. Only respected by a some commands. (default "yaml") --verbose Enable verbose logging. ``` ### SEE ALSO * [bosun](bosun.md) - Devops tool. +* [bosun env get-cert](bosun_env_get-cert.md) - Creates or reads a certificate for the specified hosts. +* [bosun env list](bosun_env_list.md) - Lists environments. * [bosun env name](bosun_env_name.md) - Prints the name of the current environment. +* [bosun env show](bosun_env_show.md) - Shows the current environment with its valueSets. +* [bosun env value-sets](bosun_env_value-sets.md) - Lists known value-sets. -###### Auto generated by spf13/cobra on 27-Dec-2018 +###### Auto generated by spf13/cobra on 16-May-2019 diff --git a/docs/bosun_env_get-cert.md b/docs/bosun_env_get-cert.md new file mode 100644 index 0000000..0387e19 --- /dev/null +++ b/docs/bosun_env_get-cert.md @@ -0,0 +1,34 @@ +## bosun env get-cert + +Creates or reads a certificate for the specified hosts. + +### Synopsis + +Requires mkcert to be installed. + +``` +bosun env get-cert {name} {part=cert|key} {hosts...} [flags] +``` + +### Options + +``` + -h, --help help for get-cert +``` + +### Options inherited from parent commands + +``` + --config-file string Config file for Bosun. You can also set BOSUN_CONFIG. (default "$HOME/.bosun/bosun.yaml") + --dry-run Display rendered plans, but do not actually execute (not supported by all commands). + --force Force the requested command to be executed even if heuristics indicate it should not be. + --no-report Disable reporting of deploys to github. + -o, --output table Output format. Options are table, `json`, or `yaml`. Only respected by a some commands. (default "yaml") + --verbose Enable verbose logging. +``` + +### SEE ALSO + +* [bosun env](bosun_env.md) - Sets the environment, and outputs a script which will set environment variables in the environment. Should be called using $() so that the shell will apply the script. + +###### Auto generated by spf13/cobra on 16-May-2019 diff --git a/docs/bosun_env_list.md b/docs/bosun_env_list.md new file mode 100644 index 0000000..a7b36a6 --- /dev/null +++ b/docs/bosun_env_list.md @@ -0,0 +1,34 @@ +## bosun env list + +Lists environments. + +### Synopsis + +Lists environments. + +``` +bosun env list [flags] +``` + +### Options + +``` + -h, --help help for list +``` + +### Options inherited from parent commands + +``` + --config-file string Config file for Bosun. You can also set BOSUN_CONFIG. (default "$HOME/.bosun/bosun.yaml") + --dry-run Display rendered plans, but do not actually execute (not supported by all commands). + --force Force the requested command to be executed even if heuristics indicate it should not be. + --no-report Disable reporting of deploys to github. + -o, --output table Output format. Options are table, `json`, or `yaml`. Only respected by a some commands. (default "yaml") + --verbose Enable verbose logging. +``` + +### SEE ALSO + +* [bosun env](bosun_env.md) - Sets the environment, and outputs a script which will set environment variables in the environment. Should be called using $() so that the shell will apply the script. + +###### Auto generated by spf13/cobra on 16-May-2019 diff --git a/docs/bosun_env_name.md b/docs/bosun_env_name.md index 6f6f2b5..e6628ce 100644 --- a/docs/bosun_env_name.md +++ b/docs/bosun_env_name.md @@ -19,10 +19,11 @@ bosun env name [flags] ### Options inherited from parent commands ``` - --ci-mode Operate in CI mode, reporting deployments and builds to github. - --config-file string Config file for Bosun. (default "$HOME/.bosun/bosun.yaml") + --config-file string Config file for Bosun. You can also set BOSUN_CONFIG. (default "$HOME/.bosun/bosun.yaml") --dry-run Display rendered plans, but do not actually execute (not supported by all commands). --force Force the requested command to be executed even if heuristics indicate it should not be. + --no-report Disable reporting of deploys to github. + -o, --output table Output format. Options are table, `json`, or `yaml`. Only respected by a some commands. (default "yaml") --verbose Enable verbose logging. ``` @@ -30,4 +31,4 @@ bosun env name [flags] * [bosun env](bosun_env.md) - Sets the environment, and outputs a script which will set environment variables in the environment. Should be called using $() so that the shell will apply the script. -###### Auto generated by spf13/cobra on 27-Dec-2018 +###### Auto generated by spf13/cobra on 16-May-2019 diff --git a/docs/bosun_env_show.md b/docs/bosun_env_show.md new file mode 100644 index 0000000..cea8b00 --- /dev/null +++ b/docs/bosun_env_show.md @@ -0,0 +1,34 @@ +## bosun env show + +Shows the current environment with its valueSets. + +### Synopsis + +Shows the current environment with its valueSets. + +``` +bosun env show [name] [flags] +``` + +### Options + +``` + -h, --help help for show +``` + +### Options inherited from parent commands + +``` + --config-file string Config file for Bosun. You can also set BOSUN_CONFIG. (default "$HOME/.bosun/bosun.yaml") + --dry-run Display rendered plans, but do not actually execute (not supported by all commands). + --force Force the requested command to be executed even if heuristics indicate it should not be. + --no-report Disable reporting of deploys to github. + -o, --output table Output format. Options are table, `json`, or `yaml`. Only respected by a some commands. (default "yaml") + --verbose Enable verbose logging. +``` + +### SEE ALSO + +* [bosun env](bosun_env.md) - Sets the environment, and outputs a script which will set environment variables in the environment. Should be called using $() so that the shell will apply the script. + +###### Auto generated by spf13/cobra on 16-May-2019 diff --git a/docs/bosun_env_value-sets.md b/docs/bosun_env_value-sets.md new file mode 100644 index 0000000..2526be9 --- /dev/null +++ b/docs/bosun_env_value-sets.md @@ -0,0 +1,34 @@ +## bosun env value-sets + +Lists known value-sets. + +### Synopsis + +Lists known value-sets. + +``` +bosun env value-sets [flags] +``` + +### Options + +``` + -h, --help help for value-sets +``` + +### Options inherited from parent commands + +``` + --config-file string Config file for Bosun. You can also set BOSUN_CONFIG. (default "$HOME/.bosun/bosun.yaml") + --dry-run Display rendered plans, but do not actually execute (not supported by all commands). + --force Force the requested command to be executed even if heuristics indicate it should not be. + --no-report Disable reporting of deploys to github. + -o, --output table Output format. Options are table, `json`, or `yaml`. Only respected by a some commands. (default "yaml") + --verbose Enable verbose logging. +``` + +### SEE ALSO + +* [bosun env](bosun_env.md) - Sets the environment, and outputs a script which will set environment variables in the environment. Should be called using $() so that the shell will apply the script. + +###### Auto generated by spf13/cobra on 16-May-2019 diff --git a/docs/bosun_git.md b/docs/bosun_git.md index 4fe76ef..8bc5431 100644 --- a/docs/bosun_git.md +++ b/docs/bosun_git.md @@ -15,17 +15,21 @@ Git commands. ### Options inherited from parent commands ``` - --ci-mode Operate in CI mode, reporting deployments and builds to github. - --config-file string Config file for Bosun. (default "$HOME/.bosun/bosun.yaml") + --config-file string Config file for Bosun. You can also set BOSUN_CONFIG. (default "$HOME/.bosun/bosun.yaml") --dry-run Display rendered plans, but do not actually execute (not supported by all commands). --force Force the requested command to be executed even if heuristics indicate it should not be. + --no-report Disable reporting of deploys to github. + -o, --output table Output format. Options are table, `json`, or `yaml`. Only respected by a some commands. (default "yaml") --verbose Enable verbose logging. ``` ### SEE ALSO * [bosun](bosun.md) - Devops tool. +* [bosun git accept-pull-request](bosun_git_accept-pull-request.md) - Accepts a pull request and merges it into master, optionally bumping the version and tagging the master branch. * [bosun git deploy](bosun_git_deploy.md) - Deploy-related commands. -* [bosun git task](bosun_git_task.md) - Creates a task in the current repo for the story, and a branch for that task. +* [bosun git pull-request](bosun_git_pull-request.md) - Opens a pull request. +* [bosun git task](bosun_git_task.md) - Creates a task in the current repo, and a branch for that task. Optionally attaches task to a story, if flags are set. +* [bosun git token](bosun_git_token.md) - Prints the github token. -###### Auto generated by spf13/cobra on 27-Dec-2018 +###### Auto generated by spf13/cobra on 16-May-2019 diff --git a/docs/bosun_git_accept-pull-request.md b/docs/bosun_git_accept-pull-request.md new file mode 100644 index 0000000..10fe115 --- /dev/null +++ b/docs/bosun_git_accept-pull-request.md @@ -0,0 +1,35 @@ +## bosun git accept-pull-request + +Accepts a pull request and merges it into master, optionally bumping the version and tagging the master branch. + +### Synopsis + +Accepts a pull request and merges it into master, optionally bumping the version and tagging the master branch. + +``` +bosun git accept-pull-request [number] [major|minor|patch|major.minor.patch] [flags] +``` + +### Options + +``` + --app strings Apps to apply version bump to. + -h, --help help for accept-pull-request +``` + +### Options inherited from parent commands + +``` + --config-file string Config file for Bosun. You can also set BOSUN_CONFIG. (default "$HOME/.bosun/bosun.yaml") + --dry-run Display rendered plans, but do not actually execute (not supported by all commands). + --force Force the requested command to be executed even if heuristics indicate it should not be. + --no-report Disable reporting of deploys to github. + -o, --output table Output format. Options are table, `json`, or `yaml`. Only respected by a some commands. (default "yaml") + --verbose Enable verbose logging. +``` + +### SEE ALSO + +* [bosun git](bosun_git.md) - Git commands. + +###### Auto generated by spf13/cobra on 16-May-2019 diff --git a/docs/bosun_git_deploy.md b/docs/bosun_git_deploy.md index 85fd897..0238794 100644 --- a/docs/bosun_git_deploy.md +++ b/docs/bosun_git_deploy.md @@ -15,10 +15,11 @@ Deploy-related commands. ### Options inherited from parent commands ``` - --ci-mode Operate in CI mode, reporting deployments and builds to github. - --config-file string Config file for Bosun. (default "$HOME/.bosun/bosun.yaml") + --config-file string Config file for Bosun. You can also set BOSUN_CONFIG. (default "$HOME/.bosun/bosun.yaml") --dry-run Display rendered plans, but do not actually execute (not supported by all commands). --force Force the requested command to be executed even if heuristics indicate it should not be. + --no-report Disable reporting of deploys to github. + -o, --output table Output format. Options are table, `json`, or `yaml`. Only respected by a some commands. (default "yaml") --verbose Enable verbose logging. ``` @@ -28,4 +29,4 @@ Deploy-related commands. * [bosun git deploy start](bosun_git_deploy_start.md) - Notifies github that a deploy has happened. * [bosun git deploy update](bosun_git_deploy_update.md) - Notifies github that a deploy has happened. -###### Auto generated by spf13/cobra on 27-Dec-2018 +###### Auto generated by spf13/cobra on 16-May-2019 diff --git a/docs/bosun_git_deploy_start.md b/docs/bosun_git_deploy_start.md index d71d661..98c9e03 100644 --- a/docs/bosun_git_deploy_start.md +++ b/docs/bosun_git_deploy_start.md @@ -19,10 +19,11 @@ bosun git deploy start {cluster} [flags] ### Options inherited from parent commands ``` - --ci-mode Operate in CI mode, reporting deployments and builds to github. - --config-file string Config file for Bosun. (default "$HOME/.bosun/bosun.yaml") + --config-file string Config file for Bosun. You can also set BOSUN_CONFIG. (default "$HOME/.bosun/bosun.yaml") --dry-run Display rendered plans, but do not actually execute (not supported by all commands). --force Force the requested command to be executed even if heuristics indicate it should not be. + --no-report Disable reporting of deploys to github. + -o, --output table Output format. Options are table, `json`, or `yaml`. Only respected by a some commands. (default "yaml") --verbose Enable verbose logging. ``` @@ -30,4 +31,4 @@ bosun git deploy start {cluster} [flags] * [bosun git deploy](bosun_git_deploy.md) - Deploy-related commands. -###### Auto generated by spf13/cobra on 27-Dec-2018 +###### Auto generated by spf13/cobra on 16-May-2019 diff --git a/docs/bosun_git_deploy_update.md b/docs/bosun_git_deploy_update.md index e2cf267..c953e58 100644 --- a/docs/bosun_git_deploy_update.md +++ b/docs/bosun_git_deploy_update.md @@ -19,10 +19,11 @@ bosun git deploy update {deployment-id} {success|failure} [flags] ### Options inherited from parent commands ``` - --ci-mode Operate in CI mode, reporting deployments and builds to github. - --config-file string Config file for Bosun. (default "$HOME/.bosun/bosun.yaml") + --config-file string Config file for Bosun. You can also set BOSUN_CONFIG. (default "$HOME/.bosun/bosun.yaml") --dry-run Display rendered plans, but do not actually execute (not supported by all commands). --force Force the requested command to be executed even if heuristics indicate it should not be. + --no-report Disable reporting of deploys to github. + -o, --output table Output format. Options are table, `json`, or `yaml`. Only respected by a some commands. (default "yaml") --verbose Enable verbose logging. ``` @@ -30,4 +31,4 @@ bosun git deploy update {deployment-id} {success|failure} [flags] * [bosun git deploy](bosun_git_deploy.md) - Deploy-related commands. -###### Auto generated by spf13/cobra on 27-Dec-2018 +###### Auto generated by spf13/cobra on 16-May-2019 diff --git a/docs/bosun_git_pull-request.md b/docs/bosun_git_pull-request.md new file mode 100644 index 0000000..bf98175 --- /dev/null +++ b/docs/bosun_git_pull-request.md @@ -0,0 +1,38 @@ +## bosun git pull-request + +Opens a pull request. + +### Synopsis + +Opens a pull request. + +``` +bosun git pull-request [flags] +``` + +### Options + +``` + --base string Target branch for merge. (default "master") + --body string Body of PR + -h, --help help for pull-request + --reviewer strings Reviewers to request. + --title string Title of PR +``` + +### Options inherited from parent commands + +``` + --config-file string Config file for Bosun. You can also set BOSUN_CONFIG. (default "$HOME/.bosun/bosun.yaml") + --dry-run Display rendered plans, but do not actually execute (not supported by all commands). + --force Force the requested command to be executed even if heuristics indicate it should not be. + --no-report Disable reporting of deploys to github. + -o, --output table Output format. Options are table, `json`, or `yaml`. Only respected by a some commands. (default "yaml") + --verbose Enable verbose logging. +``` + +### SEE ALSO + +* [bosun git](bosun_git.md) - Git commands. + +###### Auto generated by spf13/cobra on 16-May-2019 diff --git a/docs/bosun_git_task.md b/docs/bosun_git_task.md index 1944618..70fc0bb 100644 --- a/docs/bosun_git_task.md +++ b/docs/bosun_git_task.md @@ -1,13 +1,13 @@ ## bosun git task -Creates a task in the current repo for the story, and a branch for that task. +Creates a task in the current repo, and a branch for that task. Optionally attaches task to a story, if flags are set. ### Synopsis Requires github hub tool to be installed (https://hub.github.com/). ``` -bosun git task {parent-number} {task name} [flags] +bosun git task {task name} [flags] ``` ### Options @@ -15,17 +15,19 @@ bosun git task {parent-number} {task name} [flags] ``` -m, --body string Issue body. -h, --help help for task - --parent-org string Parent org. (default "naveegoinc") - --parent-repo string Parent repo. (default "stories") + --parent-org string Story org. (default "naveegoinc") + --parent-repo string Story repo. (default "stories") + --story int Number of the story to use as a parent. ``` ### Options inherited from parent commands ``` - --ci-mode Operate in CI mode, reporting deployments and builds to github. - --config-file string Config file for Bosun. (default "$HOME/.bosun/bosun.yaml") + --config-file string Config file for Bosun. You can also set BOSUN_CONFIG. (default "$HOME/.bosun/bosun.yaml") --dry-run Display rendered plans, but do not actually execute (not supported by all commands). --force Force the requested command to be executed even if heuristics indicate it should not be. + --no-report Disable reporting of deploys to github. + -o, --output table Output format. Options are table, `json`, or `yaml`. Only respected by a some commands. (default "yaml") --verbose Enable verbose logging. ``` @@ -33,4 +35,4 @@ bosun git task {parent-number} {task name} [flags] * [bosun git](bosun_git.md) - Git commands. -###### Auto generated by spf13/cobra on 27-Dec-2018 +###### Auto generated by spf13/cobra on 16-May-2019 diff --git a/docs/bosun_git_token.md b/docs/bosun_git_token.md new file mode 100644 index 0000000..a3e966f --- /dev/null +++ b/docs/bosun_git_token.md @@ -0,0 +1,34 @@ +## bosun git token + +Prints the github token. + +### Synopsis + +Prints the github token. + +``` +bosun git token [flags] +``` + +### Options + +``` + -h, --help help for token +``` + +### Options inherited from parent commands + +``` + --config-file string Config file for Bosun. You can also set BOSUN_CONFIG. (default "$HOME/.bosun/bosun.yaml") + --dry-run Display rendered plans, but do not actually execute (not supported by all commands). + --force Force the requested command to be executed even if heuristics indicate it should not be. + --no-report Disable reporting of deploys to github. + -o, --output table Output format. Options are table, `json`, or `yaml`. Only respected by a some commands. (default "yaml") + --verbose Enable verbose logging. +``` + +### SEE ALSO + +* [bosun git](bosun_git.md) - Git commands. + +###### Auto generated by spf13/cobra on 16-May-2019 diff --git a/docs/bosun_graylog.md b/docs/bosun_graylog.md index b45db78..af25422 100644 --- a/docs/bosun_graylog.md +++ b/docs/bosun_graylog.md @@ -15,10 +15,11 @@ Group of graylog-related commands. ### Options inherited from parent commands ``` - --ci-mode Operate in CI mode, reporting deployments and builds to github. - --config-file string Config file for Bosun. (default "$HOME/.bosun/bosun.yaml") + --config-file string Config file for Bosun. You can also set BOSUN_CONFIG. (default "$HOME/.bosun/bosun.yaml") --dry-run Display rendered plans, but do not actually execute (not supported by all commands). --force Force the requested command to be executed even if heuristics indicate it should not be. + --no-report Disable reporting of deploys to github. + -o, --output table Output format. Options are table, `json`, or `yaml`. Only respected by a some commands. (default "yaml") --verbose Enable verbose logging. ``` @@ -27,4 +28,4 @@ Group of graylog-related commands. * [bosun](bosun.md) - Devops tool. * [bosun graylog configure](bosun_graylog_configure.md) - Configures graylog using API -###### Auto generated by spf13/cobra on 27-Dec-2018 +###### Auto generated by spf13/cobra on 16-May-2019 diff --git a/docs/bosun_graylog_configure.md b/docs/bosun_graylog_configure.md index a458228..fb23c40 100644 --- a/docs/bosun_graylog_configure.md +++ b/docs/bosun_graylog_configure.md @@ -20,10 +20,11 @@ bosun graylog configure {config-file.yaml} [flags] ### Options inherited from parent commands ``` - --ci-mode Operate in CI mode, reporting deployments and builds to github. - --config-file string Config file for Bosun. (default "$HOME/.bosun/bosun.yaml") + --config-file string Config file for Bosun. You can also set BOSUN_CONFIG. (default "$HOME/.bosun/bosun.yaml") --dry-run Display rendered plans, but do not actually execute (not supported by all commands). --force Force the requested command to be executed even if heuristics indicate it should not be. + --no-report Disable reporting of deploys to github. + -o, --output table Output format. Options are table, `json`, or `yaml`. Only respected by a some commands. (default "yaml") --verbose Enable verbose logging. ``` @@ -31,4 +32,4 @@ bosun graylog configure {config-file.yaml} [flags] * [bosun graylog](bosun_graylog.md) - Group of graylog-related commands. -###### Auto generated by spf13/cobra on 27-Dec-2018 +###### Auto generated by spf13/cobra on 16-May-2019 diff --git a/docs/bosun_helm.md b/docs/bosun_helm.md index 118cf02..c1d6117 100644 --- a/docs/bosun_helm.md +++ b/docs/bosun_helm.md @@ -15,10 +15,11 @@ If there's a sequence of helm commands that you use a lot, add them as a command ### Options inherited from parent commands ``` - --ci-mode Operate in CI mode, reporting deployments and builds to github. - --config-file string Config file for Bosun. (default "$HOME/.bosun/bosun.yaml") + --config-file string Config file for Bosun. You can also set BOSUN_CONFIG. (default "$HOME/.bosun/bosun.yaml") --dry-run Display rendered plans, but do not actually execute (not supported by all commands). --force Force the requested command to be executed even if heuristics indicate it should not be. + --no-report Disable reporting of deploys to github. + -o, --output table Output format. Options are table, `json`, or `yaml`. Only respected by a some commands. (default "yaml") --verbose Enable verbose logging. ``` @@ -28,4 +29,4 @@ If there's a sequence of helm commands that you use a lot, add them as a command * [bosun helm init](bosun_helm_init.md) - Initializes helm/tiller. * [bosun helm publish](bosun_helm_publish.md) - Publishes one or more charts to our helm repo. -###### Auto generated by spf13/cobra on 27-Dec-2018 +###### Auto generated by spf13/cobra on 16-May-2019 diff --git a/docs/bosun_helm_init.md b/docs/bosun_helm_init.md index ed25f7d..722d940 100644 --- a/docs/bosun_helm_init.md +++ b/docs/bosun_helm_init.md @@ -19,10 +19,11 @@ bosun helm init [flags] ### Options inherited from parent commands ``` - --ci-mode Operate in CI mode, reporting deployments and builds to github. - --config-file string Config file for Bosun. (default "$HOME/.bosun/bosun.yaml") + --config-file string Config file for Bosun. You can also set BOSUN_CONFIG. (default "$HOME/.bosun/bosun.yaml") --dry-run Display rendered plans, but do not actually execute (not supported by all commands). --force Force the requested command to be executed even if heuristics indicate it should not be. + --no-report Disable reporting of deploys to github. + -o, --output table Output format. Options are table, `json`, or `yaml`. Only respected by a some commands. (default "yaml") --verbose Enable verbose logging. ``` @@ -30,4 +31,4 @@ bosun helm init [flags] * [bosun helm](bosun_helm.md) - Wrappers for custom helm commands. -###### Auto generated by spf13/cobra on 27-Dec-2018 +###### Auto generated by spf13/cobra on 16-May-2019 diff --git a/docs/bosun_helm_publish.md b/docs/bosun_helm_publish.md index efd4bce..7a3fd2f 100644 --- a/docs/bosun_helm_publish.md +++ b/docs/bosun_helm_publish.md @@ -26,10 +26,11 @@ bosun helm publish [chart-paths...] [flags] ### Options inherited from parent commands ``` - --ci-mode Operate in CI mode, reporting deployments and builds to github. - --config-file string Config file for Bosun. (default "$HOME/.bosun/bosun.yaml") + --config-file string Config file for Bosun. You can also set BOSUN_CONFIG. (default "$HOME/.bosun/bosun.yaml") --dry-run Display rendered plans, but do not actually execute (not supported by all commands). --force Force the requested command to be executed even if heuristics indicate it should not be. + --no-report Disable reporting of deploys to github. + -o, --output table Output format. Options are table, `json`, or `yaml`. Only respected by a some commands. (default "yaml") --verbose Enable verbose logging. ``` @@ -37,4 +38,4 @@ bosun helm publish [chart-paths...] [flags] * [bosun helm](bosun_helm.md) - Wrappers for custom helm commands. -###### Auto generated by spf13/cobra on 27-Dec-2018 +###### Auto generated by spf13/cobra on 16-May-2019 diff --git a/docs/bosun_helmsman.md b/docs/bosun_helmsman.md index 0c4cc2a..41ed79f 100644 --- a/docs/bosun_helmsman.md +++ b/docs/bosun_helmsman.md @@ -36,10 +36,11 @@ bosun helmsman {helmsman-file} [additional-helmsman-files...} [flags] ### Options inherited from parent commands ``` - --ci-mode Operate in CI mode, reporting deployments and builds to github. - --config-file string Config file for Bosun. (default "$HOME/.bosun/bosun.yaml") + --config-file string Config file for Bosun. You can also set BOSUN_CONFIG. (default "$HOME/.bosun/bosun.yaml") --dry-run Display rendered plans, but do not actually execute (not supported by all commands). --force Force the requested command to be executed even if heuristics indicate it should not be. + --no-report Disable reporting of deploys to github. + -o, --output table Output format. Options are table, `json`, or `yaml`. Only respected by a some commands. (default "yaml") --verbose Enable verbose logging. ``` @@ -47,4 +48,4 @@ bosun helmsman {helmsman-file} [additional-helmsman-files...} [flags] * [bosun](bosun.md) - Devops tool. -###### Auto generated by spf13/cobra on 27-Dec-2018 +###### Auto generated by spf13/cobra on 16-May-2019 diff --git a/docs/bosun_kube.md b/docs/bosun_kube.md index 462d3b4..9b8964c 100644 --- a/docs/bosun_kube.md +++ b/docs/bosun_kube.md @@ -15,17 +15,21 @@ You must have the cluster set in kubectl. ### Options inherited from parent commands ``` - --ci-mode Operate in CI mode, reporting deployments and builds to github. - --config-file string Config file for Bosun. (default "$HOME/.bosun/bosun.yaml") + --config-file string Config file for Bosun. You can also set BOSUN_CONFIG. (default "$HOME/.bosun/bosun.yaml") --dry-run Display rendered plans, but do not actually execute (not supported by all commands). --force Force the requested command to be executed even if heuristics indicate it should not be. + --no-report Disable reporting of deploys to github. + -o, --output table Output format. Options are table, `json`, or `yaml`. Only respected by a some commands. (default "yaml") --verbose Enable verbose logging. ``` ### SEE ALSO * [bosun](bosun.md) - Devops tool. +* [bosun kube add-eks](bosun_kube_add-eks.md) - Adds an EKS cluster to your kubeconfig. +* [bosun kube add-namespace](bosun_kube_add-namespace.md) - Adds a namespace to your cluster. +* [bosun kube dashboard](bosun_kube_dashboard.md) - Opens dashboard for current cluster. * [bosun kube dashboard-token](bosun_kube_dashboard-token.md) - Writes out a dashboard UI access token. * [bosun kube pull-secret](bosun_kube_pull-secret.md) - Sets a pull secret in kubernetes for https://docker.n5o.black. -###### Auto generated by spf13/cobra on 27-Dec-2018 +###### Auto generated by spf13/cobra on 16-May-2019 diff --git a/docs/bosun_kube_add-eks.md b/docs/bosun_kube_add-eks.md new file mode 100644 index 0000000..ef4153d --- /dev/null +++ b/docs/bosun_kube_add-eks.md @@ -0,0 +1,34 @@ +## bosun kube add-eks + +Adds an EKS cluster to your kubeconfig. + +### Synopsis + +You must the AWS CLI installed. + +``` +bosun kube add-eks {name} [region] [flags] +``` + +### Options + +``` + -h, --help help for add-eks +``` + +### Options inherited from parent commands + +``` + --config-file string Config file for Bosun. You can also set BOSUN_CONFIG. (default "$HOME/.bosun/bosun.yaml") + --dry-run Display rendered plans, but do not actually execute (not supported by all commands). + --force Force the requested command to be executed even if heuristics indicate it should not be. + --no-report Disable reporting of deploys to github. + -o, --output table Output format. Options are table, `json`, or `yaml`. Only respected by a some commands. (default "yaml") + --verbose Enable verbose logging. +``` + +### SEE ALSO + +* [bosun kube](bosun_kube.md) - Group of commands wrapping kubectl. + +###### Auto generated by spf13/cobra on 16-May-2019 diff --git a/docs/bosun_kube_add-namespace.md b/docs/bosun_kube_add-namespace.md new file mode 100644 index 0000000..db18370 --- /dev/null +++ b/docs/bosun_kube_add-namespace.md @@ -0,0 +1,34 @@ +## bosun kube add-namespace + +Adds a namespace to your cluster. + +### Synopsis + +Adds a namespace to your cluster. + +``` +bosun kube add-namespace {name} [flags] +``` + +### Options + +``` + -h, --help help for add-namespace +``` + +### Options inherited from parent commands + +``` + --config-file string Config file for Bosun. You can also set BOSUN_CONFIG. (default "$HOME/.bosun/bosun.yaml") + --dry-run Display rendered plans, but do not actually execute (not supported by all commands). + --force Force the requested command to be executed even if heuristics indicate it should not be. + --no-report Disable reporting of deploys to github. + -o, --output table Output format. Options are table, `json`, or `yaml`. Only respected by a some commands. (default "yaml") + --verbose Enable verbose logging. +``` + +### SEE ALSO + +* [bosun kube](bosun_kube.md) - Group of commands wrapping kubectl. + +###### Auto generated by spf13/cobra on 16-May-2019 diff --git a/docs/bosun_kube_dashboard-token.md b/docs/bosun_kube_dashboard-token.md index 1d3721d..8768130 100644 --- a/docs/bosun_kube_dashboard-token.md +++ b/docs/bosun_kube_dashboard-token.md @@ -19,10 +19,11 @@ bosun kube dashboard-token [flags] ### Options inherited from parent commands ``` - --ci-mode Operate in CI mode, reporting deployments and builds to github. - --config-file string Config file for Bosun. (default "$HOME/.bosun/bosun.yaml") + --config-file string Config file for Bosun. You can also set BOSUN_CONFIG. (default "$HOME/.bosun/bosun.yaml") --dry-run Display rendered plans, but do not actually execute (not supported by all commands). --force Force the requested command to be executed even if heuristics indicate it should not be. + --no-report Disable reporting of deploys to github. + -o, --output table Output format. Options are table, `json`, or `yaml`. Only respected by a some commands. (default "yaml") --verbose Enable verbose logging. ``` @@ -30,4 +31,4 @@ bosun kube dashboard-token [flags] * [bosun kube](bosun_kube.md) - Group of commands wrapping kubectl. -###### Auto generated by spf13/cobra on 27-Dec-2018 +###### Auto generated by spf13/cobra on 16-May-2019 diff --git a/docs/bosun_kube_dashboard.md b/docs/bosun_kube_dashboard.md new file mode 100644 index 0000000..033f229 --- /dev/null +++ b/docs/bosun_kube_dashboard.md @@ -0,0 +1,35 @@ +## bosun kube dashboard + +Opens dashboard for current cluster. + +### Synopsis + +You must have the cluster set in kubectl. + +``` +bosun kube dashboard [flags] +``` + +### Options + +``` + -h, --help help for dashboard + --url Display dashboard URL instead of opening a browser +``` + +### Options inherited from parent commands + +``` + --config-file string Config file for Bosun. You can also set BOSUN_CONFIG. (default "$HOME/.bosun/bosun.yaml") + --dry-run Display rendered plans, but do not actually execute (not supported by all commands). + --force Force the requested command to be executed even if heuristics indicate it should not be. + --no-report Disable reporting of deploys to github. + -o, --output table Output format. Options are table, `json`, or `yaml`. Only respected by a some commands. (default "yaml") + --verbose Enable verbose logging. +``` + +### SEE ALSO + +* [bosun kube](bosun_kube.md) - Group of commands wrapping kubectl. + +###### Auto generated by spf13/cobra on 16-May-2019 diff --git a/docs/bosun_kube_pull-secret.md b/docs/bosun_kube_pull-secret.md index 1afc61a..63941fd 100644 --- a/docs/bosun_kube_pull-secret.md +++ b/docs/bosun_kube_pull-secret.md @@ -22,10 +22,11 @@ bosun kube pull-secret [username] [password] [flags] ### Options inherited from parent commands ``` - --ci-mode Operate in CI mode, reporting deployments and builds to github. - --config-file string Config file for Bosun. (default "$HOME/.bosun/bosun.yaml") + --config-file string Config file for Bosun. You can also set BOSUN_CONFIG. (default "$HOME/.bosun/bosun.yaml") --dry-run Display rendered plans, but do not actually execute (not supported by all commands). --force Force the requested command to be executed even if heuristics indicate it should not be. + --no-report Disable reporting of deploys to github. + -o, --output table Output format. Options are table, `json`, or `yaml`. Only respected by a some commands. (default "yaml") --verbose Enable verbose logging. ``` @@ -33,4 +34,4 @@ bosun kube pull-secret [username] [password] [flags] * [bosun kube](bosun_kube.md) - Group of commands wrapping kubectl. -###### Auto generated by spf13/cobra on 27-Dec-2018 +###### Auto generated by spf13/cobra on 16-May-2019 diff --git a/docs/bosun_lpass.md b/docs/bosun_lpass.md index dc5e6ef..ada953d 100644 --- a/docs/bosun_lpass.md +++ b/docs/bosun_lpass.md @@ -15,10 +15,11 @@ Root command for LastPass commands. ### Options inherited from parent commands ``` - --ci-mode Operate in CI mode, reporting deployments and builds to github. - --config-file string Config file for Bosun. (default "$HOME/.bosun/bosun.yaml") + --config-file string Config file for Bosun. You can also set BOSUN_CONFIG. (default "$HOME/.bosun/bosun.yaml") --dry-run Display rendered plans, but do not actually execute (not supported by all commands). --force Force the requested command to be executed even if heuristics indicate it should not be. + --no-report Disable reporting of deploys to github. + -o, --output table Output format. Options are table, `json`, or `yaml`. Only respected by a some commands. (default "yaml") --verbose Enable verbose logging. ``` @@ -27,4 +28,4 @@ Root command for LastPass commands. * [bosun](bosun.md) - Devops tool. * [bosun lpass password](bosun_lpass_password.md) - Gets (or generates if not found) a password in LastPass. -###### Auto generated by spf13/cobra on 27-Dec-2018 +###### Auto generated by spf13/cobra on 16-May-2019 diff --git a/docs/bosun_lpass_password.md b/docs/bosun_lpass_password.md index 3fdb369..9ae3c42 100644 --- a/docs/bosun_lpass_password.md +++ b/docs/bosun_lpass_password.md @@ -19,10 +19,11 @@ bosun lpass password {folder/name} {username} {url} [flags] ### Options inherited from parent commands ``` - --ci-mode Operate in CI mode, reporting deployments and builds to github. - --config-file string Config file for Bosun. (default "$HOME/.bosun/bosun.yaml") + --config-file string Config file for Bosun. You can also set BOSUN_CONFIG. (default "$HOME/.bosun/bosun.yaml") --dry-run Display rendered plans, but do not actually execute (not supported by all commands). --force Force the requested command to be executed even if heuristics indicate it should not be. + --no-report Disable reporting of deploys to github. + -o, --output table Output format. Options are table, `json`, or `yaml`. Only respected by a some commands. (default "yaml") --verbose Enable verbose logging. ``` @@ -30,4 +31,4 @@ bosun lpass password {folder/name} {username} {url} [flags] * [bosun lpass](bosun_lpass.md) - Root command for LastPass commands. -###### Auto generated by spf13/cobra on 27-Dec-2018 +###### Auto generated by spf13/cobra on 16-May-2019 diff --git a/docs/bosun_meta.md b/docs/bosun_meta.md new file mode 100644 index 0000000..8f66dbb --- /dev/null +++ b/docs/bosun_meta.md @@ -0,0 +1,33 @@ +## bosun meta + +Commands for managing bosun itself. + +### Synopsis + +Commands for managing bosun itself. + +### Options + +``` + -h, --help help for meta +``` + +### Options inherited from parent commands + +``` + --config-file string Config file for Bosun. You can also set BOSUN_CONFIG. (default "$HOME/.bosun/bosun.yaml") + --dry-run Display rendered plans, but do not actually execute (not supported by all commands). + --force Force the requested command to be executed even if heuristics indicate it should not be. + --no-report Disable reporting of deploys to github. + -o, --output table Output format. Options are table, `json`, or `yaml`. Only respected by a some commands. (default "yaml") + --verbose Enable verbose logging. +``` + +### SEE ALSO + +* [bosun](bosun.md) - Devops tool. +* [bosun meta downgrade](bosun_meta_downgrade.md) - Downgrades bosun to a previous release. +* [bosun meta upgrade](bosun_meta_upgrade.md) - Upgrades bosun if a newer release is available +* [bosun meta version](bosun_meta_version.md) - Shows bosun version + +###### Auto generated by spf13/cobra on 16-May-2019 diff --git a/docs/bosun_meta_downgrade.md b/docs/bosun_meta_downgrade.md new file mode 100644 index 0000000..ae323f6 --- /dev/null +++ b/docs/bosun_meta_downgrade.md @@ -0,0 +1,35 @@ +## bosun meta downgrade + +Downgrades bosun to a previous release. + +### Synopsis + +Downgrades bosun to a previous release. + +``` +bosun meta downgrade [flags] +``` + +### Options + +``` + -h, --help help for downgrade + -p, --pre-release Upgrade to pre-release version. +``` + +### Options inherited from parent commands + +``` + --config-file string Config file for Bosun. You can also set BOSUN_CONFIG. (default "$HOME/.bosun/bosun.yaml") + --dry-run Display rendered plans, but do not actually execute (not supported by all commands). + --force Force the requested command to be executed even if heuristics indicate it should not be. + --no-report Disable reporting of deploys to github. + -o, --output table Output format. Options are table, `json`, or `yaml`. Only respected by a some commands. (default "yaml") + --verbose Enable verbose logging. +``` + +### SEE ALSO + +* [bosun meta](bosun_meta.md) - Commands for managing bosun itself. + +###### Auto generated by spf13/cobra on 16-May-2019 diff --git a/docs/bosun_meta_version.md b/docs/bosun_meta_version.md new file mode 100644 index 0000000..f1330d3 --- /dev/null +++ b/docs/bosun_meta_version.md @@ -0,0 +1,34 @@ +## bosun meta version + +Shows bosun version + +### Synopsis + +Shows bosun version + +``` +bosun meta version [flags] +``` + +### Options + +``` + -h, --help help for version +``` + +### Options inherited from parent commands + +``` + --config-file string Config file for Bosun. You can also set BOSUN_CONFIG. (default "$HOME/.bosun/bosun.yaml") + --dry-run Display rendered plans, but do not actually execute (not supported by all commands). + --force Force the requested command to be executed even if heuristics indicate it should not be. + --no-report Disable reporting of deploys to github. + -o, --output table Output format. Options are table, `json`, or `yaml`. Only respected by a some commands. (default "yaml") + --verbose Enable verbose logging. +``` + +### SEE ALSO + +* [bosun meta](bosun_meta.md) - Commands for managing bosun itself. + +###### Auto generated by spf13/cobra on 16-May-2019 diff --git a/docs/bosun_minikube.md b/docs/bosun_minikube.md index d1b38c7..243954c 100644 --- a/docs/bosun_minikube.md +++ b/docs/bosun_minikube.md @@ -15,10 +15,11 @@ You must have the cluster set in kubectl. ### Options inherited from parent commands ``` - --ci-mode Operate in CI mode, reporting deployments and builds to github. - --config-file string Config file for Bosun. (default "$HOME/.bosun/bosun.yaml") + --config-file string Config file for Bosun. You can also set BOSUN_CONFIG. (default "$HOME/.bosun/bosun.yaml") --dry-run Display rendered plans, but do not actually execute (not supported by all commands). --force Force the requested command to be executed even if heuristics indicate it should not be. + --no-report Disable reporting of deploys to github. + -o, --output table Output format. Options are table, `json`, or `yaml`. Only respected by a some commands. (default "yaml") --verbose Enable verbose logging. ``` @@ -28,4 +29,4 @@ You must have the cluster set in kubectl. * [bosun minikube forward](bosun_minikube_forward.md) - Forwards ports to the services running on minikube * [bosun minikube up](bosun_minikube_up.md) - Brings up minikube if it's not currently running. -###### Auto generated by spf13/cobra on 27-Dec-2018 +###### Auto generated by spf13/cobra on 16-May-2019 diff --git a/docs/bosun_minikube_forward.md b/docs/bosun_minikube_forward.md index 6d566c9..dbb19a6 100644 --- a/docs/bosun_minikube_forward.md +++ b/docs/bosun_minikube_forward.md @@ -20,10 +20,11 @@ bosun minikube forward [flags] ### Options inherited from parent commands ``` - --ci-mode Operate in CI mode, reporting deployments and builds to github. - --config-file string Config file for Bosun. (default "$HOME/.bosun/bosun.yaml") + --config-file string Config file for Bosun. You can also set BOSUN_CONFIG. (default "$HOME/.bosun/bosun.yaml") --dry-run Display rendered plans, but do not actually execute (not supported by all commands). --force Force the requested command to be executed even if heuristics indicate it should not be. + --no-report Disable reporting of deploys to github. + -o, --output table Output format. Options are table, `json`, or `yaml`. Only respected by a some commands. (default "yaml") --verbose Enable verbose logging. ``` @@ -31,4 +32,4 @@ bosun minikube forward [flags] * [bosun minikube](bosun_minikube.md) - Group of commands wrapping kubectl. -###### Auto generated by spf13/cobra on 27-Dec-2018 +###### Auto generated by spf13/cobra on 16-May-2019 diff --git a/docs/bosun_minikube_up.md b/docs/bosun_minikube_up.md index 67bb468..dc48ed4 100644 --- a/docs/bosun_minikube_up.md +++ b/docs/bosun_minikube_up.md @@ -13,16 +13,18 @@ bosun minikube up [flags] ### Options ``` - -h, --help help for up + --driver string The driver to use for minikube. (default "virtualbox") + -h, --help help for up ``` ### Options inherited from parent commands ``` - --ci-mode Operate in CI mode, reporting deployments and builds to github. - --config-file string Config file for Bosun. (default "$HOME/.bosun/bosun.yaml") + --config-file string Config file for Bosun. You can also set BOSUN_CONFIG. (default "$HOME/.bosun/bosun.yaml") --dry-run Display rendered plans, but do not actually execute (not supported by all commands). --force Force the requested command to be executed even if heuristics indicate it should not be. + --no-report Disable reporting of deploys to github. + -o, --output table Output format. Options are table, `json`, or `yaml`. Only respected by a some commands. (default "yaml") --verbose Enable verbose logging. ``` @@ -30,4 +32,4 @@ bosun minikube up [flags] * [bosun minikube](bosun_minikube.md) - Group of commands wrapping kubectl. -###### Auto generated by spf13/cobra on 27-Dec-2018 +###### Auto generated by spf13/cobra on 16-May-2019 diff --git a/docs/bosun_mongo.md b/docs/bosun_mongo.md new file mode 100644 index 0000000..98eb5d4 --- /dev/null +++ b/docs/bosun_mongo.md @@ -0,0 +1,31 @@ +## bosun mongo + +Commands for working with MongoDB. + +### Synopsis + +Commands for working with MongoDB. + +### Options + +``` + -h, --help help for mongo +``` + +### Options inherited from parent commands + +``` + --config-file string Config file for Bosun. You can also set BOSUN_CONFIG. (default "$HOME/.bosun/bosun.yaml") + --dry-run Display rendered plans, but do not actually execute (not supported by all commands). + --force Force the requested command to be executed even if heuristics indicate it should not be. + --no-report Disable reporting of deploys to github. + -o, --output table Output format. Options are table, `json`, or `yaml`. Only respected by a some commands. (default "yaml") + --verbose Enable verbose logging. +``` + +### SEE ALSO + +* [bosun](bosun.md) - Devops tool. +* [bosun mongo import](bosun_mongo_import.md) - Import a Mongo database + +###### Auto generated by spf13/cobra on 16-May-2019 diff --git a/docs/bosun_mongo_import.md b/docs/bosun_mongo_import.md new file mode 100644 index 0000000..d363d42 --- /dev/null +++ b/docs/bosun_mongo_import.md @@ -0,0 +1,51 @@ +## bosun mongo import + +Import a Mongo database + +### Synopsis + +Import a Mongo database + +``` +bosun mongo import [flags] +``` + +### Examples + +``` +mongo import db.yaml +``` + +### Options + +``` + --auth-source string The authSource to use when validating the credentials (default "admin") + -h, --help help for import + --host string The host address for connecting to the MongoDB server. (Default: 127.0.0.1) (default "127.0.0.1") + --kube-port int The port to use for mapping kubectl port-forward to your local host. Only used with --kube-port-forward (default 27017) + --kube-port-forward Tunnels communication to Mongo using the kubectl port-forward + --kube-service-name string Sets the kubernetes service name to use for forwarding. Only used with --kube-port-foward (default "svc/mongodb") + --mongo-database string The name of the database that will updated by this operation. If not set, the name of the file is used without the file extension. + --password string The password to use when connecting to Mongo + --port string The port for connecting to the MongoDB server. (Default: 27017) (default "27017") + --rebuild-db Forces a rebuild of the database by dropping and re-creating all collections + --username string The username to use when connecting to Mongo + --vault-auth string The database credentials path to use with vault. Setting this supersedes using username and password. +``` + +### Options inherited from parent commands + +``` + --config-file string Config file for Bosun. You can also set BOSUN_CONFIG. (default "$HOME/.bosun/bosun.yaml") + --dry-run Display rendered plans, but do not actually execute (not supported by all commands). + --force Force the requested command to be executed even if heuristics indicate it should not be. + --no-report Disable reporting of deploys to github. + -o, --output table Output format. Options are table, `json`, or `yaml`. Only respected by a some commands. (default "yaml") + --verbose Enable verbose logging. +``` + +### SEE ALSO + +* [bosun mongo](bosun_mongo.md) - Commands for working with MongoDB. + +###### Auto generated by spf13/cobra on 16-May-2019 diff --git a/docs/bosun_platform.md b/docs/bosun_platform.md new file mode 100644 index 0000000..f9a904b --- /dev/null +++ b/docs/bosun_platform.md @@ -0,0 +1,35 @@ +## bosun platform + +Contains platform related sub-commands. + +### Synopsis + +Contains platform related sub-commands. + +### Options + +``` + -h, --help help for platform +``` + +### Options inherited from parent commands + +``` + --config-file string Config file for Bosun. You can also set BOSUN_CONFIG. (default "$HOME/.bosun/bosun.yaml") + --dry-run Display rendered plans, but do not actually execute (not supported by all commands). + --force Force the requested command to be executed even if heuristics indicate it should not be. + --no-report Disable reporting of deploys to github. + -o, --output table Output format. Options are table, `json`, or `yaml`. Only respected by a some commands. (default "yaml") + --verbose Enable verbose logging. +``` + +### SEE ALSO + +* [bosun](bosun.md) - Devops tool. +* [bosun platform include](bosun_platform_include.md) - Adds an app from the workspace to the platform. +* [bosun platform list](bosun_platform_list.md) - Lists platforms. +* [bosun platform pull](bosun_platform_pull.md) - Pulls the latest code, and updates the `latest` release. +* [bosun platform show](bosun_platform_show.md) - Shows the named platform, or the current platform if no name provided. +* [bosun platform use](bosun_platform_use.md) - Sets the platform. + +###### Auto generated by spf13/cobra on 16-May-2019 diff --git a/docs/bosun_platform_include.md b/docs/bosun_platform_include.md new file mode 100644 index 0000000..be918c7 --- /dev/null +++ b/docs/bosun_platform_include.md @@ -0,0 +1,38 @@ +## bosun platform include + +Adds an app from the workspace to the platform. + +### Synopsis + +Adds an app from the workspace to the platform. + +``` +bosun platform include [appNames...] [flags] +``` + +### Options + +``` + --all Will include all items. + --exclude strings Will exclude items with labels matching filter (like x==y or x?=prefix-.*). + -h, --help help for include + --include strings Will include items with labels matching filter (like x==y or x?=prefix-.*). + --labels strings Will include any items where a label with that key is present. +``` + +### Options inherited from parent commands + +``` + --config-file string Config file for Bosun. You can also set BOSUN_CONFIG. (default "$HOME/.bosun/bosun.yaml") + --dry-run Display rendered plans, but do not actually execute (not supported by all commands). + --force Force the requested command to be executed even if heuristics indicate it should not be. + --no-report Disable reporting of deploys to github. + -o, --output table Output format. Options are table, `json`, or `yaml`. Only respected by a some commands. (default "yaml") + --verbose Enable verbose logging. +``` + +### SEE ALSO + +* [bosun platform](bosun_platform.md) - Contains platform related sub-commands. + +###### Auto generated by spf13/cobra on 16-May-2019 diff --git a/docs/bosun_platform_list.md b/docs/bosun_platform_list.md new file mode 100644 index 0000000..a195855 --- /dev/null +++ b/docs/bosun_platform_list.md @@ -0,0 +1,34 @@ +## bosun platform list + +Lists platforms. + +### Synopsis + +Lists platforms. + +``` +bosun platform list [flags] +``` + +### Options + +``` + -h, --help help for list +``` + +### Options inherited from parent commands + +``` + --config-file string Config file for Bosun. You can also set BOSUN_CONFIG. (default "$HOME/.bosun/bosun.yaml") + --dry-run Display rendered plans, but do not actually execute (not supported by all commands). + --force Force the requested command to be executed even if heuristics indicate it should not be. + --no-report Disable reporting of deploys to github. + -o, --output table Output format. Options are table, `json`, or `yaml`. Only respected by a some commands. (default "yaml") + --verbose Enable verbose logging. +``` + +### SEE ALSO + +* [bosun platform](bosun_platform.md) - Contains platform related sub-commands. + +###### Auto generated by spf13/cobra on 16-May-2019 diff --git a/docs/bosun_platform_pull.md b/docs/bosun_platform_pull.md new file mode 100644 index 0000000..b9bdd10 --- /dev/null +++ b/docs/bosun_platform_pull.md @@ -0,0 +1,38 @@ +## bosun platform pull + +Pulls the latest code, and updates the `latest` release. + +### Synopsis + +Pulls the latest code, and updates the `latest` release. + +``` +bosun platform pull [names...] [flags] +``` + +### Options + +``` + --all Will include all items. + --exclude strings Will exclude items with labels matching filter (like x==y or x?=prefix-.*). + -h, --help help for pull + --include strings Will include items with labels matching filter (like x==y or x?=prefix-.*). + --labels strings Will include any items where a label with that key is present. +``` + +### Options inherited from parent commands + +``` + --config-file string Config file for Bosun. You can also set BOSUN_CONFIG. (default "$HOME/.bosun/bosun.yaml") + --dry-run Display rendered plans, but do not actually execute (not supported by all commands). + --force Force the requested command to be executed even if heuristics indicate it should not be. + --no-report Disable reporting of deploys to github. + -o, --output table Output format. Options are table, `json`, or `yaml`. Only respected by a some commands. (default "yaml") + --verbose Enable verbose logging. +``` + +### SEE ALSO + +* [bosun platform](bosun_platform.md) - Contains platform related sub-commands. + +###### Auto generated by spf13/cobra on 16-May-2019 diff --git a/docs/bosun_platform_show.md b/docs/bosun_platform_show.md new file mode 100644 index 0000000..2966aad --- /dev/null +++ b/docs/bosun_platform_show.md @@ -0,0 +1,34 @@ +## bosun platform show + +Shows the named platform, or the current platform if no name provided. + +### Synopsis + +Shows the named platform, or the current platform if no name provided. + +``` +bosun platform show [name] [flags] +``` + +### Options + +``` + -h, --help help for show +``` + +### Options inherited from parent commands + +``` + --config-file string Config file for Bosun. You can also set BOSUN_CONFIG. (default "$HOME/.bosun/bosun.yaml") + --dry-run Display rendered plans, but do not actually execute (not supported by all commands). + --force Force the requested command to be executed even if heuristics indicate it should not be. + --no-report Disable reporting of deploys to github. + -o, --output table Output format. Options are table, `json`, or `yaml`. Only respected by a some commands. (default "yaml") + --verbose Enable verbose logging. +``` + +### SEE ALSO + +* [bosun platform](bosun_platform.md) - Contains platform related sub-commands. + +###### Auto generated by spf13/cobra on 16-May-2019 diff --git a/docs/bosun_platform_use.md b/docs/bosun_platform_use.md new file mode 100644 index 0000000..c4baaf3 --- /dev/null +++ b/docs/bosun_platform_use.md @@ -0,0 +1,34 @@ +## bosun platform use + +Sets the platform. + +### Synopsis + +Sets the platform. + +``` +bosun platform use [name] [flags] +``` + +### Options + +``` + -h, --help help for use +``` + +### Options inherited from parent commands + +``` + --config-file string Config file for Bosun. You can also set BOSUN_CONFIG. (default "$HOME/.bosun/bosun.yaml") + --dry-run Display rendered plans, but do not actually execute (not supported by all commands). + --force Force the requested command to be executed even if heuristics indicate it should not be. + --no-report Disable reporting of deploys to github. + -o, --output table Output format. Options are table, `json`, or `yaml`. Only respected by a some commands. (default "yaml") + --verbose Enable verbose logging. +``` + +### SEE ALSO + +* [bosun platform](bosun_platform.md) - Contains platform related sub-commands. + +###### Auto generated by spf13/cobra on 16-May-2019 diff --git a/docs/bosun_release.md b/docs/bosun_release.md index 525f65d..9fca00f 100644 --- a/docs/bosun_release.md +++ b/docs/bosun_release.md @@ -1,36 +1,45 @@ ## bosun release -Release commands. +Contains sub-commands for releases. ### Synopsis -Release commands. +Contains sub-commands for releases. ### Options ``` - -h, --help help for release + -h, --help help for release + -r, --release release use {name} The release to use for this command (overrides current release set with release use {name}). ``` ### Options inherited from parent commands ``` - --ci-mode Operate in CI mode, reporting deployments and builds to github. - --config-file string Config file for Bosun. (default "$HOME/.bosun/bosun.yaml") + --config-file string Config file for Bosun. You can also set BOSUN_CONFIG. (default "$HOME/.bosun/bosun.yaml") --dry-run Display rendered plans, but do not actually execute (not supported by all commands). --force Force the requested command to be executed even if heuristics indicate it should not be. + --no-report Disable reporting of deploys to github. + -o, --output table Output format. Options are table, `json`, or `yaml`. Only respected by a some commands. (default "yaml") --verbose Enable verbose logging. ``` ### SEE ALSO * [bosun](bosun.md) - Devops tool. -* [bosun release add](bosun_release_add.md) - Adds one or more apps to a release. -* [bosun release create](bosun_release_create.md) - Creates a new release. +* [bosun release delete](bosun_release_delete.md) - Deletes a release. * [bosun release deploy](bosun_release_deploy.md) - Deploys the release. +* [bosun release diff](bosun_release_diff.md) - Reports the differences between the values for an app in two scenarios. +* [bosun release dot](bosun_release_dot.md) - Prints a dot diagram of the release. +* [bosun release impact](bosun_release_impact.md) - Reports on the changes deploying the release will inflict on the current environment. * [bosun release list](bosun_release_list.md) - Lists known releases. -* [bosun release show](bosun_release_show.md) - Lists known releases. +* [bosun release merge](bosun_release_merge.md) - Merges the release branch back to master for each app in the release (or the listed apps) +* [bosun release plan](bosun_release_plan.md) - Contains sub-commands for release planning. +* [bosun release replan](bosun_release_replan.md) - Returns the release to the planning stage. +* [bosun release show](bosun_release_show.md) - Lists the apps in the current release. +* [bosun release show-values](bosun_release_show-values.md) - Shows the values which will be used for a release. +* [bosun release test](bosun_release_test.md) - Runs the tests for the apps in the release. * [bosun release use](bosun_release_use.md) - Sets the release which release commands will work against. * [bosun release validate](bosun_release_validate.md) - Validates the release. -###### Auto generated by spf13/cobra on 27-Dec-2018 +###### Auto generated by spf13/cobra on 16-May-2019 diff --git a/docs/bosun_release_delete.md b/docs/bosun_release_delete.md new file mode 100644 index 0000000..c66e2b4 --- /dev/null +++ b/docs/bosun_release_delete.md @@ -0,0 +1,35 @@ +## bosun release delete + +Deletes a release. + +### Synopsis + +Deletes a release. + +``` +bosun release delete [name] [flags] +``` + +### Options + +``` + -h, --help help for delete +``` + +### Options inherited from parent commands + +``` + --config-file string Config file for Bosun. You can also set BOSUN_CONFIG. (default "$HOME/.bosun/bosun.yaml") + --dry-run Display rendered plans, but do not actually execute (not supported by all commands). + --force Force the requested command to be executed even if heuristics indicate it should not be. + --no-report Disable reporting of deploys to github. + -o, --output table Output format. Options are table, `json`, or `yaml`. Only respected by a some commands. (default "yaml") + -r, --release release use {name} The release to use for this command (overrides current release set with release use {name}). + --verbose Enable verbose logging. +``` + +### SEE ALSO + +* [bosun release](bosun_release.md) - Contains sub-commands for releases. + +###### Auto generated by spf13/cobra on 16-May-2019 diff --git a/docs/bosun_release_deploy.md b/docs/bosun_release_deploy.md index a5209c1..4eff7b6 100644 --- a/docs/bosun_release_deploy.md +++ b/docs/bosun_release_deploy.md @@ -13,21 +13,30 @@ bosun release deploy [flags] ### Options ``` - -h, --help help for deploy + --all Will include all items. + --exclude strings Will exclude items with labels matching filter (like x==y or x?=prefix-.*). + -h, --help help for deploy + --include strings Will include items with labels matching filter (like x==y or x?=prefix-.*). + --labels strings Will include any items where a label with that key is present. + -s, --set strings Value overrides to set in this deploy, as path.to.key=value pairs. + --skip-validation Skips running validation before deploying the release. + -v, --value-sets strings Additional value sets to include in this deploy. ``` ### Options inherited from parent commands ``` - --ci-mode Operate in CI mode, reporting deployments and builds to github. - --config-file string Config file for Bosun. (default "$HOME/.bosun/bosun.yaml") - --dry-run Display rendered plans, but do not actually execute (not supported by all commands). - --force Force the requested command to be executed even if heuristics indicate it should not be. - --verbose Enable verbose logging. + --config-file string Config file for Bosun. You can also set BOSUN_CONFIG. (default "$HOME/.bosun/bosun.yaml") + --dry-run Display rendered plans, but do not actually execute (not supported by all commands). + --force Force the requested command to be executed even if heuristics indicate it should not be. + --no-report Disable reporting of deploys to github. + -o, --output table Output format. Options are table, `json`, or `yaml`. Only respected by a some commands. (default "yaml") + -r, --release release use {name} The release to use for this command (overrides current release set with release use {name}). + --verbose Enable verbose logging. ``` ### SEE ALSO -* [bosun release](bosun_release.md) - Release commands. +* [bosun release](bosun_release.md) - Contains sub-commands for releases. -###### Auto generated by spf13/cobra on 27-Dec-2018 +###### Auto generated by spf13/cobra on 16-May-2019 diff --git a/docs/bosun_release_diff.md b/docs/bosun_release_diff.md new file mode 100644 index 0000000..4d364f6 --- /dev/null +++ b/docs/bosun_release_diff.md @@ -0,0 +1,46 @@ +## bosun release diff + +Reports the differences between the values for an app in two scenarios. + +### Synopsis + +If the release part of the scenario is not provided, a transient release will be created and used instead. + +``` +bosun release diff {app} [release/]{env} [release]/{env} [flags] +``` + +### Examples + +``` +This command will show the differences between the values deployed +to the blue environment in release 2.4.2 and the current values for the +green environment: + +diff go-between 2.4.2/blue green + +``` + +### Options + +``` + -h, --help help for diff +``` + +### Options inherited from parent commands + +``` + --config-file string Config file for Bosun. You can also set BOSUN_CONFIG. (default "$HOME/.bosun/bosun.yaml") + --dry-run Display rendered plans, but do not actually execute (not supported by all commands). + --force Force the requested command to be executed even if heuristics indicate it should not be. + --no-report Disable reporting of deploys to github. + -o, --output table Output format. Options are table, `json`, or `yaml`. Only respected by a some commands. (default "yaml") + -r, --release release use {name} The release to use for this command (overrides current release set with release use {name}). + --verbose Enable verbose logging. +``` + +### SEE ALSO + +* [bosun release](bosun_release.md) - Contains sub-commands for releases. + +###### Auto generated by spf13/cobra on 16-May-2019 diff --git a/docs/bosun_release_dot.md b/docs/bosun_release_dot.md new file mode 100644 index 0000000..05a2492 --- /dev/null +++ b/docs/bosun_release_dot.md @@ -0,0 +1,35 @@ +## bosun release dot + +Prints a dot diagram of the release. + +### Synopsis + +Prints a dot diagram of the release. + +``` +bosun release dot [flags] +``` + +### Options + +``` + -h, --help help for dot +``` + +### Options inherited from parent commands + +``` + --config-file string Config file for Bosun. You can also set BOSUN_CONFIG. (default "$HOME/.bosun/bosun.yaml") + --dry-run Display rendered plans, but do not actually execute (not supported by all commands). + --force Force the requested command to be executed even if heuristics indicate it should not be. + --no-report Disable reporting of deploys to github. + -o, --output table Output format. Options are table, `json`, or `yaml`. Only respected by a some commands. (default "yaml") + -r, --release release use {name} The release to use for this command (overrides current release set with release use {name}). + --verbose Enable verbose logging. +``` + +### SEE ALSO + +* [bosun release](bosun_release.md) - Contains sub-commands for releases. + +###### Auto generated by spf13/cobra on 16-May-2019 diff --git a/docs/bosun_release_impact.md b/docs/bosun_release_impact.md new file mode 100644 index 0000000..0734a43 --- /dev/null +++ b/docs/bosun_release_impact.md @@ -0,0 +1,39 @@ +## bosun release impact + +Reports on the changes deploying the release will inflict on the current environment. + +### Synopsis + +Reports on the changes deploying the release will inflict on the current environment. + +``` +bosun release impact [flags] +``` + +### Options + +``` + --all Will include all items. + --exclude strings Will exclude items with labels matching filter (like x==y or x?=prefix-.*). + -h, --help help for impact + --include strings Will include items with labels matching filter (like x==y or x?=prefix-.*). + --labels strings Will include any items where a label with that key is present. +``` + +### Options inherited from parent commands + +``` + --config-file string Config file for Bosun. You can also set BOSUN_CONFIG. (default "$HOME/.bosun/bosun.yaml") + --dry-run Display rendered plans, but do not actually execute (not supported by all commands). + --force Force the requested command to be executed even if heuristics indicate it should not be. + --no-report Disable reporting of deploys to github. + -o, --output table Output format. Options are table, `json`, or `yaml`. Only respected by a some commands. (default "yaml") + -r, --release release use {name} The release to use for this command (overrides current release set with release use {name}). + --verbose Enable verbose logging. +``` + +### SEE ALSO + +* [bosun release](bosun_release.md) - Contains sub-commands for releases. + +###### Auto generated by spf13/cobra on 16-May-2019 diff --git a/docs/bosun_release_list.md b/docs/bosun_release_list.md index a7fc83e..00982e5 100644 --- a/docs/bosun_release_list.md +++ b/docs/bosun_release_list.md @@ -19,15 +19,17 @@ bosun release list [flags] ### Options inherited from parent commands ``` - --ci-mode Operate in CI mode, reporting deployments and builds to github. - --config-file string Config file for Bosun. (default "$HOME/.bosun/bosun.yaml") - --dry-run Display rendered plans, but do not actually execute (not supported by all commands). - --force Force the requested command to be executed even if heuristics indicate it should not be. - --verbose Enable verbose logging. + --config-file string Config file for Bosun. You can also set BOSUN_CONFIG. (default "$HOME/.bosun/bosun.yaml") + --dry-run Display rendered plans, but do not actually execute (not supported by all commands). + --force Force the requested command to be executed even if heuristics indicate it should not be. + --no-report Disable reporting of deploys to github. + -o, --output table Output format. Options are table, `json`, or `yaml`. Only respected by a some commands. (default "yaml") + -r, --release release use {name} The release to use for this command (overrides current release set with release use {name}). + --verbose Enable verbose logging. ``` ### SEE ALSO -* [bosun release](bosun_release.md) - Release commands. +* [bosun release](bosun_release.md) - Contains sub-commands for releases. -###### Auto generated by spf13/cobra on 27-Dec-2018 +###### Auto generated by spf13/cobra on 16-May-2019 diff --git a/docs/bosun_release_merge.md b/docs/bosun_release_merge.md new file mode 100644 index 0000000..55775c9 --- /dev/null +++ b/docs/bosun_release_merge.md @@ -0,0 +1,39 @@ +## bosun release merge + +Merges the release branch back to master for each app in the release (or the listed apps) + +### Synopsis + +Merges the release branch back to master for each app in the release (or the listed apps) + +``` +bosun release merge [apps...] [flags] +``` + +### Options + +``` + --all Will include all items. + --exclude strings Will exclude items with labels matching filter (like x==y or x?=prefix-.*). + -h, --help help for merge + --include strings Will include items with labels matching filter (like x==y or x?=prefix-.*). + --labels strings Will include any items where a label with that key is present. +``` + +### Options inherited from parent commands + +``` + --config-file string Config file for Bosun. You can also set BOSUN_CONFIG. (default "$HOME/.bosun/bosun.yaml") + --dry-run Display rendered plans, but do not actually execute (not supported by all commands). + --force Force the requested command to be executed even if heuristics indicate it should not be. + --no-report Disable reporting of deploys to github. + -o, --output table Output format. Options are table, `json`, or `yaml`. Only respected by a some commands. (default "yaml") + -r, --release release use {name} The release to use for this command (overrides current release set with release use {name}). + --verbose Enable verbose logging. +``` + +### SEE ALSO + +* [bosun release](bosun_release.md) - Contains sub-commands for releases. + +###### Auto generated by spf13/cobra on 16-May-2019 diff --git a/docs/bosun_release_plan.md b/docs/bosun_release_plan.md new file mode 100644 index 0000000..406748a --- /dev/null +++ b/docs/bosun_release_plan.md @@ -0,0 +1,41 @@ +## bosun release plan + +Contains sub-commands for release planning. + +### Synopsis + +Contains sub-commands for release planning. + +``` +bosun release plan [flags] +``` + +### Options + +``` + -h, --help help for plan +``` + +### Options inherited from parent commands + +``` + --config-file string Config file for Bosun. You can also set BOSUN_CONFIG. (default "$HOME/.bosun/bosun.yaml") + --dry-run Display rendered plans, but do not actually execute (not supported by all commands). + --force Force the requested command to be executed even if heuristics indicate it should not be. + --no-report Disable reporting of deploys to github. + -o, --output table Output format. Options are table, `json`, or `yaml`. Only respected by a some commands. (default "yaml") + -r, --release release use {name} The release to use for this command (overrides current release set with release use {name}). + --verbose Enable verbose logging. +``` + +### SEE ALSO + +* [bosun release](bosun_release.md) - Contains sub-commands for releases. +* [bosun release plan app](bosun_release_plan_app.md) - Sets the disposition of an app in the release. +* [bosun release plan commit](bosun_release_plan_commit.md) - Commit the current release plan. +* [bosun release plan discard](bosun_release_plan_discard.md) - Discard the current release plan. +* [bosun release plan edit](bosun_release_plan_edit.md) - Opens release plan in an editor. +* [bosun release plan show](bosun_release_plan_show.md) - Shows the current release plan. +* [bosun release plan start](bosun_release_plan_start.md) - Begins planning a new release. + +###### Auto generated by spf13/cobra on 16-May-2019 diff --git a/docs/bosun_release_plan_app.md b/docs/bosun_release_plan_app.md new file mode 100644 index 0000000..e5194ab --- /dev/null +++ b/docs/bosun_release_plan_app.md @@ -0,0 +1,42 @@ +## bosun release plan app + +Sets the disposition of an app in the release. + +### Synopsis + +Alternatively, you can edit the plan directly in the platform yaml file. + +``` +bosun release plan app [flags] +``` + +### Options + +``` + --all Will include all items. + --bump string The version bump to apply to upgrades among matched apps. + --deploy Set to deploy matched apps. + --exclude strings Will exclude items with labels matching filter (like x==y or x?=prefix-.*). + -h, --help help for app + --include strings Will include items with labels matching filter (like x==y or x?=prefix-.*). + --labels strings Will include any items where a label with that key is present. + --reason string The reason to set for the status change for matched apps. +``` + +### Options inherited from parent commands + +``` + --config-file string Config file for Bosun. You can also set BOSUN_CONFIG. (default "$HOME/.bosun/bosun.yaml") + --dry-run Display rendered plans, but do not actually execute (not supported by all commands). + --force Force the requested command to be executed even if heuristics indicate it should not be. + --no-report Disable reporting of deploys to github. + -o, --output table Output format. Options are table, `json`, or `yaml`. Only respected by a some commands. (default "yaml") + -r, --release release use {name} The release to use for this command (overrides current release set with release use {name}). + --verbose Enable verbose logging. +``` + +### SEE ALSO + +* [bosun release plan](bosun_release_plan.md) - Contains sub-commands for release planning. + +###### Auto generated by spf13/cobra on 16-May-2019 diff --git a/docs/bosun_release_plan_commit.md b/docs/bosun_release_plan_commit.md new file mode 100644 index 0000000..e229b5a --- /dev/null +++ b/docs/bosun_release_plan_commit.md @@ -0,0 +1,35 @@ +## bosun release plan commit + +Commit the current release plan. + +### Synopsis + +Commit the current release plan. + +``` +bosun release plan commit [flags] +``` + +### Options + +``` + -h, --help help for commit +``` + +### Options inherited from parent commands + +``` + --config-file string Config file for Bosun. You can also set BOSUN_CONFIG. (default "$HOME/.bosun/bosun.yaml") + --dry-run Display rendered plans, but do not actually execute (not supported by all commands). + --force Force the requested command to be executed even if heuristics indicate it should not be. + --no-report Disable reporting of deploys to github. + -o, --output table Output format. Options are table, `json`, or `yaml`. Only respected by a some commands. (default "yaml") + -r, --release release use {name} The release to use for this command (overrides current release set with release use {name}). + --verbose Enable verbose logging. +``` + +### SEE ALSO + +* [bosun release plan](bosun_release_plan.md) - Contains sub-commands for release planning. + +###### Auto generated by spf13/cobra on 16-May-2019 diff --git a/docs/bosun_release_plan_discard.md b/docs/bosun_release_plan_discard.md new file mode 100644 index 0000000..5c920cd --- /dev/null +++ b/docs/bosun_release_plan_discard.md @@ -0,0 +1,35 @@ +## bosun release plan discard + +Discard the current release plan. + +### Synopsis + +Discard the current release plan. + +``` +bosun release plan discard [flags] +``` + +### Options + +``` + -h, --help help for discard +``` + +### Options inherited from parent commands + +``` + --config-file string Config file for Bosun. You can also set BOSUN_CONFIG. (default "$HOME/.bosun/bosun.yaml") + --dry-run Display rendered plans, but do not actually execute (not supported by all commands). + --force Force the requested command to be executed even if heuristics indicate it should not be. + --no-report Disable reporting of deploys to github. + -o, --output table Output format. Options are table, `json`, or `yaml`. Only respected by a some commands. (default "yaml") + -r, --release release use {name} The release to use for this command (overrides current release set with release use {name}). + --verbose Enable verbose logging. +``` + +### SEE ALSO + +* [bosun release plan](bosun_release_plan.md) - Contains sub-commands for release planning. + +###### Auto generated by spf13/cobra on 16-May-2019 diff --git a/docs/bosun_release_plan_edit.md b/docs/bosun_release_plan_edit.md new file mode 100644 index 0000000..805aa03 --- /dev/null +++ b/docs/bosun_release_plan_edit.md @@ -0,0 +1,35 @@ +## bosun release plan edit + +Opens release plan in an editor. + +### Synopsis + +Opens release plan in an editor. + +``` +bosun release plan edit [flags] +``` + +### Options + +``` + -h, --help help for edit +``` + +### Options inherited from parent commands + +``` + --config-file string Config file for Bosun. You can also set BOSUN_CONFIG. (default "$HOME/.bosun/bosun.yaml") + --dry-run Display rendered plans, but do not actually execute (not supported by all commands). + --force Force the requested command to be executed even if heuristics indicate it should not be. + --no-report Disable reporting of deploys to github. + -o, --output table Output format. Options are table, `json`, or `yaml`. Only respected by a some commands. (default "yaml") + -r, --release release use {name} The release to use for this command (overrides current release set with release use {name}). + --verbose Enable verbose logging. +``` + +### SEE ALSO + +* [bosun release plan](bosun_release_plan.md) - Contains sub-commands for release planning. + +###### Auto generated by spf13/cobra on 16-May-2019 diff --git a/docs/bosun_release_plan_show.md b/docs/bosun_release_plan_show.md new file mode 100644 index 0000000..f0e3b20 --- /dev/null +++ b/docs/bosun_release_plan_show.md @@ -0,0 +1,35 @@ +## bosun release plan show + +Shows the current release plan. + +### Synopsis + +Shows the current release plan. + +``` +bosun release plan show [flags] +``` + +### Options + +``` + -h, --help help for show +``` + +### Options inherited from parent commands + +``` + --config-file string Config file for Bosun. You can also set BOSUN_CONFIG. (default "$HOME/.bosun/bosun.yaml") + --dry-run Display rendered plans, but do not actually execute (not supported by all commands). + --force Force the requested command to be executed even if heuristics indicate it should not be. + --no-report Disable reporting of deploys to github. + -o, --output table Output format. Options are table, `json`, or `yaml`. Only respected by a some commands. (default "yaml") + -r, --release release use {name} The release to use for this command (overrides current release set with release use {name}). + --verbose Enable verbose logging. +``` + +### SEE ALSO + +* [bosun release plan](bosun_release_plan.md) - Contains sub-commands for release planning. + +###### Auto generated by spf13/cobra on 16-May-2019 diff --git a/docs/bosun_release_plan_start.md b/docs/bosun_release_plan_start.md new file mode 100644 index 0000000..370ecd2 --- /dev/null +++ b/docs/bosun_release_plan_start.md @@ -0,0 +1,39 @@ +## bosun release plan start + +Begins planning a new release. + +### Synopsis + +Begins planning a new release. + +``` +bosun release plan start [flags] +``` + +### Options + +``` + --bump string The version bump of the release. + -h, --help help for start + --name string The name of the release (defaults to the version if not provided). + --patch-parent string The release the plan will prefer to create branches from. + --version string The version of the release. +``` + +### Options inherited from parent commands + +``` + --config-file string Config file for Bosun. You can also set BOSUN_CONFIG. (default "$HOME/.bosun/bosun.yaml") + --dry-run Display rendered plans, but do not actually execute (not supported by all commands). + --force Force the requested command to be executed even if heuristics indicate it should not be. + --no-report Disable reporting of deploys to github. + -o, --output table Output format. Options are table, `json`, or `yaml`. Only respected by a some commands. (default "yaml") + -r, --release release use {name} The release to use for this command (overrides current release set with release use {name}). + --verbose Enable verbose logging. +``` + +### SEE ALSO + +* [bosun release plan](bosun_release_plan.md) - Contains sub-commands for release planning. + +###### Auto generated by spf13/cobra on 16-May-2019 diff --git a/docs/bosun_release_replan.md b/docs/bosun_release_replan.md new file mode 100644 index 0000000..0a89350 --- /dev/null +++ b/docs/bosun_release_replan.md @@ -0,0 +1,35 @@ +## bosun release replan + +Returns the release to the planning stage. + +### Synopsis + +Returns the release to the planning stage. + +``` +bosun release replan [flags] +``` + +### Options + +``` + -h, --help help for replan +``` + +### Options inherited from parent commands + +``` + --config-file string Config file for Bosun. You can also set BOSUN_CONFIG. (default "$HOME/.bosun/bosun.yaml") + --dry-run Display rendered plans, but do not actually execute (not supported by all commands). + --force Force the requested command to be executed even if heuristics indicate it should not be. + --no-report Disable reporting of deploys to github. + -o, --output table Output format. Options are table, `json`, or `yaml`. Only respected by a some commands. (default "yaml") + -r, --release release use {name} The release to use for this command (overrides current release set with release use {name}). + --verbose Enable verbose logging. +``` + +### SEE ALSO + +* [bosun release](bosun_release.md) - Contains sub-commands for releases. + +###### Auto generated by spf13/cobra on 16-May-2019 diff --git a/docs/bosun_release_show-values.md b/docs/bosun_release_show-values.md new file mode 100644 index 0000000..eff5aad --- /dev/null +++ b/docs/bosun_release_show-values.md @@ -0,0 +1,35 @@ +## bosun release show-values + +Shows the values which will be used for a release. + +### Synopsis + +Shows the values which will be used for a release. + +``` +bosun release show-values {app} [flags] +``` + +### Options + +``` + -h, --help help for show-values +``` + +### Options inherited from parent commands + +``` + --config-file string Config file for Bosun. You can also set BOSUN_CONFIG. (default "$HOME/.bosun/bosun.yaml") + --dry-run Display rendered plans, but do not actually execute (not supported by all commands). + --force Force the requested command to be executed even if heuristics indicate it should not be. + --no-report Disable reporting of deploys to github. + -o, --output table Output format. Options are table, `json`, or `yaml`. Only respected by a some commands. (default "yaml") + -r, --release release use {name} The release to use for this command (overrides current release set with release use {name}). + --verbose Enable verbose logging. +``` + +### SEE ALSO + +* [bosun release](bosun_release.md) - Contains sub-commands for releases. + +###### Auto generated by spf13/cobra on 16-May-2019 diff --git a/docs/bosun_release_show.md b/docs/bosun_release_show.md index b000646..911c492 100644 --- a/docs/bosun_release_show.md +++ b/docs/bosun_release_show.md @@ -1,10 +1,10 @@ ## bosun release show -Lists known releases. +Lists the apps in the current release. ### Synopsis -Lists known releases. +Lists the apps in the current release. ``` bosun release show [flags] @@ -19,15 +19,17 @@ bosun release show [flags] ### Options inherited from parent commands ``` - --ci-mode Operate in CI mode, reporting deployments and builds to github. - --config-file string Config file for Bosun. (default "$HOME/.bosun/bosun.yaml") - --dry-run Display rendered plans, but do not actually execute (not supported by all commands). - --force Force the requested command to be executed even if heuristics indicate it should not be. - --verbose Enable verbose logging. + --config-file string Config file for Bosun. You can also set BOSUN_CONFIG. (default "$HOME/.bosun/bosun.yaml") + --dry-run Display rendered plans, but do not actually execute (not supported by all commands). + --force Force the requested command to be executed even if heuristics indicate it should not be. + --no-report Disable reporting of deploys to github. + -o, --output table Output format. Options are table, `json`, or `yaml`. Only respected by a some commands. (default "yaml") + -r, --release release use {name} The release to use for this command (overrides current release set with release use {name}). + --verbose Enable verbose logging. ``` ### SEE ALSO -* [bosun release](bosun_release.md) - Release commands. +* [bosun release](bosun_release.md) - Contains sub-commands for releases. -###### Auto generated by spf13/cobra on 27-Dec-2018 +###### Auto generated by spf13/cobra on 16-May-2019 diff --git a/docs/bosun_release_test.md b/docs/bosun_release_test.md new file mode 100644 index 0000000..d3d08a0 --- /dev/null +++ b/docs/bosun_release_test.md @@ -0,0 +1,39 @@ +## bosun release test + +Runs the tests for the apps in the release. + +### Synopsis + +Runs the tests for the apps in the release. + +``` +bosun release test [flags] +``` + +### Options + +``` + --all Will include all items. + --exclude strings Will exclude items with labels matching filter (like x==y or x?=prefix-.*). + -h, --help help for test + --include strings Will include items with labels matching filter (like x==y or x?=prefix-.*). + --labels strings Will include any items where a label with that key is present. +``` + +### Options inherited from parent commands + +``` + --config-file string Config file for Bosun. You can also set BOSUN_CONFIG. (default "$HOME/.bosun/bosun.yaml") + --dry-run Display rendered plans, but do not actually execute (not supported by all commands). + --force Force the requested command to be executed even if heuristics indicate it should not be. + --no-report Disable reporting of deploys to github. + -o, --output table Output format. Options are table, `json`, or `yaml`. Only respected by a some commands. (default "yaml") + -r, --release release use {name} The release to use for this command (overrides current release set with release use {name}). + --verbose Enable verbose logging. +``` + +### SEE ALSO + +* [bosun release](bosun_release.md) - Contains sub-commands for releases. + +###### Auto generated by spf13/cobra on 16-May-2019 diff --git a/docs/bosun_release_use.md b/docs/bosun_release_use.md index acd6653..dd17cef 100644 --- a/docs/bosun_release_use.md +++ b/docs/bosun_release_use.md @@ -19,15 +19,17 @@ bosun release use {name} [flags] ### Options inherited from parent commands ``` - --ci-mode Operate in CI mode, reporting deployments and builds to github. - --config-file string Config file for Bosun. (default "$HOME/.bosun/bosun.yaml") - --dry-run Display rendered plans, but do not actually execute (not supported by all commands). - --force Force the requested command to be executed even if heuristics indicate it should not be. - --verbose Enable verbose logging. + --config-file string Config file for Bosun. You can also set BOSUN_CONFIG. (default "$HOME/.bosun/bosun.yaml") + --dry-run Display rendered plans, but do not actually execute (not supported by all commands). + --force Force the requested command to be executed even if heuristics indicate it should not be. + --no-report Disable reporting of deploys to github. + -o, --output table Output format. Options are table, `json`, or `yaml`. Only respected by a some commands. (default "yaml") + -r, --release release use {name} The release to use for this command (overrides current release set with release use {name}). + --verbose Enable verbose logging. ``` ### SEE ALSO -* [bosun release](bosun_release.md) - Release commands. +* [bosun release](bosun_release.md) - Contains sub-commands for releases. -###### Auto generated by spf13/cobra on 27-Dec-2018 +###### Auto generated by spf13/cobra on 16-May-2019 diff --git a/docs/bosun_release_validate.md b/docs/bosun_release_validate.md index 0d22be2..6f9e7af 100644 --- a/docs/bosun_release_validate.md +++ b/docs/bosun_release_validate.md @@ -4,30 +4,36 @@ Validates the release. ### Synopsis -Validation checks that all apps in this release have a published chart and docker image for this release. +Validation checks that all apps (or the named apps) in the current release have a published chart and docker image. ``` -bosun release validate [flags] +bosun release validate [names...] [flags] ``` ### Options ``` - -h, --help help for validate + --all Will include all items. + --exclude strings Will exclude items with labels matching filter (like x==y or x?=prefix-.*). + -h, --help help for validate + --include strings Will include items with labels matching filter (like x==y or x?=prefix-.*). + --labels strings Will include any items where a label with that key is present. ``` ### Options inherited from parent commands ``` - --ci-mode Operate in CI mode, reporting deployments and builds to github. - --config-file string Config file for Bosun. (default "$HOME/.bosun/bosun.yaml") - --dry-run Display rendered plans, but do not actually execute (not supported by all commands). - --force Force the requested command to be executed even if heuristics indicate it should not be. - --verbose Enable verbose logging. + --config-file string Config file for Bosun. You can also set BOSUN_CONFIG. (default "$HOME/.bosun/bosun.yaml") + --dry-run Display rendered plans, but do not actually execute (not supported by all commands). + --force Force the requested command to be executed even if heuristics indicate it should not be. + --no-report Disable reporting of deploys to github. + -o, --output table Output format. Options are table, `json`, or `yaml`. Only respected by a some commands. (default "yaml") + -r, --release release use {name} The release to use for this command (overrides current release set with release use {name}). + --verbose Enable verbose logging. ``` ### SEE ALSO -* [bosun release](bosun_release.md) - Release commands. +* [bosun release](bosun_release.md) - Contains sub-commands for releases. -###### Auto generated by spf13/cobra on 27-Dec-2018 +###### Auto generated by spf13/cobra on 16-May-2019 diff --git a/docs/bosun_repo.md b/docs/bosun_repo.md new file mode 100644 index 0000000..c54a0b5 --- /dev/null +++ b/docs/bosun_repo.md @@ -0,0 +1,37 @@ +## bosun repo + +Contains sub-commands for interacting with repos. Has some overlap with the git sub-command. + +### Synopsis + +Most repo sub-commands take one or more optional name parameters. +If no name parameters are provided, the command will attempt to find a repo which +contains the current working path. + +### Options + +``` + -h, --help help for repo +``` + +### Options inherited from parent commands + +``` + --config-file string Config file for Bosun. You can also set BOSUN_CONFIG. (default "$HOME/.bosun/bosun.yaml") + --dry-run Display rendered plans, but do not actually execute (not supported by all commands). + --force Force the requested command to be executed even if heuristics indicate it should not be. + --no-report Disable reporting of deploys to github. + -o, --output table Output format. Options are table, `json`, or `yaml`. Only respected by a some commands. (default "yaml") + --verbose Enable verbose logging. +``` + +### SEE ALSO + +* [bosun](bosun.md) - Devops tool. +* [bosun repo clone](bosun_repo_clone.md) - Clones the named repo(s). +* [bosun repo fetch](bosun_repo_fetch.md) - Fetches the repo(s). +* [bosun repo list](bosun_repo_list.md) - Lists the known repos and their clone status. +* [bosun repo path](bosun_repo_path.md) - Outputs the path where the repo is cloned on the local system. +* [bosun repo pull](bosun_repo_pull.md) - Pulls the repo(s). + +###### Auto generated by spf13/cobra on 16-May-2019 diff --git a/docs/bosun_repo_clone.md b/docs/bosun_repo_clone.md new file mode 100644 index 0000000..aa458ea --- /dev/null +++ b/docs/bosun_repo_clone.md @@ -0,0 +1,39 @@ +## bosun repo clone + +Clones the named repo(s). + +### Synopsis + +Uses the first directory in `gitRoots` from the root config. + +``` +bosun repo clone {name} [name...] [flags] +``` + +### Options + +``` + --all Will include all items. + --dir org/repo The directory to clone into. (The repo will be cloned into org/repo in this directory.) + --exclude strings Will exclude items with labels matching filter (like x==y or x?=prefix-.*). + -h, --help help for clone + --include strings Will include items with labels matching filter (like x==y or x?=prefix-.*). + --labels strings Will include any items where a label with that key is present. +``` + +### Options inherited from parent commands + +``` + --config-file string Config file for Bosun. You can also set BOSUN_CONFIG. (default "$HOME/.bosun/bosun.yaml") + --dry-run Display rendered plans, but do not actually execute (not supported by all commands). + --force Force the requested command to be executed even if heuristics indicate it should not be. + --no-report Disable reporting of deploys to github. + -o, --output table Output format. Options are table, `json`, or `yaml`. Only respected by a some commands. (default "yaml") + --verbose Enable verbose logging. +``` + +### SEE ALSO + +* [bosun repo](bosun_repo.md) - Contains sub-commands for interacting with repos. Has some overlap with the git sub-command. + +###### Auto generated by spf13/cobra on 16-May-2019 diff --git a/docs/bosun_repo_fetch.md b/docs/bosun_repo_fetch.md new file mode 100644 index 0000000..7f690e5 --- /dev/null +++ b/docs/bosun_repo_fetch.md @@ -0,0 +1,38 @@ +## bosun repo fetch + +Fetches the repo(s). + +### Synopsis + +Fetches the repo(s). + +``` +bosun repo fetch [repo] [repo...] [flags] +``` + +### Options + +``` + --all Will include all items. + --exclude strings Will exclude items with labels matching filter (like x==y or x?=prefix-.*). + -h, --help help for fetch + --include strings Will include items with labels matching filter (like x==y or x?=prefix-.*). + --labels strings Will include any items where a label with that key is present. +``` + +### Options inherited from parent commands + +``` + --config-file string Config file for Bosun. You can also set BOSUN_CONFIG. (default "$HOME/.bosun/bosun.yaml") + --dry-run Display rendered plans, but do not actually execute (not supported by all commands). + --force Force the requested command to be executed even if heuristics indicate it should not be. + --no-report Disable reporting of deploys to github. + -o, --output table Output format. Options are table, `json`, or `yaml`. Only respected by a some commands. (default "yaml") + --verbose Enable verbose logging. +``` + +### SEE ALSO + +* [bosun repo](bosun_repo.md) - Contains sub-commands for interacting with repos. Has some overlap with the git sub-command. + +###### Auto generated by spf13/cobra on 16-May-2019 diff --git a/docs/bosun_repo_list.md b/docs/bosun_repo_list.md new file mode 100644 index 0000000..38b3eb1 --- /dev/null +++ b/docs/bosun_repo_list.md @@ -0,0 +1,38 @@ +## bosun repo list + +Lists the known repos and their clone status. + +### Synopsis + +Lists the known repos and their clone status. + +``` +bosun repo list [flags] +``` + +### Options + +``` + --all Will include all items. + --exclude strings Will exclude items with labels matching filter (like x==y or x?=prefix-.*). + -h, --help help for list + --include strings Will include items with labels matching filter (like x==y or x?=prefix-.*). + --labels strings Will include any items where a label with that key is present. +``` + +### Options inherited from parent commands + +``` + --config-file string Config file for Bosun. You can also set BOSUN_CONFIG. (default "$HOME/.bosun/bosun.yaml") + --dry-run Display rendered plans, but do not actually execute (not supported by all commands). + --force Force the requested command to be executed even if heuristics indicate it should not be. + --no-report Disable reporting of deploys to github. + -o, --output table Output format. Options are table, `json`, or `yaml`. Only respected by a some commands. (default "yaml") + --verbose Enable verbose logging. +``` + +### SEE ALSO + +* [bosun repo](bosun_repo.md) - Contains sub-commands for interacting with repos. Has some overlap with the git sub-command. + +###### Auto generated by spf13/cobra on 16-May-2019 diff --git a/docs/bosun_repo_path.md b/docs/bosun_repo_path.md new file mode 100644 index 0000000..6d70aae --- /dev/null +++ b/docs/bosun_repo_path.md @@ -0,0 +1,34 @@ +## bosun repo path + +Outputs the path where the repo is cloned on the local system. + +### Synopsis + +Outputs the path where the repo is cloned on the local system. + +``` +bosun repo path {name} [flags] +``` + +### Options + +``` + -h, --help help for path +``` + +### Options inherited from parent commands + +``` + --config-file string Config file for Bosun. You can also set BOSUN_CONFIG. (default "$HOME/.bosun/bosun.yaml") + --dry-run Display rendered plans, but do not actually execute (not supported by all commands). + --force Force the requested command to be executed even if heuristics indicate it should not be. + --no-report Disable reporting of deploys to github. + -o, --output table Output format. Options are table, `json`, or `yaml`. Only respected by a some commands. (default "yaml") + --verbose Enable verbose logging. +``` + +### SEE ALSO + +* [bosun repo](bosun_repo.md) - Contains sub-commands for interacting with repos. Has some overlap with the git sub-command. + +###### Auto generated by spf13/cobra on 16-May-2019 diff --git a/docs/bosun_repo_pull.md b/docs/bosun_repo_pull.md new file mode 100644 index 0000000..20ad283 --- /dev/null +++ b/docs/bosun_repo_pull.md @@ -0,0 +1,38 @@ +## bosun repo pull + +Pulls the repo(s). + +### Synopsis + +Pulls the repo(s). + +``` +bosun repo pull [repo] [repo...] [flags] +``` + +### Options + +``` + --all Will include all items. + --exclude strings Will exclude items with labels matching filter (like x==y or x?=prefix-.*). + -h, --help help for pull + --include strings Will include items with labels matching filter (like x==y or x?=prefix-.*). + --labels strings Will include any items where a label with that key is present. +``` + +### Options inherited from parent commands + +``` + --config-file string Config file for Bosun. You can also set BOSUN_CONFIG. (default "$HOME/.bosun/bosun.yaml") + --dry-run Display rendered plans, but do not actually execute (not supported by all commands). + --force Force the requested command to be executed even if heuristics indicate it should not be. + --no-report Disable reporting of deploys to github. + -o, --output table Output format. Options are table, `json`, or `yaml`. Only respected by a some commands. (default "yaml") + --verbose Enable verbose logging. +``` + +### SEE ALSO + +* [bosun repo](bosun_repo.md) - Contains sub-commands for interacting with repos. Has some overlap with the git sub-command. + +###### Auto generated by spf13/cobra on 16-May-2019 diff --git a/docs/bosun_script.md b/docs/bosun_script.md index 4223df9..04330c8 100644 --- a/docs/bosun_script.md +++ b/docs/bosun_script.md @@ -20,10 +20,11 @@ bosun script {script-file} [flags] ### Options inherited from parent commands ``` - --ci-mode Operate in CI mode, reporting deployments and builds to github. - --config-file string Config file for Bosun. (default "$HOME/.bosun/bosun.yaml") + --config-file string Config file for Bosun. You can also set BOSUN_CONFIG. (default "$HOME/.bosun/bosun.yaml") --dry-run Display rendered plans, but do not actually execute (not supported by all commands). --force Force the requested command to be executed even if heuristics indicate it should not be. + --no-report Disable reporting of deploys to github. + -o, --output table Output format. Options are table, `json`, or `yaml`. Only respected by a some commands. (default "yaml") --verbose Enable verbose logging. ``` @@ -32,4 +33,4 @@ bosun script {script-file} [flags] * [bosun](bosun.md) - Devops tool. * [bosun script list](bosun_script_list.md) - List scripts from current environment. -###### Auto generated by spf13/cobra on 27-Dec-2018 +###### Auto generated by spf13/cobra on 16-May-2019 diff --git a/docs/bosun_script_list.md b/docs/bosun_script_list.md index 65b0b14..3fe91c2 100644 --- a/docs/bosun_script_list.md +++ b/docs/bosun_script_list.md @@ -19,10 +19,11 @@ bosun script list [flags] ### Options inherited from parent commands ``` - --ci-mode Operate in CI mode, reporting deployments and builds to github. - --config-file string Config file for Bosun. (default "$HOME/.bosun/bosun.yaml") + --config-file string Config file for Bosun. You can also set BOSUN_CONFIG. (default "$HOME/.bosun/bosun.yaml") --dry-run Display rendered plans, but do not actually execute (not supported by all commands). --force Force the requested command to be executed even if heuristics indicate it should not be. + --no-report Disable reporting of deploys to github. + -o, --output table Output format. Options are table, `json`, or `yaml`. Only respected by a some commands. (default "yaml") --verbose Enable verbose logging. ``` @@ -30,4 +31,4 @@ bosun script list [flags] * [bosun script](bosun_script.md) - Run a scripted sequence of commands. -###### Auto generated by spf13/cobra on 27-Dec-2018 +###### Auto generated by spf13/cobra on 16-May-2019 diff --git a/docs/bosun_tools.md b/docs/bosun_tools.md new file mode 100644 index 0000000..ddd79f9 --- /dev/null +++ b/docs/bosun_tools.md @@ -0,0 +1,32 @@ +## bosun tools + +Commands for listing and installing tools. + +### Synopsis + +Commands for listing and installing tools. + +### Options + +``` + -h, --help help for tools +``` + +### Options inherited from parent commands + +``` + --config-file string Config file for Bosun. You can also set BOSUN_CONFIG. (default "$HOME/.bosun/bosun.yaml") + --dry-run Display rendered plans, but do not actually execute (not supported by all commands). + --force Force the requested command to be executed even if heuristics indicate it should not be. + --no-report Disable reporting of deploys to github. + -o, --output table Output format. Options are table, `json`, or `yaml`. Only respected by a some commands. (default "yaml") + --verbose Enable verbose logging. +``` + +### SEE ALSO + +* [bosun](bosun.md) - Devops tool. +* [bosun tools install](bosun_tools_install.md) - Installs a tool. +* [bosun tools list](bosun_tools_list.md) - Lists known tools + +###### Auto generated by spf13/cobra on 16-May-2019 diff --git a/docs/bosun_tools_install.md b/docs/bosun_tools_install.md new file mode 100644 index 0000000..33baafb --- /dev/null +++ b/docs/bosun_tools_install.md @@ -0,0 +1,34 @@ +## bosun tools install + +Installs a tool. + +### Synopsis + +Installs a tool. + +``` +bosun tools install {tool} [flags] +``` + +### Options + +``` + -h, --help help for install +``` + +### Options inherited from parent commands + +``` + --config-file string Config file for Bosun. You can also set BOSUN_CONFIG. (default "$HOME/.bosun/bosun.yaml") + --dry-run Display rendered plans, but do not actually execute (not supported by all commands). + --force Force the requested command to be executed even if heuristics indicate it should not be. + --no-report Disable reporting of deploys to github. + -o, --output table Output format. Options are table, `json`, or `yaml`. Only respected by a some commands. (default "yaml") + --verbose Enable verbose logging. +``` + +### SEE ALSO + +* [bosun tools](bosun_tools.md) - Commands for listing and installing tools. + +###### Auto generated by spf13/cobra on 16-May-2019 diff --git a/docs/bosun_tools_list.md b/docs/bosun_tools_list.md new file mode 100644 index 0000000..0f2c992 --- /dev/null +++ b/docs/bosun_tools_list.md @@ -0,0 +1,34 @@ +## bosun tools list + +Lists known tools + +### Synopsis + +Lists known tools + +``` +bosun tools list [flags] +``` + +### Options + +``` + -h, --help help for list +``` + +### Options inherited from parent commands + +``` + --config-file string Config file for Bosun. You can also set BOSUN_CONFIG. (default "$HOME/.bosun/bosun.yaml") + --dry-run Display rendered plans, but do not actually execute (not supported by all commands). + --force Force the requested command to be executed even if heuristics indicate it should not be. + --no-report Disable reporting of deploys to github. + -o, --output table Output format. Options are table, `json`, or `yaml`. Only respected by a some commands. (default "yaml") + --verbose Enable verbose logging. +``` + +### SEE ALSO + +* [bosun tools](bosun_tools.md) - Commands for listing and installing tools. + +###### Auto generated by spf13/cobra on 16-May-2019 diff --git a/docs/bosun_upgrade.md b/docs/bosun_upgrade.md new file mode 100644 index 0000000..e201d2a --- /dev/null +++ b/docs/bosun_upgrade.md @@ -0,0 +1,35 @@ +## bosun upgrade + +Upgrades bosun if a newer release is available + +### Synopsis + +Upgrades bosun if a newer release is available + +``` +bosun upgrade [flags] +``` + +### Options + +``` + -h, --help help for upgrade + -p, --pre-release Upgrade to pre-release version. +``` + +### Options inherited from parent commands + +``` + --config-file string Config file for Bosun. You can also set BOSUN_CONFIG. (default "$HOME/.bosun/bosun.yaml") + --dry-run Display rendered plans, but do not actually execute (not supported by all commands). + --force Force the requested command to be executed even if heuristics indicate it should not be. + --no-report Disable reporting of deploys to github. + -o, --output table Output format. Options are table, `json`, or `yaml`. Only respected by a some commands. (default "yaml") + --verbose Enable verbose logging. +``` + +### SEE ALSO + +* [bosun](bosun.md) - Devops tool. + +###### Auto generated by spf13/cobra on 16-May-2019 diff --git a/docs/bosun_vault.md b/docs/bosun_vault.md index f3d6786..a9e01b2 100644 --- a/docs/bosun_vault.md +++ b/docs/bosun_vault.md @@ -36,10 +36,11 @@ vault green-auth.yaml green-kube.yaml green-default.yaml ### Options inherited from parent commands ``` - --ci-mode Operate in CI mode, reporting deployments and builds to github. - --config-file string Config file for Bosun. (default "$HOME/.bosun/bosun.yaml") + --config-file string Config file for Bosun. You can also set BOSUN_CONFIG. (default "$HOME/.bosun/bosun.yaml") --dry-run Display rendered plans, but do not actually execute (not supported by all commands). --force Force the requested command to be executed even if heuristics indicate it should not be. + --no-report Disable reporting of deploys to github. + -o, --output table Output format. Options are table, `json`, or `yaml`. Only respected by a some commands. (default "yaml") --verbose Enable verbose logging. ``` @@ -51,4 +52,4 @@ vault green-auth.yaml green-kube.yaml green-default.yaml * [bosun vault secret](bosun_vault_secret.md) - Gets a secret value from vault, optionally populating the value if not found. * [bosun vault unseal](bosun_vault_unseal.md) - Unseals vault using the keys at the provided path, if it exists. Intended to be run from within kubernetes, with the shard secret mounted. -###### Auto generated by spf13/cobra on 27-Dec-2018 +###### Auto generated by spf13/cobra on 16-May-2019 diff --git a/docs/bosun_vault_bootstrap-dev.md b/docs/bosun_vault_bootstrap-dev.md index 6f6a264..0d26640 100644 --- a/docs/bosun_vault_bootstrap-dev.md +++ b/docs/bosun_vault_bootstrap-dev.md @@ -32,10 +32,11 @@ vault bootstrap-dev ### Options inherited from parent commands ``` - --ci-mode Operate in CI mode, reporting deployments and builds to github. - --config-file string Config file for Bosun. (default "$HOME/.bosun/bosun.yaml") + --config-file string Config file for Bosun. You can also set BOSUN_CONFIG. (default "$HOME/.bosun/bosun.yaml") --dry-run Display rendered plans, but do not actually execute (not supported by all commands). --force Force the requested command to be executed even if heuristics indicate it should not be. + --no-report Disable reporting of deploys to github. + -o, --output table Output format. Options are table, `json`, or `yaml`. Only respected by a some commands. (default "yaml") --verbose Enable verbose logging. ``` @@ -43,4 +44,4 @@ vault bootstrap-dev * [bosun vault](bosun_vault.md) - Updates VaultClient using layout files. Supports --dry-run flag. -###### Auto generated by spf13/cobra on 27-Dec-2018 +###### Auto generated by spf13/cobra on 16-May-2019 diff --git a/docs/bosun_vault_jwt.md b/docs/bosun_vault_jwt.md index 5be9b13..2160e86 100644 --- a/docs/bosun_vault_jwt.md +++ b/docs/bosun_vault_jwt.md @@ -32,10 +32,11 @@ vault init-dev ### Options inherited from parent commands ``` - --ci-mode Operate in CI mode, reporting deployments and builds to github. - --config-file string Config file for Bosun. (default "$HOME/.bosun/bosun.yaml") + --config-file string Config file for Bosun. You can also set BOSUN_CONFIG. (default "$HOME/.bosun/bosun.yaml") --dry-run Display rendered plans, but do not actually execute (not supported by all commands). --force Force the requested command to be executed even if heuristics indicate it should not be. + --no-report Disable reporting of deploys to github. + -o, --output table Output format. Options are table, `json`, or `yaml`. Only respected by a some commands. (default "yaml") --verbose Enable verbose logging. ``` @@ -43,4 +44,4 @@ vault init-dev * [bosun vault](bosun_vault.md) - Updates VaultClient using layout files. Supports --dry-run flag. -###### Auto generated by spf13/cobra on 27-Dec-2018 +###### Auto generated by spf13/cobra on 16-May-2019 diff --git a/docs/bosun_vault_secret.md b/docs/bosun_vault_secret.md index 94ae912..20ec862 100644 --- a/docs/bosun_vault_secret.md +++ b/docs/bosun_vault_secret.md @@ -24,10 +24,11 @@ bosun vault secret {path} [key] [flags] ### Options inherited from parent commands ``` - --ci-mode Operate in CI mode, reporting deployments and builds to github. - --config-file string Config file for Bosun. (default "$HOME/.bosun/bosun.yaml") + --config-file string Config file for Bosun. You can also set BOSUN_CONFIG. (default "$HOME/.bosun/bosun.yaml") --dry-run Display rendered plans, but do not actually execute (not supported by all commands). --force Force the requested command to be executed even if heuristics indicate it should not be. + --no-report Disable reporting of deploys to github. + -o, --output table Output format. Options are table, `json`, or `yaml`. Only respected by a some commands. (default "yaml") --verbose Enable verbose logging. ``` @@ -35,4 +36,4 @@ bosun vault secret {path} [key] [flags] * [bosun vault](bosun_vault.md) - Updates VaultClient using layout files. Supports --dry-run flag. -###### Auto generated by spf13/cobra on 27-Dec-2018 +###### Auto generated by spf13/cobra on 16-May-2019 diff --git a/docs/bosun_vault_unseal.md b/docs/bosun_vault_unseal.md index 393da34..be55cf7 100644 --- a/docs/bosun_vault_unseal.md +++ b/docs/bosun_vault_unseal.md @@ -21,10 +21,11 @@ bosun vault unseal {path/to/keys} [flags] ### Options inherited from parent commands ``` - --ci-mode Operate in CI mode, reporting deployments and builds to github. - --config-file string Config file for Bosun. (default "$HOME/.bosun/bosun.yaml") + --config-file string Config file for Bosun. You can also set BOSUN_CONFIG. (default "$HOME/.bosun/bosun.yaml") --dry-run Display rendered plans, but do not actually execute (not supported by all commands). --force Force the requested command to be executed even if heuristics indicate it should not be. + --no-report Disable reporting of deploys to github. + -o, --output table Output format. Options are table, `json`, or `yaml`. Only respected by a some commands. (default "yaml") --verbose Enable verbose logging. ``` @@ -32,4 +33,4 @@ bosun vault unseal {path/to/keys} [flags] * [bosun vault](bosun_vault.md) - Updates VaultClient using layout files. Supports --dry-run flag. -###### Auto generated by spf13/cobra on 27-Dec-2018 +###### Auto generated by spf13/cobra on 16-May-2019 diff --git a/docs/bosun_workspace.md b/docs/bosun_workspace.md new file mode 100644 index 0000000..493a6ac --- /dev/null +++ b/docs/bosun_workspace.md @@ -0,0 +1,43 @@ +## bosun workspace + +Workspace commands configure and manipulate the bindings between app repos and your local machine. + +### Synopsis + +A workspace contains the core configuration that is used when bosun is run. +It stores the current environment, the current release (if any), a listing of imported bosun files, +the apps discovered in them, and the current state of those apps in the workspace. +The app state includes the location of the app on the file system (for apps which have been cloned) +and the minikube deploy status of the app. + +A workspace is based on a workspace config file. The default location is $HOME/.bosun/bosun.yaml, +but it can be overridden by setting the BOSUN_CONFIG environment variable or passing the --config-file flag. + +### Options + +``` + -h, --help help for workspace +``` + +### Options inherited from parent commands + +``` + --config-file string Config file for Bosun. You can also set BOSUN_CONFIG. (default "$HOME/.bosun/bosun.yaml") + --dry-run Display rendered plans, but do not actually execute (not supported by all commands). + --force Force the requested command to be executed even if heuristics indicate it should not be. + --no-report Disable reporting of deploys to github. + -o, --output table Output format. Options are table, `json`, or `yaml`. Only respected by a some commands. (default "yaml") + --verbose Enable verbose logging. +``` + +### SEE ALSO + +* [bosun](bosun.md) - Devops tool. +* [bosun workspace dump](bosun_workspace_dump.md) - Prints current merged config, or the config of an app. +* [bosun workspace get](bosun_workspace_get.md) - Gets a value in the workspace config. Use a dotted path to reference the value. +* [bosun workspace import](bosun_workspace_import.md) - Includes the file in the user's bosun.yaml. If file is not provided, searches for a bosun.yaml file in this or a parent directory. +* [bosun workspace set](bosun_workspace_set.md) - Sets a value in the workspace config. Use a dotted path to reference the value. +* [bosun workspace show](bosun_workspace_show.md) - Shows various config components. +* [bosun workspace tidy](bosun_workspace_tidy.md) - Cleans up workspace. + +###### Auto generated by spf13/cobra on 16-May-2019 diff --git a/docs/bosun_workspace_dump.md b/docs/bosun_workspace_dump.md new file mode 100644 index 0000000..af83b58 --- /dev/null +++ b/docs/bosun_workspace_dump.md @@ -0,0 +1,34 @@ +## bosun workspace dump + +Prints current merged config, or the config of an app. + +### Synopsis + +Prints current merged config, or the config of an app. + +``` +bosun workspace dump [app] [flags] +``` + +### Options + +``` + -h, --help help for dump +``` + +### Options inherited from parent commands + +``` + --config-file string Config file for Bosun. You can also set BOSUN_CONFIG. (default "$HOME/.bosun/bosun.yaml") + --dry-run Display rendered plans, but do not actually execute (not supported by all commands). + --force Force the requested command to be executed even if heuristics indicate it should not be. + --no-report Disable reporting of deploys to github. + -o, --output table Output format. Options are table, `json`, or `yaml`. Only respected by a some commands. (default "yaml") + --verbose Enable verbose logging. +``` + +### SEE ALSO + +* [bosun workspace](bosun_workspace.md) - Workspace commands configure and manipulate the bindings between app repos and your local machine. + +###### Auto generated by spf13/cobra on 16-May-2019 diff --git a/docs/bosun_workspace_get.md b/docs/bosun_workspace_get.md new file mode 100644 index 0000000..07f846b --- /dev/null +++ b/docs/bosun_workspace_get.md @@ -0,0 +1,34 @@ +## bosun workspace get + +Gets a value in the workspace config. Use a dotted path to reference the value. + +### Synopsis + +Gets a value in the workspace config. Use a dotted path to reference the value. + +``` +bosun workspace get {JSONPath} [flags] +``` + +### Options + +``` + -h, --help help for get +``` + +### Options inherited from parent commands + +``` + --config-file string Config file for Bosun. You can also set BOSUN_CONFIG. (default "$HOME/.bosun/bosun.yaml") + --dry-run Display rendered plans, but do not actually execute (not supported by all commands). + --force Force the requested command to be executed even if heuristics indicate it should not be. + --no-report Disable reporting of deploys to github. + -o, --output table Output format. Options are table, `json`, or `yaml`. Only respected by a some commands. (default "yaml") + --verbose Enable verbose logging. +``` + +### SEE ALSO + +* [bosun workspace](bosun_workspace.md) - Workspace commands configure and manipulate the bindings between app repos and your local machine. + +###### Auto generated by spf13/cobra on 16-May-2019 diff --git a/docs/bosun_workspace_set.md b/docs/bosun_workspace_set.md new file mode 100644 index 0000000..acac5b1 --- /dev/null +++ b/docs/bosun_workspace_set.md @@ -0,0 +1,34 @@ +## bosun workspace set + +Sets a value in the workspace config. Use a dotted path to reference the value. + +### Synopsis + +Sets a value in the workspace config. Use a dotted path to reference the value. + +``` +bosun workspace set {path} {value} [flags] +``` + +### Options + +``` + -h, --help help for set +``` + +### Options inherited from parent commands + +``` + --config-file string Config file for Bosun. You can also set BOSUN_CONFIG. (default "$HOME/.bosun/bosun.yaml") + --dry-run Display rendered plans, but do not actually execute (not supported by all commands). + --force Force the requested command to be executed even if heuristics indicate it should not be. + --no-report Disable reporting of deploys to github. + -o, --output table Output format. Options are table, `json`, or `yaml`. Only respected by a some commands. (default "yaml") + --verbose Enable verbose logging. +``` + +### SEE ALSO + +* [bosun workspace](bosun_workspace.md) - Workspace commands configure and manipulate the bindings between app repos and your local machine. + +###### Auto generated by spf13/cobra on 16-May-2019 diff --git a/docs/bosun_workspace_show.md b/docs/bosun_workspace_show.md new file mode 100644 index 0000000..fa8df78 --- /dev/null +++ b/docs/bosun_workspace_show.md @@ -0,0 +1,31 @@ +## bosun workspace show + +Shows various config components. + +### Synopsis + +Shows various config components. + +### Options + +``` + -h, --help help for show +``` + +### Options inherited from parent commands + +``` + --config-file string Config file for Bosun. You can also set BOSUN_CONFIG. (default "$HOME/.bosun/bosun.yaml") + --dry-run Display rendered plans, but do not actually execute (not supported by all commands). + --force Force the requested command to be executed even if heuristics indicate it should not be. + --no-report Disable reporting of deploys to github. + -o, --output table Output format. Options are table, `json`, or `yaml`. Only respected by a some commands. (default "yaml") + --verbose Enable verbose logging. +``` + +### SEE ALSO + +* [bosun workspace](bosun_workspace.md) - Workspace commands configure and manipulate the bindings between app repos and your local machine. +* [bosun workspace show imports](bosun_workspace_show_imports.md) - Prints the imports. + +###### Auto generated by spf13/cobra on 16-May-2019 diff --git a/docs/bosun_workspace_show_imports.md b/docs/bosun_workspace_show_imports.md new file mode 100644 index 0000000..4c87c87 --- /dev/null +++ b/docs/bosun_workspace_show_imports.md @@ -0,0 +1,34 @@ +## bosun workspace show imports + +Prints the imports. + +### Synopsis + +Prints the imports. + +``` +bosun workspace show imports [flags] +``` + +### Options + +``` + -h, --help help for imports +``` + +### Options inherited from parent commands + +``` + --config-file string Config file for Bosun. You can also set BOSUN_CONFIG. (default "$HOME/.bosun/bosun.yaml") + --dry-run Display rendered plans, but do not actually execute (not supported by all commands). + --force Force the requested command to be executed even if heuristics indicate it should not be. + --no-report Disable reporting of deploys to github. + -o, --output table Output format. Options are table, `json`, or `yaml`. Only respected by a some commands. (default "yaml") + --verbose Enable verbose logging. +``` + +### SEE ALSO + +* [bosun workspace show](bosun_workspace_show.md) - Shows various config components. + +###### Auto generated by spf13/cobra on 16-May-2019 diff --git a/docs/bosun_workspace_tidy.md b/docs/bosun_workspace_tidy.md new file mode 100644 index 0000000..d39f706 --- /dev/null +++ b/docs/bosun_workspace_tidy.md @@ -0,0 +1,38 @@ +## bosun workspace tidy + +Cleans up workspace. + +### Synopsis + +Cleans up workspace by: +- Removing redundant imports. +- Finding apps which have been cloned into registered git roots. +- Other things as we think of them... + + +``` +bosun workspace tidy [flags] +``` + +### Options + +``` + -h, --help help for tidy +``` + +### Options inherited from parent commands + +``` + --config-file string Config file for Bosun. You can also set BOSUN_CONFIG. (default "$HOME/.bosun/bosun.yaml") + --dry-run Display rendered plans, but do not actually execute (not supported by all commands). + --force Force the requested command to be executed even if heuristics indicate it should not be. + --no-report Disable reporting of deploys to github. + -o, --output table Output format. Options are table, `json`, or `yaml`. Only respected by a some commands. (default "yaml") + --verbose Enable verbose logging. +``` + +### SEE ALSO + +* [bosun workspace](bosun_workspace.md) - Workspace commands configure and manipulate the bindings between app repos and your local machine. + +###### Auto generated by spf13/cobra on 16-May-2019 diff --git a/go.mod b/go.mod index 019834b..2592bc2 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ require ( github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 // indirect github.com/Azure/go-autorest v11.2.8+incompatible github.com/Jeffail/gabs v1.1.1 // indirect - github.com/Masterminds/semver v1.4.2 + github.com/Masterminds/semver v1.4.2 // indirect github.com/Masterminds/sprig v2.17.1+incompatible github.com/Microsoft/go-winio v0.4.11 // indirect github.com/NYTimes/gziphandler v1.0.1 // indirect @@ -34,11 +34,10 @@ require ( github.com/containerd/continuity v0.0.0-20181203112020-004b46473808 // indirect github.com/coreos/bbolt v1.3.2 // indirect github.com/coreos/go-oidc v2.0.0+incompatible // indirect - github.com/coreos/go-semver v0.2.0 github.com/coreos/go-systemd v0.0.0-20190212144455-93d5ec2c7f76 // indirect github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f // indirect + github.com/cpuguy83/go-md2man v1.0.10 // indirect github.com/dancannon/gorethink v4.0.0+incompatible // indirect - github.com/davecgh/go-spew v1.1.1 github.com/denisenkom/go-mssqldb v0.0.0-20181014144952-4e0d7dc8888f // indirect github.com/dghubble/sling v1.2.0 github.com/dgrijalva/jwt-go v3.2.0+incompatible // indirect @@ -53,6 +52,7 @@ require ( github.com/gammazero/deque v0.0.0-20190130191400-2afb3858e9c7 // indirect github.com/gammazero/workerpool v0.0.0-20181230203049-86a96b5d5d92 // indirect github.com/garyburd/redigo v1.6.0 // indirect + github.com/ghodss/yaml v1.0.0 github.com/go-errors/errors v1.0.1 // indirect github.com/go-ldap/ldap v2.5.1+incompatible // indirect github.com/go-sql-driver/mysql v1.4.1 // indirect @@ -164,8 +164,10 @@ require ( github.com/spf13/viper v1.3.1 github.com/stevenle/topsort v0.0.0-20130922064739-8130c1d7596b github.com/streadway/amqp v0.0.0-20190225234609-30f8ed68076e // indirect + github.com/tidwall/pretty v0.0.0-20190325153808-1166b9ac2b65 // indirect github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5 // indirect github.com/ugorji/go/codec v0.0.0-20181209151446-772ced7fd4c2 // indirect + github.com/vbauerster/mpb/v4 v4.7.0 github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c // indirect github.com/xdg/stringprep v1.0.0 // indirect github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 // indirect @@ -175,9 +177,8 @@ require ( go.uber.org/atomic v1.3.2 // indirect go.uber.org/multierr v1.1.0 // indirect go.uber.org/zap v1.9.1 // indirect - golang.org/x/crypto v0.0.0-20190103213133-ff983b9c42bc + golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734 golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890 - golang.org/x/sys v0.0.0-20190102155601-82a175fd1598 // indirect google.golang.org/appengine v1.4.0 // indirect gopkg.in/alecthomas/kingpin.v3-unstable v3.0.0-20180810215634-df19058c872c // indirect gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d // indirect diff --git a/go.sum b/go.sum index 2c9c067..e10e0a7 100644 --- a/go.sum +++ b/go.sum @@ -38,6 +38,8 @@ github.com/SAP/go-hdb v0.13.1 h1:BuZlUZtqbF/oVSQ8Vp+/+wOtcBLh55zwMV7XnvYcz8g= github.com/SAP/go-hdb v0.13.1/go.mod h1:etBT+FAi1t5k3K3tf5vQTnosgYmhDkRi8jEnQqCnxF0= github.com/SermoDigital/jose v0.9.1 h1:atYaHPD3lPICcbK1owly3aPm0iaJGSGPi0WD4vLznv8= github.com/SermoDigital/jose v0.9.1/go.mod h1:ARgCUhI1MHQH+ONky/PAtmVHQrP5JlGY0F3poXOp/fA= +github.com/VividCortex/ewma v1.1.1 h1:MnEK4VOv6n0RSY4vtRe3h11qjxL3+t0B8yOL8iMXdcM= +github.com/VividCortex/ewma v1.1.1/go.mod h1:2Tkkvm3sRDVXaiyucHiACn4cqf7DpdyLvmxzcbUokwA= github.com/alecthomas/gometalinter v2.0.11+incompatible/go.mod h1:qfIpQGGz3d+NmgyPBqv+LSh50emm1pt72EtcX2vKYQk= github.com/alecthomas/gometalinter v2.0.12+incompatible h1:RBUbc8pKtqRoVCymENDl7cpWS9Ht5XNnwwk0cKjpteI= github.com/alecthomas/gometalinter v2.0.12+incompatible/go.mod h1:qfIpQGGz3d+NmgyPBqv+LSh50emm1pt72EtcX2vKYQk= @@ -113,6 +115,8 @@ github.com/coreos/go-systemd v0.0.0-20190212144455-93d5ec2c7f76 h1:FE783w8WFh+Rv github.com/coreos/go-systemd v0.0.0-20190212144455-93d5ec2c7f76/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f h1:lBNOc5arjvs8E5mO2tbpBpLoyyu8B6e44T7hJy6potg= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/cpuguy83/go-md2man v1.0.10 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF22jk= +github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/dancannon/gorethink v4.0.0+incompatible h1:KFV7Gha3AuqT+gr0B/eKvGhbjmUv0qGF43aKCIKVE9A= github.com/dancannon/gorethink v4.0.0+incompatible/go.mod h1:BLvkat9KmZc1efyYwhz3WnybhRZtgF1K929FD8z1avU= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= @@ -413,8 +417,6 @@ github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXW github.com/oliveagle/jsonpath v0.0.0-20180606110733-2e52cf6e6852 h1:Yl0tPBa8QPjGmesFh1D0rDy+q1Twx6FyU7VWHi8wZbI= github.com/oliveagle/jsonpath v0.0.0-20180606110733-2e52cf6e6852/go.mod h1:eqOVx5Vwu4gd2mmMZvVZsgIqNSaW3xxRThUJ0k/TPk4= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs= -github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.8.0 h1:VkHVNpR4iVnU8XQR6DBm8BqYjN7CRzw+xKUbVVbbW9w= github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU= @@ -471,6 +473,7 @@ github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rs/xid v1.2.1 h1:mhH9Nq+C1fY2l1XIpgxIiUOfNpRBYH1kKcr+qfKgjRc= github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= +github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/ryanuber/go-glob v0.0.0-20170128012129-256dc444b735 h1:7YvPJVmEeFHR1Tj9sZEYsmarJEQfMVYpd/Vyy/A8dqE= github.com/ryanuber/go-glob v0.0.0-20170128012129-256dc444b735/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= @@ -541,6 +544,8 @@ github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA= +github.com/tidwall/pretty v0.0.0-20190325153808-1166b9ac2b65 h1:rQ229MBgvW68s1/g6f1/63TgYwYxfF4E+bi/KC19P8g= +github.com/tidwall/pretty v0.0.0-20190325153808-1166b9ac2b65/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5 h1:LnC5Kc/wtumK+WB441p7ynQJzVuNRJiqddSIE3IlSEQ= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tsenart/deadcode v0.0.0-20160724212837-210d2dc333e9 h1:vY5WqiEon0ZSTGM3ayVVi+twaHKHDFUVloaQ/wug9/c= @@ -550,6 +555,8 @@ github.com/ugorji/go/codec v0.0.0-20181209151446-772ced7fd4c2 h1:EICbibRW4JNKMcY github.com/ugorji/go/codec v0.0.0-20181209151446-772ced7fd4c2/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= github.com/ulikunitz/xz v0.5.5 h1:pFrO0lVpTBXLpYw+pnLj6TbvHuyjXMfjGeCwSqCVwok= github.com/ulikunitz/xz v0.5.5/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8= +github.com/vbauerster/mpb/v4 v4.7.0 h1:Et+zVewxG6qmfBf4Ez+nDhLbCSh6WhZrUPHg9a6e+hw= +github.com/vbauerster/mpb/v4 v4.7.0/go.mod h1:ugxYn2kSUrY10WK5CWDUZvQxjdwKFN9K3Ja3/z6p4X0= github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c h1:u40Z8hqBAAQyv+vATcGgV0YCnDjqSL7/q/JyPhhJSPk= github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I= github.com/xdg/stringprep v1.0.0 h1:d9X0esnoa3dFsV0FG35rAT0RIhYFlPq7MiP+DW89La0= @@ -577,8 +584,9 @@ golang.org/x/crypto v0.0.0-20180820150726-614d502a4dac/go.mod h1:6SG95UA2DQfeDnf golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20190103213133-ff983b9c42bc h1:F5tKCVGp+MUAHhKp5MZtGqAlGX3+oCsiL1Q629FL90M= -golang.org/x/crypto v0.0.0-20190103213133-ff983b9c42bc/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734 h1:p/H982KKEjUnLJkM3tt/LemDnOc1GiZL5FCVlORJ5zo= +golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -594,6 +602,8 @@ golang.org/x/net v0.0.0-20181220203305-927f97764cc3 h1:eH6Eip3UpmR+yM/qI9Ijluzb1 golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3 h1:ulvT7fqt0yHWzpJwI57MezWnYDVpCAYBVuYst/L+fAY= golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890 h1:uESlIz09WIHT2I+pasSXcpLYqYK8wHcdCetU3VuMBJE= @@ -615,8 +625,10 @@ golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181213200352-4d1cda033e06/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181218192612-074acd46bca6/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190102155601-82a175fd1598 h1:S8GOgffXV1X3fpVG442QRfWOt0iFl79eHJ7OPt725bo= -golang.org/x/sys v0.0.0-20190102155601-82a175fd1598/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190429190828-d89cdac9e872 h1:cGjJzUd8RgBw428LXP65YXni0aiGNA4Bl+ls8SmLOm8= +golang.org/x/sys v0.0.0-20190429190828-d89cdac9e872/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2 h1:z99zHgr7hKfrUcX/KsoJk5FJfjTceCKIp96+biqP4To= diff --git a/magefile.go b/magefile.go deleted file mode 100644 index 84616f2..0000000 --- a/magefile.go +++ /dev/null @@ -1,35 +0,0 @@ -// +build mage - -package main - -import ( - "github.com/magefile/mage/sh" - "log" -) - -// Default target to run when none is specified -// If not set, running mage will list available targets -// var Default = Build - -// Publish builds and publishes the docker image docker.n5o.black/public/bosun:latest. -func Publish() error { - version, err := sh.Output("bosun", "app", "version") - check(err) - - check(sh.RunV("docker", "build", "-t", "docker.n5o.black/public/bosun:latest", ".")) - - check(sh.RunV("docker", "tag", "docker.n5o.black/public/bosun:latest", "docker.n5o.black/public/bosun:"+version)) - - check(sh.RunV("docker", "push", "docker.n5o.black/public/bosun:" + version)) - - check(sh.RunV("docker", "push", "docker.n5o.black/public/bosun:latest")) - - return nil -} - -func check(err error) { - if err != nil { - log.Fatal(err) - } -} - diff --git a/pkg/bosun/app.go b/pkg/bosun/app.go new file mode 100644 index 0000000..f8e71bd --- /dev/null +++ b/pkg/bosun/app.go @@ -0,0 +1,741 @@ +package bosun + +import ( + "fmt" + "github.com/fatih/color" + "github.com/naveego/bosun/pkg" + "github.com/naveego/bosun/pkg/filter" + "github.com/naveego/bosun/pkg/git" + "github.com/naveego/bosun/pkg/helm" + "github.com/naveego/bosun/pkg/util" + "github.com/pkg/errors" + "github.com/stevenle/topsort" + "gopkg.in/yaml.v2" + "io/ioutil" + "os" + "os/exec" + "path/filepath" + "strings" + "time" +) + +type App struct { + *AppConfig + Repo *Repo // a pointer to the repo if bosun is aware of it + HelmRelease *HelmRelease + branch string + commit string + gitTag string + isCloned bool + labels filter.Labels +} + +func (a *App) GetLabels() filter.Labels { + if a.labels == nil { + a.labels = filter.LabelsFromMap(map[string]string{ + LabelName: a.Name, + LabelPath: a.FromPath, + LabelVersion: a.Version.String(), + }) + + a.labels[LabelBranch] = filter.LabelFunc(func() string { return a.GetBranchName().String() }) + a.labels[LabelCommit] = filter.LabelFunc(a.GetCommit) + + if a.HasChart() { + a.labels[LabelDeployable] = filter.LabelString("true") + } + + for k, v := range a.Labels { + a.labels[k] = v + } + } + return a.labels +} + +type AppsSortedByName []*App +type DependenciesSortedByTopology []string + +func NewApp(appConfig *AppConfig) *App { + return &App{ + AppConfig: appConfig, + isCloned: true, + } +} + +func NewAppFromDependency(dep *Dependency) *App { + return &App{ + AppConfig: &AppConfig{ + FromPath: dep.FromPath, + Name: dep.Name, + Version: dep.Version, + RepoName: dep.Repo, + IsRef: true, + }, + isCloned: false, + } +} + +func (a AppsSortedByName) Len() int { + return len(a) +} + +func (a AppsSortedByName) Less(i, j int) bool { + return strings.Compare(a[i].Name, a[j].Name) < 0 +} + +func (a AppsSortedByName) Swap(i, j int) { + a[i], a[j] = a[j], a[i] +} + +func (a *App) CheckRepoCloned() error { + if !a.IsRepoCloned() { + return ErrNotCloned + } + return nil +} + +func (a *App) CheckOutBranch(name string) error { + if !a.IsRepoCloned() { + return ErrNotCloned + } + local := a.Repo.LocalRepo + if local.GetCurrentBranch() == name { + return nil + } + if local.IsDirty() { + return errors.Errorf("current branch %q is dirty", local.GetCurrentBranch()) + } + + _, err := local.git().Exec("checkout", name) + return err +} + +func (a *App) GetLocalRepoPath() (string, error) { + if !a.IsRepoCloned() { + return "", errors.New("repo is not cloned") + } + return git.GetRepoPath(a.FromPath) +} + +func (a *App) IsRepoCloned() bool { + if a.Repo == nil { + return false + } + return a.Repo.CheckCloned() == nil +} + +func (a *App) GetRepoPath() string { + if a.Repo == nil || a.Repo.LocalRepo == nil { + return "" + } + + return a.Repo.LocalRepo.Path +} + +func (a *App) GetBranchName() git.BranchName { + if a.IsRepoCloned() { + return a.Repo.GetLocalBranchName() + } + return "" +} + +func (a *App) GetCommit() string { + if a.IsRepoCloned() && a.commit == "" { + g, _ := git.NewGitWrapper(a.FromPath) + a.commit = strings.Trim(g.Commit(), "'") + } + return a.commit +} + +func (a *App) HasChart() bool { + return a.ChartPath != "" || a.Chart != "" +} + +func (a *App) Dir() string { + return filepath.Dir(a.FromPath) +} + +func (a *App) GetRunCommand() (*exec.Cmd, error) { + + if a.RunCommand == nil || len(a.RunCommand) == 0 { + return nil, errors.Errorf("no runCommand in %q", a.FromPath) + } + + c := exec.Command(a.RunCommand[0], a.RunCommand[1:]...) + c.Dir = a.Dir() + c.Stdout = os.Stdout + c.Stderr = os.Stderr + + return c, nil +} + +func (a *App) GetAbsolutePathToChart() string { + return resolvePath(a.FromPath, a.ChartPath) +} + +func (a *App) getAbsoluteChartPathOrChart(ctx BosunContext) string { + if a.IsFromManifest { + return a.Chart + } + + if a.ChartPath != "" { + return ctx.ResolvePath(a.ChartPath) + } + return a.Chart +} + +func (a *App) getChartName() string { + if a.Chart != "" { + return a.Chart + } + name := filepath.Base(a.ChartPath) + // TODO: Configure chart repo at WS or File level. + return fmt.Sprintf("helm.n5o.black/%s", name) +} + +func (a *App) PublishChart(ctx BosunContext, force bool) error { + if err := a.CheckRepoCloned(); err != nil { + return err + } + + branch := a.GetBranchName() + if !branch.IsMaster() && !branch.IsRelease() { + if ctx.GetParams().Force { + ctx.Log.WithField("branch", branch).Warn("You should only publish the chart from the master or release branches (overridden by --force).") + } else { + ctx.Log.WithField("branch", branch).Warn("You can only push charts from the master or release branches (override by setting the --force flag).") + return nil + } + } + + err := helm.PublishChart(a.getChartName(), a.GetAbsolutePathToChart(), force) + return err +} + +func (a *AppConfig) GetImages() []AppImageConfig { + images := a.Images + defaultProjectName := "private" + if a.HarborProject != "" { + defaultProjectName = a.HarborProject + } + if len(images) == 0 { + images = []AppImageConfig{{ImageName: a.Name}} + } + + var formattedImages []AppImageConfig + for _, i := range images { + if i.ProjectName == "" { + i.ProjectName = defaultProjectName + } + + formattedImages = append(formattedImages, i) + } + + return formattedImages +} + +// GetPrefixedImageNames returns the untagged names of the images for this repo. +func (a *App) GetPrefixedImageNames() []string { + var prefixedNames []string + for _, image := range a.GetImages() { + prefixedNames = append(prefixedNames, image.GetFullName()) + } + return prefixedNames +} + +// GetImageName returns the image name with the tags appended. If no arguments are provided, +// it will be tagged "latest"; if one arg is provided it will be used as the tag; +// if 2 args are provided it will be tagged "arg[0]-arg[1]". +func (a *App) GetTaggedImageNames(versionAndRelease ...string) []string { + var taggedNames []string + names := a.GetPrefixedImageNames() + for _, name := range names { + taggedName := name + switch len(versionAndRelease) { + case 0: + taggedName = fmt.Sprintf("%s:latest", taggedName) + case 1: + taggedName = fmt.Sprintf("%s:%s", taggedName, versionAndRelease[0]) + case 2: + taggedName = fmt.Sprintf("%s:%s-%s", taggedName, versionAndRelease[0], versionAndRelease[1]) + } + taggedNames = append(taggedNames, taggedName) + } + + return taggedNames +} + +func (a *App) BuildImages(ctx BosunContext) error { + + var report []string + for _, image := range a.GetImages() { + if image.ImageName == "" { + return errors.New("imageName not set in image config (did you accidentally set `name` instead?)") + } + dockerfilePath := image.Dockerfile + if dockerfilePath == "" { + dockerfilePath = ctx.ResolvePath("Dockerfile") + } else { + dockerfilePath = ctx.ResolvePath(dockerfilePath) + } + contextPath := image.ContextPath + if contextPath == "" { + contextPath = filepath.Dir(dockerfilePath) + } else { + contextPath = ctx.ResolvePath(contextPath) + } + + args := []string{ + "build", + "-f", dockerfilePath, + "--build-arg", fmt.Sprintf("VERSION_NUMBER=%s", a.Version), + "--build-arg", fmt.Sprintf("COMMIT=%s", a.GetCommit()), + "--build-arg", fmt.Sprintf("BUILD_NUMBER=%s", os.Getenv("BUILD_NUMBER")), + "--tag", image.GetFullName(), + contextPath, + } + + ctx.Log.Infof("Building image %q from %q with context %q", image.ImageName, dockerfilePath, contextPath) + _, err := pkg.NewCommand("docker", args...).RunOutLog() + if err != nil { + return errors.Wrapf(err, "build image %q from %q with context %q", image.ImageName, dockerfilePath, contextPath) + } + + report = append(report, fmt.Sprintf("Built image from %q with context %q: %s", dockerfilePath, contextPath, image.GetFullName())) + } + + fmt.Println() + for _, line := range report { + color.Green("%s\n", line) + } + + return nil +} + +func (a *App) PublishImages(ctx BosunContext) error { + + var report []string + + tags := []string{"latest", a.Version.String()} + + branch := a.GetBranchName() + if branch != "master" && !branch.IsRelease() { + if ctx.GetParams().Force { + ctx.Log.WithField("branch", branch).Warn("You should only push images from the master or release branches (overridden by --force).") + } else { + ctx.Log.WithField("branch", branch).Warn("You can only push images from the master or release branches (override by setting the --force flag).") + return nil + } + } + + release, err := branch.Release() + if err == nil { + tags = append(tags, fmt.Sprintf("%s-%s", a.Version, release)) + } + + for _, tag := range tags { + for _, taggedName := range a.GetTaggedImageNames(tag) { + ctx.Log.Infof("Tagging and pushing %q", taggedName) + untaggedName := strings.Split(taggedName, ":")[0] + _, err := pkg.NewCommand("docker", "tag", untaggedName, taggedName).RunOutLog() + if err != nil { + return err + } + _, err = pkg.NewCommand("docker", "push", taggedName).RunOutLog() + if err != nil { + return err + } + report = append(report, fmt.Sprintf("Tagged and pushed %s", taggedName)) + } + } + + fmt.Println() + for _, line := range report { + color.Green("%s\n", line) + } + + return nil +} + +func GetDependenciesInTopologicalOrder(apps map[string][]string, roots ...string) (DependenciesSortedByTopology, error) { + + const target = "__TARGET__" + + graph := topsort.NewGraph() + + graph.AddNode(target) + + for _, root := range roots { + graph.AddNode(root) + graph.AddEdge(target, root) + } + + // add our root node to the graph + + for name, deps := range apps { + graph.AddNode(name) + for _, dep := range deps { + // make sure dep is in the graph + graph.AddNode(dep) + graph.AddEdge(name, dep) + } + } + + sortedNames, err := graph.TopSort(target) + if err != nil { + return nil, err + } + + var result DependenciesSortedByTopology + for _, name := range sortedNames { + if name == target { + continue + } + + result = append(result, name) + } + + return result, nil +} + +// +// func (a *App) GetAppReleaseConfig(ctx BosunContext) (*AppReleaseConfig, error) { +// var err error +// ctx = ctx.WithApp(a) +// +// isTransient := ctx.Deploy == nil || ctx.Deploy.Transient +// +// r := &AppReleaseConfig{ +// Name: a.Name, +// Namespace: a.Namespace, +// Version: a.Version, +// ReportDeployment: a.ReportDeployment, +// SyncedAt: time.Now(), +// } +// +// ctx.Log.Debug("Getting app release config.") +// +// if !isTransient && a.BranchForRelease { +// +// g, err := git.NewGitWrapper(a.FromPath) +// if err != nil { +// return nil, err +// } +// +// branchName := fmt.Sprintf("release/%s", ctx.Deploy.Name) +// +// branches, err := g.Exec("branch", "-a") +// if err != nil { +// return nil, err +// } +// if strings.Contains(branches, branchName) { +// ctx.Log.Info("Checking out release branch...") +// _, err := g.Exec("checkout", branchName) +// if err != nil { +// return nil, err +// } +// _, err = g.Exec("pull") +// if err != nil { +// return nil, err +// } +// } else { +// +// if ctx.Deploy.IsPatch { +// return nil, errors.New("patch release not implemented yet, you must create the release branch manually") +// } +// +// ctx.Log.Info("Creating release branch...") +// _, err = g.Exec("checkout", "master") +// if err != nil { +// return nil, errors.Wrap(err, "checkout master") +// } +// _, err = g.Exec("pull") +// if err != nil { +// return nil, errors.Wrap(err, "pull master") +// } +// +// _, err = g.Exec("branch", branchName, "origin/master") +// if err != nil { +// return nil, err +// } +// _, err = g.Exec("checkout", branchName) +// if err != nil { +// return nil, err +// } +// _, err = g.Exec("push", "-u", "origin", branchName) +// if err != nil { +// return nil, err +// } +// } +// +// r.Branch = a.GetBranchName() +// r.Repo = a.GetRepoPath() +// r.Commit = a.GetCommit() +// +// } +// +// if isTransient { +// r.Chart = ctx.ResolvePath(a.ChartPath) +// } else { +// r.Chart = a.getChartName() +// } +// +// if a.BranchForRelease { +// r.ImageNames = a.GetPrefixedImageNames() +// if isTransient || ctx.Deploy == nil { +// r.ImageTag = "latest" +// } else { +// r.ImageTag = fmt.Sprintf("%s-%s", r.Version, ctx.Deploy.Name) +// } +// } +// +// r.Values, err = a.ExportValues(ctx) +// if err != nil { +// return nil, errors.Errorf("export values for release: %s", err) +// } +// +// r.Actions, err = a.ExportActions(ctx) +// if err != nil { +// return nil, errors.Errorf("export actions for release: %s", err) +// } +// +// for _, dep := range a.DependsOn { +// r.DependsOn = append(r.DependsOn, dep.Name) +// } +// +// return r, nil +// } + +// BumpVersion bumps the version (including saving the source fragment +// and updating the chart. The `bump` parameter may be one of +// major|minor|patch|major.minor.patch. If major.minor.patch is provided, +// the version is set to that value. +func (a *App) BumpVersion(ctx BosunContext, bump string) error { + version, err := NewVersion(bump) + if err == nil { + a.Version = version + } else { + switch bump { + case "major": + a.Version = a.Version.BumpMajor() + case "minor": + a.Version = a.Version.BumpMinor() + case "patch": + a.Version = a.Version.BumpPatch() + default: + return errors.Errorf("invalid version component %q (want major, minor, or patch)", bump) + } + } + + packageJSONPath := filepath.Join(filepath.Dir(a.FromPath), "package.json") + if _, err = os.Stat(packageJSONPath); err == nil { + ctx.Log.Info("package.json detected, its version will be updated.") + err = pkg.NewCommand("npm", "--no-git-tag-version", "--allow-same-version", "version", bump). + WithDir(filepath.Dir(a.FromPath)). + RunE() + if err != nil { + return errors.Errorf("failed to update version in package.json: %s", err) + } + } + + if a.HasChart() { + m, err := a.getChartAsMap() + if err != nil { + return err + } + + m["version"] = a.Version + if a.BranchForRelease { + m["appVersion"] = a.Version + } + err = a.saveChart(m) + if err != nil { + return err + } + } + + if a.GoVersionFile != "" { + + } + + return a.Parent.Save() +} + +func (a *App) getChartAsMap() (map[string]interface{}, error) { + err := a.CheckRepoCloned() + if err != nil { + return nil, err + } + + if a.ChartPath == "" { + return nil, errors.New("chartPath not set") + } + + path := filepath.Join(a.GetAbsolutePathToChart(), "Chart.yaml") + b, err := ioutil.ReadFile(path) + if err != nil { + return nil, err + } + + var out map[string]interface{} + err = yaml.Unmarshal(b, &out) + return out, err +} + +func (a *App) saveChart(m map[string]interface{}) error { + b, err := yaml.Marshal(m) + if err != nil { + return err + } + path := filepath.Join(a.GetAbsolutePathToChart(), "Chart.yaml") + stat, err := os.Stat(path) + if err != nil { + return err + } + err = ioutil.WriteFile(path, b, stat.Mode()) + return err +} + +func omitStrings(from []string, toOmit ...string) []string { + var out []string + for _, s := range from { + matched := false + for _, o := range toOmit { + if o == s { + matched = true + continue + } + } + if !matched { + out = append(out, s) + } + } + return out +} + +// ExportValues creates an ValueSetMap instance with all the values +// for releasing this app, reified into their environments, including values from +// files and from the default values.yaml file for the chart. +func (a *App) ExportValues(ctx BosunContext) (ValueSetMap, error) { + ctx = ctx.WithApp(a) + var err error + envs := map[string]*EnvironmentConfig{} + for envNames := range a.Values { + for _, envName := range strings.Split(envNames, ",") { + if _, ok := envs[envName]; !ok { + env, err := ctx.Bosun.GetEnvironment(envName) + if err != nil { + ctx.Log.Warnf("App values include key for environment %q, but no such environment has been defined.", envName) + continue + } + envs[envName] = env + } + } + } + var defaultValues Values + + if a.HasChart() { + chartRef := a.getAbsoluteChartPathOrChart(ctx) + valuesYaml, err := pkg.NewCommand( + "helm", "inspect", "values", + chartRef, + "--version", a.Version.String(), + ).RunOut() + if err != nil { + return nil, errors.Errorf("load default values from %q: %s", chartRef, err) + } + defaultValues, err = ReadValues([]byte(valuesYaml)) + if err != nil { + return nil, errors.Errorf("parse default values from %q: %s", chartRef, err) + } + } else { + defaultValues = Values{} + } + + valueCopy := a.Values.CanonicalizedCopy() + + for name, values := range valueCopy { + + values, err = values.WithFilesLoaded(ctx) + if err != nil { + return nil, errors.Wrapf(err, "loading files for value set %q", name) + } + // make sure values from bosun app overwrite defaults from helm chart + static := defaultValues.Clone() + static.Merge(values.Static) + values.Static = static + values.Files = nil + valueCopy[name] = values + } + + return valueCopy, nil +} + +func (a *App) ExportActions(ctx BosunContext) ([]*AppAction, error) { + var err error + var actions []*AppAction + for _, action := range a.Actions { + if action.When == ActionManual { + ctx.Log.Debugf("Skipping export of action %q because it is marked as manual.", action.Name) + } else { + err = action.MakeSelfContained(ctx) + if err != nil { + return nil, errors.Errorf("prepare action %q for release: %s", action.Name, err) + } + actions = append(actions, action) + } + } + + return actions, nil +} + +func (a *App) GetManifest(ctx BosunContext) (*AppManifest, error) { + + if a.manifest != nil { + // App already has a manifest, probably because it was created + // from an AppConfig that was obtained from an AppManifest. + return a.manifest, nil + } + + var appManifest *AppManifest + + err := util.TryCatch(a.Name, func() error { + + appConfig := a.AppConfig + var err error + + appConfig.Values, err = a.ExportValues(ctx) + if err != nil { + return errors.Errorf("export values for manifest: %s", err) + } + + appConfig.Actions, err = a.ExportActions(ctx) + if err != nil { + return errors.Errorf("export actions for manifest: %s", err) + } + + hashes := AppHashes{} + + if a.Repo.CheckCloned() == nil { + hashes.Commit = a.Repo.LocalRepo.GetCurrentCommit() + } + + hashes.AppConfig, err = util.HashToStringViaYaml(appConfig) + + appManifest = &AppManifest{ + AppConfig: appConfig, + AppMetadata: &AppMetadata{ + Name: appConfig.Name, + Repo: appConfig.RepoName, + Timestamp: time.Now(), + Version: a.Version, + Branch: a.GetBranchName().String(), + Hashes: hashes, + }, + } + + return nil + }) + + return appManifest, err +} diff --git a/pkg/bosun/app_actions.go b/pkg/bosun/app_actions.go index 5df9176..3598c37 100644 --- a/pkg/bosun/app_actions.go +++ b/pkg/bosun/app_actions.go @@ -187,9 +187,10 @@ func (a *AppAction) GetActions() []Action { } type VaultAction struct { - File string `yaml:"file,omitempty" json:"file,omitempty"` - Layout *pkg.VaultLayout `yaml:"layout,omitempty" json:"layout,omitempty"` - Literal string `yaml:"literal,omitempty" json:"literal,omitempty"` + CacheKey string `yaml:"cacheKey" json:"cacheKey"` + File string `yaml:"file,omitempty" json:"file,omitempty"` + Layout *pkg.VaultLayout `yaml:"layout,omitempty" json:"layout,omitempty"` + Literal string `yaml:"literal,omitempty" json:"literal,omitempty"` } func (a *VaultAction) Execute(ctx BosunContext) error { @@ -226,7 +227,7 @@ func (a *VaultAction) Execute(ctx BosunContext) error { return nil } - err = vaultLayout.Apply(vaultClient) + err = vaultLayout.Apply(a.CacheKey, ctx.GetParams().Force, vaultClient) if err != nil { return err } diff --git a/pkg/bosun/app_config.go b/pkg/bosun/app_config.go new file mode 100644 index 0000000..e3c559e --- /dev/null +++ b/pkg/bosun/app_config.go @@ -0,0 +1,215 @@ +package bosun + +import ( + "fmt" + "github.com/naveego/bosun/pkg/filter" + "github.com/naveego/bosun/pkg/semver" + "github.com/naveego/bosun/pkg/zenhub" + "github.com/pkg/errors" + "gopkg.in/yaml.v2" + "path/filepath" + "strings" +) + +type AppConfig struct { + Name string `yaml:"name" json:"name" json:"name" json:"name"` + FromPath string `yaml:"-" json:"-"` + ProjectManagementPlugin *ProjectManagementPlugin `yaml:"projectManagementPlugin,omitempty" json:"projectManagementPlugin,omitempty"` + BranchForRelease bool `yaml:"branchForRelease,omitempty" json:"branchForRelease,omitempty"` + // ContractsOnly means that the app doesn't have any compiled/deployed code, it just defines contracts or documentation. + ContractsOnly bool `yaml:"contractsOnly,omitempty" json:"contractsOnly,omitempty"` + ReportDeployment bool `yaml:"reportDeployment,omitempty" json:"reportDeployment,omitempty"` + Namespace string `yaml:"namespace,omitempty" json:"namespace,omitempty"` + RepoName string `yaml:"repo,omitempty" json:"repo,omitempty"` + HarborProject string `yaml:"harborProject,omitempty" json:"harborProject,omitempty"` + Version semver.Version `yaml:"version,omitempty" json:"version,omitempty"` + // The location of a standard go version file for this app. + GoVersionFile string `yaml:"goVersionFile,omitempty" json:"goVersionFile,omitempty"` + Chart string `yaml:"chart,omitempty" json:"chart,omitempty"` + ChartPath string `yaml:"chartPath,omitempty" json:"chartPath,omitempty"` + RunCommand []string `yaml:"runCommand,omitempty,flow" json:"runCommand,omitempty,flow"` + DependsOn []Dependency `yaml:"dependsOn,omitempty" json:"dependsOn,omitempty"` + Labels filter.Labels `yaml:"labels,omitempty" json:"labels,omitempty"` + Minikube *AppMinikubeConfig `yaml:"minikube,omitempty" json:"minikube,omitempty"` + Images []AppImageConfig `yaml:"images" json:"images"` + Values ValueSetMap `yaml:"values,omitempty" json:"values,omitempty"` + Scripts []*Script `yaml:"scripts,omitempty" json:"scripts,omitempty"` + Actions []*AppAction `yaml:"actions,omitempty" json:"actions,omitempty"` + Parent *File `yaml:"-" json:"-"` + // If true, this app repo is only a ref, not a real cloned repo. + IsRef bool `yaml:"-" json:"-"` + IsFromManifest bool `yaml:"-"` // Will be true if this config was embedded in an AppManifest. + manifest *AppManifest `yaml:"-" json:"-"` // Will contain a pointer to the container if this AppConfig is contained in an AppManifest +} + +func (a *AppConfig) MarshalYAML() (interface{}, error) { + if a == nil { + return nil, nil + } + type proxy AppConfig + p := proxy(*a) + + return &p, nil +} + +func (a *AppConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { + type proxy AppConfig + var p proxy + if a != nil { + p = proxy(*a) + } + + err := unmarshal(&p) + + if err == nil { + *a = AppConfig(p) + } + + if a.Chart == "" && a.ChartPath != "" { + a.Chart = filepath.Base(a.ChartPath) + } + + return err +} + +func (a *AppConfig) ErrIfFromManifest(msg string, args ...interface{}) error { + if a.IsFromManifest { + return errors.Errorf("app %q: %s", a.Name, fmt.Sprintf(msg, args...)) + } + return nil +} + +type ProjectManagementPlugin struct { + Name string `yaml:"name" json:"name"` + ZenHub *zenhub.RepoConfig `yaml:"zenHub,omitempty" json:"zenHub"` +} + +type AppMinikubeConfig struct { + // The ports which should be made exposed through nodePorts + // when running on minikube. + Ports []int `yaml:"ports,omitempty" json:"ports,omitempty"` + // The services which should be replaced when toggling an + // app to run on the host. + RoutableServices []AppRoutableService `yaml:"routableServices" json:"routableServices"` +} + +type AppRoutableService struct { + Name string `yaml:"name" json:"name,omitempty"` + PortName string `yaml:"portName" json:"portName,omitempty"` + // Deprecated, use localhostPort instead + ExternalPort int `yaml:"externalPort" json:"externalPort,omitempty"` + LocalhostPort int `yaml:"localhostPort" json:"localhostPort,omitempty"` +} + +type Dependency struct { + Name string `yaml:"name" json:"name,omitempty"` + FromPath string `yaml:"-" json:"fromPath,omitempty"` + Repo string `yaml:"repo,omitempty" json:"repo,omitempty"` + App *App `yaml:"-" json:"-"` + Version semver.Version `yaml:"version,omitempty" json:"version,omitempty"` +} + +type Dependencies []Dependency + +func (d Dependencies) Len() int { return len(d) } +func (d Dependencies) Less(i, j int) bool { return strings.Compare(d[i].Name, d[j].Name) < 0 } +func (d Dependencies) Swap(i, j int) { d[i], d[j] = d[j], d[i] } + +type appValuesConfigV1 struct { + Set map[string]*CommandValue `yaml:"set,omitempty" json:"set,omitempty"` + Dynamic map[string]*CommandValue `yaml:"dynamic,omitempty" json:"dynamic,omitempty"` + Files []string `yaml:"files,omitempty" json:"files,omitempty"` + Static Values `yaml:"static,omitempty" json:"static,omitempty"` +} + +func (a *AppConfig) SetParent(fragment *File) { + a.FromPath = fragment.FromPath + a.Parent = fragment + for i := range a.Scripts { + a.Scripts[i].FromPath = a.FromPath + } + for i := range a.DependsOn { + a.DependsOn[i].FromPath = a.FromPath + } + for i := range a.Actions { + a.Actions[i].FromPath = a.FromPath + } +} + +// GetNamespace returns the app's namespace, or "default" if it isn't set +func (a *AppConfig) GetNamespace() string { + if a.Namespace != "" { + return a.Namespace + } + return "default" +} + +// Combine returns a new ValueSet with the values from +// other added after (and/or overwriting) the values from this instance) +func (a ValueSet) Combine(other ValueSet) ValueSet { + + // clone the valueSet to ensure we don't mutate `a` + y, _ := yaml.Marshal(a) + var out ValueSet + _ = yaml.Unmarshal(y, &out) + + // clone the other valueSet to ensure we don't capture items from it + y, _ = yaml.Marshal(other) + _ = yaml.Unmarshal(y, &other) + + if out.Dynamic == nil { + out.Dynamic = map[string]*CommandValue{} + } + if out.Static == nil { + out.Static = Values{} + } + + out.Files = append(out.Files, other.Files...) + + out.Static.Merge(other.Static) + + for k, v := range other.Dynamic { + out.Dynamic[k] = v + } + + return out +} + +type AppStatesByEnvironment map[string]AppStateMap + +type AppStateMap map[string]AppState + +type AppState struct { + Branch string `yaml:"branch,omitempty" json:"branch,omitempty"` + Status string `yaml:"deployment,omitempty" json:"deployment,omitempty"` + Routing string `yaml:"routing,omitempty" json:"routing,omitempty"` + Version string `yaml:"version,omitempty" json:"version,omitempty"` + Diff string `yaml:"-" json:"-"` + Error error `yaml:"-" json:"-"` + Force bool `yaml:"-" json:"-"` + Unavailable bool `yaml:"-" json:"-"` +} + +func (a AppState) String() string { + hasDiff := a.Diff != "" + return fmt.Sprintf("status:%s routing:%s version:%s hasDiff:%t, force:%t", + a.Status, + a.Routing, + a.Version, + hasDiff, + a.Force) +} + +const ( + RoutingLocalhost = "localhost" + RoutingCluster = "cluster" + RoutingNA = "n/a" + StatusDeployed = "DEPLOYED" + StatusNotFound = "NOTFOUND" + StatusDeleted = "DELETED" + StatusFailed = "FAILED" + StatusPendingUpgrade = "PENDING_UPGRADE" + StatusUnchanged = "UNCHANGED" +) + +type Routing string diff --git a/pkg/bosun/app_release.go b/pkg/bosun/app_deploy.go similarity index 69% rename from pkg/bosun/app_release.go rename to pkg/bosun/app_deploy.go index 3e8c3a9..40e47dc 100644 --- a/pkg/bosun/app_release.go +++ b/pkg/bosun/app_deploy.go @@ -4,17 +4,20 @@ import ( "fmt" "github.com/fatih/color" "github.com/naveego/bosun/pkg" + "github.com/naveego/bosun/pkg/filter" "github.com/naveego/bosun/pkg/git" + "github.com/naveego/bosun/pkg/helm" "github.com/naveego/bosun/pkg/util" "github.com/pkg/errors" "gopkg.in/yaml.v2" "io/ioutil" "os" + "path/filepath" "strings" "time" ) -type AppReleasesSortedByName []*AppRelease +type AppReleasesSortedByName []*AppDeploy func (a AppReleasesSortedByName) Len() int { return len(a) @@ -28,78 +31,85 @@ func (a AppReleasesSortedByName) Swap(i, j int) { a[i], a[j] = a[j], a[i] } -type AppReleaseConfig struct { - Name string `yaml:"name" json:"name"` - Namespace string `yaml:"namespace" json:"namespace"` - Repo string `yaml:"repo" json:"repo"` - Branch string `yaml:"branch" json:"branch"` - Commit string `yaml:"commit" json:"commit"` - Version string `yaml:"version" json:"version"` - SyncedAt time.Time `yaml:"syncedAt" json:"syncedAt"` - Chart string `yaml:"chart" json:"chart"` - ImageNames []string `yaml:"images,omitempty" json:"images,omitempty"` - ImageTag string `yaml:"imageTag,omitempty" json:"imageTag,omitempty"` - ReportDeployment bool `yaml:"reportDeployment" json:"reportDeployment"` - DependsOn []string `yaml:"dependsOn" json:"dependsOn"` - Actions []*AppAction `yaml:"actions" json:"actions"` - // Values copied from app repo. - Values AppValuesByEnvironment `yaml:"values" json:"values"` - // Values manually added to this release. - ValueOverrides AppValuesByEnvironment `yaml:"valueOverrides" json:"valueOverrides"` - ParentConfig *ReleaseConfig `yaml:"-" json:"-"` -} +// type AppReleaseConfig struct { +// Name string `yaml:"name" json:"name"` +// Namespace string `yaml:"namespace" json:"namespace"` +// Repo string `yaml:"repo" json:"repo"` +// Branch git.BranchName `yaml:"branch" json:"branch"` +// Commit string `yaml:"commit" json:"commit"` +// Version semver.Version `yaml:"version" json:"version"` +// SyncedAt time.Time `yaml:"syncedAt" json:"syncedAt"` +// Chart string `yaml:"chart" json:"chart"` +// ImageNames []string `yaml:"images,omitempty" json:"images,omitempty"` +// ImageTag string `yaml:"imageTag,omitempty" json:"imageTag,omitempty"` +// ReportDeployment bool `yaml:"reportDeployment" json:"reportDeployment"` +// DependsOn []string `yaml:"dependsOn" json:"dependsOn"` +// Actions []*AppAction `yaml:"actions" json:"actions"` +// // Values copied from app repo. +// Values ValueSetMap `yaml:"values" json:"values"` +// // Values manually added to this release. +// ValueOverrides ValueSetMap `yaml:"valueOverrides" json:"valueOverrides"` +// ParentConfig *ReleaseConfig `yaml:"-" json:"-"` +// } + +type AppDeploy struct { + FromPath string + *AppManifest `yaml:"-" json:"-"` + // App *App `yaml:"-" json:"-"` + Excluded bool `yaml:"-" json:"-"` + ActualState AppState + DesiredState AppState + helmRelease *HelmRelease + labels filter.Labels + AppDeploySettings AppDeploySettings +} + +// Chart gets the path to the chart, or the full name of the chart. +func (a *AppDeploy) Chart(ctx BosunContext) string { + + var chartHandle helm.ChartHandle + + if a.AppConfig.IsFromManifest || a.AppConfig.ChartPath == "" { + chartHandle = helm.ChartHandle(a.AppConfig.Chart) + if !chartHandle.HasRepo() { + p, err := ctx.Bosun.GetCurrentPlatform() + if err == nil { + defaultChartRepo := p.DefaultChartRepo + chartHandle = chartHandle.WithRepo(defaultChartRepo) + } + } + return chartHandle.String() -func (r *AppReleaseConfig) SetParent(config *ReleaseConfig) { - r.ParentConfig = config -} + } -type AppRelease struct { - *AppReleaseConfig - AppRepo *AppRepo `yaml:"-" json:"-"` - Excluded bool `yaml:"-" json:"-"` - ActualState AppState - DesiredState AppState - helmRelease *HelmRelease - labels Labels -} + return filepath.Join(filepath.Dir(a.AppConfig.FromPath), a.AppConfig.ChartPath) -func (a *AppRelease) Labels() Labels { - if a.labels == nil { - a.labels = map[string]LabelValue{ - string(FilterKeyName): LabelString(a.Name), - string(FilterKeyPath): LabelString(a.AppRepo.FromPath), - string(FilterKeyBranch): LabelString(a.Branch), - string(FilterKeyCommit): LabelString(a.Commit), - string(FilterKeyVersion): LabelString(a.Version), - } - for k, v := range a.AppRepo.AppLabels { - a.labels[k] = v - } - } - return a.labels } -func NewAppRelease(ctx BosunContext, config *AppReleaseConfig) (*AppRelease, error) { - release := &AppRelease{ - AppReleaseConfig: config, - AppRepo: ctx.Bosun.GetApps()[config.Name], - DesiredState: ctx.Bosun.ws.AppStates[ctx.Env.Name][config.Name], +func NewAppDeploy(context BosunContext, settings DeploySettings, manifest *AppManifest) (*AppDeploy, error) { + + appDeploy := &AppDeploy{ + AppManifest: manifest, + AppDeploySettings: settings.GetAppDeploySettings(manifest.Name), } - return release, nil + return appDeploy, nil } -func NewAppReleaseFromRepo(ctx BosunContext, repo *AppRepo) (*AppRelease, error) { - cfg, err := repo.GetAppReleaseConfig(ctx) - if err != nil { - return nil, err +func (a *AppDeploy) GetLabels() filter.Labels { + if a.labels == nil { + a.labels = filter.LabelsFromMap(map[string]string{ + LabelName: a.Name, + LabelVersion: a.Version.String(), + LabelBranch: a.Branch, + LabelCommit: a.Hashes.Commit, + }) } - - return NewAppRelease(ctx, cfg) + return a.labels } -func (a *AppRelease) LoadActualState(ctx BosunContext, diff bool) error { - ctx = ctx.WithAppRelease(a) +func (a *AppDeploy) LoadActualState(ctx BosunContext, diff bool) error { + ctx = ctx.WithAppDeploy(a) a.ActualState = AppState{} @@ -133,9 +143,9 @@ func (a *AppRelease) LoadActualState(ctx BosunContext, diff bool) error { // check if the app has a service with an ExternalName; if it does, it must have been // creating using `app toggle` and is routed to localhost. - if ctx.Env.IsLocal && a.AppRepo.Minikube != nil { - for _, routableService := range a.AppRepo.Minikube.RoutableServices { - svcYaml, err := pkg.NewCommand("kubectl", "get", "svc", "--namespace", a.AppRepo.Namespace, routableService.Name, "-o", "yaml").RunOut() + if ctx.Env.IsLocal && a.AppConfig.Minikube != nil { + for _, routableService := range a.AppConfig.Minikube.RoutableServices { + svcYaml, err := pkg.NewCommand("kubectl", "get", "svc", "--namespace", a.AppConfig.Namespace, routableService.Name, "-o", "yaml").RunOut() if err != nil { log.WithError(err).Errorf("Error getting service config %q", routableService.Name) continue @@ -172,7 +182,7 @@ type HelmRelease struct { Namespace string `yaml:"Namespace" json:"Namespace"` } -func (a *AppRelease) GetHelmRelease(name string) (*HelmRelease, error) { +func (a *AppDeploy) GetHelmRelease(name string) (*HelmRelease, error) { if a.helmRelease == nil { releases, err := a.GetHelmList(fmt.Sprintf(`^%s$`, name)) @@ -190,7 +200,7 @@ func (a *AppRelease) GetHelmRelease(name string) (*HelmRelease, error) { return a.helmRelease, nil } -func (a *AppRelease) GetHelmList(filter ...string) ([]*HelmRelease, error) { +func (a *AppDeploy) GetHelmList(filter ...string) ([]*HelmRelease, error) { args := append([]string{"list", "--all", "--output", "yaml"}, filter...) data, err := pkg.NewCommand("helm", args...).RunOut() @@ -216,9 +226,9 @@ type PlanStep struct { Action func(ctx BosunContext) error } -func (a *AppRelease) PlanReconciliation(ctx BosunContext) (Plan, error) { +func (a *AppDeploy) PlanReconciliation(ctx BosunContext) (Plan, error) { - ctx = ctx.WithAppRelease(a) + ctx = ctx.WithAppDeploy(a) if !ctx.Bosun.IsClusterAvailable() { return nil, errors.New("cluster not available") @@ -272,8 +282,8 @@ func (a *AppRelease) PlanReconciliation(ctx BosunContext) (Plan, error) { } if desired.Status == StatusDeployed { - for i := range a.Actions { - action := a.Actions[i] + for i := range a.AppConfig.Actions { + action := a.AppConfig.Actions[i] if strings.Contains(string(action.When), ActionBeforeDeploy) { steps = append(steps, PlanStep{ Name: action.Name, @@ -311,8 +321,8 @@ func (a *AppRelease) PlanReconciliation(ctx BosunContext) (Plan, error) { } if desired.Status == StatusDeployed { - for i := range a.Actions { - action := a.Actions[i] + for i := range a.AppConfig.Actions { + action := a.AppConfig.Actions[i] if strings.Contains(string(action.When), ActionAfterDeploy) { steps = append(steps, PlanStep{ Name: action.Name, @@ -329,12 +339,12 @@ func (a *AppRelease) PlanReconciliation(ctx BosunContext) (Plan, error) { } -type ReleaseValues struct { +type PersistableValues struct { Values Values FilePath string } -func (r *ReleaseValues) PersistValues() (string, error) { +func (r *PersistableValues) PersistValues() (string, error) { if r.FilePath == "" { // b, err := r.Values.YAML() @@ -359,7 +369,7 @@ func (r *ReleaseValues) PersistValues() (string, error) { } -func (r *ReleaseValues) Cleanup() { +func (r *PersistableValues) Cleanup() { err := os.Remove(r.FilePath) if err != nil && !os.IsNotExist(err) { pkg.Log.WithError(err).WithField("path", r.FilePath). @@ -367,8 +377,8 @@ func (r *ReleaseValues) Cleanup() { } } -func (a *AppRelease) GetReleaseValues(ctx BosunContext) (*ReleaseValues, error) { - r := &ReleaseValues{ +func (a *AppDeploy) GetResolvedValues(ctx BosunContext) (*PersistableValues, error) { + r := &PersistableValues{ Values: Values{}, } @@ -379,23 +389,19 @@ func (a *AppRelease) GetReleaseValues(ctx BosunContext) (*ReleaseValues, error) if err := r.Values.AddEnvAsPath(EnvPrefix, EnvAppBranch, a.Branch); err != nil { return nil, err } - if err := r.Values.AddEnvAsPath(EnvPrefix, EnvAppCommit, a.Commit); err != nil { + if err := r.Values.AddEnvAsPath(EnvPrefix, EnvAppCommit, a.Hashes.Commit); err != nil { return nil, err } - importedValues := a.Values[ctx.Env.Name] - overrideValues := a.ValueOverrides[ctx.Env.Name] + importedValues := a.AppConfig.Values.ExtractValueSetByNames(ctx.Env.ValueSets...) - appValues := []AppValuesConfig{importedValues, overrideValues} - if ctx.Env.AppValues != nil { - appValues = append(appValues, *ctx.Env.AppValues) - } + appValues := append([]ValueSet{importedValues}, a.AppDeploySettings.ValueSets...) for _, v := range appValues { r.Values.Merge(v.Static) - // Get the values defined using the `dynamic` element in the app's config: + // Get the values defined using the `dynamic` element: for k, v := range v.Dynamic { value, err := v.Resolve(ctx) if err != nil { @@ -408,8 +414,6 @@ func (a *AppRelease) GetReleaseValues(ctx BosunContext) (*ReleaseValues, error) } } - r.Values["tag"] = a.ImageTag - // Finally, apply any overrides from parameters passed to this invocation of bosun. for k, v := range ctx.GetParams().ValueOverrides { err := r.Values.SetAtPath(k, v) @@ -422,8 +426,8 @@ func (a *AppRelease) GetReleaseValues(ctx BosunContext) (*ReleaseValues, error) return r, nil } -func (a *AppRelease) Reconcile(ctx BosunContext) error { - ctx = ctx.WithAppRelease(a) +func (a *AppDeploy) Reconcile(ctx BosunContext) error { + ctx = ctx.WithAppDeploy(a) log := ctx.Log if a.DesiredState.Status == StatusUnchanged { @@ -431,7 +435,7 @@ func (a *AppRelease) Reconcile(ctx BosunContext) error { return nil } - values, err := a.GetReleaseValues(ctx) + values, err := a.GetResolvedValues(ctx) if err != nil { return errors.Errorf("create values map for app %q: %s", a.Name, err) } @@ -445,7 +449,7 @@ func (a *AppRelease) Reconcile(ctx BosunContext) error { } defer values.Cleanup() - ctx = ctx.WithReleaseValues(values) + ctx = ctx.WithPersistableValues(values) // clear helm release cache after work is done defer func() { a.helmRelease = nil }() @@ -460,10 +464,10 @@ func (a *AppRelease) Reconcile(ctx BosunContext) error { reportDeploy := !params.DryRun && !params.NoReport && - !ctx.Release.IsTransient() && + !a.AppDeploySettings.Environment.IsLocal && a.DesiredState.Status == StatusDeployed && !env.IsLocal && - a.ReportDeployment + a.AppConfig.ReportDeployment log.Info("Planning reconciliation...") @@ -520,11 +524,11 @@ func (a *AppRelease) Reconcile(ctx BosunContext) error { return nil } -func (a *AppRelease) diff(ctx BosunContext) (string, error) { +func (a *AppDeploy) diff(ctx BosunContext) (string, error) { args := omitStrings(a.makeHelmArgs(ctx), "--dry-run", "--debug") - msg, err := pkg.NewCommand("helm", "diff", "upgrade", a.Name, a.Chart, "--version", a.Version). + msg, err := pkg.NewCommand("helm", "diff", "upgrade", a.Name, a.Chart(ctx)). WithArgs(args...). RunOut() @@ -541,7 +545,7 @@ func (a *AppRelease) diff(ctx BosunContext) (string, error) { return msg, nil } -func (a *AppRelease) Delete(ctx BosunContext) error { +func (a *AppDeploy) Delete(ctx BosunContext) error { args := []string{"delete"} if a.DesiredState.Status == StatusNotFound { args = append(args, "--purge") @@ -553,10 +557,10 @@ func (a *AppRelease) Delete(ctx BosunContext) error { return err } -func (a *AppRelease) Rollback(ctx BosunContext) error { +func (a *AppDeploy) Rollback(ctx BosunContext) error { args := []string{"rollback"} args = append(args, a.Name, a.helmRelease.Revision) - args = append(args, a.getHelmNamespaceArgs(ctx)...) + // args = append(args, a.getHelmNamespaceArgs(ctx)...) args = append(args, a.getHelmDryRunArgs(ctx)...) out, err := pkg.NewCommand("helm", args...).RunOut() @@ -564,15 +568,15 @@ func (a *AppRelease) Rollback(ctx BosunContext) error { return err } -func (a *AppRelease) Install(ctx BosunContext) error { - args := append([]string{"install", "--name", a.Name, a.Chart}, a.makeHelmArgs(ctx)...) +func (a *AppDeploy) Install(ctx BosunContext) error { + args := append([]string{"install", "--name", a.Name, a.Chart(ctx)}, a.makeHelmArgs(ctx)...) out, err := pkg.NewCommand("helm", args...).RunOut() ctx.Log.Debug(out) return err } -func (a *AppRelease) Upgrade(ctx BosunContext) error { - args := append([]string{"upgrade", a.Name, a.Chart}, a.makeHelmArgs(ctx)...) +func (a *AppDeploy) Upgrade(ctx BosunContext) error { + args := append([]string{"upgrade", a.Name, a.Chart(ctx)}, a.makeHelmArgs(ctx)...) if a.DesiredState.Force { args = append(args, "--force") } @@ -581,7 +585,7 @@ func (a *AppRelease) Upgrade(ctx BosunContext) error { return err } -func (a *AppRelease) GetStatus() (string, error) { +func (a *AppDeploy) GetStatus() (string, error) { release, err := a.GetHelmRelease(a.Name) if err != nil { return "", err @@ -593,13 +597,13 @@ func (a *AppRelease) GetStatus() (string, error) { return release.Status, nil } -func (a *AppRelease) RouteToLocalhost(ctx BosunContext) error { +func (a *AppDeploy) RouteToLocalhost(ctx BosunContext) error { - ctx = ctx.WithAppRelease(a) + ctx = ctx.WithAppDeploy(a) ctx.Log.Info("Configuring app to route traffic to localhost.") - if a.AppRepo.Minikube == nil || len(a.AppRepo.Minikube.RoutableServices) == 0 { + if a.AppConfig.Minikube == nil || len(a.AppConfig.Minikube.RoutableServices) == 0 { return errors.New(`to route to localhost, app must have a minikube entry like this: minikube: routableServices: @@ -614,7 +618,7 @@ func (a *AppRelease) RouteToLocalhost(ctx BosunContext) error { return errors.New("minikube.hostIP is not set in root config file; it should be the IP of your machine reachable from the minikube VM") } - for _, routableService := range a.AppRepo.Minikube.RoutableServices { + for _, routableService := range a.AppConfig.Minikube.RoutableServices { log := ctx.Log.WithField("routable_service", routableService.Name) log.Info("Updating service...") @@ -634,10 +638,10 @@ func (a *AppRelease) RouteToLocalhost(ctx BosunContext) error { localhostPort = routableService.ExternalPort } - routedSvc.SetAtPath("spec.clusterIP", "") - routedSvc.SetAtPath("spec.type", "ExternalName") - routedSvc.SetAtPath("spec.externalName", hostIP) - routedSvc.SetAtPath("spec.ports", []Values{ + _ = routedSvc.SetAtPath("spec.clusterIP", "") + _ = routedSvc.SetAtPath("spec.type", "ExternalName") + _ = routedSvc.SetAtPath("spec.externalName", hostIP) + _ = routedSvc.SetAtPath("spec.ports", []Values{ { "port": localhostPort, "protocol": "TCP", @@ -647,7 +651,7 @@ func (a *AppRelease) RouteToLocalhost(ctx BosunContext) error { routedSvcYaml, _ := routedSvc.YAML() - { + err = func() error { tmp, err := util.NewTempFile("routed-service", []byte(routedSvcYaml)) if err != nil { return errors.Wrap(err, "create service file") @@ -662,6 +666,10 @@ func (a *AppRelease) RouteToLocalhost(ctx BosunContext) error { if err != nil { return errors.Errorf("error applying service\n%s\n---\n%s", routedSvcYaml, err) } + return nil + }() + if err != nil { + return errors.Wrapf(err, "updating service for %q", a.Name) } log.Info("Updated service.") @@ -670,51 +678,46 @@ func (a *AppRelease) RouteToLocalhost(ctx BosunContext) error { return nil } -func (a *AppRelease) makeHelmArgs(ctx BosunContext) []string { +func (a *AppDeploy) makeHelmArgs(ctx BosunContext) []string { var args []string + if !a.AppDeploySettings.UseLocalContent { + args = append(args, "--version", a.Version.String()) + } + args = append(args, "--set", fmt.Sprintf("domain=%s", ctx.Env.Domain)) args = append(args, a.getHelmNamespaceArgs(ctx)...) - args = append(args, "-f", ctx.ReleaseValues.FilePath) - - if ctx.Env.IsLocal { - args = append(args, "--set", "imagePullPolicy=IfNotPresent") - if a.DesiredState.Routing == RoutingLocalhost { - args = append(args, "--set", fmt.Sprintf("routeToHost=true")) - } else { - args = append(args, "--set", fmt.Sprintf("routeToHost=false")) - } - } else { - args = append(args, "--set", "routeToHost=false") - } + args = append(args, "-f", ctx.Values.FilePath) args = append(args, a.getHelmDryRunArgs(ctx)...) return args } -func (a *AppRelease) getHelmNamespaceArgs(ctx BosunContext) []string { - if a.Namespace != "" && a.Namespace != "default" { - return []string{"--namespace", a.Namespace} +func (a *AppDeploy) getHelmNamespaceArgs(ctx BosunContext) []string { + namespace := "default" + if a.AppConfig.Namespace != "" { + namespace = a.AppConfig.Namespace } - return []string{} + + return []string{"--namespace", namespace} } -func (a *AppRelease) getHelmDryRunArgs(ctx BosunContext) []string { +func (a *AppDeploy) getHelmDryRunArgs(ctx BosunContext) []string { if ctx.IsDryRun() { return []string{"--dry-run", "--debug"} } return []string{} } -func (a *AppRelease) Recycle(ctx BosunContext) error { - ctx = ctx.WithAppRelease(a) +func (a *AppDeploy) Recycle(ctx BosunContext) error { + ctx = ctx.WithAppDeploy(a) ctx.Log.Info("Deleting pods...") - err := pkg.NewCommand("kubectl", "delete", "--namespace", a.AppRepo.Namespace, "pods", "--selector=release="+a.AppRepo.Name).RunE() + err := pkg.NewCommand("kubectl", "delete", "--namespace", a.AppConfig.GetNamespace(), "pods", "--selector=release="+a.AppConfig.Name).RunE() if err != nil { return err } @@ -722,7 +725,7 @@ func (a *AppRelease) Recycle(ctx BosunContext) error { for { podsReady := true - out, err := pkg.NewCommand("kubectl", "get", "pods", "--namespace", a.AppRepo.Namespace, "--selector=release="+a.AppRepo.Name, + out, err := pkg.NewCommand("kubectl", "get", "pods", "--namespace", a.AppConfig.GetNamespace(), "--selector=release="+a.AppConfig.Name, "-o", `jsonpath={range .items[*]}{@.metadata.name}:{@.status.conditions[?(@.type=='Ready')].status};{end}`).RunOut() if err != nil { return err diff --git a/pkg/bosun/app_image_config.go b/pkg/bosun/app_image_config.go new file mode 100644 index 0000000..fe9094e --- /dev/null +++ b/pkg/bosun/app_image_config.go @@ -0,0 +1,52 @@ +package bosun + +import "fmt" + +type AppImageConfig struct { + ImageName string `yaml:"imageName" json:"imageName,omitempty"` + ProjectName string `yaml:"projectName,omitempty" json:"projectName,omitempty"` + Dockerfile string `yaml:"dockerfile,omitempty" json:"dockerfile,omitempty"` + ContextPath string `yaml:"contextPath,omitempty" json:"contextPath,omitempty"` +} + +func (a AppImageConfig) GetFullName() string { + return fmt.Sprintf("docker.n5o.black/%s/%s", a.ProjectName, a.ImageName) +} + +func (a AppImageConfig) GetFullNameWithTag(tag string) string { + return fmt.Sprintf("docker.n5o.black/%s/%s:%s", a.ProjectName, a.ImageName, tag) +} + +func (a *AppImageConfig) MarshalYAML() (interface{}, error) { + if a == nil { + return nil, nil + } + type proxy AppImageConfig + p := proxy(*a) + + return &p, nil +} + +func (a *AppImageConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { + type proxy AppImageConfig + var p proxy + if a != nil { + p = proxy(*a) + } + + err := unmarshal(&p) + if err != nil { + return err + } + + *a = AppImageConfig(p) + + // handle "name" as "imageName" + var m map[string]string + _ = unmarshal(&m) + if name, ok := m["name"]; ok { + a.ImageName = name + } + + return err +} diff --git a/pkg/bosun/app_manifest.go b/pkg/bosun/app_manifest.go new file mode 100644 index 0000000..4e9febe --- /dev/null +++ b/pkg/bosun/app_manifest.go @@ -0,0 +1,73 @@ +package bosun + +import ( + "fmt" + "github.com/naveego/bosun/pkg/semver" + "time" +) + +type AppMetadata struct { + Name string `yaml:"name" json:"name"` + Repo string `yaml:"repo" json:"repo"` + Version semver.Version `yaml:"version" json:"version"` + Hashes AppHashes `yaml:"hashes"` + Timestamp time.Time `yaml:"timestamp" json:"timestamp"` + Branch string `yaml:"branch" json:"branch"` +} + +func (a AppMetadata) Format(f fmt.State, c rune) { + switch c { + case 'c': + _, _ = f.Write([]byte(a.Hashes.Commit)) + default: + _, _ = f.Write([]byte(a.String())) + } +} + +func (a AppMetadata) String() string { + return fmt.Sprintf("%s@%s", a.Name, a.Version) +} + +// AppManifest contains the configuration for an app in a ReleaseManifest +// as part of a Platform. Instances should be manipulated using methods +// on Platform, not updated directly. +type AppManifest struct { + *AppMetadata `yaml:"metadata"` + AppConfig *AppConfig `yaml:"appConfig" json:"appConfig"` +} + +func (a *AppManifest) MarshalYAML() (interface{}, error) { + if a == nil { + return nil, nil + } + type proxy AppManifest + p := proxy(*a) + + return &p, nil +} + +func (a *AppManifest) UnmarshalYAML(unmarshal func(interface{}) error) error { + type proxy AppManifest + var p proxy + if a != nil { + p = proxy(*a) + } + + err := unmarshal(&p) + + if err == nil { + *a = AppManifest(p) + } + + if a.AppConfig != nil { + a.AppConfig.IsFromManifest = true + a.AppConfig.manifest = a + } + + return err +} + +func (a AppMetadata) DiffersFrom(other *AppMetadata) bool { + + return a.Version != other.Version || a.Hashes != other.Hashes +} diff --git a/pkg/bosun/app_repo.go b/pkg/bosun/app_repo.go deleted file mode 100644 index a0b8f7a..0000000 --- a/pkg/bosun/app_repo.go +++ /dev/null @@ -1,674 +0,0 @@ -package bosun - -import ( - "fmt" - "github.com/Masterminds/semver" - "github.com/fatih/color" - "github.com/naveego/bosun/pkg" - "github.com/naveego/bosun/pkg/git" - "github.com/naveego/bosun/pkg/helm" - "github.com/pkg/errors" - "github.com/stevenle/topsort" - "gopkg.in/yaml.v2" - "io/ioutil" - "os" - "os/exec" - "path/filepath" - "strings" - "time" -) - -type AppRepo struct { - *AppRepoConfig - HelmRelease *HelmRelease - branch string - commit string - gitTag string - isCloned bool - labels Labels -} - -func (a *AppRepo) Labels() Labels { - if a.labels == nil { - a.labels = Labels{ - string(FilterKeyName): LabelString(a.Name), - string(FilterKeyPath): LabelString(a.FromPath), - string(FilterKeyBranch): LabelThunk(a.GetBranch), - string(FilterKeyCommit): LabelThunk(a.GetCommit), - string(FilterKeyVersion): LabelString(a.Version), - } - for k, v := range a.AppLabels { - a.labels[k] = v - } - } - return a.labels -} - -type ReposSortedByName []*AppRepo -type DependenciesSortedByTopology []string - -func NewApp(appConfig *AppRepoConfig) *AppRepo { - return &AppRepo{ - AppRepoConfig: appConfig, - isCloned: true, - } -} - -func NewRepoFromDependency(dep *Dependency) *AppRepo { - return &AppRepo{ - AppRepoConfig: &AppRepoConfig{ - FromPath: dep.FromPath, - Name: dep.Name, - Version: dep.Version, - Repo: dep.Repo, - IsRef: true, - }, - isCloned: false, - } -} - -func (a ReposSortedByName) Len() int { - return len(a) -} - -func (a ReposSortedByName) Less(i, j int) bool { - return strings.Compare(a[i].Name, a[j].Name) < 0 -} - -func (a ReposSortedByName) Swap(i, j int) { - a[i], a[j] = a[j], a[i] -} - -func (a *AppRepo) CheckRepoCloned() error { - if !a.IsRepoCloned() { - return ErrNotCloned - } - return nil -} - -func (a *AppRepo) CloneRepo(ctx BosunContext, githubDir string) error { - if a.IsRepoCloned() { - return nil - } - - dir := filepath.Join(githubDir, a.Repo) - err := pkg.NewCommand("git", "clone", - "--depth", "1", - "--no-single-branch", - fmt.Sprintf("git@github.com:%s.git", a.Repo), - dir). - RunE() - - if err != nil { - return err - } - - return nil -} - -func (a *AppRepo) GetLocalRepoPath() (string, error) { - if !a.IsRepoCloned() { - return "", errors.New("repo is not cloned") - } - return git.GetRepoPath(a.FromPath) -} - -func (a *AppRepo) PullRepo(ctx BosunContext) error { - err := a.CheckRepoCloned() - if err != nil { - return err - } - - g, _ := git.NewGitWrapper(a.FromPath) - err = g.Pull() - - return err -} - -func (a *AppRepo) FetchRepo(ctx BosunContext) error { - err := a.CheckRepoCloned() - if err != nil { - return err - } - - g, _ := git.NewGitWrapper(a.FromPath) - err = g.Pull() - - return err -} - -func (a *AppRepo) Merge(fromBranch, toBranch string) error { - err := a.CheckRepoCloned() - if err != nil { - return err - } - - g, _ := git.NewGitWrapper(a.FromPath) - - err = g.Fetch() - if err != nil { - return err - } - - _, err = g.Exec("checkout", fromBranch) - if err != nil { - - } - - err = g.Pull() - - return err -} - -func (a *AppRepo) IsRepoCloned() bool { - - if a.FromPath == "" || a.IsRef { - return false - } - - if _, err := os.Stat(a.FromPath); os.IsNotExist(err) { - return false - } - - return true -} - -func (a *AppRepo) GetRepo() string { - if a.Repo == "" { - repoPath, _ := git.GetRepoPath(a.FromPath) - org, repo := git.GetOrgAndRepoFromPath(repoPath) - a.Repo = fmt.Sprintf("%s/%s", org, repo) - } - - return a.Repo -} - -func (a *AppRepo) GetBranch() string { - if a.IsRepoCloned() { - if a.branch == "" { - g, _ := git.NewGitWrapper(a.FromPath) - a.branch = g.Branch() - } - } - return a.branch -} - -func (a *AppRepo) GetReleaseFromBranch() string { - b := a.GetBranch() - if b != "" && strings.HasPrefix(b, "release/") { - return strings.Replace(b, "release/", "", 1) - } - return "" -} - -func (a *AppRepo) GetCommit() string { - if a.IsRepoCloned() && a.commit == "" { - g, _ := git.NewGitWrapper(a.FromPath) - a.commit = strings.Trim(g.Commit(), "'") - } - return a.commit -} - -func (a *AppRepo) HasChart() bool { - return a.ChartPath != "" || a.Chart != "" -} - -func (a *AppRepo) Dir() string { - return filepath.Dir(a.FromPath) -} - -func (a *AppRepo) GetRunCommand() (*exec.Cmd, error) { - - if a.RunCommand == nil || len(a.RunCommand) == 0 { - return nil, errors.Errorf("no runCommand in %q", a.FromPath) - } - - c := exec.Command(a.RunCommand[0], a.RunCommand[1:]...) - c.Dir = a.Dir() - c.Stdout = os.Stdout - c.Stderr = os.Stderr - - return c, nil -} - -func (a *AppRepo) GetAbsolutePathToChart() string { - return resolvePath(a.FromPath, a.ChartPath) -} - -func (a *AppRepo) getAbsoluteChartPathOrChart(ctx BosunContext) string { - if a.ChartPath != "" { - return ctx.ResolvePath(a.ChartPath) - } - return a.Chart -} - -func (a *AppRepo) getChartName() string { - if a.Chart != "" { - return a.Chart - } - name := filepath.Base(a.ChartPath) - return fmt.Sprintf("helm.n5o.black/%s", name) -} - -func (a *AppRepo) PublishChart(ctx BosunContext, force bool) error { - if err := a.CheckRepoCloned(); err != nil { - return err - } - - branch := a.GetBranch() - if branch != "master" && !strings.HasPrefix(branch, "release/") { - if ctx.GetParams().Force { - ctx.Log.WithField("branch", branch).Warn("You should only publish the chart from the master or release branches (overridden by --force).") - } else { - ctx.Log.WithField("branch", branch).Warn("You can only push charts from the master or release branches (override by setting the --force flag).") - return nil - } - } - - err := helm.PublishChart(a.GetAbsolutePathToChart(), force) - return err -} - -func (a *AppRepo) GetImages() []AppImageConfig { - images := a.Images - defaultProjectName := "private" - if a.HarborProject != "" { - defaultProjectName = a.HarborProject - } - if len(images) == 0 { - images = []AppImageConfig{{ImageName: a.Name}} - } - - var formattedImages []AppImageConfig - for _, i := range images { - if i.ProjectName == "" { - i.ProjectName = defaultProjectName - } - - formattedImages = append(formattedImages, i) - } - - return formattedImages -} - -// GetPrefixedImageNames returns the untagged names of the images for this repo. -func (a *AppRepo) GetPrefixedImageNames() []string { - var prefixedNames []string - for _, image := range a.GetImages() { - prefixedNames = append(prefixedNames, image.GetPrefixedName()) - } - return prefixedNames -} - -// GetImageName returns the image name with the tags appended. If no arguments are provided, -// it will be tagged "latest"; if one arg is provided it will be used as the tag; -// if 2 args are provided it will be tagged "arg[0]-arg[1]". -func (a *AppRepo) GetTaggedImageNames(versionAndRelease ...string) []string { - var taggedNames []string - names := a.GetPrefixedImageNames() - for _, name := range names { - taggedName := name - switch len(versionAndRelease) { - case 0: - taggedName = fmt.Sprintf("%s:latest", taggedName) - case 1: - taggedName = fmt.Sprintf("%s:%s", taggedName, versionAndRelease[0]) - case 2: - taggedName = fmt.Sprintf("%s:%s-%s", taggedName, versionAndRelease[0], versionAndRelease[1]) - } - taggedNames = append(taggedNames, taggedName) - } - - return taggedNames -} - -func (a *AppRepo) BuildImages(ctx BosunContext) error { - - var report []string - for _, image := range a.GetImages() { - dockerfilePath := image.Dockerfile - if dockerfilePath == "" { - dockerfilePath = ctx.ResolvePath("Dockerfile") - } else { - dockerfilePath = ctx.ResolvePath(dockerfilePath) - } - contextPath := image.ContextPath - if contextPath == "" { - contextPath = filepath.Dir(dockerfilePath) - } else { - contextPath = ctx.ResolvePath(contextPath) - } - - args := []string{ - "build", - "-f", dockerfilePath, - "--build-arg", fmt.Sprintf("VERSION_NUMBER=%s", a.Version), - "--build-arg", fmt.Sprintf("COMMIT=%s", a.GetCommit()), - "--build-arg", fmt.Sprintf("BUILD_NUMBER=%s", os.Getenv("BUILD_NUMBER")), - "--tag", image.GetPrefixedName(), - contextPath, - } - - ctx.Log.Infof("Building image %q from %q with context %q", image.ImageName, dockerfilePath, contextPath) - _, err := pkg.NewCommand("docker", args...).RunOutLog() - if err != nil { - return errors.Wrapf(err, "build image %q from %q with context %q", image.ImageName, dockerfilePath, contextPath) - } - - report = append(report, fmt.Sprintf("Built image from %q with context %q: %s", dockerfilePath, contextPath, image.GetPrefixedName())) - } - - fmt.Println() - for _, line := range report { - color.Green("%s\n", line) - } - - return nil -} - -func (a *AppRepo) PublishImages(ctx BosunContext) error { - - var report []string - - tags := []string{"latest", a.Version} - - branch := a.GetBranch() - if branch != "master" && !strings.HasPrefix(branch, "release/") { - if ctx.GetParams().Force { - ctx.Log.WithField("branch", branch).Warn("You should only push images from the master or release branches (overridden by --force).") - } else { - ctx.Log.WithField("branch", branch).Warn("You can only push images from the master or release branches (override by setting the --force flag).") - return nil - } - } - - release := a.GetReleaseFromBranch() - if release != "" { - tags = append(tags, fmt.Sprintf("%s-%s", a.Version, release)) - } - - for _, tag := range tags { - for _, taggedName := range a.GetTaggedImageNames(tag) { - ctx.Log.Infof("Tagging and pushing %q", taggedName) - untaggedName := strings.Split(taggedName, ":")[0] - _, err := pkg.NewCommand("docker", "tag", untaggedName, taggedName).RunOutLog() - if err != nil { - return err - } - _, err = pkg.NewCommand("docker", "push", taggedName).RunOutLog() - if err != nil { - return err - } - report = append(report, fmt.Sprintf("Tagged and pushed %s", taggedName)) - } - } - - fmt.Println() - for _, line := range report { - color.Green("%s\n", line) - } - - return nil -} - -func GetDependenciesInTopologicalOrder(apps map[string][]string, roots ...string) (DependenciesSortedByTopology, error) { - - const target = "__TARGET__" - - graph := topsort.NewGraph() - - graph.AddNode(target) - - for _, root := range roots { - graph.AddNode(root) - graph.AddEdge(target, root) - } - - // add our root node to the graph - - for name, deps := range apps { - graph.AddNode(name) - for _, dep := range deps { - // make sure dep is in the graph - graph.AddNode(dep) - graph.AddEdge(name, dep) - } - } - - sortedNames, err := graph.TopSort(target) - if err != nil { - return nil, err - } - - var result DependenciesSortedByTopology - for _, name := range sortedNames { - if name == target { - continue - } - - result = append(result, name) - } - - return result, nil -} - -func (a *AppRepo) GetAppReleaseConfig(ctx BosunContext) (*AppReleaseConfig, error) { - var err error - ctx = ctx.WithAppRepo(a) - - isTransient := ctx.Release == nil || ctx.Release.Transient - - r := &AppReleaseConfig{ - Name: a.Name, - Namespace: a.Namespace, - Version: a.Version, - ReportDeployment: a.ReportDeployment, - SyncedAt: time.Now(), - } - - ctx.Log.Debug("Getting app release config.") - - if !isTransient && a.BranchForRelease { - - g, err := git.NewGitWrapper(a.FromPath) - if err != nil { - return nil, err - } - - branchName := fmt.Sprintf("release/%s", ctx.Release.Name) - - branches, err := g.Exec("branch", "-a") - if err != nil { - return nil, err - } - if strings.Contains(branches, branchName) { - ctx.Log.Info("Checking out release branch...") - _, err := g.Exec("checkout", branchName) - if err != nil { - return nil, err - } - _, err = g.Exec("pull") - if err != nil { - return nil, err - } - } else { - - if ctx.Release.IsPatch { - return nil, errors.New("patch release not implemented yet, you must create the release branch manually") - } - - ctx.Log.Info("Creating release branch...") - _, err = g.Exec("checkout", "master") - if err != nil { - return nil, errors.Wrap(err, "checkout master") - } - _, err = g.Exec("pull") - if err != nil { - return nil, errors.Wrap(err, "pull master") - } - - _, err = g.Exec("branch", branchName, "origin/master") - if err != nil { - return nil, err - } - _, err = g.Exec("checkout", branchName) - if err != nil { - return nil, err - } - _, err = g.Exec("push", "-u", "origin", branchName) - if err != nil { - return nil, err - } - } - - r.Branch = a.GetBranch() - r.Repo = a.GetRepo() - r.Commit = a.GetCommit() - - } - - if isTransient { - r.Chart = ctx.ResolvePath(a.ChartPath) - } else { - r.Chart = a.getChartName() - } - - if a.BranchForRelease { - r.ImageNames = a.GetPrefixedImageNames() - if isTransient || ctx.Release == nil { - r.ImageTag = "latest" - } else { - r.ImageTag = fmt.Sprintf("%s-%s", r.Version, ctx.Release.Name) - } - } - - r.Values, err = a.ExportValues(ctx) - if err != nil { - return nil, errors.Errorf("export values for release: %s", err) - } - - r.Actions, err = a.ExportActions(ctx) - if err != nil { - return nil, errors.Errorf("export actions for release: %s", err) - } - - for _, dep := range a.DependsOn { - r.DependsOn = append(r.DependsOn, dep.Name) - } - - return r, nil -} - -// BumpVersion bumps the version (including saving the source fragment -// and updating the chart. The `bump` parameter may be one of -// major|minor|patch|major.minor.patch. If major.minor.patch is provided, -// the version is set to that value. -func (a *AppRepo) BumpVersion(ctx BosunContext, bump string) error { - version, err := semver.NewVersion(bump) - if err == nil { - a.Version = version.String() - } else { - version, err = semver.NewVersion(a.Version) - if err != nil { - return errors.Errorf("app has invalid version %q: %s", a.Version, err) - } - var v2 semver.Version - - switch bump { - case "major": - v2 = version.IncMajor() - case "minor": - v2 = version.IncMinor() - case "patch": - v2 = version.IncPatch() - default: - return errors.Errorf("invalid version component %q (want major, minor, or patch)", bump) - } - a.Version = v2.String() - } - - packageJSONPath := filepath.Join(filepath.Dir(a.FromPath), "package.json") - if _, err = os.Stat(packageJSONPath); err == nil { - ctx.Log.Info("package.json detected, its version will be updated.") - err = pkg.NewCommand("npm", "--no-git-tag-version", "--allow-same-version", "version", bump). - WithDir(filepath.Dir(a.FromPath)). - RunE() - if err != nil { - return errors.Errorf("failed to update version in package.json: %s", err) - } - } - - if a.HasChart() { - m, err := a.getChartAsMap() - if err != nil { - return err - } - - m["version"] = a.Version - if a.BranchForRelease { - m["appVersion"] = a.Version - } - err = a.saveChart(m) - if err != nil { - return err - } - } - - if a.GoVersionFile != "" { - - } - - return a.Fragment.Save() -} - -func (a *AppRepo) getChartAsMap() (map[string]interface{}, error) { - err := a.CheckRepoCloned() - if err != nil { - return nil, err - } - - if a.ChartPath == "" { - return nil, errors.New("chartPath not set") - } - - path := filepath.Join(a.GetAbsolutePathToChart(), "Chart.yaml") - b, err := ioutil.ReadFile(path) - if err != nil { - return nil, err - } - - var out map[string]interface{} - err = yaml.Unmarshal(b, &out) - return out, err -} - -func (a *AppRepo) saveChart(m map[string]interface{}) error { - b, err := yaml.Marshal(m) - if err != nil { - return err - } - path := filepath.Join(a.GetAbsolutePathToChart(), "Chart.yaml") - stat, err := os.Stat(path) - if err != nil { - return err - } - err = ioutil.WriteFile(path, b, stat.Mode()) - return err -} - -func omitStrings(from []string, toOmit ...string) []string { - var out []string - for _, s := range from { - matched := false - for _, o := range toOmit { - if o == s { - matched = true - continue - } - } - if !matched { - out = append(out, s) - } - } - return out -} diff --git a/pkg/bosun/app_repo_config.go b/pkg/bosun/app_repo_config.go deleted file mode 100644 index fcdd7f0..0000000 --- a/pkg/bosun/app_repo_config.go +++ /dev/null @@ -1,363 +0,0 @@ -package bosun - -import ( - "fmt" - "github.com/imdario/mergo" - "github.com/naveego/bosun/pkg" - "github.com/naveego/bosun/pkg/zenhub" - "github.com/pkg/errors" - "strings" -) - -type AppRepoConfig struct { - Name string `yaml:"name" json:"name" json:"name" json:"name"` - FromPath string `yaml:"-" json:"-"` - ProjectManagementPlugin *ProjectManagementPlugin `yaml:"projectManagementPlugin,omitempty" json:"projectManagementPlugin,omitempty"` - BranchForRelease bool `yaml:"branchForRelease,omitempty" json:"branchForRelease,omitempty"` - // ContractsOnly means that the app doesn't have any compiled/deployed code, it just defines contracts or documentation. - ContractsOnly bool `yaml:"contractsOnly,omitempty" json:"contractsOnly,omitempty"` - ReportDeployment bool `yaml:"reportDeployment,omitempty" json:"reportDeployment,omitempty"` - Namespace string `yaml:"namespace,omitempty" json:"namespace,omitempty"` - Repo string `yaml:"repo,omitempty" json:"repo,omitempty"` - HarborProject string `yaml:"harborProject,omitempty" json:"harborProject,omitempty"` - Version string `yaml:"version,omitempty" json:"version,omitempty"` - // The location of a standard go version file for this app. - GoVersionFile string `yaml:"goVersionFile,omitempty" json:"goVersionFile,omitempty"` - Chart string `yaml:"chart,omitempty" json:"chart,omitempty"` - ChartPath string `yaml:"chartPath,omitempty" json:"chartPath,omitempty"` - RunCommand []string `yaml:"runCommand,omitempty,flow" json:"runCommand,omitempty,flow"` - DependsOn []Dependency `yaml:"dependsOn,omitempty" json:"dependsOn,omitempty"` - AppLabels Labels `yaml:"labels,omitempty" json:"labels,omitempty"` - Minikube *AppMinikubeConfig `yaml:"minikube,omitempty" json:"minikube,omitempty"` - Images []AppImageConfig `yaml:"images" json:"images"` - Values AppValuesByEnvironment `yaml:"values,omitempty" json:"values,omitempty"` - Scripts []*Script `yaml:"scripts,omitempty" json:"scripts,omitempty"` - Actions []*AppAction `yaml:"actions,omitempty" json:"actions,omitempty"` - Fragment *File `yaml:"-" json:"-"` - // If true, this app repo is only a ref, not a real cloned repo. - IsRef bool `yaml:"-" json:"-"` -} - -type ProjectManagementPlugin struct { - Name string `yaml:"name" json:"name"` - ZenHub *zenhub.RepoConfig `yaml:"zenHub,omitempty" json:"zenHub"` -} - -type AppImageConfig struct { - ImageName string `yaml:"imageName" json:"imageName,omitempty"` - ProjectName string `yaml:"projectName,omitempty" json:"projectName,omitempty"` - Dockerfile string `yaml:"dockerfile,omitempty" json:"dockerfile,omitempty"` - ContextPath string `yaml:"contextPath,omitempty" json:"contextPath,omitempty"` -} - -func (a AppImageConfig) GetPrefixedName() string { - return fmt.Sprintf("docker.n5o.black/%s/%s", a.ProjectName, a.ImageName) -} - -type AppMinikubeConfig struct { - // The ports which should be made exposed through nodePorts - // when running on minikube. - Ports []int `yaml:"ports,omitempty" json:"ports,omitempty"` - // The services which should be replaced when toggling an - // app to run on the host. - RoutableServices []AppRoutableService `yaml:"routableServices" json:"routableServices"` -} - -type AppRoutableService struct { - Name string `yaml:"name" json:"name,omitempty"` - PortName string `yaml:"portName" json:"portName,omitempty"` - // Deprecated, use localhostPort instead - ExternalPort int `yaml:"externalPort" json:"externalPort,omitempty"` - LocalhostPort int `yaml:"localhostPort" json:"localhostPort,omitempty"` -} - -type Dependency struct { - Name string `yaml:"name" json:"name,omitempty"` - FromPath string `yaml:"-" json:"fromPath,omitempty"` - Repo string `yaml:"repo,omitempty" json:"repo,omitempty"` - App *AppRepo `yaml:"-" json:"-"` - Version string `yaml:"version,omitempty" json:"version,omitempty"` -} - -type Dependencies []Dependency - -func (d Dependencies) Len() int { return len(d) } -func (d Dependencies) Less(i, j int) bool { return strings.Compare(d[i].Name, d[j].Name) < 0 } -func (d Dependencies) Swap(i, j int) { d[i], d[j] = d[j], d[i] } - -type AppValuesConfig struct { - Dynamic map[string]*CommandValue `yaml:"dynamic,omitempty" json:"dynamic,omitempty"` - Files []string `yaml:"files,omitempty" json:"files,omitempty"` - Static Values `yaml:"static,omitempty" json:"static,omitempty"` -} - -func (a *AppValuesConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { - var m map[string]interface{} - err := unmarshal(&m) - if err != nil { - return errors.WithStack(err) - } - if _, ok := m["set"]; ok { - // is v1 - var v1 appValuesConfigV1 - err = unmarshal(&v1) - if err != nil { - return errors.WithStack(err) - } - if a == nil { - *a = AppValuesConfig{} - } - if v1.Static == nil { - v1.Static = Values{} - } - if v1.Set == nil { - v1.Set = map[string]*CommandValue{} - } - a.Files = v1.Files - a.Static = v1.Static - a.Dynamic = v1.Set - // handle case where set AND dynamic both have values - if v1.Dynamic != nil { - err = mergo.Map(a.Dynamic, v1.Dynamic) - } - return err - } - - type proxy AppValuesConfig - var p proxy - err = unmarshal(&p) - if err != nil { - return errors.WithStack(err) - } - *a = AppValuesConfig(p) - return nil -} - -type appValuesConfigV1 struct { - Set map[string]*CommandValue `yaml:"set,omitempty" json:"set,omitempty"` - Dynamic map[string]*CommandValue `yaml:"dynamic,omitempty" json:"dynamic,omitempty"` - Files []string `yaml:"files,omitempty" json:"files,omitempty"` - Static Values `yaml:"static,omitempty" json:"static,omitempty"` -} - -func (a *AppRepoConfig) SetFragment(fragment *File) { - a.FromPath = fragment.FromPath - a.Fragment = fragment - for i := range a.Scripts { - a.Scripts[i].FromPath = a.FromPath - } - for i := range a.DependsOn { - a.DependsOn[i].FromPath = a.FromPath - } - for i := range a.Actions { - a.Actions[i].FromPath = a.FromPath - } -} - -// Combine returns a new AppValuesConfig with the values from -// other added after (and/or overwriting) the values from this instance) -func (a AppValuesConfig) Combine(other AppValuesConfig) AppValuesConfig { - out := AppValuesConfig{ - Dynamic: make(map[string]*CommandValue), - Static: Values{}, - } - out.Files = append(out.Files, a.Files...) - out.Files = append(out.Files, other.Files...) - - out.Static.Merge(a.Static) - out.Static.Merge(other.Static) - - for k, v := range a.Dynamic { - out.Dynamic[k] = v - } - for k, v := range other.Dynamic { - out.Dynamic[k] = v - } - - return out -} - -type AppValuesByEnvironment map[string]AppValuesConfig - -func (a AppValuesByEnvironment) GetValuesConfig(ctx BosunContext) AppValuesConfig { - out := AppValuesConfig{} - name := ctx.Env.Name - - // more precise values should override less precise values - priorities := make([][]AppValuesConfig, 10, 10) - - for k, v := range a { - keys := strings.Split(k, ",") - for _, k2 := range keys { - if k2 == name { - priorities[len(keys)] = append(priorities[len(keys)], v) - } - } - } - - for i := len(priorities) - 1; i >= 0; i-- { - for _, v := range priorities[i] { - out = out.Combine(v) - } - } - - return out -} - -// WithFilesLoaded resolves all file system dependencies into static values -// on this instance, then clears those dependencies. -func (a AppValuesConfig) WithFilesLoaded(ctx BosunContext) (AppValuesConfig, error) { - - out := AppValuesConfig{ - Static: a.Static.Clone(), - } - - mergedValues := Values{} - - // merge together values loaded from files - for _, file := range a.Files { - file = ctx.ResolvePath(file) - valuesFromFile, err := ReadValuesFile(file) - if err != nil { - return out, errors.Errorf("reading values file %q for env key %q: %s", file, ctx.Env.Name, err) - } - mergedValues.Merge(valuesFromFile) - } - - // make sure any existing static values are merged OVER the values from the file - mergedValues.Merge(out.Static) - out.Static = mergedValues - - out.Dynamic = a.Dynamic - - return out, nil -} - -func (a *AppRepoConfig) GetValuesConfig(ctx BosunContext) AppValuesConfig { - out := a.Values.GetValuesConfig(ctx.WithDir(a.FromPath)) - - if out.Static == nil { - out.Static = Values{} - } - if out.Dynamic == nil { - out.Dynamic = map[string]*CommandValue{} - } - - return out -} - -// ExportValues creates an AppValuesByEnvironment instance with all the values -// for releasing this app, reified into their environments, including values from -// files and from the default values.yaml file for the chart. -func (a *AppRepo) ExportValues(ctx BosunContext) (AppValuesByEnvironment, error) { - ctx = ctx.WithAppRepo(a) - var err error - envs := map[string]*EnvironmentConfig{} - for envNames := range a.Values { - for _, envName := range strings.Split(envNames, ",") { - if _, ok := envs[envName]; !ok { - env, err := ctx.Bosun.GetEnvironment(envName) - if err != nil { - ctx.Log.Warnf("App values include key for environment %q, but no such environment has been defined.", envName) - continue - } - envs[envName] = env - } - } - } - var defaultValues Values - - if a.HasChart() { - chartRef := a.getAbsoluteChartPathOrChart(ctx) - valuesYaml, err := pkg.NewCommand( - "helm", "inspect", "values", - chartRef, - "--version", a.Version, - ).RunOut() - if err != nil { - return nil, errors.Errorf("load default values from %q: %s", chartRef, err) - } - defaultValues, err = ReadValues([]byte(valuesYaml)) - if err != nil { - return nil, errors.Errorf("parse default values from %q: %s", chartRef, err) - } - } else { - defaultValues = Values{} - } - - out := AppValuesByEnvironment{} - - for _, env := range envs { - envCtx := ctx.WithEnv(env) - valuesConfig := a.GetValuesConfig(envCtx) - valuesConfig, err = valuesConfig.WithFilesLoaded(envCtx) - if err != nil { - return nil, err - } - // make sure values from bosun app overwrite defaults from helm chart - static := defaultValues.Clone() - static.Merge(valuesConfig.Static) - valuesConfig.Static = static - valuesConfig.Files = nil - out[env.Name] = valuesConfig - } - - return out, nil -} - -func (a *AppRepo) ExportActions(ctx BosunContext) ([]*AppAction, error) { - var err error - var actions []*AppAction - for _, action := range a.Actions { - if action.When == ActionManual { - ctx.Log.Debugf("Skipping export of action %q because it is marked as manual.", action.Name) - } else { - err = action.MakeSelfContained(ctx) - if err != nil { - return nil, errors.Errorf("prepare action %q for release: %s", action.Name, err) - } - actions = append(actions, action) - } - } - - return actions, nil -} - -type AppStatesByEnvironment map[string]AppStateMap - -type AppStateMap map[string]AppState - -type AppState struct { - Branch string `yaml:"branch,omitempty" json:"branch,omitempty"` - Status string `yaml:"deployment,omitempty" json:"deployment,omitempty"` - Routing string `yaml:"routing,omitempty" json:"routing,omitempty"` - Version string `yaml:"version,omitempty" json:"version,omitempty"` - Diff string `yaml:"-" json:"-"` - Error error `yaml:"-" json:"-"` - Force bool `yaml:"-" json:"-"` - Unavailable bool `yaml:"-" json:"-"` -} - -func (a AppState) String() string { - hasDiff := a.Diff != "" - return fmt.Sprintf("status:%s routing:%s version:%s hasDiff:%t, force:%t", - a.Status, - a.Routing, - a.Version, - hasDiff, - a.Force) -} - -const ( - RoutingLocalhost = "localhost" - RoutingCluster = "cluster" - RoutingNA = "n/a" - StatusDeployed = "DEPLOYED" - StatusNotFound = "NOTFOUND" - StatusDeleted = "DELETED" - StatusFailed = "FAILED" - StatusPendingUpgrade = "PENDING_UPGRADE" - StatusUnchanged = "UNCHANGED" -) - -type Routing string diff --git a/pkg/bosun/app_repo_test.go b/pkg/bosun/app_test.go similarity index 93% rename from pkg/bosun/app_repo_test.go rename to pkg/bosun/app_test.go index 44ec556..074f645 100644 --- a/pkg/bosun/app_repo_test.go +++ b/pkg/bosun/app_test.go @@ -6,7 +6,7 @@ import ( . "github.com/onsi/gomega" ) -var _ = Describe("AppRepo", func() { +var _ = Describe("App", func() { It("should support topological sort", func() { diff --git a/pkg/bosun/bosun.go b/pkg/bosun/bosun.go index 3f67ca1..87d62bb 100644 --- a/pkg/bosun/bosun.go +++ b/pkg/bosun/bosun.go @@ -7,6 +7,7 @@ import ( "github.com/google/go-github/v20/github" vault "github.com/hashicorp/vault/api" "github.com/naveego/bosun/pkg" + "github.com/naveego/bosun/pkg/mirror" "github.com/pkg/errors" "github.com/sirupsen/logrus" "gopkg.in/yaml.v2" @@ -15,6 +16,7 @@ import ( "os/exec" "path/filepath" "sort" + "strings" "time" ) @@ -22,13 +24,14 @@ type Bosun struct { params Parameters ws *Workspace file *File - repos map[string]*AppRepo - release *Release + apps map[string]*App vaultClient *vault.Client env *EnvironmentConfig clusterAvailable *bool log *logrus.Entry environmentConfirmed *bool + repos map[string]*Repo + platform *Platform } type Parameters struct { @@ -48,8 +51,9 @@ func New(params Parameters, ws *Workspace) (*Bosun, error) { params: params, ws: ws, file: ws.MergedBosunFile, - repos: make(map[string]*AppRepo), + apps: make(map[string]*App), log: pkg.Log, + repos: map[string]*Repo{}, } if params.DryRun { @@ -58,47 +62,112 @@ func New(params Parameters, ws *Workspace) (*Bosun, error) { } for _, dep := range b.file.AppRefs { - b.repos[dep.Name] = NewRepoFromDependency(dep) + b.apps[dep.Name] = NewAppFromDependency(dep) } for _, a := range b.file.Apps { if a != nil { - b.addApp(a) + _, err := b.addApp(a) + if err != nil { + return nil, errors.Wrapf(err, "add app %q", a.Name) + } } } if !params.NoCurrentEnv { - b.configureCurrentEnv() + err := b.configureCurrentEnv() + if err != nil { + return nil, err + } } return b, nil } -func (b *Bosun) addApp(config *AppRepoConfig) *AppRepo { +func (b *Bosun) addApp(config *AppConfig) (*App, error) { + if config.Name == "" { + return nil, errors.New("cannot accept an app with no name") + } app := NewApp(config) - b.repos[config.Name] = app + if app.RepoName == "" { + repos := b.GetRepos() + for _, r := range repos { + if r.LocalRepo != nil { + if strings.HasPrefix(app.FromPath, r.LocalRepo.Path) { + // This app is part of this repo, whether the app knows it or not. + app.RepoName = r.Name + app.Repo = r + r.Apps[app.Name] = config + } + } + } + } - for _, d2 := range app.DependsOn { - if _, ok := b.repos[d2.Name]; !ok { - b.repos[d2.Name] = NewRepoFromDependency(&d2) + if app.RepoName != "" { + // find or add repo for app + repo, err := b.GetRepo(app.RepoName) + if err != nil { + repo = &Repo{ + Apps: map[string]*AppConfig{ + app.Name: config, + }, + RepoConfig: RepoConfig{ + ConfigShared: ConfigShared{ + Name: app.Name, + }, + }, + } + b.repos[app.RepoName] = repo + config.Parent.Repos = append(config.Parent.Repos, &repo.RepoConfig) } + app.Repo = repo } - return app + b.apps[config.Name] = app + + // for _, d2 := range app.DependsOn { + // if _, ok := b.apps[d2.Name]; !ok { + // b.apps[d2.Name] = NewAppFromDependency(&d2) + // } + // } + + return app, nil } -func (b *Bosun) GetAppsSortedByName() ReposSortedByName { - var ms ReposSortedByName +func (b *Bosun) GetAppsSortedByName() []*App { + var ms AppsSortedByName - for _, x := range b.repos { - ms = append(ms, x) + apps := b.GetApps() + + for _, x := range apps { + if x.Name != "" { + ms = append(ms, x) + } } sort.Sort(ms) return ms } -func (b *Bosun) GetApps() map[string]*AppRepo { - return b.repos +func (b *Bosun) GetApps() map[string]*App { + out := map[string]*App{} + + for _, app := range b.apps { + out[app.Name] = app + } + + p, err := b.GetCurrentPlatform() + if err == nil { + master, err := p.GetMasterManifest() + if err == nil { + for appName, appManifest := range master.AppManifests { + if _, ok := out[appName]; !ok { + out[appName] = NewApp(appManifest.AppConfig) + } + } + } + } + + return out } func (b *Bosun) GetAppDesiredStates() map[string]AppState { @@ -115,6 +184,39 @@ func (b *Bosun) GetAppDependencyMap() map[string][]string { return deps } +func (b *Bosun) GetAppDependencies(name string) ([]string, error) { + + visited := map[string]bool{} + + return b.getAppDependencies(name, visited) +} + +func (b *Bosun) getAppDependencies(name string, visited map[string]bool) ([]string, error) { + visited[name] = true + + app, err := b.GetApp(name) + if err != nil { + return nil, err + } + + var out []string + + for _, dep := range app.DependsOn { + if visited[dep.Name] { + continue + } + visited[dep.Name] = true + out = append(out, dep.Name) + + children, err := b.getAppDependencies(dep.Name, visited) + if err != nil { + return nil, errors.Errorf("%s:%s", name, err) + } + out = append(out, children...) + } + return out, nil +} + func (b *Bosun) GetVaultClient() (*vault.Client, error) { var err error if b.vaultClient == nil { @@ -149,16 +251,62 @@ func (b *Bosun) GetScript(name string) (*Script, error) { return nil, errors.Errorf("no script found with name %q", name) } -func (b *Bosun) GetApp(name string) (*AppRepo, error) { - m, ok := b.repos[name] +func (b *Bosun) GetAppWithRepo(name string) (*App, error) { + m, ok := b.apps[name] if !ok { - return nil, errors.Errorf("no service named %q", name) + return nil, errors.Errorf("no app with name %q has been cloned", name) } return m, nil } -func (b *Bosun) GetOrAddAppForPath(path string) (*AppRepo, error) { - for _, m := range b.repos { +func (b *Bosun) GetApp(name string) (*App, error) { + m, ok := b.apps[name] + if !ok { + + p, err := b.GetCurrentPlatform() + if err != nil { + return nil, errors.Errorf("no app named %q, and no platform available for finding latest release", name) + } + + manifest, err := p.GetLatestAppManifestByName(name) + if err != nil { + return nil, err + } + + return NewApp(manifest.AppConfig), nil + } + return m, nil +} + +func (b *Bosun) ReloadApp(name string) (*App, error) { + app, ok := b.apps[name] + if !ok { + return nil, errors.Errorf("no app named %q", name) + } + + file := &File{ + AppRefs: map[string]*Dependency{}, + } + + err := pkg.LoadYaml(app.FromPath, &file) + if err != nil { + return nil, err + } + + file.SetFromPath(app.FromPath) + + for _, updatedApp := range file.Apps { + if updatedApp.Name == name { + app, err = b.addApp(updatedApp) + return app, err + } + } + + return nil, errors.Errorf("could not find app in source file at %q", app.FromPath) +} + +func (b *Bosun) GetOrAddAppForPath(path string) (*App, error) { + for _, m := range b.apps { if m.FromPath == path { return m, nil } @@ -176,7 +324,10 @@ func (b *Bosun) GetOrAddAppForPath(path string) (*AppRepo, error) { var name string for _, m := range imported.Apps { - b.addApp(m) + _, err = b.addApp(m) + if err != nil { + return nil, err + } name = m.Name } @@ -354,6 +505,71 @@ func (b *Bosun) GetEnvironments() []*EnvironmentConfig { return b.file.Environments } +func (b *Bosun) GetValueSet(name string) (*ValueSet, error) { + for _, vs := range b.file.ValueSets { + if vs.Name == name { + return vs, nil + } + } + return nil, errors.Errorf("no valueSet named %q", name) +} + +func (b *Bosun) GetValueSetSlice(names []string) ([]ValueSet, error) { + var out []ValueSet + want := map[string]bool{} + for _, name := range names { + want[name] = false + } + + for _, vs := range b.file.ValueSets { + if _, wanted := want[vs.Name]; wanted { + out = append(out, *vs) + want[vs.Name] = true + } + } + + for name, found := range want { + if !found { + return nil, errors.Errorf("wanted value set %q was not found", name) + } + } + + return out, nil +} + +func (b *Bosun) GetValueSetsForEnv(env *EnvironmentConfig) ([]*ValueSet, error) { + vss := map[string]*ValueSet{} + for _, vs := range b.file.ValueSets { + vss[vs.Name] = vs + } + + var out []*ValueSet + for _, name := range env.ValueSets { + vs, ok := vss[name] + if !ok { + return nil, errors.Errorf("no valueSet with name %q", name) + } + out = append(out, vs) + } + + mirror.Sort(out, func(a, b *ValueSet) bool { + return a.Name < b.Name + }) + + return out, nil +} + +func (b *Bosun) GetValueSets() []*ValueSet { + out := make([]*ValueSet, len(b.file.ValueSets)) + copy(out, b.file.ValueSets) + + mirror.Sort(out, func(a, b *ValueSet) bool { + return a.Name < b.Name + }) + + return out +} + func (b *Bosun) NewContext() BosunContext { dir, _ := os.Getwd() @@ -366,60 +582,103 @@ func (b *Bosun) NewContext() BosunContext { } -func (b *Bosun) GetCurrentRelease() (*Release, error) { +func (b *Bosun) GetCurrentReleaseManifest(loadAppManifests bool) (*ReleaseManifest, error) { var err error - if b.release == nil { - if b.ws.Release == "" { - return nil, errors.New("current release not set, call `bosun release use {name}` to set current release") - } - if b.ws.Release != "" { - for _, r := range b.file.Releases { - if r.Name == b.ws.Release { - b.release, err = NewRelease(b.NewContext(), r) - if err != nil { - return nil, errors.Errorf("creating release from config %q: %s", r.Name, err) - } - } - } - } + if b.ws.CurrentRelease == "" { + return nil, errors.New("current release not set, call `bosun release use {name}` to set current release") } - if b.release == nil { - return nil, errors.Errorf("current release %q could not be found, call `bosun release use {name}` to set current release", b.ws.Release) + + p, err := b.GetCurrentPlatform() + if err != nil { + return nil, err + } + + rm, err := p.GetReleaseManifestByName(b.ws.CurrentRelease, loadAppManifests) + return rm, err +} + +func (b *Bosun) GetCurrentReleaseMetadata() (*ReleaseMetadata, error) { + var err error + if b.ws.CurrentRelease == "" { + return nil, errors.New("current release not set, call `bosun release use {name}` to set current release") + } + + p, err := b.GetCurrentPlatform() + if err != nil { + return nil, err } - return b.release, nil + rm, err := p.GetReleaseMetadataByName(b.ws.CurrentRelease) + return rm, err } -func (b *Bosun) GetReleaseConfigs() []*ReleaseConfig { - var releases []*ReleaseConfig - for _, r := range b.file.Releases { - releases = append(releases, r) +func (b *Bosun) GetCurrentPlatform() (*Platform, error) { + if b.platform != nil { + return b.platform, nil + } + + switch len(b.file.Platforms) { + case 0: + return nil, errors.New("no platforms found") + case 1: + b.platform = b.file.Platforms[0] + return b.platform, nil + default: + if b.ws.CurrentPlatform == "" { + return nil, errors.New("no current platform selected; use `bosun ws use-platform` to set it") + } + for _, p := range b.file.Platforms { + if p.Name == b.ws.CurrentPlatform { + b.platform = p + return b.platform, nil + } + } + return nil, errors.Errorf("current platform %q is not found", b.ws.CurrentPlatform) } - return releases } -func (b *Bosun) GetReleaseConfig(name string) (*ReleaseConfig, error) { - for _, r := range b.file.Releases { - if r.Name == name { - return r, nil +func (b *Bosun) GetPlatform(name string) (*Platform, error) { + for _, p := range b.file.Platforms { + if p.Name == name { + b.platform = p + return b.platform, nil } } - return nil, errors.Errorf("no release with name %q", name) + return nil, errors.Errorf("current platform %q is not found", b.ws.CurrentPlatform) +} + +func (b *Bosun) GetPlatforms() ([]*Platform, error) { + out := make([]*Platform, len(b.file.Platforms)) + copy(out, b.file.Platforms) + mirror.Sort(out, func(a, b *Platform) bool { + return a.Name < b.Name + }) + + return out, nil +} + +func (b *Bosun) UsePlatform(name string) error { + for _, p := range b.file.Platforms { + if p.Name == name { + b.ws.CurrentPlatform = name + return nil + } + } + return errors.Errorf("no platform named %q", name) } func (b *Bosun) UseRelease(name string) error { - rc, err := b.GetReleaseConfig(name) + p, err := b.GetCurrentPlatform() if err != nil { return err } - - b.release, err = NewRelease(b.NewContext(), rc) + _, err = p.GetReleaseMetadataByName(name) if err != nil { return err } - b.ws.Release = name + b.ws.CurrentRelease = name return nil } @@ -437,15 +696,60 @@ func (b *Bosun) TidyWorkspace() { log := ctx.Log var importMap = map[string]struct{}{} + for _, repo := range b.GetRepos() { + if repo.CheckCloned() != nil { + for _, root := range b.ws.GitRoots { + clonedFolder := filepath.Join(root, repo.Name) + if _, err := os.Stat(clonedFolder); err != nil { + if os.IsNotExist(err) { + log.Debugf("Repo %s not found at %s", repo.Name, clonedFolder) + } else { + log.Warnf("Error looking for app %s: %s", repo.Name, err) + } + } + bosunFilePath := filepath.Join(clonedFolder, "bosun.yaml") + if _, err := os.Stat(bosunFilePath); err != nil { + if os.IsNotExist(err) { + log.Warnf("Repo %s seems to be cloned to %s, but there is no bosun.yaml file in that folder", repo.Name, clonedFolder) + } else { + log.Warnf("Error looking for bosun.yaml in repo %s: %s", repo.Name, err) + } + } else { + log.Infof("Found cloned repo %s at %s, will add to known local repos.", repo.Name, bosunFilePath) + localRepo := &LocalRepo{ + Name: repo.Name, + Path: clonedFolder, + } + b.AddLocalRepo(localRepo) + break + } + } + } + } + for _, app := range b.GetApps() { if app.IsRepoCloned() { importMap[app.FromPath] = struct{}{} log.Debugf("App %s found at %s", app.Name, app.FromPath) + + repo, err := b.GetRepo(app.RepoName) + if err != nil || repo.LocalRepo == nil { + log.Infof("App %s is cloned but its repo is not registered. Registering repo %s...", app.Name, app.RepoName) + path, err := app.GetLocalRepoPath() + if err != nil { + log.WithError(err).Errorf("Error getting local repo path for %s.", app.Name) + } + b.AddLocalRepo(&LocalRepo{ + Name: app.RepoName, + Path: path, + }) + } + continue } - log.Debugf("Found app with no cloned repo: %s from %s", app.Name, app.Repo) + log.Debugf("Found app with no cloned repo: %s from %s", app.Name, app.RepoName) for _, root := range b.ws.GitRoots { - clonedFolder := filepath.Join(root, app.Repo) + clonedFolder := filepath.Join(root, app.RepoName) if _, err := os.Stat(clonedFolder); err != nil { if os.IsNotExist(err) { log.Debugf("App %s not found at %s", app.Name, clonedFolder) @@ -466,6 +770,7 @@ func (b *Bosun) TidyWorkspace() { break } } + } for _, importPath := range b.ws.Imports { @@ -543,13 +848,13 @@ func (b *Bosun) ConfirmEnvironment() error { return errors.Errorf("The %q environment is protected, so you must confirm that you want to perform this action.\n(you can do this by setting the --confirm-env to the name of the environment)", b.env.Name) } -func (b *Bosun) GetTools() []ToolDef { +func (b *Bosun) GetTools() []*ToolDef { return b.ws.MergedBosunFile.Tools } func (b *Bosun) GetTool(name string) (ToolDef, error) { for _, tool := range b.ws.MergedBosunFile.Tools { if tool.Name == name { - return tool, nil + return *tool, nil } } return ToolDef{}, errors.Errorf("no tool named %q is known", name) @@ -604,3 +909,66 @@ func (b *Bosun) GetTestSuite(name string) (*E2ESuite, error) { return nil, errors.Errorf("no test suite found with name %q", name) } + +func (b *Bosun) GetRepo(name string) (*Repo, error) { + repos := b.GetRepos() + for _, repo := range repos { + if repo.Name == name { + return repo, nil + } + } + return nil, errors.Errorf("no repo with name %q", name) +} + +func (b *Bosun) GetRepos() []*Repo { + + if len(b.repos) == 0 { + b.repos = map[string]*Repo{} + for _, repoConfig := range b.ws.MergedBosunFile.Repos { + for _, app := range b.ws.MergedBosunFile.Apps { + if app.RepoName == repoConfig.Name { + var repo *Repo + var ok bool + if repo, ok = b.repos[repoConfig.Name]; !ok { + repo = &Repo{ + RepoConfig: *repoConfig, + Apps: map[string]*AppConfig{}, + } + if lr, ok := b.ws.LocalRepos[repo.Name]; ok { + repo.LocalRepo = lr + } + b.repos[repo.Name] = repo + } + repo.Apps[app.Name] = app + } + } + } + + } + + var names []string + for name := range b.repos { + names = append(names, name) + } + + sort.Strings(names) + + var out []*Repo + + for _, name := range names { + out = append(out, b.repos[name]) + } + + return out +} + +func (b *Bosun) AddLocalRepo(localRepo *LocalRepo) { + if b.ws.LocalRepos == nil { + b.ws.LocalRepos = map[string]*LocalRepo{} + } + b.ws.LocalRepos[localRepo.Name] = localRepo + + if repo, ok := b.repos[localRepo.Name]; ok { + repo.LocalRepo = localRepo + } +} diff --git a/pkg/bosun/command_value_test.go b/pkg/bosun/command_value_test.go index 5f8d191..d2ee9a1 100644 --- a/pkg/bosun/command_value_test.go +++ b/pkg/bosun/command_value_test.go @@ -12,6 +12,10 @@ import ( "strings" ) +func yamlize(y string) string { + return strings.Replace(y, "\t", " ", -1) +} + type container struct { DV *CommandValue `yaml:"dv" json:"dv"` } @@ -146,7 +150,7 @@ echo %testVar% }) It("should include env values", func() { - ctx = ctx.WithReleaseValues(&ReleaseValues{ + ctx = ctx.WithPersistableValues(&PersistableValues{ Values: Values{ "test": Values{ "nested": "value", diff --git a/pkg/bosun/config.go b/pkg/bosun/config.go index db658a2..ab83bc1 100644 --- a/pkg/bosun/config.go +++ b/pkg/bosun/config.go @@ -6,3 +6,11 @@ type ConfigShared struct { Description string `yaml:"description,omitempty" json:"description,omitempty"` File *File `yaml:"-" json:"-"` } + +func (c *ConfigShared) SetFromPath(fp string) { + c.FromPath = fp +} + +func (c *ConfigShared) SetParent(p *File) { + c.File = p +} diff --git a/pkg/bosun/constants.go b/pkg/bosun/constants.go index 5139d2e..3379b66 100644 --- a/pkg/bosun/constants.go +++ b/pkg/bosun/constants.go @@ -20,3 +20,12 @@ const ( ) var ErrNotCloned = errors.New("not cloned") + +const ( + LabelName = "name" + LabelPath = "path" + LabelBranch = "branch" + LabelCommit = "commit" + LabelVersion = "version" + LabelDeployable = "deployable" +) diff --git a/pkg/bosun/context.go b/pkg/bosun/context.go index 4992342..29b407f 100644 --- a/pkg/bosun/context.go +++ b/pkg/bosun/context.go @@ -5,6 +5,7 @@ import ( "fmt" vault "github.com/hashicorp/vault/api" "github.com/naveego/bosun/pkg" + "github.com/naveego/bosun/pkg/util" "github.com/pkg/errors" "github.com/sirupsen/logrus" "gopkg.in/yaml.v2" @@ -16,14 +17,14 @@ import ( ) type BosunContext struct { - Bosun *Bosun - Env *EnvironmentConfig - Dir string - Log *logrus.Entry - ReleaseValues *ReleaseValues - Release *Release - AppRepo *AppRepo - AppRelease *AppRelease + Bosun *Bosun + Env *EnvironmentConfig + Dir string + Log *logrus.Entry + Values *PersistableValues + // Release *Deploy + AppRepo *App + AppRelease *AppDeploy valuesAsEnvVars map[string]string ctx context.Context contextValues map[string]interface{} @@ -79,36 +80,37 @@ func (c BosunContext) GetKeyedValue(key string) interface{} { return c.contextValues[key] } -func (c BosunContext) WithRelease(r *Release) BosunContext { - c.Release = r - c.Log = c.Log.WithField("release", r.Name) - c.LogLine(1, "[Context] Changed Release.") - return c -} +// +// func (c BosunContext) WithRelease(r *Deploy) BosunContext { +// c.Release = r +// c.Log = c.Log.WithField("release", r.Name) +// c.LogLine(1, "[Context] Changed Deploy.") +// return c +// } -func (c BosunContext) WithAppRepo(a *AppRepo) BosunContext { +func (c BosunContext) WithApp(a *App) BosunContext { if c.AppRepo == a { return c } c.AppRepo = a - c.Log = c.Log.WithField("repo", a.Name) + c.Log = c.Log.WithField("app", a.Name) c.Log.Debug("") - c.LogLine(1, "[Context] Changed AppRepo.") + c.LogLine(1, "[Context] Changed App.") return c.WithDir(a.FromPath) } -func (c BosunContext) WithAppRelease(a *AppRelease) BosunContext { +func (c BosunContext) WithAppDeploy(a *AppDeploy) BosunContext { if c.AppRelease == a { return c } c.AppRelease = a - c.Log = c.Log.WithField("app", a.Name) - c.LogLine(1, "[Context] Changed AppRelease.") - return c + c.Log = c.Log.WithField("appDeploy", a.Name) + c.LogLine(1, "[Context] Changed AppDeploy.") + return c.WithDir(a.AppConfig.FromPath) } -func (c BosunContext) WithReleaseValues(v *ReleaseValues) BosunContext { - c.ReleaseValues = v +func (c BosunContext) WithPersistableValues(v *PersistableValues) BosunContext { + c.Values = v c.valuesAsEnvVars = nil yml, _ := yaml.Marshal(v.Values) @@ -119,12 +121,15 @@ func (c BosunContext) WithReleaseValues(v *ReleaseValues) BosunContext { func (c BosunContext) GetValuesAsEnvVars() map[string]string { if c.valuesAsEnvVars == nil { - if c.ReleaseValues != nil { - c.valuesAsEnvVars = c.ReleaseValues.Values.ToEnv("BOSUN_") + if c.Values != nil { + c.valuesAsEnvVars = c.Values.Values.ToEnv("BOSUN_") } else { - c.valuesAsEnvVars = map[string]string{ - EnvCluster: c.Env.Cluster, - EnvDomain: c.Env.Domain, + if c.Env != nil { + + c.valuesAsEnvVars = map[string]string{ + EnvCluster: c.Env.Cluster, + EnvDomain: c.Env.Domain, + } } } } @@ -163,7 +168,10 @@ func (c BosunContext) WithTimeout(timeout time.Duration) BosunContext { // It will also expand some environment variables: // $ENVIRONMENT and $BOSUN_ENVIRONMENT => Env.Name // $DOMAIN AND BOSUN_DOMAIN => Env.Domain -func (c BosunContext) ResolvePath(path string) string { +// It will also include any additional expansions provided. +func (c BosunContext) ResolvePath(path string, expansions ...string) string { + + expMap := util.StringSliceToMap(expansions...) path = os.Expand(path, func(name string) string { switch name { case "ENVIRONMENT", "BOSUN_ENVIRONMENT": @@ -171,6 +179,9 @@ func (c BosunContext) ResolvePath(path string) string { case "DOMAIN", "BOSUN_DOMAIN": return c.Env.Domain default: + if v, ok := expMap[name]; ok { + return v + } return name } }) @@ -193,8 +204,8 @@ func (c BosunContext) GetTemplateArgs() pkg.TemplateValues { Cluster: c.Env.Cluster, Domain: c.Env.Domain, } - if c.ReleaseValues != nil { - values := c.ReleaseValues.Values + if c.Values != nil { + values := c.Values.Values values.MustSetAtPath("cluster", c.Env.Cluster) values.MustSetAtPath("domain", c.Env.Domain) tv.Values = values @@ -240,33 +251,34 @@ func (c BosunContext) UseMinikubeForDockerIfAvailable() { }) } -func (c BosunContext) AddAppFileToReleaseBundle(path string, content []byte) (string, error) { - app := c.AppRepo - if app == nil { - return "", errors.New("no app set in context") - } - release := c.Release - if release == nil { - return "", errors.New("no release set in context") - } - - bundleFilePath := release.AddBundleFile(app.Name, path, content) - return bundleFilePath, nil -} - -func (c BosunContext) GetAppFileFromReleaseBundle(path string) ([]byte, string, error) { - app := c.AppRepo - if app == nil { - return nil, "", errors.New("no app set in context") - } - release := c.Release - if release == nil { - return nil, "", errors.New("no release set in context") - } - - content, bundleFilePath, err := release.GetBundleFileContent(app.Namespace, path) - return content, bundleFilePath, err -} +// +// func (c BosunContext) AddAppFileToReleaseBundle(path string, content []byte) (string, error) { +// app := c.AppRepo +// if app == nil { +// return "", errors.New("no app set in context") +// } +// release := c.Release +// if release == nil { +// return "", errors.New("no release set in context") +// } +// +// bundleFilePath := release.AddBundleFile(app.Name, path, content) +// return bundleFilePath, nil +// } +// +// func (c BosunContext) GetAppFileFromReleaseBundle(path string) ([]byte, string, error) { +// app := c.AppRepo +// if app == nil { +// return nil, "", errors.New("no app set in context") +// } +// release := c.Release +// if release == nil { +// return nil, "", errors.New("no release set in context") +// } +// +// content, bundleFilePath, err := release.GetBundleFileContent(app.Namespace, path) +// return content, bundleFilePath, err +// } func (c BosunContext) IsVerbose() bool { return c.GetParams().Verbose diff --git a/pkg/bosun/deploy.go b/pkg/bosun/deploy.go new file mode 100644 index 0000000..c73ba83 --- /dev/null +++ b/pkg/bosun/deploy.go @@ -0,0 +1,439 @@ +package bosun + +import ( + "bufio" + "github.com/naveego/bosun/pkg" + "github.com/naveego/bosun/pkg/filter" + "github.com/pkg/errors" + "io" + "os/exec" + "regexp" + "sort" + "strings" +) + +// +// type ReleaseConfig struct { +// Name string `yaml:"name" json:"name"` +// Version string `yaml:"version" json:"version"` +// Description string `yaml:"description" json:"description"` +// FromPath string `yaml:"fromPath" json:"fromPath"` +// Manifest *ReleaseManifest `yaml:"manifest"` +// // AppReleaseConfigs map[string]*AppReleaseConfig `yaml:"apps" json:"apps"` +// Exclude map[string]bool `yaml:"exclude,omitempty" json:"exclude,omitempty"` +// IsPatch bool `yaml:"isPatch,omitempty" json:"isPatch,omitempty"` +// Parent *File `yaml:"-" json:"-"` +// BundleFiles map[string]*BundleFile `yaml:"bundleFiles,omitempty"` +// } + +type BundleFile struct { + App string `yaml:"namespace"` + Path string `yaml:"path"` + Content []byte `yaml:"-"` +} + +// +// func (r *ReleaseConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { +// type rcm ReleaseConfig +// var proxy rcm +// err := unmarshal(&proxy) +// if err == nil { +// if proxy.Exclude == nil { +// proxy.Exclude = map[string]bool{} +// } +// *r = ReleaseConfig(proxy) +// } +// return err +// } +// +// func (r *ReleaseConfig) Save() error { +// +// return nil +// } + +type Deploy struct { + *DeploySettings + AppDeploys map[string]*AppDeploy + Filtered map[string]bool // contains any app deploys which were part of the release but which were filtered out +} + +type DeploySettings struct { + Environment *EnvironmentConfig + ValueSets []ValueSet + Manifest *ReleaseManifest + Apps map[string]*App + AppDeploySettings map[string]AppDeploySettings + UseLocalContent bool + Filter *filter.Chain // If set, only apps which match the filter will be deployed. + IgnoreDependencies bool + ForceDeployApps map[string]bool +} + +type AppDeploySettings struct { + Environment *EnvironmentConfig + ValueSets []ValueSet + UseLocalContent bool // if true, the app will be deployed using the local chart +} + +func (d DeploySettings) GetAppDeploySettings(name string) AppDeploySettings { + appSettings := d.AppDeploySettings[name] + + // combine deploy and app value sets, with app value sets at a higher priority: + vs := append([]ValueSet{}, d.ValueSets...) + appSettings.ValueSets = append(vs, appSettings.ValueSets...) + + appSettings.Environment = d.Environment + appSettings.UseLocalContent = d.UseLocalContent + + return appSettings +} + +func NewDeploy(ctx BosunContext, settings DeploySettings) (*Deploy, error) { + deploy := &Deploy{ + DeploySettings: &settings, + AppDeploys: AppDeployMap{}, + } + + if settings.Manifest != nil { + for _, manifest := range settings.Manifest.AppManifests { + if !settings.Manifest.DefaultDeployApps[manifest.Name] { + if !settings.ForceDeployApps[manifest.Name] { + ctx.Log.Debugf("Skipping %q because it is not default nor forced.", manifest.Name) + } + } + appManifest := settings.Manifest.AppManifests[manifest.Name] + appDeploy, err := NewAppDeploy(ctx, settings, appManifest) + if err != nil { + return nil, errors.Wrapf(err, "create app deploy from manifest for %q", appManifest.Name) + } + deploy.AppDeploys[appDeploy.Name] = appDeploy + } + } else if len(settings.Apps) > 0 { + + for _, app := range settings.Apps { + appManifest, err := app.GetManifest(ctx) + if err != nil { + return nil, errors.Wrapf(err, "create app manifest for %q", app.Name) + } + appDeploy, err := NewAppDeploy(ctx, settings, appManifest) + if err != nil { + return nil, errors.Wrapf(err, "create app deploy from manifest for %q", appManifest.Name) + } + deploy.AppDeploys[appDeploy.Name] = appDeploy + } + } else { + return nil, errors.New("either settings.Manifest or settings.Apps must be populated") + } + + if settings.Filter != nil { + appDeploys := deploy.AppDeploys + filtered, err := settings.Filter.FromErr(appDeploys) + if err != nil { + return nil, errors.Wrap(err, "all apps were filtered out") + } + deploy.AppDeploys = filtered.(map[string]*AppDeploy) + deploy.Filtered = map[string]bool{} + for name := range appDeploys { + if _, ok := deploy.AppDeploys[name]; !ok { + ctx.Log.Warnf("App %q was filtered out of the release.", name) + deploy.Filtered[name] = true + } + } + } + + return deploy, nil + +} + +type AppDeployMap map[string]*AppDeploy + +func (a AppDeployMap) GetAppsSortedByName() AppReleasesSortedByName { + var out AppReleasesSortedByName + for _, app := range a { + out = append(out, app) + } + + sort.Sort(out) + return out +} + +func (a *AppDeploy) Validate(ctx BosunContext) []error { + + var errs []error + + out, err := pkg.NewCommand("helm", "search", a.AppConfig.Chart, "-v", a.Version.String()).RunOut() + if err != nil { + errs = append(errs, errors.Errorf("search for %s@%s failed: %s", a.AppConfig.Chart, a.Version, err)) + } + if !strings.Contains(out, a.AppConfig.Chart) { + errs = append(errs, errors.Errorf("chart %s@%s not found", a.AppConfig.Chart, a.Version)) + } + + if !a.AppConfig.BranchForRelease { + return errs + } + + values, err := a.GetResolvedValues(ctx) + if err != nil { + return []error{err} + } + + for _, imageConfig := range a.AppConfig.GetImages() { + + tag, ok := values.Values["tag"].(string) + if !ok { + tag = a.Version.String() + } + + imageName := imageConfig.GetFullNameWithTag(tag) + err = checkImageExists(ctx, imageName) + + if err != nil { + errs = append(errs, errors.Errorf("image %q: %s", imageConfig, err)) + } + } + + // if a.App.IsCloned() { + // appBranch := a.App.GetBranchName() + // if appBranch != a.Branch { + // errs = append(errs, errors.Errorf("app was added to release from branch %s, but is currently on branch %s", a.Branch, appBranch)) + // } + // + // appCommit := a.App.GetCommit() + // if appCommit != a.Commit { + // errs = append(errs, errors.Errorf("app was added to release at commit %s, but is currently on commit %s", a.Commit, appCommit)) + // } + // } + + return errs +} + +func checkImageExists(ctx BosunContext, name string) error { + + ctx.UseMinikubeForDockerIfAvailable() + + cmd := exec.Command("docker", "pull", name) + stdout, err := cmd.StdoutPipe() + stderr, err := cmd.StderrPipe() + if err != nil { + return err + } + reader := io.MultiReader(stdout, stderr) + scanner := bufio.NewScanner(reader) + + if err := cmd.Start(); err != nil { + return err + } + + defer cmd.Process.Kill() + + var lines []string + + for scanner.Scan() { + line := scanner.Text() + lines = append(lines, line) + if strings.Contains(line, "Pulling from") { + return nil + } + if strings.Contains(line, "Error") { + return errors.New(line) + } + } + + if err := scanner.Err(); err != nil { + return err + } + + cmd.Process.Kill() + + state, err := cmd.Process.Wait() + if err != nil { + return err + } + + if !state.Success() { + return errors.Errorf("Pull failed: %s\n%s", state.String(), strings.Join(lines, "\n")) + } + + return nil +} + +// +// func (r *Deploy) IncludeDependencies(ctx BosunContext) error { +// ctx = ctx.WithRelease(r) +// deps := ctx.Bosun.GetAppDependencyMap() +// var appNames []string +// for _, app := range r.AppReleaseConfigs { +// appNames = append(appNames, app.Name) +// } +// +// // this is inefficient but it gets us all the dependencies +// topology, err := GetDependenciesInTopologicalOrder(deps, appNames...) +// +// if err != nil { +// return errors.Errorf("repos could not be sorted in dependency order: %s", err) +// } +// +// for _, dep := range topology { +// if r.AppReleaseConfigs[dep] == nil { +// if _, ok := r.Exclude[dep]; ok { +// ctx.Log.Warnf("Dependency %q is not being added because it is in the exclude list. "+ +// "Add it using the `add` command if want to override this exclusion.", dep) +// continue +// } +// app, err := ctx.Bosun.GetApp(dep) +// if err != nil { +// return errors.Errorf("an app or dependency %q could not be found: %s", dep, err) +// } else { +// err = r.MakeAppAvailable(ctx, app) +// if err != nil { +// return errors.Errorf("could not include app %q: %s", app.Name, err) +// } +// } +// } +// } +// return nil +// } + +func (r *Deploy) Deploy(ctx BosunContext) error { + + var requestedAppNames []string + dependencies := map[string][]string{} + for _, app := range r.AppDeploys { + requestedAppNames = append(requestedAppNames, app.Name) + for _, dep := range app.AppConfig.DependsOn { + dependencies[app.Name] = append(dependencies[app.Name], dep.Name) + } + } + + topology, err := GetDependenciesInTopologicalOrder(dependencies, requestedAppNames...) + + if err != nil { + return errors.Errorf("repos could not be sorted in dependency order: %s", err) + } + + var toDeploy []*AppDeploy + + for _, dep := range topology { + app, ok := r.AppDeploys[dep] + if !ok { + if r.IgnoreDependencies { + continue + } + if filtered := r.Filtered[dep]; filtered { + continue + } + + return errors.Errorf("an app specifies a dependency that could not be found: %q (filtered: %#v)", dep, r.Filtered) + } + + if app.DesiredState.Status == StatusUnchanged { + ctx.WithAppDeploy(app).Log.Infof("Skipping deploy because desired state was %q.", StatusUnchanged) + continue + } + + toDeploy = append(toDeploy, app) + } + + for _, app := range toDeploy { + + app.DesiredState.Status = StatusDeployed + if app.DesiredState.Routing == "" { + app.DesiredState.Routing = RoutingCluster + } + + ctx.Bosun.SetDesiredState(app.Name, app.DesiredState) + + app.DesiredState.Force = ctx.GetParams().Force + + err = app.Reconcile(ctx) + + if err != nil { + return err + } + } + + err = ctx.Bosun.Save() + return err +} + +// +// func (r *Deploy) MakeAppAvailable(ctx BosunContext, app *App) error { +// +// var err error +// var config *AppReleaseConfig +// if r.AppReleaseConfigs == nil { +// r.AppReleaseConfigs = map[string]*AppReleaseConfig{} +// } +// +// ctx = ctx.WithRelease(r) +// +// config, err = app.GetAppReleaseConfig(ctx) +// if err != nil { +// return errors.Wrap(err, "make app release") +// } +// r.AppReleaseConfigs[app.Name] = config +// +// r.Apps[app.Name], err = NewAppRelease(ctx, config) +// +// return nil +// } +// +// func (r *Deploy) AddBundleFile(app string, path string, content []byte) string { +// key := fmt.Sprintf("%s|%s", app, path) +// shortPath := safeFileNameRE.ReplaceAllString(strings.TrimLeft(path, "./\\"), "_") +// bf := &BundleFile{ +// App: app, +// Path: shortPath, +// Content: content, +// } +// if r.BundleFiles == nil { +// r.BundleFiles = map[string]*BundleFile{} +// } +// r.BundleFiles[key] = bf +// return filepath.Join("./", r.Name, app, shortPath) +// } +// +// // GetBundleFileContent returns the content and path to a bundle file, or an error if it fails. +// func (r *Deploy) GetBundleFileContent(app, path string) ([]byte, string, error) { +// key := fmt.Sprintf("%s|%s", app, path) +// bf, ok := r.BundleFiles[key] +// if !ok { +// return nil, "", errors.Errorf("no bundle for app %q and path %q", app, path) +// } +// +// bundleFilePath := filepath.Join(filepath.Dir(r.FromPath), r.Name, bf.App, bf.Path) +// content, err := ioutil.ReadFile(bundleFilePath) +// return content, bundleFilePath, err +// } +// +// func (r *ReleaseConfig) SaveBundle() error { +// bundleDir := filepath.Join(filepath.Dir(r.FromPath), r.Name) +// +// err := os.MkdirAll(bundleDir, 0770) +// if err != nil { +// return err +// } +// +// for _, bf := range r.BundleFiles { +// if bf.Content == nil { +// continue +// } +// +// appDir := filepath.Join(bundleDir, bf.App) +// err := os.MkdirAll(bundleDir, 0770) +// if err != nil { +// return err +// } +// +// bundleFilepath := filepath.Join(appDir, bf.Path) +// err = ioutil.WriteFile(bundleFilepath, bf.Content, 0770) +// if err != nil { +// return errors.Wrapf(err, "writing bundle file for app %q, path %q", bf.App, bf.Path) +// } +// } +// +// return nil +// } + +var safeFileNameRE = regexp.MustCompile(`([^A-z0-9_.]+)`) diff --git a/pkg/bosun/e2e.go b/pkg/bosun/e2e.go index de547c6..6aa9bc8 100644 --- a/pkg/bosun/e2e.go +++ b/pkg/bosun/e2e.go @@ -144,7 +144,7 @@ func (s *E2ESuite) LoadTests(ctx BosunContext) error { func (s *E2ESuite) Run(ctx BosunContext, tests ...string) ([]*E2EResult, error) { runID := xid.New().String() - releaseValues := &ReleaseValues{ + releaseValues := &PersistableValues{ Values: Values{ "e2e": Values{ "runID": runID, @@ -154,7 +154,7 @@ func (s *E2ESuite) Run(ctx BosunContext, tests ...string) ([]*E2EResult, error) ctx = ctx.WithDir(s.FromPath). WithLogField("suite", s.Name). - WithReleaseValues(releaseValues) + WithPersistableValues(releaseValues) err := s.LoadTests(ctx) if err != nil { diff --git a/pkg/bosun/environment.go b/pkg/bosun/environment.go index 13312c7..2a4fa94 100644 --- a/pkg/bosun/environment.go +++ b/pkg/bosun/environment.go @@ -22,8 +22,9 @@ type EnvironmentConfig struct { Scripts []*Script `yaml:"scripts,omitempty" json:"scripts,omitempty"` // Contains app value overrides which should be applied when deploying // apps to this environment. - AppValues *AppValuesConfig `yaml:"appValues" json:"appValues"` + AppValues *ValueSet `yaml:"appValues" json:"appValues"` HelmRepos []HelmRepo `yaml:"helmRepos,omitempty" json:"helmRepos,omitempty"` + ValueSets []string `yaml:"valueSets,omitempty" json:"valueSets,omitempty"` } type EnvironmentVariable struct { @@ -40,8 +41,8 @@ type EnvironmentCommand struct { } type HelmRepo struct { - Name string `yaml:"name" json:"name"` - URL string `yaml:"url" json:"url"` + Name string `yaml:"name" json:"name"` + URL string `yaml:"url" json:"url"` Environment map[string]string `yaml:"environment" json:"environment"` } @@ -117,7 +118,7 @@ func (e *EnvironmentConfig) ForceEnsure(ctx BosunContext) error { _, err := pkg.NewCommand("kubectl", "config", "use-context", e.Cluster).RunOut() if err != nil { log.Println(color.RedString("Error setting kubernetes context: %s\n", err)) - log.Println(color.YellowString(`try running "bosun kube add-eks %s"`, e.Cluster )) + log.Println(color.YellowString(`try running "bosun kube add-eks %s"`, e.Cluster)) } for _, v := range e.Variables { diff --git a/pkg/bosun/file.go b/pkg/bosun/file.go index b834482..3c7d460 100644 --- a/pkg/bosun/file.go +++ b/pkg/bosun/file.go @@ -2,6 +2,8 @@ package bosun import ( "fmt" + "github.com/imdario/mergo" + "github.com/naveego/bosun/pkg/mirror" "github.com/pkg/errors" "gopkg.in/yaml.v2" "io/ioutil" @@ -11,159 +13,155 @@ import ( // File represents a loaded bosun.yaml file. type File struct { Imports []string `yaml:"imports,omitempty" json:"imports"` - Environments []*EnvironmentConfig `yaml:"environments" json:"environments"` - AppRefs map[string]*Dependency `yaml:"appRefs" json:"appRefs"` - Apps []*AppRepoConfig `yaml:"apps" json:"apps"` + Environments []*EnvironmentConfig `yaml:"environments,omitempty" json:"environments"` + AppRefs map[string]*Dependency `yaml:"appRefs,omitempty" json:"appRefs"` + Apps []*AppConfig `yaml:"apps,omitempty" json:"apps"` + Repos []*RepoConfig `yaml:"repos,omitempty" json:"repos"` FromPath string `yaml:"fromPath" json:"fromPath"` Config *Workspace `yaml:"-" json:"-"` - Releases []*ReleaseConfig `yaml:"releases,omitempty" json:"releases"` - Tools []ToolDef `yaml:"tools,omitempty" json:"tools"` + Tools []*ToolDef `yaml:"tools,omitempty" json:"tools"` TestSuites []*E2ESuiteConfig `yaml:"testSuites,omitempty" json:"testSuites,omitempty"` Scripts []*Script `yaml:"scripts,omitempty" json:"scripts,omitempty"` + ValueSets []*ValueSet `yaml:"valueSets,omitempty" json:"valueSets,omitempty"` + Platforms []*Platform `yaml:"platforms,omitempty" json:"platforms,omitempty"` // merged indicates that this File has had File instances merged into it and cannot be saved. merged bool `yaml:"-" json:"-"` } -func (c *File) SetFromPath(path string) { +func (f *File) MarshalYAML() (interface{}, error) { + if f == nil { + return nil, nil + } + type proxy File + p := proxy(*f) - c.FromPath = path + return &p, nil +} - for _, e := range c.Environments { - e.SetFromPath(path) +func (f *File) UnmarshalYAML(unmarshal func(interface{}) error) error { + type proxy File + var p proxy + if f != nil { + p = proxy(*f) } - for _, m := range c.Apps { - m.SetFragment(c) - } + err := unmarshal(&p) - for _, m := range c.AppRefs { - m.FromPath = path + if err == nil { + *f = File(p) } - for _, m := range c.Releases { - m.SetParent(c) - } + return err +} - for _, s := range c.Scripts { - s.SetFromPath(path) - } +type ParentSetter interface { + SetParent(*File) +} - for i := range c.Tools { - c.Tools[i].FromPath = c.FromPath - } +type FromPathSetter interface { + SetFromPath(string) +} - for i := range c.TestSuites { - c.TestSuites[i].SetFromPath(c.FromPath) - } +func (f *File) SetFromPath(path string) { + + f.FromPath = path + + mirror.ApplyFuncRecursively(f, func(x ParentSetter) { + x.SetParent(f) + }) + + mirror.ApplyFuncRecursively(f, func(x FromPathSetter) { + x.SetFromPath(f.FromPath) + }) } -func (c *File) Merge(other *File) error { +func (f *File) Merge(other *File) error { - c.merged = true + f.merged = true for _, otherEnv := range other.Environments { - err := c.mergeEnvironment(otherEnv) + err := f.mergeEnvironment(otherEnv) if err != nil { return errors.Wrap(err, "merge environment") } } - if c.AppRefs == nil { - c.AppRefs = make(map[string]*Dependency) - } - - for k, other := range other.AppRefs { - other.Name = k - c.AppRefs[k] = other - } - for _, otherApp := range other.Apps { - if err := c.mergeApp(otherApp); err != nil { + if err := f.mergeApp(otherApp); err != nil { return errors.Wrapf(err, "merge app %q", otherApp.Name) } } - for _, release := range other.Releases { - if err := c.mergeRelease(release); err != nil { - return errors.Wrapf(err, "merge release %q", release.Name) - } - } - - for _, other := range other.Scripts { - c.Scripts = append(c.Scripts, other) - } - - c.TestSuites = append(c.TestSuites, other.TestSuites...) - c.Tools = append(c.Tools, other.Tools...) + err := mergo.Merge(f, other, mergo.WithAppendSlice) + return err - return nil + // + // f.Scripts = append(f.Scripts, other.Scripts...) + // f.Repos = append(f.Repos, other.Repos...) + // f.TestSuites = append(f.TestSuites, other.TestSuites...) + // f.Tools = append(f.Tools, other.Tools...) + // + // return nil } -func (c *File) Save() error { - if c.merged { +func (f *File) Save() error { + if f.merged { panic("a merged File cannot be saved") } - b, err := yaml.Marshal(c) + b, err := yaml.Marshal(f) if err != nil { return err } b = stripFromPath.ReplaceAll(b, []byte{}) - err = ioutil.WriteFile(c.FromPath, b, 0600) + err = ioutil.WriteFile(f.FromPath, b, 0600) if err != nil { return err } - for _, release := range c.Releases { - err = release.SaveBundle() - if err != nil { - return errors.Wrapf(err, "saving bundle for release %q", release.Name) - } - } - return nil } var stripFromPath = regexp.MustCompile(`\s*fromPath:.*`) -func (c *File) mergeApp(incoming *AppRepoConfig) error { - for _, app := range c.Apps { +func (f *File) mergeApp(incoming *AppConfig) error { + for _, app := range f.Apps { if app.Name == incoming.Name { return errors.Errorf("app %q imported from %q, but it was already imported from %q", incoming.Name, incoming.FromPath, app.FromPath) } } - c.Apps = append(c.Apps, incoming) + f.Apps = append(f.Apps, incoming) return nil } -func (c *File) mergeEnvironment(env *EnvironmentConfig) error { +func (f *File) mergeEnvironment(env *EnvironmentConfig) error { if env.Name == "all" { - for _, e := range c.Environments { + for _, e := range f.Environments { e.Merge(env) } return nil } - for _, e := range c.Environments { + for _, e := range f.Environments { if e.Name == env.Name { e.Merge(env) return nil } } - c.Environments = append(c.Environments, env) + f.Environments = append(f.Environments, env) return nil } -func (c *File) GetEnvironmentConfig(name string) *EnvironmentConfig { - for _, e := range c.Environments { +func (f *File) GetEnvironmentConfig(name string) *EnvironmentConfig { + for _, e := range f.Environments { if e.Name == name { return e } @@ -171,15 +169,3 @@ func (c *File) GetEnvironmentConfig(name string) *EnvironmentConfig { panic(fmt.Sprintf("no environment named %q", name)) } - -func (c *File) mergeRelease(release *ReleaseConfig) error { - for _, e := range c.Releases { - if e.Name == release.Name { - return errors.Errorf("already have a release named %q, from %q", release.Name, e.FromPath) - - } - } - - c.Releases = append(c.Releases, release) - return nil -} diff --git a/pkg/bosun/filter.go b/pkg/bosun/filter.go deleted file mode 100644 index 195182a..0000000 --- a/pkg/bosun/filter.go +++ /dev/null @@ -1,193 +0,0 @@ -package bosun - -import ( - "github.com/fatih/color" - "reflect" - "regexp" - "strings" -) - -const ( - FilterKeyName = "name" - FilterKeyBranch = "branch" - FilterKeyCommit = "commit" - FilterKeyVersion = "version" - FilterKeyPath = "path" -) - -type Filter struct { - Key string - Value string - Operator string -} - -type LabelValue interface { - Value() string -} - -type LabelThunk func() string -func (l LabelThunk) Value() string { return l() } - -type Labels map[string]LabelValue - -type LabelString string -func (l LabelString) Value() string { return string(l) } - -func (l *Labels) UnmarshalYAML(unmarshal func(interface{}) error) error { - var arr []string - err := unmarshal(&arr) - proxy := map[string]string{} - if err == nil { - for _, name := range arr { - proxy[name] = "true" - } - } else { - err = unmarshal(proxy) - } - out := Labels{} - - for k, v := range proxy { - out[k] = LabelString(v) - } - *l = out - return err -} - -func FilterMatchAll() []Filter { - return []Filter{{Key: FilterKeyName, Value: ".*", Operator: "?="}} -} - -func FiltersFromNames(names ...string) []Filter { - var out []Filter - for _, name := range names { - out = append(out, - Filter{Key: FilterKeyName, Value: name, Operator: "="}, - Filter{Key: FilterKeyPath, Value: name, Operator: "="}, - ) - } - return out -} - -func FiltersFromArgs(args ...string) []Filter { - var out []Filter - for _, arg := range args { - matches := parseFilterRE.FindStringSubmatch(arg) - if len(matches) != 4 { - color.Red("Invalid filter: %s", arg) - continue - } - out = append(out, Filter{Key: matches[1], Value: matches[3], Operator: matches[2]}) - } - return out -} - -func FiltersFromAppLabels(args ...string) []Filter { - var out []Filter - for _, arg := range args { - segs := strings.Split(arg, "=") - switch len(segs) { - case 1: - out = append(out, Filter{Key: arg, Value: "true", Operator: "="}) - case 2: - out = append(out, Filter{Key: segs[0], Value: segs[1], Operator: "="}) - } - } - return out -} - -var parseFilterRE = regexp.MustCompile(`(\w+)(\W+)(\w+)`) - -func ApplyFilter(from interface{}, includeMatched bool, filters []Filter) interface{} { - fromValue := reflect.ValueOf(from) - var out reflect.Value - - switch fromValue.Kind() { - case reflect.Map: - out = reflect.MakeMap(fromValue.Type()) - keys := fromValue.MapKeys() - for _, key := range keys { - value := fromValue.MapIndex(key) - labelled, ok := value.Interface().(Labelled) - var matched bool - for _, filter := range filters { - if ok { - matched := MatchFilter(labelled, filter) - if matched { - break - } - } - } - if matched == includeMatched { - out.SetMapIndex(key, value) - } - } - case reflect.Slice: - length := fromValue.Len() - out = reflect.MakeSlice(fromValue.Type(), 0, fromValue.Len()) - for i := 0; i < length; i++ { - value := fromValue.Index(i) - labelled, ok := value.Interface().(Labelled) - var matched bool - for _, filter := range filters { - if ok { - matched = MatchFilter(labelled, filter) - if matched { - break - } - } - } - if matched == includeMatched { - out = reflect.Append(out, value) - } - } - } - - return out.Interface() -} - -func ExcludeMatched(from []interface{}, filters []Filter) []interface{} { - var out []interface{} - for _, item := range from { - labelled, ok := item.(Labelled) - for _, filter := range filters { - if ok { - matched := MatchFilter(labelled, filter) - if !matched { - out = append(out, item) - break - } - } - } - } - return out -} - -func MatchFilter(labelled Labelled, filter Filter) bool { - labels := labelled.Labels() - - switch filter.Operator { - case "=", "==", "": - value, ok := labels[filter.Key] - if ok { - return value.Value() == filter.Value - } - case "?=": - re, err := regexp.Compile(filter.Value) - if err != nil { - color.Red("Invalid regex in filter %s?=%s: %s", filter.Key, filter.Value, err) - return false - } - value, ok := labels[filter.Key] - if ok { - return re.MatchString(value.Value()) - } else { - return false - } - } - - return false -} - -type Labelled interface { - Labels() Labels -} diff --git a/pkg/bosun/filter_test.go b/pkg/bosun/filter_test.go deleted file mode 100644 index 03209b8..0000000 --- a/pkg/bosun/filter_test.go +++ /dev/null @@ -1,74 +0,0 @@ -package bosun_test - -import ( - . "github.com/naveego/bosun/pkg/bosun" - . "github.com/onsi/ginkgo" - . "github.com/onsi/gomega" -) - -var _ = XDescribe("Filter", func() { - - var repos = []*AppRepo{ - { - AppRepoConfig: &AppRepoConfig{ - Name: "app-0", - }, - }, - { - AppRepoConfig: &AppRepoConfig{ - Name: "app-1", - }, - }, - { - AppRepoConfig: &AppRepoConfig{ - Name: "app-2", - }, - }, - { - AppRepoConfig: &AppRepoConfig{ - Name: "app-3", - }, - }, - } - - var reposMap = map[string]*AppRepo{ - "app-0": repos[0], - "app-1": repos[1], - "app-2": repos[2], - "app-3": repos[3], - } - - // var releases = []*AppRelease{ - // { - // AppRepo:repos[0], - // - // },{ - // AppRepo:repos[1], - // - // },{ - // AppRepo:repos[2], - // - // },{ - // AppRepo:repos[3], - // - // }, - // } - // - // var releasesMap = map[string]*AppRelease{ - // "app-0": releases[0], - // "app-1": releases[1], - // "app-2": releases[2], - // "app-3": releases[3], - // - // } - - It("should filter app repos in array by name", func() { - actual := ApplyFilter(repos, true, FiltersFromNames("app-2", "app-3")) - Expect(actual).To(BeEquivalentTo([]*AppRepo{repos[2], repos[3]})) - }) - - It("should filter app repos in map by name", func() { - actual := ApplyFilter(reposMap, true, FiltersFromNames("app-2", "app-3")) - Expect(actual).To(BeEquivalentTo(map[string]*AppRepo{"app-2":repos[2], "app-3":repos[3]})) - }) -}) diff --git a/pkg/bosun/local_repo.go b/pkg/bosun/local_repo.go new file mode 100644 index 0000000..080f40c --- /dev/null +++ b/pkg/bosun/local_repo.go @@ -0,0 +1,138 @@ +package bosun + +import ( + "github.com/naveego/bosun/pkg/git" + "github.com/pkg/errors" + "strings" +) + +type LocalRepo struct { + Name string `yaml:"-" json:""` + Path string `yaml:"path,omitempty" json:"path,omitempty"` + branch git.BranchName `yaml:"-" json:"-"` +} + +func (r *LocalRepo) mustBeCloned() { + if r == nil { + panic("not cloned; you should have checked Repo.CheckCloned() before interacting with the local repo") + } +} + +func (r *LocalRepo) IsDirty() bool { + r.mustBeCloned() + g, _ := git.NewGitWrapper(r.Path) + return g.IsDirty() +} + +func (r *LocalRepo) Commit(message string, filesToAdd ...string) error { + r.mustBeCloned() + + g, err := git.NewGitWrapper(r.Path) + if err != nil { + return err + } + + addArgs := append([]string{"add"}, filesToAdd...) + _, err = g.Exec(addArgs...) + if err != nil { + return err + } + + _, err = g.Exec("commit", "-m", message) + + if err != nil { + return err + } + + return nil +} + +func (r *LocalRepo) Push() error { + r.mustBeCloned() + + g, err := git.NewGitWrapper(r.Path) + if err != nil { + return err + } + + _, err = g.Exec("push") + return err +} + +func (r *LocalRepo) Branch(ctx BosunContext, parentBranch string, name string) error { + if r == nil { + return errors.New("not cloned") + } + + g, err := git.NewGitWrapper(r.Path) + if err != nil { + return err + } + + _, err = g.Exec("fetch") + if err != nil { + return err + } + + branches, err := g.Exec("branch", "-a") + if err != nil { + return err + } + + if strings.Contains(branches, name) { + ctx.Log.Info("Checking out release branch...") + _, err = g.Exec("checkout", name) + if err != nil { + return err + } + _, err = g.Exec("pull") + if err != nil { + return err + } + } else { + ctx.Log.Infof("Creating branch %s...", name) + + _, err = g.Exec("checkout", parentBranch) + if err != nil { + return errors.Wrapf(err, "check out parent branch %q", parentBranch) + } + + _, err = g.Exec("pull") + if err != nil { + return errors.Wrapf(err, "pulling parent branch %q", parentBranch) + } + + _, err = g.Exec("branch", name) + if err != nil { + return err + } + _, err = g.Exec("checkout", name) + if err != nil { + return err + } + + _, err = g.Exec("push", "-u", "origin", name) + if err != nil { + return err + } + } + + return nil +} + +func (r *LocalRepo) GetCurrentCommit() string { + r.mustBeCloned() + return r.git().Commit() +} + +func (r *LocalRepo) git() git.GitWrapper { + g, err := git.NewGitWrapper(r.Path) + if err != nil { + panic(err) + } + return g +} + +func (r *LocalRepo) GetCurrentBranch() string { + return r.git().Branch() +} diff --git a/pkg/bosun/platform.go b/pkg/bosun/platform.go new file mode 100644 index 0000000..6ecfd1d --- /dev/null +++ b/pkg/bosun/platform.go @@ -0,0 +1,698 @@ +package bosun + +import ( + "fmt" + "github.com/naveego/bosun/pkg/semver" + "github.com/pkg/errors" + "gopkg.in/yaml.v2" + "io/ioutil" + "math" + "os" + "path/filepath" + "sort" + "strings" +) + +const ( + MasterName = "master" +) + +var ( + MasterVersion = semver.New("0.0.0-master") + MaxVersion = semver.Version{Major: math.MaxInt64} +) + +// Platform is a collection of releasable apps which work together in a single cluster. +// The platform contains a history of all releases created for the platform. +type Platform struct { + ConfigShared `yaml:",inline"` + DefaultChartRepo string `yaml:"defaultChartRepo"` + ReleaseBranchFormat string `yaml:"releaseBranchFormat"` + MasterBranch string `yaml:"masterBranch"` + ReleaseDirectory string `yaml:"releaseDirectory" json:"releaseDirectory"` + MasterMetadata *ReleaseMetadata `yaml:"master" json:"master"` + Plan *ReleasePlan `yaml:"plan,omitempty"` + MasterManifest *ReleaseManifest `yaml:"-" json:"-"` + ReleaseMetadata []*ReleaseMetadata `yaml:"releases" json:"releases"` + Repos []*Repo `yaml:"repos" json:"repos"` + Apps []*AppMetadata `yaml:"apps"` + ReleaseManifests map[string]*ReleaseManifest `yaml:"-" json:"-"` +} + +func (p *Platform) MarshalYAML() (interface{}, error) { + if p == nil { + return nil, nil + } + type proxy Platform + px := proxy(*p) + + return &px, nil +} + +func (p *Platform) UnmarshalYAML(unmarshal func(interface{}) error) error { + type proxy Platform + var px proxy + if p != nil { + px = proxy(*p) + } + + err := unmarshal(&px) + + if err == nil { + *p = Platform(px) + } + + if p.MasterBranch == "" { + p.MasterBranch = "master" + } + if p.ReleaseBranchFormat == "" { + p.ReleaseBranchFormat = "release/*" + } + if p.ReleaseManifests == nil { + p.ReleaseManifests = map[string]*ReleaseManifest{} + } + if p.MasterMetadata != nil { + p.MasterMetadata.Branch = p.MasterBranch + } + + return err +} + +func (p *Platform) GetReleaseMetadataSortedByVersion(descending bool, includeLatest bool) []*ReleaseMetadata { + out := make([]*ReleaseMetadata, len(p.ReleaseMetadata)) + copy(out, p.ReleaseMetadata) + if descending { + sort.Sort(sort.Reverse(releaseMetadataSorting(out))) + } else { + sort.Sort(releaseMetadataSorting(out)) + } + + if includeLatest { + out = append(out, p.MasterMetadata) + } + + return out +} + +func (p *Platform) MakeReleaseBranchName(releaseName string) string { + if releaseName == MasterName { + return p.MasterBranch + } + return strings.Replace(p.ReleaseBranchFormat, "*", releaseName, 1) +} + +type ReleasePlanSettings struct { + Name string + Version semver.Version + BranchParent string + Bump string +} + +func (p *Platform) CreateReleasePlan(ctx BosunContext, settings ReleasePlanSettings) (*ReleasePlan, error) { + ctx.Log.Debug("Creating new release plan.") + if p.Plan != nil { + return nil, errors.Errorf("another plan is currently being edited, commit or discard the plan before starting a new one") + } + + var err error + if settings.Bump == "" && settings.Version.Empty() { + return nil, errors.New("either version or bump must be provided") + } + if settings.Bump != "" { + previousReleaseMetadata := p.GetPreviousReleaseMetadata(MaxVersion) + if previousReleaseMetadata == nil { + previousReleaseMetadata = p.MasterMetadata + } + settings.Version, err = previousReleaseMetadata.Version.Bump(settings.Bump) + if err != nil { + return nil, errors.WithStack(err) + } + } + if settings.Name == "" { + settings.Name = settings.Version.String() + } + + metadata := &ReleaseMetadata{ + Version: settings.Version, + Name: settings.Name, + Branch: p.MakeReleaseBranchName(settings.Name), + } + + if settings.BranchParent != "" { + branchParentMetadata, err := p.GetReleaseMetadataByName(settings.BranchParent) + if err != nil { + return nil, errors.Wrapf(err, "getting patch parent") + } + metadata.PreferredParentBranch = branchParentMetadata.Branch + } + + existing, _ := p.GetReleaseMetadataByName(metadata.Name) + if existing == nil { + existing, _ = p.GetReleaseMetadataByVersion(metadata.Version) + } + if existing != nil { + return nil, errors.Errorf("release already exists with name %q and version %v", metadata.Name, metadata.Version) + } + + metadata.Branch = p.ReleaseBranchFormat + + manifest := &ReleaseManifest{ + ReleaseMetadata: metadata, + } + manifest.init() + + latestManifest, err := p.GetMasterManifest() + if err != nil { + return nil, errors.Wrap(err, "get latest manifest") + } + + plan := NewReleasePlan(metadata) + + previousMetadata := p.GetPreviousReleaseMetadata(metadata.Version) + if previousMetadata == nil { + previousMetadata = p.GetMasterMetadata() + } + + var previousManifest *ReleaseManifest + previousManifest, err = p.GetReleaseManifest(previousMetadata, true) + if err != nil { + return nil, errors.Wrap(err, "get previous release manifest") + } + ctx.Log.Infof("Treating release %s as the previous release.", previousMetadata) + fetched := map[string][]string{} + + for appName, appManifest := range latestManifest.AppManifests { + log := ctx.Log.WithField("app", appName) + + appPlan := &AppPlan{ + Name: appName, + Repo: appManifest.Repo, + } + app, err := ctx.Bosun.GetApp(appName) + if err != nil { + log.WithError(err).Warn("Could not get app.") + continue + } + if !app.HasChart() { + continue + } + + if previousAppMetadata, ok := previousManifest.AppMetadata[appName]; ok { + appPlan.PreviousReleaseName = previousManifest.Name + appPlan.FromBranch = previousAppMetadata.Branch + appPlan.PreviousReleaseVersion = previousAppMetadata.Version.String() + appPlan.CurrentVersionInMaster = app.Version.String() + + if app.BranchForRelease && app.IsRepoCloned() { + var changes []string + if changes, ok = fetched[app.RepoName]; !ok { + localRepo := app.Repo.LocalRepo + g := localRepo.git() + log.Info("Fetching latest commits.") + err = g.Fetch() + if err != nil { + log.WithError(err).Warn("Couldn't fetch.") + } else { + changes, err = g.ExecLines("log", "--left-right", "--cherry-pick", "--no-merges", "--oneline", "--no-color", fmt.Sprintf("%s...origin/%s", p.MasterBranch, appPlan.FromBranch)) + if err != nil { + log.WithError(err).Warn("Couldn't find unreleased commits.") + } + } + fetched[app.RepoName] = changes + } + + appPlan.CommitsNotInPreviousRelease = changes + } + } else { + appPlan.PreviousReleaseName = MasterName + appPlan.FromBranch = p.MasterBranch + } + plan.Apps[appName] = appPlan + } + + ctx.Log.Infof("Created new release plan %s.", manifest) + p.Plan = plan + + return plan, nil +} + +func (p *Platform) RePlanRelease(ctx BosunContext, metadata *ReleaseMetadata) (*ReleasePlan, error) { + if p.Plan != nil { + return nil, errors.Errorf("another plan is currently being edited, commit or discard the plan before starting a new one") + } + + manifest, err := p.GetReleaseManifest(metadata, false) + if err != nil { + return nil, err + } + + p.Plan = manifest.Plan + + ctx.Log.Infof("Readied new release plan for %s.", manifest) + + return p.Plan, nil +} + +func (p *Platform) CommitPlan(ctx BosunContext) (*ReleaseManifest, error) { + + if p.Plan == nil { + return nil, errors.New("no plan active") + } + + plan := p.Plan + + releaseMetadata := plan.ReleaseMetadata + releaseManifest := NewReleaseManifest(releaseMetadata) + + for appName, appPlan := range plan.Apps { + + if appPlan.ToBranch == "" { + ctx.Log.Infof("No upgrade available for app %q; adding version from release %q, with no deploy requested.", appName, appPlan.PreviousReleaseName) + var appManifest *AppManifest + var err error + previousReleaseName := appPlan.PreviousReleaseName + if previousReleaseName == "" { + previousReleaseName = MasterName + } + + appManifest, err = p.GetAppManifestFromRelease(previousReleaseName, appName) + + if err != nil { + return nil, err + } + + releaseManifest.AddApp(appManifest, appPlan.Deploy) + continue + } + + if appPlan.FromBranch == "" { + return nil, errors.Errorf("app %s: toBranch set to %q but fromBranch was empty", appName, appPlan.ToBranch) + } + + if appPlan.Bump == "" { + return nil, errors.Errorf("app %s: branching from %q to %q, but bump was empty (should be 'none', 'patch', 'minor', or 'major'", appName, appPlan.FromBranch, appPlan.ToBranch) + } + + err := releaseManifest.UpgradeApp(ctx, appName, appPlan.FromBranch, appPlan.ToBranch, appPlan.Bump) + if err != nil { + return nil, errors.Wrapf(err, "upgrading app %s", appName) + } + + } + + p.ReleaseManifests[releaseManifest.Name] = releaseManifest + p.ReleaseMetadata = append(p.ReleaseMetadata, releaseMetadata) + + ctx.Log.Infof("Added release %q to releases for platform.", releaseManifest.Name) + + releaseManifest.MarkDirty() + + p.Plan = nil + + return releaseManifest, nil +} + +// DeleteRelease deletes the release immediately, it calls save itself. +func (p *Platform) DeleteRelease(ctx BosunContext, name string) error { + + dir := p.GetManifestDirectoryPath(name) + err := os.RemoveAll(dir) + if err != nil { + return err + } + + delete(p.ReleaseManifests, name) + + var releaseMetadata []*ReleaseMetadata + for _, rm := range p.ReleaseMetadata { + if rm.Name != name { + releaseMetadata = append(releaseMetadata, rm) + } + } + p.ReleaseMetadata = releaseMetadata + + return p.Save(ctx) +} + +// DiscardPlan discards the current plan; it calls save itself. +func (p *Platform) DiscardPlan(ctx BosunContext) error { + if p.Plan != nil { + + ctx.Log.Warnf("Discarding plan for release %q.", p.Plan.ReleaseMetadata.Name) + p.Plan = nil + return p.Save(ctx) + } + return nil +} + +// Save saves the platform. This will update the file containing the platform, +// and will write out any release manifests which have been loaded in this platform. +func (p *Platform) Save(ctx BosunContext) error { + + if ctx.GetParams().DryRun { + ctx.Log.Warn("Skipping platform save because dry run flag was set.") + } + + ctx.Log.Info("Saving platform...") + sort.Sort(sort.Reverse(releaseMetadataSorting(p.ReleaseMetadata))) + + manifests := p.ReleaseManifests + if p.MasterManifest != nil { + manifests[MasterName] = p.MasterManifest + } + + // save the release manifests + for _, manifest := range manifests { + if !manifest.dirty { + ctx.Log.Debugf("Skipping save of manifest %q because it wasn't dirty.", manifest.Name) + continue + } + dir := p.GetManifestDirectoryPath(manifest.Name) + err := os.MkdirAll(dir, 0700) + if err != nil { + return errors.Wrapf(err, "create directory for release %q", manifest.Name) + } + + y, err := yaml.Marshal(manifest) + if err != nil { + return errors.Wrapf(err, "marshal manifest %q", manifest.Name) + } + + manifestPath := filepath.Join(dir, "manifest.yaml") + err = ioutil.WriteFile(manifestPath, y, 0600) + + for _, appRelease := range manifest.AppManifests { + path := filepath.Join(dir, appRelease.Name+".yaml") + b, err := yaml.Marshal(appRelease) + if err != nil { + return errors.Wrapf(err, "marshal app %q", appRelease.Name) + } + err = ioutil.WriteFile(path, b, 0700) + if err != nil { + return errors.Wrapf(err, "write app %q", appRelease.Name) + } + } + + for _, toDelete := range manifest.toDelete { + path := filepath.Join(dir, toDelete+".yaml") + _ = os.Remove(path) + } + } + + err := p.File.Save() + + if err != nil { + return err + } + + ctx.Log.Info("Platform saved.") + return nil +} + +func (p *Platform) GetReleaseMetadataByName(name string) (*ReleaseMetadata, error) { + if name == MasterName { + return p.GetMasterMetadata(), nil + } + + for _, rm := range p.ReleaseMetadata { + if rm.Name == name { + return rm, nil + } + } + + return nil, errors.Errorf("this platform has no release named %q ", name) +} + +func (p *Platform) GetReleaseMetadataByVersion(v semver.Version) (*ReleaseMetadata, error) { + for _, rm := range p.ReleaseMetadata { + if rm.Version.Equal(v) { + return rm, nil + } + } + + return nil, errors.Errorf("this platform has no release with version %q", v) +} + +func (p *Platform) GetPreviousReleaseMetadata(version semver.Version) *ReleaseMetadata { + + for _, r := range p.GetReleaseMetadataSortedByVersion(true, false) { + if r.Version.LessThan(version) { + return r + } + } + + return nil +} + +func (p *Platform) GetManifestDirectoryPath(name string) string { + dir := filepath.Join(filepath.Dir(p.FromPath), p.ReleaseDirectory, name) + return dir +} + +func (p *Platform) GetReleaseManifestByName(name string, loadAppReleases bool) (*ReleaseManifest, error) { + releaseMetadata, err := p.GetReleaseMetadataByName(name) + if err != nil { + return nil, err + } + releaseManifest, err := p.GetReleaseManifest(releaseMetadata, loadAppReleases) + if err != nil { + return nil, err + } + + return releaseManifest, nil +} + +func (p *Platform) GetReleaseManifest(metadata *ReleaseMetadata, loadAppReleases bool) (*ReleaseManifest, error) { + dir := p.GetManifestDirectoryPath(metadata.Name) + manifestPath := filepath.Join(dir, "manifest.yaml") + + b, err := ioutil.ReadFile(manifestPath) + if err != nil { + return nil, errors.Wrapf(err, "read manifest for %q", metadata.Name) + } + + var manifest *ReleaseManifest + err = yaml.Unmarshal(b, &manifest) + if err != nil { + return nil, errors.Wrapf(err, "unmarshal manifest for %q", metadata.Name) + } + + manifest.Platform = p + + if loadAppReleases { + allAppMetadata := manifest.GetAllAppMetadata() + + for appName, _ := range allAppMetadata { + appReleasePath := filepath.Join(dir, appName+".yaml") + b, err = ioutil.ReadFile(appReleasePath) + if err != nil { + return nil, errors.Wrapf(err, "load appRelease for app %q", appName) + } + var appManifest *AppManifest + err = yaml.Unmarshal(b, &appManifest) + if err != nil { + return nil, errors.Wrapf(err, "unmarshal appRelease for app %q", appName) + } + + appManifest.AppConfig.FromPath = appReleasePath + + manifest.AppManifests[appName] = appManifest + } + } + + if p.ReleaseManifests == nil { + p.ReleaseManifests = map[string]*ReleaseManifest{} + } + p.ReleaseManifests[metadata.Name] = manifest + return manifest, err +} + +func (p *Platform) GetMasterMetadata() *ReleaseMetadata { + if p.MasterMetadata == nil { + p.MasterMetadata = &ReleaseMetadata{ + Name: "latest", + } + } + + return p.MasterMetadata +} +func (p *Platform) GetMasterManifest() (*ReleaseManifest, error) { + if p.MasterManifest != nil { + return p.MasterManifest, nil + } + + metadata := p.GetMasterMetadata() + manifest, err := p.GetReleaseManifest(metadata, true) + if err != nil { + manifest = &ReleaseManifest{ + ReleaseMetadata: metadata, + } + manifest.init() + p.MasterManifest = manifest + } + + return manifest, nil +} + +func (p *Platform) IncludeApp(ctx BosunContext, name string) error { + manifest, err := p.GetMasterManifest() + if err != nil { + return err + } + + app, err := ctx.Bosun.GetApp(name) + if err != nil { + return err + } + + appManifest, err := app.GetManifest(ctx) + if err != nil { + return err + } + + manifest.AddApp(appManifest, false) + + return nil +} + +// RefreshApp checks out the master branch of the app, then reloads it. +// If a release is being planned, the plan will be updated with the refreshed app. +func (p *Platform) RefreshApp(ctx BosunContext, name string) error { + manifest, err := p.GetMasterManifest() + if err != nil { + return err + } + + b := ctx.Bosun + app, err := b.GetApp(name) + if err != nil { + return err + } + ctx = ctx.WithApp(app) + + currentAppManifest, err := manifest.GetAppManifest(name) + if err != nil { + return err + } + + currentBranch := app.GetBranchName() + + if !currentBranch.IsMaster() { + defer func() { + e := app.CheckOutBranch(string(currentBranch)) + if e != nil { + ctx.Log.WithError(e).Warnf("Returning to branch %q failed.", currentBranch) + } + }() + err = app.CheckOutBranch(p.MasterBranch) + if err != nil { + return errors.Wrapf(err, "could not check out %q branch for app %q", p.MasterBranch, name) + } + } + + app, err = b.ReloadApp(name) + if err != nil { + return errors.Wrap(err, "reload app") + } + + appManifest, err := app.GetManifest(ctx) + if err != nil { + return err + } + + if appManifest.DiffersFrom(currentAppManifest.AppMetadata) { + ctx.Log.Info("Updating manifest.") + manifest.AddApp(appManifest, false) + } else { + ctx.Log.Debug("No changes detected.") + } + + currentRelease, err := b.GetCurrentReleaseManifest(true) + if err != nil { + ctx.Log.WithError(err).Warn("No current release to update.") + } else { + err = currentRelease.RefreshApp(ctx, name) + } + + return nil +} + +func (p *Platform) GetAppManifestFromRelease(releaseName string, appName string) (*AppManifest, error) { + + releaseManifest, err := p.GetReleaseManifestByName(releaseName, true) + if err != nil { + return nil, err + } + + appManifest, ok := releaseManifest.AppManifests[appName] + if !ok { + return nil, errors.Errorf("release %q did not have a manifest for app %q", releaseName, appName) + + } + return appManifest, nil +} + +func (p *Platform) GetLatestAppManifestByName(appName string) (*AppManifest, error) { + + latestRelease, err := p.GetMasterManifest() + if err != nil { + return nil, err + } + + appManifest, err := latestRelease.GetAppManifest(appName) + return appManifest, err +} + +func (p *Platform) GetLatestReleaseMetadata() (*ReleaseMetadata, error) { + rm := p.GetReleaseMetadataSortedByVersion(true, true) + if len(rm) == 0 { + return nil, errors.New("no releases found") + } + + return rm[0], nil +} + +func (p *Platform) GetLatestReleaseManifest(loadApps bool) (*ReleaseManifest, error) { + latestReleaseMetadata, err := p.GetLatestReleaseMetadata() + if err != nil { + return nil, err + } + + manifest, err := p.GetReleaseManifest(latestReleaseMetadata, loadApps) + return manifest, err +} + +func (p *Platform) GetMostRecentlyReleasedAppMetadata(name string) (*AppMetadata, error) { + releaseManifest, err := p.GetLatestReleaseManifest(false) + if err != nil { + return nil, err + } + + appMetadata, ok := releaseManifest.AppMetadata[name] + if !ok { + return nil, errors.Errorf("no app %q in release %q", name, releaseManifest.Name) + } + + return appMetadata, nil +} + +type StatusDiff struct { + From string + To string +} + +func NewVersion(version string) (semver.Version, error) { + v := semver.Version{} + + if err := v.Set(version); err != nil { + return v, err + } + + return v, nil +} + +type AppHashes struct { + Commit string `yaml:"commit,omitempty"` + Chart string `yaml:"chart,omitempty"` + AppConfig string `yaml:"appConfig,omitempty"` +} diff --git a/pkg/bosun/platform_test.go b/pkg/bosun/platform_test.go new file mode 100644 index 0000000..51dc717 --- /dev/null +++ b/pkg/bosun/platform_test.go @@ -0,0 +1,25 @@ +package bosun_test + +import ( + "github.com/ghodss/yaml" + "github.com/naveego/bosun/pkg/bosun" + "github.com/naveego/bosun/pkg/semver" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var _ = Describe("Platform", func() { + It("should round-trip version", func() { + + sut := bosun.ReleaseMetadata{ + Name: "Deploy", + Version: semver.New("0.1.4-alpha"), + } + y, err := yaml.Marshal(sut) + Expect(err).ToNot(HaveOccurred()) + Expect(string(y)).To(ContainSubstring("0.1.4-alpha")) + var actual bosun.ReleaseMetadata + Expect(yaml.Unmarshal(y, &actual)).To(Succeed()) + Expect(actual).To(BeEquivalentTo(sut)) + }) +}) diff --git a/pkg/bosun/release.go b/pkg/bosun/release.go deleted file mode 100644 index 23adc95..0000000 --- a/pkg/bosun/release.go +++ /dev/null @@ -1,371 +0,0 @@ -package bosun - -import ( - "bufio" - "fmt" - "github.com/naveego/bosun/pkg" - "github.com/pkg/errors" - "io" - "io/ioutil" - "os" - "os/exec" - "path/filepath" - "regexp" - "sort" - "strings" -) - -type ReleaseConfig struct { - Name string `yaml:"name" json:"name"` - Version string `yaml:"version" json:"version"` - Description string `yaml:"description" json:"description"` - FromPath string `yaml:"fromPath" json:"fromPath"` - AppReleaseConfigs map[string]*AppReleaseConfig `yaml:"apps" json:"apps"` - Exclude map[string]bool `yaml:"exclude,omitempty" json:"exclude,omitempty"` - IsPatch bool `yaml:"isPatch,omitempty" json:"isPatch,omitempty"` - Parent *File `yaml:"-" json:"-"` - BundleFiles map[string]*BundleFile `yaml:"bundleFiles,omitempty"` -} - -type BundleFile struct { - App string `yaml:"namespace"` - Path string `yaml:"path"` - Content []byte `yaml:"-"` -} - -func (r *ReleaseConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { - type rcm ReleaseConfig - var proxy rcm - err := unmarshal(&proxy) - if err == nil { - if proxy.Exclude == nil { - proxy.Exclude = map[string]bool{} - } - *r = ReleaseConfig(proxy) - } - return err -} - -type Release struct { - *ReleaseConfig - // Indicates that this is not a real release which is stored on disk. - // If this is true: - // - release branch creation and checking is disabled - // - local charts are used if available - Transient bool - AppReleases AppReleaseMap -} - -// IsTransient returns true if r is nil or has Transient set to true. -func (r *Release) IsTransient() bool { - return r == nil || r.Transient -} - -func NewRelease(ctx BosunContext, r *ReleaseConfig) (*Release, error) { - var err error - if r.AppReleaseConfigs == nil { - r.AppReleaseConfigs = map[string]*AppReleaseConfig{} - } - release := &Release{ - ReleaseConfig: r, - AppReleases: map[string]*AppRelease{}, - } - for name, config := range r.AppReleaseConfigs { - release.AppReleases[name], err = NewAppRelease(ctx, config) - if err != nil { - return nil, errors.Errorf("creating app release for config %q: %s", name, err) - } - } - - return release, nil -} - -func (r *ReleaseConfig) SetParent(f *File) { - r.FromPath = f.FromPath - r.Parent = f - for _, app := range r.AppReleaseConfigs { - app.SetParent(r) - } -} - -type AppReleaseMap map[string]*AppRelease - -func (a AppReleaseMap) GetAppsSortedByName() AppReleasesSortedByName { - var out AppReleasesSortedByName - for _, app := range a { - out = append(out, app) - } - - sort.Sort(out) - return out -} - -func (a *AppRelease) Validate(ctx BosunContext) []error { - - var errs []error - - out, err := pkg.NewCommand("helm", "search", a.Chart, "-v", a.Version).RunOut() - if err != nil { - errs = append(errs, errors.Errorf("search for %s@%s failed: %s", a.Chart, a.Version, err)) - } - if !strings.Contains(out, a.Chart) { - errs = append(errs, errors.Errorf("chart %s@%s not found", a.Chart, a.Version)) - } - - if !a.AppRepo.BranchForRelease { - return errs - } - - for _, imageName := range a.ImageNames { - imageName = fmt.Sprintf("%s:%s", imageName, a.ImageTag) - err = checkImageExists(ctx, imageName) - - if err != nil { - errs = append(errs, errors.Errorf("image %q: %s", imageName, err)) - } - } - - // if a.AppRepo.IsRepoCloned() { - // appBranch := a.AppRepo.GetBranch() - // if appBranch != a.Branch { - // errs = append(errs, errors.Errorf("app was added to release from branch %s, but is currently on branch %s", a.Branch, appBranch)) - // } - // - // appCommit := a.AppRepo.GetCommit() - // if appCommit != a.Commit { - // errs = append(errs, errors.Errorf("app was added to release at commit %s, but is currently on commit %s", a.Commit, appCommit)) - // } - // } - - return errs -} - -func checkImageExists(ctx BosunContext, name string) error { - - ctx.UseMinikubeForDockerIfAvailable() - - cmd := exec.Command("docker", "pull", name) - stdout, err := cmd.StdoutPipe() - stderr, err := cmd.StderrPipe() - if err != nil { - return err - } - reader := io.MultiReader(stdout, stderr) - scanner := bufio.NewScanner(reader) - - if err := cmd.Start(); err != nil { - return err - } - - defer cmd.Process.Kill() - - var lines []string - - for scanner.Scan() { - line := scanner.Text() - lines = append(lines, line) - if strings.Contains(line, "Pulling from") { - return nil - } - if strings.Contains(line, "Error") { - return errors.New(line) - } - } - - if err := scanner.Err(); err != nil { - return err - } - - cmd.Process.Kill() - - state, err := cmd.Process.Wait() - if err != nil { - return err - } - - if !state.Success() { - return errors.Errorf("Pull failed: %s\n%s", state.String(), strings.Join(lines, "\n")) - } - - return nil -} - -func (r *Release) IncludeDependencies(ctx BosunContext) error { - ctx = ctx.WithRelease(r) - deps := ctx.Bosun.GetAppDependencyMap() - var appNames []string - for _, app := range r.AppReleaseConfigs { - appNames = append(appNames, app.Name) - } - - // this is inefficient but it gets us all the dependencies - topology, err := GetDependenciesInTopologicalOrder(deps, appNames...) - - if err != nil { - return errors.Errorf("repos could not be sorted in dependency order: %s", err) - } - - for _, dep := range topology { - if r.AppReleaseConfigs[dep] == nil { - if _, ok := r.Exclude[dep]; ok { - ctx.Log.Warnf("Dependency %q is not being added because it is in the exclude list. "+ - "Add it using the `add` command if want to override this exclusion.", dep) - continue - } - app, err := ctx.Bosun.GetApp(dep) - if err != nil { - return errors.Errorf("an app or dependency %q could not be found: %s", dep, err) - } else { - err = r.IncludeApp(ctx, app) - if err != nil { - return errors.Errorf("could not include app %q: %s", app.Name, err) - } - } - } - } - return nil -} - -func (r *Release) Deploy(ctx BosunContext) error { - - ctx = ctx.WithRelease(r) - - var requestedAppNames []string - dependencies := map[string][]string{} - for _, app := range r.AppReleaseConfigs { - requestedAppNames = append(requestedAppNames, app.Name) - for _, dep := range app.DependsOn { - dependencies[app.Name] = append(dependencies[app.Name], dep) - } - } - - topology, err := GetDependenciesInTopologicalOrder(dependencies, requestedAppNames...) - - if err != nil { - return errors.Errorf("repos could not be sorted in dependency order: %s", err) - } - - var toDeploy []*AppRelease - - for _, dep := range topology { - app, ok := r.AppReleases[dep] - if !ok { - if r.Transient { - continue - } - if excluded := r.Exclude[dep]; excluded { - continue - } - - return errors.Errorf("an app specifies a dependency that could not be found: %q (excluded: %v)", dep, r.Exclude) - } - - if app.DesiredState.Status == StatusUnchanged { - ctx.WithAppRelease(app).Log.Infof("Skipping deploy because desired state was %q.", StatusUnchanged) - continue - } - - toDeploy = append(toDeploy, app) - } - - for _, app := range toDeploy { - - app.DesiredState.Status = StatusDeployed - if app.DesiredState.Routing == "" { - app.DesiredState.Routing = RoutingCluster - } - - ctx.Bosun.SetDesiredState(app.Name, app.DesiredState) - - app.DesiredState.Force = ctx.GetParams().Force - - err = app.Reconcile(ctx) - - if err != nil { - return err - } - } - - err = ctx.Bosun.Save() - return err -} - -func (r *Release) IncludeApp(ctx BosunContext, app *AppRepo) error { - - var err error - var config *AppReleaseConfig - if r.AppReleaseConfigs == nil { - r.AppReleaseConfigs = map[string]*AppReleaseConfig{} - } - - ctx = ctx.WithRelease(r) - - config, err = app.GetAppReleaseConfig(ctx) - if err != nil { - return errors.Wrap(err, "make app release") - } - r.AppReleaseConfigs[app.Name] = config - - r.AppReleases[app.Name], err = NewAppRelease(ctx, config) - - return nil -} - -func (r *Release) AddBundleFile(app string, path string, content []byte) string { - key := fmt.Sprintf("%s|%s", app, path) - shortPath := safeFileNameRE.ReplaceAllString(strings.TrimLeft(path, "./\\"), "_") - bf := &BundleFile{ - App: app, - Path: shortPath, - Content: content, - } - if r.BundleFiles == nil { - r.BundleFiles = map[string]*BundleFile{} - } - r.BundleFiles[key] = bf - return filepath.Join("./", r.Name, app, shortPath) -} - -// GetBundleFileContent returns the content and path to a bundle file, or an error if it fails. -func (r *Release) GetBundleFileContent(app, path string) ([]byte, string, error) { - key := fmt.Sprintf("%s|%s", app, path) - bf, ok := r.BundleFiles[key] - if !ok { - return nil, "", errors.Errorf("no bundle for app %q and path %q", app, path) - } - - bundleFilePath := filepath.Join(filepath.Dir(r.FromPath), r.Name, bf.App, bf.Path) - content, err := ioutil.ReadFile(bundleFilePath) - return content, bundleFilePath, err -} - -func (r *ReleaseConfig) SaveBundle() error { - bundleDir := filepath.Join(filepath.Dir(r.FromPath), r.Name) - - err := os.MkdirAll(bundleDir,0770) - if err != nil { - return err - } - - for _, bf := range r.BundleFiles { - if bf.Content == nil { - continue - } - - appDir := filepath.Join(bundleDir, bf.App) - err := os.MkdirAll(bundleDir,0770) - if err != nil { - return err - } - - bundleFilepath := filepath.Join(appDir, bf.Path) - err = ioutil.WriteFile(bundleFilepath, bf.Content, 0770) - if err != nil { - return errors.Wrapf(err, "writing bundle file for app %q, path %q", bf.App, bf.Path) - } - } - - return nil -} - -var safeFileNameRE = regexp.MustCompile(`([^A-z0-9_.]+)`) \ No newline at end of file diff --git a/pkg/bosun/release_manifest.go b/pkg/bosun/release_manifest.go new file mode 100644 index 0000000..aeef09f --- /dev/null +++ b/pkg/bosun/release_manifest.go @@ -0,0 +1,326 @@ +package bosun + +import ( + "fmt" + "github.com/naveego/bosun/pkg/semver" + "github.com/naveego/bosun/pkg/util" + "github.com/pkg/errors" +) + +type ReleaseMetadata struct { + Name string `yaml:"name"` + Version semver.Version `yaml:"version"` + Branch string `yaml:"branch"` + PreferredParentBranch string `yaml:"preferredParentBranch,omitempty"` + Description string `yaml:"description"` +} + +func (r ReleaseMetadata) String() string { + if r.Name == r.Version.String() { + return r.Name + } + return fmt.Sprintf("%s@%s", r.Name, r.Version) +} + +type releaseMetadataSorting []*ReleaseMetadata + +func (p releaseMetadataSorting) Len() int { return len(p) } + +func (p releaseMetadataSorting) Less(i, j int) bool { + return p[i].Version.LessThan(p[j].Version) +} + +func (p releaseMetadataSorting) Swap(i, j int) { p[i], p[j] = p[j], p[i] } + +// ReleaseManifest represents a release for a platform. +// Instances should be manipulated using methods on the platform, +// not updated directly. +type ReleaseManifest struct { + *ReleaseMetadata `yaml:"metadata"` + DefaultDeployApps map[string]bool `yaml:"defaultDeployApps"` + AppMetadata map[string]*AppMetadata `yaml:"apps"` + AppManifests map[string]*AppManifest `yaml:"-" json:"-"` + Plan *ReleasePlan `yaml:"plan"` + Platform *Platform `yaml:"-"` + toDelete []string `yaml:"-"` + dirty bool `yaml:"-"` +} + +func NewReleaseManifest(metadata *ReleaseMetadata) *ReleaseManifest { + r := &ReleaseManifest{ReleaseMetadata: metadata} + r.init() + return r +} + +func (r *ReleaseManifest) MarshalYAML() (interface{}, error) { + if r == nil { + return nil, nil + } + type proxy ReleaseManifest + p := proxy(*r) + + return &p, nil +} + +func (r *ReleaseManifest) UnmarshalYAML(unmarshal func(interface{}) error) error { + type proxy ReleaseManifest + var p proxy + if r != nil { + p = proxy(*r) + } + + err := unmarshal(&p) + + if err == nil { + *r = ReleaseManifest(p) + } + + r.init() + + return err +} + +// init ensures the instance is ready to use. +func (r *ReleaseManifest) init() { + if r.AppManifests == nil { + r.AppManifests = map[string]*AppManifest{} + } + if r.AppMetadata == nil { + r.AppMetadata = map[string]*AppMetadata{} + } +} + +func (r *ReleaseManifest) Headers() []string { + return []string{"Name", "Version", "Timestamp", "Commit Hash", "Deploying"} +} + +func (r *ReleaseManifest) Rows() [][]string { + var out [][]string + for _, name := range util.SortedKeys(r.AppMetadata) { + deploy := r.DefaultDeployApps[name] + app := r.AppMetadata[name] + deploying := "" + if deploy { + deploying = "YES" + } + out = append(out, []string{app.Name, app.Version.String(), app.Timestamp.String(), app.Hashes.Commit, deploying}) + } + return out +} + +func (r *ReleaseManifest) GetAllAppMetadata() map[string]*AppMetadata { + return r.AppMetadata +} + +// UpgradeApp upgrades the named app by creating a release branch and bumping the version +// in that branch based on the bump parameter. If the bump parameter is "none" then the app +// won't be bumped. +func (r *ReleaseManifest) UpgradeApp(ctx BosunContext, name, fromBranch, toBranch, bump string) error { + r.init() + r.MarkDirty() + + b := ctx.Bosun + app, err := b.GetApp(name) + if err != nil { + return err + } + + appConfig := app.AppConfig + + if appConfig.BranchForRelease { + + ctx.Log.Infof("Upgrade requested for for app %q; creating release branch and upgrading manifest...", name) + + if !app.IsRepoCloned() { + return errors.New("repo is not cloned but must be branched for release; what is going on?") + } + + localRepo := app.Repo.LocalRepo + if localRepo.IsDirty() { + return errors.New("repo is dirty, commit or stash your changes before adding it to the release") + } + + err = app.BumpVersion(ctx, bump) + if err != nil { + return errors.Wrap(err, "bumping version") + } + + err = localRepo.Commit(fmt.Sprintf("chore(version): Bump version to %s for release %s.", app.Version, r.Name), ".") + if err != nil { + return errors.Wrap(err, "committing bumped version") + } + + err = localRepo.Push() + if err != nil { + return errors.Wrap(err, "pushing bumped version") + } + + ctx.Log.Info("Creating branch if needed...") + + err = localRepo.Branch(ctx, fromBranch, toBranch) + + if err != nil { + return errors.Wrap(err, "create branch for release") + } + + if bump == "" { + bump = "patch" + } + + ctx.Log.Info("Committing updated files after bumping version...") + err = localRepo.Commit("chore(version): Bumping version for release.", ".") + if err != nil { + return err + } + + ctx.Log.Info("Pushing bumped version commit...") + err = localRepo.Push() + if err != nil { + return err + } + + ctx.Log.Info("Branching and version bumping completed.") + + app, err = ctx.Bosun.ReloadApp(app.Name) + if err != nil { + return errors.Wrap(err, "reload app after switching to new branch") + } + } + + appManifest, err := app.GetManifest(ctx) + if err != nil { + return err + } + + r.AddApp(appManifest, true) + + return nil +} + +func (r *ReleaseManifest) RefreshApp(ctx BosunContext, name string) error { + + b := ctx.Bosun + app, err := b.GetApp(name) + if err != nil { + return err + } + ctx = ctx.WithApp(app) + currentAppManifest, err := r.GetAppManifest(name) + if err != nil { + return err + } + + if app.IsRepoCloned() { + + appReleaseBranch := currentAppManifest.Branch + currentBranch := app.GetBranchName() + + if appReleaseBranch != string(currentBranch) { + defer func() { + e := app.CheckOutBranch(string(currentBranch)) + if e != nil { + ctx.Log.WithError(e).Warnf("Returning to branch %q failed.", currentBranch) + } + }() + err = app.CheckOutBranch(appReleaseBranch) + if err != nil { + return errors.Wrapf(err, "could not check out %q branch for app %q", appReleaseBranch, name) + } + } + + err = app.Repo.Pull(ctx) + if err != nil { + return errors.Wrapf(err, "pull app %q", name) + } + + } + + app, err = b.ReloadApp(name) + if err != nil { + return errors.Wrap(err, "reload app") + } + + appManifest, err := app.GetManifest(ctx) + if err != nil { + return err + } + + if appManifest.DiffersFrom(currentAppManifest.AppMetadata) { + ctx.Log.Info("Updating manifest.") + r.AddApp(appManifest, true) + } else { + ctx.Log.Debug("No changes detected.") + } + + return nil +} + +// SyncApp refreshes the app's manifest from the release branch of that app. +func (r *ReleaseManifest) SyncApp(ctx BosunContext, name string) error { + r.MarkDirty() + + b := ctx.Bosun + app, err := b.GetApp(name) + if err != nil { + return err + } + + appManifest, err := app.GetManifest(ctx) + if err != nil { + return err + } + + r.AppManifests[appManifest.Name] = appManifest + + return nil +} + +func (r *ReleaseManifest) ExportDiagram() string { + export := `# dot -Tpng myfile.dot >myfile.png +digraph g { + rankdir="LR"; + node[style="rounded",shape="box"] + edge[splines="curved"]` + for _, app := range r.AppManifests { + + export += fmt.Sprintf("%q;\n", app.Name) + for _, dep := range app.AppConfig.DependsOn { + export += fmt.Sprintf("%q -> %q;\n", app.Name, dep.Name) + } + } + + export += "}" + return export +} + +func (r *ReleaseManifest) RemoveApp(appName string) { + r.MarkDirty() + r.init() + delete(r.AppMetadata, appName) + delete(r.AppManifests, appName) + delete(r.DefaultDeployApps, appName) + r.toDelete = append(r.toDelete, appName) +} + +func (r *ReleaseManifest) AddApp(manifest *AppManifest, addToDefaultDeploys bool) { + r.MarkDirty() + r.init() + r.AppManifests[manifest.Name] = manifest + r.AppMetadata[manifest.Name] = manifest.AppMetadata + if addToDefaultDeploys { + r.DefaultDeployApps[manifest.Name] = true + } +} + +func (r *ReleaseManifest) MarkDirty() { + r.dirty = true +} + +func (r *ReleaseManifest) GetAppManifest(name string) (*AppManifest, error) { + if a, ok := r.AppManifests[name]; ok { + return a, nil + } + + return nil, errors.Errorf("no app manifest with name %q in release %q", name, r.Name) + +} diff --git a/pkg/bosun/release_plan.go b/pkg/bosun/release_plan.go new file mode 100644 index 0000000..8a1d12d --- /dev/null +++ b/pkg/bosun/release_plan.go @@ -0,0 +1,98 @@ +package bosun + +import ( + "fmt" + "github.com/naveego/bosun/pkg/util" + "github.com/pkg/errors" + "strings" +) + +type ReleasePlan struct { + Apps map[string]*AppPlan `yaml:"apps"` + ReleaseMetadata *ReleaseMetadata `yaml:"releaseManifest"` +} + +func (ReleasePlan) Headers() []string { + return []string{"Name", "Previous Release", "From Branch", "To Branch", "Bump", "Deploy"} +} + +func (r ReleasePlan) Rows() [][]string { + var out [][]string + for _, name := range util.SortedKeys(r.Apps) { + appPlan := r.Apps[name] + + previousVersion := appPlan.PreviousReleaseName + + out = append(out, []string{ + appPlan.Name, + previousVersion, + appPlan.PreviousReleaseName, + appPlan.FromBranch, + appPlan.ToBranch, + appPlan.Bump, + fmt.Sprint(appPlan.Deploy), + }) + } + return out +} + +func (r ReleasePlan) GetAppPlan(name string) (*AppPlan, error) { + if a, ok := r.Apps[name]; ok { + return a, nil + } + return nil, errors.Errorf("no plan for app %q", name) +} + +func NewReleasePlan(releaseMetadata *ReleaseMetadata) *ReleasePlan { + return &ReleasePlan{ + ReleaseMetadata: releaseMetadata, + Apps: map[string]*AppPlan{}, + } +} + +type AppPlan struct { + Name string `yaml:"name"` + Repo string `yaml:"repo"` + Deploy bool `yaml:"deploy"` + ToBranch string `yaml:"toBranch"` + FromBranch string `yaml:"fromBranch"` + Bump string `yaml:"bump"` + Reason string `yaml:"reason,omitempty"` + PreviousReleaseName string `yaml:"previousRelease"` + PreviousReleaseVersion string `yaml:"previousReleaseVersion"` + CurrentVersionInMaster string `yaml:"currentVersionInMaster"` + CommitsNotInPreviousRelease []string `yaml:"commitsNotInPreviousRelease,omitempty"` +} + +func (a *AppPlan) IsBumpUnset() bool { + return a.Bump == "" || strings.HasPrefix(strings.ToLower(a.Bump), "no") +} + +func (a AppPlan) String() string { + + w := new(strings.Builder) + _, _ = fmt.Fprintf(w, "%s: ", a.Name) + if a.PreviousReleaseName == "" { + _, _ = fmt.Fprintf(w, "never released;") + } else { + _, _ = fmt.Fprintf(w, "previously released from %s;", a.PreviousReleaseName) + } + + if a.FromBranch != "" { + if a.ToBranch != "" { + _, _ = fmt.Fprintf(w, "branching: %s -> %s;", a.FromBranch, a.ToBranch) + } else { + _, _ = fmt.Fprintf(w, "using branch: %s;", a.FromBranch) + } + + } + if a.Bump != "" { + _, _ = fmt.Fprintf(w, "bump: %s;", a.Bump) + } + if a.Deploy { + _, _ = fmt.Fprint(w, " (will be deployed by default) ") + } else { + _, _ = fmt.Fprint(w, " (will NOT be deployed by default) ") + } + return w.String() +} diff --git a/pkg/bosun/repo.go b/pkg/bosun/repo.go new file mode 100644 index 0000000..44b04dc --- /dev/null +++ b/pkg/bosun/repo.go @@ -0,0 +1,128 @@ +package bosun + +import ( + "fmt" + "github.com/naveego/bosun/pkg" + "github.com/naveego/bosun/pkg/filter" + "github.com/naveego/bosun/pkg/git" + "github.com/pkg/errors" + "path/filepath" +) + +type RepoConfig struct { + ConfigShared `yaml:",inline"` + FilteringLabels map[string]string `yaml:"labels" json:"labels"` +} + +type Repo struct { + RepoConfig + LocalRepo *LocalRepo + Apps map[string]*AppConfig +} + +func (r Repo) GetLabels() filter.Labels { + out := filter.Labels{ + "name": filter.LabelString(r.Name), + } + if r.LocalRepo != nil { + out["path"] = filter.LabelString(r.LocalRepo.Path) + } + for k, v := range r.RepoConfig.FilteringLabels { + out[k] = filter.LabelString(v) + } + return out +} + +func (r *Repo) CheckCloned() error { + if r == nil { + return errors.New("repo is unknown") + } + if r.LocalRepo == nil { + return errors.Errorf("repo %q is not cloned", r.Name) + } + return nil +} + +func (r *Repo) Clone(ctx BosunContext, toDir string) error { + if err := r.CheckCloned(); err != nil { + return err + } + + dir, _ := filepath.Abs(filepath.Join(toDir, r.Name)) + + err := pkg.NewCommand("git", "clone", + "--depth", "1", + "--no-single-branch", + fmt.Sprintf("git@github.com:%s.git", r.Name), + dir). + RunE() + + if err != nil { + return err + } + + r.LocalRepo = &LocalRepo{ + Name: r.Name, + Path: dir, + } + + ctx.Bosun.AddLocalRepo(r.LocalRepo) + + return nil +} + +func (r Repo) GetLocalBranchName() git.BranchName { + if r.LocalRepo == nil { + return "" + } + + if r.LocalRepo.branch == "" { + g, _ := git.NewGitWrapper(r.LocalRepo.Path) + r.LocalRepo.branch = git.BranchName(g.Branch()) + } + return r.LocalRepo.branch +} + +func (r *Repo) Pull(ctx BosunContext) error { + if err := r.CheckCloned(); err != nil { + return err + } + + g, _ := git.NewGitWrapper(r.LocalRepo.Path) + err := g.Pull() + + return err +} + +func (r *Repo) Fetch(ctx BosunContext) error { + if err := r.CheckCloned(); err != nil { + return err + } + + g, _ := git.NewGitWrapper(r.LocalRepo.Path) + err := g.Fetch() + + return err +} + +func (r *Repo) Merge(fromBranch, toBranch string) error { + if err := r.CheckCloned(); err != nil { + return err + } + + g, _ := git.NewGitWrapper(r.LocalRepo.Path) + + err := g.Fetch() + if err != nil { + return err + } + + _, err = g.Exec("checkout", fromBranch) + if err != nil { + return err + } + + err = g.Pull() + + return err +} diff --git a/pkg/bosun/script.go b/pkg/bosun/script.go index 595b812..19ce9e9 100644 --- a/pkg/bosun/script.go +++ b/pkg/bosun/script.go @@ -116,16 +116,16 @@ func (s *Script) Execute(ctx BosunContext, steps ...int) error { } if len(s.Params) > 0 { - if ctx.ReleaseValues == nil { + if ctx.Values == nil { return errors.New("script has params but no release values provided") } - releaseValues := *ctx.ReleaseValues + releaseValues := *ctx.Values for _, param := range s.Params { _, ok := releaseValues.Values[param.Name] if !ok { if param.DefaultValue == nil { - return errors.Errorf("script param %q does not have a value set") + return errors.Errorf("script param %q does not have a value set", param.Name) } releaseValues.Values[param.Name] = param.DefaultValue } diff --git a/pkg/bosun/tools.go b/pkg/bosun/tools.go index 54f4d7a..87e6504 100644 --- a/pkg/bosun/tools.go +++ b/pkg/bosun/tools.go @@ -11,7 +11,7 @@ import ( "strings" ) -type ToolDefs []ToolDef +type ToolDefs []*ToolDef func (t ToolDefs) Len() int { return len(t) } diff --git a/pkg/bosun/valueSet_test.go b/pkg/bosun/valueSet_test.go new file mode 100644 index 0000000..c15b263 --- /dev/null +++ b/pkg/bosun/valueSet_test.go @@ -0,0 +1,84 @@ +package bosun_test + +import ( + . "github.com/naveego/bosun/pkg/bosun" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + "gopkg.in/yaml.v2" +) + +var _ = Describe("ValueSetMap", func() { + + input := yamlize( + // language=yaml + ` + green: + static: + green1: d + redgreen1: c + redgreenmap: + a: greenA + d: greenD + red,green: + static: + redgreen1: a + redgreen2: f + redgreenmap: + a: redgreenA + b: redgreenB + red: + static: + red1: b + redgreenmap: + a: redA + c: redC + blue: + static: + blue1: e +`) + + It("should extract values by name", func() { + + var sut ValueSetMap + Expect(yaml.Unmarshal([]byte(input), &sut)).To(Succeed()) + redValues := sut.ExtractValueSetByName("green") + Expect(redValues.Static).To(HaveKeyWithValue("green1", "d"), "it is in the green set") + Expect(redValues.Static).To(HaveKeyWithValue("redgreen1", "c"), "the green key has a higher priority than the red,green key") + Expect(redValues.Static).To(HaveKeyWithValue("redgreen2", "f"), "the red,green key should be integrated") + }) + + It("should extract multiple values by name", func() { + var sut ValueSetMap + Expect(yaml.Unmarshal([]byte(input), &sut)).To(Succeed()) + redValues := sut.ExtractValueSetByNames("green", "red") + Expect(redValues.Static).To(HaveKeyWithValue("green1", "d"), "it is in the green set") + Expect(redValues.Static).To(HaveKeyWithValue("redgreen1", "a"), "the green key has a higher priority than the red,green key") + Expect(redValues.Static).To(HaveKeyWithValue("redgreen2", "f"), "the red,green key should be integrated") + Expect(redValues.Static).To(HaveKeyWithValue("red1", "b"), "the red,green key should be integrated") + }) + It("should canonicalize correctly", func() { + var sut ValueSetMap + Expect(yaml.Unmarshal([]byte(input), &sut)).To(Succeed()) + actual := sut.CanonicalizedCopy() + Expect(actual["red"].Static).To( + And( + HaveKeyWithValue("redgreen1", "a"), + HaveKeyWithValue("redgreen2", "f"), + HaveKeyWithValue("red1", "b"), + HaveKeyWithValue("redgreenmap", And( + HaveKeyWithValue("a", "redA"), + HaveKeyWithValue("b", "redgreenB"), + HaveKeyWithValue("c", "redC"), + )), + )) + Expect(actual["green"].Static).To( + And( + HaveKeyWithValue("green1", "d"), + HaveKeyWithValue("redgreen1", "c"), + HaveKeyWithValue("redgreen2", "f"), + )) + Expect(actual["blue"].Static).To(HaveKeyWithValue("blue1", "e")) + + }) + +}) diff --git a/pkg/bosun/value_set.go b/pkg/bosun/value_set.go new file mode 100644 index 0000000..37c9d7a --- /dev/null +++ b/pkg/bosun/value_set.go @@ -0,0 +1,166 @@ +package bosun + +import ( + "github.com/imdario/mergo" + "github.com/pkg/errors" + "strings" +) + +type ValueSet struct { + ConfigShared `yaml:",inline"` + Dynamic map[string]*CommandValue `yaml:"dynamic,omitempty" json:"dynamic,omitempty"` + Files []string `yaml:"files,omitempty" json:"files,omitempty"` + Static Values `yaml:"static,omitempty" json:"static,omitempty"` +} + +func (a *ValueSet) UnmarshalYAML(unmarshal func(interface{}) error) error { + var m map[string]interface{} + err := unmarshal(&m) + if err != nil { + return errors.WithStack(err) + } + if _, ok := m["set"]; ok { + // is v1 + var v1 appValuesConfigV1 + err = unmarshal(&v1) + if err != nil { + return errors.WithStack(err) + } + if a == nil { + *a = ValueSet{} + } + if v1.Static == nil { + v1.Static = Values{} + } + if v1.Set == nil { + v1.Set = map[string]*CommandValue{} + } + a.Files = v1.Files + a.Static = v1.Static + a.Dynamic = v1.Set + // handle case where set AND dynamic both have values + if v1.Dynamic != nil { + err = mergo.Map(a.Dynamic, v1.Dynamic) + } + return err + } + + type proxy ValueSet + var p proxy + err = unmarshal(&p) + if err != nil { + return errors.WithStack(err) + } + *a = ValueSet(p) + return nil +} + +// ValueSetMap is a map of (possibly multiple) names +// to ValueSets. The the keys can be single names (like "red") +// or multiple, comma-delimited names (like "red,green"). +// Use ExtractValueSetByName to get a merged ValueSet +// comprising the ValueSets under each key which contains that name. +type ValueSetMap map[string]ValueSet + +// ExtractValueSetByName returns a merged ValueSet +// comprising the ValueSets under each key which contains the provided names. +// ValueSets with the same name are merged in order from least specific key +// to most specific, so values under the key "red" will overwrite values under "red,green", +// which will overwrite values under "red,green,blue", and so on. Then the +// ValueSets with each name are merged in the order the names were provided. +func (a ValueSetMap) ExtractValueSetByName(name string) ValueSet { + + out := ValueSet{} + + // More precise values should override less precise values + // We assume no ValueSetMap will ever have more than 10 + // named keys in it. + priorities := make([][]ValueSet, 10, 10) + + for k, v := range a { + keys := strings.Split(k, ",") + for _, k2 := range keys { + if k2 == name { + priorities[len(keys)] = append(priorities[len(keys)], v) + } + } + } + + for i := len(priorities) - 1; i >= 0; i-- { + for _, v := range priorities[i] { + out = out.Combine(v) + } + } + + return out +} + +// ExtractValueSetByName returns a merged ValueSet +// comprising the ValueSets under each key which contains the provided names. +// ValueSets with the same name are merged in order from least specific key +// to most specific, so values under the key "red" will overwrite values under "red,green", +// which will overwrite values under "red,green,blue", and so on. Then the +// ValueSets with each name are merged in the order the names were provided. +func (a ValueSetMap) ExtractValueSetByNames(names ...string) ValueSet { + + out := ValueSet{} + + for _, name := range names { + vs := a.ExtractValueSetByName(name) + out = out.Combine(vs) + } + + return out +} + +// CanonicalizedCopy returns a copy of this ValueSetMap with +// only single-name keys, by de-normalizing any multi-name keys. +// Each ValueSet will have its name set to the value of the name it's under. +func (a ValueSetMap) CanonicalizedCopy() ValueSetMap { + + out := ValueSetMap{} + + for k := range a { + names := strings.Split(k, ",") + for _, name := range names { + out[name] = ValueSet{} + } + } + + for name := range out { + vs := a.ExtractValueSetByName(name) + vs.Name = name + out[name] = vs + } + + return out +} + +// WithFilesLoaded resolves all file system dependencies into static values +// on this instance, then clears those dependencies. +func (a ValueSet) WithFilesLoaded(ctx BosunContext) (ValueSet, error) { + + out := ValueSet{ + Static: a.Static.Clone(), + } + + mergedValues := Values{} + + // merge together values loaded from files + for _, file := range a.Files { + file = ctx.ResolvePath(file, "VALUE_SET", a.Name) + valuesFromFile, err := ReadValuesFile(file) + if err != nil { + return out, errors.Errorf("reading values file %q for env key %q: %s", file, ctx.Env.Name, err) + } + mergedValues.Merge(valuesFromFile) + } + + // make sure any existing static values are merged OVER the values from the file + mergedValues.Merge(out.Static) + out.Static = mergedValues + + out.Dynamic = a.Dynamic + + return out, nil +} diff --git a/pkg/bosun/app_values.go b/pkg/bosun/values.go similarity index 99% rename from pkg/bosun/app_values.go rename to pkg/bosun/values.go index 49d6a55..8f8983b 100644 --- a/pkg/bosun/app_values.go +++ b/pkg/bosun/values.go @@ -237,7 +237,7 @@ func (v Values) setAtPath(path []string, value interface{}) error { } // Merge takes the properties in src and merges them into Values. Maps -// are merged while values and arrays are replaced. +// are merged (keys are overwritten) while values and arrays are replaced. func (v Values) Merge(src Values) { for key, srcVal := range src { destVal, found := v[key] diff --git a/pkg/bosun/workspace.go b/pkg/bosun/workspace.go index 9af1781..29fea59 100644 --- a/pkg/bosun/workspace.go +++ b/pkg/bosun/workspace.go @@ -12,16 +12,18 @@ const logConfigs = false type Workspace struct { Path string `yaml:"-" json:"-"` CurrentEnvironment string `yaml:"currentEnvironment" json:"currentEnvironment"` + CurrentPlatform string `yaml:"currentPlatform" json:"currentPlatform"` + CurrentRelease string `yaml:"currentRelease" json:"currentRelease"` Imports []string `yaml:"imports,omitempty" json:"imports"` GitRoots []string `yaml:"gitRoots" json:"gitRoots"` - Release string `yaml:"release" json:"release"` HostIPInMinikube string `yaml:"hostIPInMinikube" json:"hostIpInMinikube"` AppStates AppStatesByEnvironment `yaml:"appStates" json:"appStates"` - ClonePaths map[string]string `yaml:"clonePaths" json:"clonePaths"` + ClonePaths map[string]string `yaml:"clonePaths,omitempty" json:"clonePaths,omitempty"` MergedBosunFile *File `yaml:"-" json:"merged"` ImportedBosunFiles map[string]*File `yaml:"-" json:"imported"` GithubToken *CommandValue `yaml:"githubToken" json:"githubToken"` Minikube MinikubeConfig `yaml:"minikube" json:"minikube"` + LocalRepos map[string]*LocalRepo `yaml:"localRepos" json:"localRepos"` } func (r *Workspace) UnmarshalYAML(unmarshal func(interface{}) error) error { diff --git a/pkg/bosun/workspace_test.go b/pkg/bosun/workspace_test.go deleted file mode 100644 index d8adfc6..0000000 --- a/pkg/bosun/workspace_test.go +++ /dev/null @@ -1,81 +0,0 @@ -package bosun_test - -import ( - . "github.com/naveego/bosun/pkg/bosun" - . "github.com/onsi/ginkgo" - . "github.com/onsi/gomega" - "gopkg.in/yaml.v2" - "strings" -) - -func yamlize(y string) string { - return strings.Replace(y, "\t", " ", -1) -} - -var _ = Describe("File", func() { - - Describe("AppValuesByEnvironment", func() { - It("should merge values when unmarshalled", func() { - - input := yamlize( - `name: app -values: - green: - set: - green1: d - redgreen1: c - files: - - greenfile - red,green: - set: - redgreen1: a - files: - - redgreenfile - red: - set: - red1: b - files: - - redfile - -`) - var sut AppRepoConfig - - Expect(yaml.Unmarshal([]byte(input), &sut)).To(Succeed()) - sut.FromPath = RootPkgBosunDir - - redValues := sut.GetValuesConfig(BosunContext{Env: &EnvironmentConfig{Name: "red"}}) - - Expect(redValues).To(BeEquivalentTo(AppValuesConfig{ - Dynamic: map[string]*CommandValue{ - "redgreen1": {Value: "a"}, - "red1": {Value: "b"}, - }, - Files: []string{ - "redgreenfile", - "redfile", - }, - Static: Values{}, - })) - - greenValues := sut.GetValuesConfig(BosunContext{Env: &EnvironmentConfig{Name: "green"}}) - - Expect(greenValues).To(BeEquivalentTo(AppValuesConfig{ - Dynamic: map[string]*CommandValue{ - "redgreen1": {Value: "c"}, - "green1": {Value: "d"}, - }, - Files: []string{ - "redgreenfile", - "greenfile", - }, - Static: Values{}, - })) - - b, err := yaml.Marshal(sut) - Expect(err).ToNot(HaveOccurred()) - roundtripped := string(b) - Expect(roundtripped).To(ContainSubstring("values")) - - }) - }) -}) diff --git a/pkg/filter/chain.go b/pkg/filter/chain.go new file mode 100644 index 0000000..a251436 --- /dev/null +++ b/pkg/filter/chain.go @@ -0,0 +1,154 @@ +package filter + +import ( + "fmt" + "github.com/pkg/errors" + "math" + "strings" +) + +type Chain struct { + steps []step + current *step + min *int + max *int +} + +type step struct { + include []Filter + exclude []Filter +} + +func (s step) String() string { + var include, exclude []string + for _, i := range s.include { + include = append(include, i.String()) + } + for _, i := range s.exclude { + exclude = append(exclude, i.String()) + } + out := "" + if len(include) > 0 { + out += fmt.Sprintf("include(%s)", strings.Join(include, ",")) + } + if len(exclude) > 0 { + out += fmt.Sprintf("exclude(%s)", strings.Join(include, ",")) + } + return out +} + +func Try() Chain { + return Chain{} +} + +func (c Chain) Including(f ...Filter) Chain { + if c.current == nil { + c.current = &step{} + } + c.current.include = append(c.current.include, f...) + return c +} + +func (c Chain) Excluding(f ...Filter) Chain { + if c.current == nil { + c.current = &step{} + } + c.current.exclude = append(c.current.exclude, f...) + return c +} + +func (c Chain) Then() Chain { + if c.current == nil { + panic("no current step (should call Including or Excluding first") + } + c.steps = append(c.steps, *c.current) + c.current = nil + return c +} + +func (c Chain) ToGet(min, max int) Chain { + c.max = &max + c.min = &min + return c +} + +func (c Chain) ToGetExactly(want int) Chain { + c.max = &want + c.min = &want + return c +} + +func (c Chain) ToGetAtLeast(want int) Chain { + m := math.MaxInt64 + c.max = &m + c.min = &want + return c +} + +// From returns the filtered set, or an empty value of the same type as from if an expectation failed. +func (c Chain) From(from interface{}) interface{} { + out, _ := c.FromErr(from) + return out +} + +func (c Chain) String() string { + var steps []string + for _, s := range c.steps { + steps = append(steps, s.String()) + } + return strings.Join(steps, "\n") +} + +// FromErr returns the filtered set, or an error if all steps failed expectations. +func (c Chain) FromErr(from interface{}) (interface{}, error) { + + f := newFilterable(from) + + if f.len() == 0 { + return from, nil + } + + if c.current != nil { + c.steps = append(c.steps, *c.current) + } + if len(c.steps) == 0 { + return from, nil + } + min := 1 + max := math.MaxInt64 + if c.min != nil { + min = *c.min + } + if c.max != nil { + max = *c.max + } + + var after filterable + for _, s := range c.steps { + if len(s.include) > 0 && len(s.exclude) > 0 { + after = applyFilters(f, s.include, true) + after = applyFilters(after, s.exclude, false) + } else if len(s.include) > 0 { + after = applyFilters(f, s.include, true) + } else if len(s.exclude) > 0 { + after = applyFilters(f, s.exclude, false) + } else { + after = f.cloneEmpty() + } + if after.len() >= min && after.len() <= max { + return after.val.Interface(), nil + } + } + + maxString := "∞" + if max < math.MaxInt64 { + maxString = fmt.Sprint(max) + } + + return f.cloneEmpty().val.Interface(), errors.Errorf("no steps in chain could reduce initial set of %d items (%T) to requested size of [%d,%s]\nsteps:\n%s", + f.len(), + from, + min, + maxString, + c) +} diff --git a/pkg/filter/chain_test.go b/pkg/filter/chain_test.go new file mode 100644 index 0000000..68c4528 --- /dev/null +++ b/pkg/filter/chain_test.go @@ -0,0 +1,116 @@ +package filter_test + +import ( + . "github.com/naveego/bosun/pkg/filter" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var _ = Describe("Chain", func() { + + ab := Item{ + Name: "ab", + Labels: map[string]string{ + "a": "A", + "b": "B", + }, + } + abc := Item{ + Name: "abc", + Labels: map[string]string{ + "a": "A", + "b": "B", + "c": "C", + }, + } + c := Item{ + Name: "c", + Labels: map[string]string{ + "c": "C", + }, + } + d := Item{ + Name: "d", + Labels: map[string]string{ + "d": "D", + }, + } + items := []Item{ + ab, + abc, + c, + d, + } + + Describe("when one step is provided", func() { + It("include should whitelist", func() { + Expect(Try().Including(MustParse("a==A")).From(items)).To(ConsistOf(ab, abc)) + }) + + It("include exclude blacklist", func() { + Expect(Try().Excluding(MustParse("c==C")).From(items)).To(ConsistOf(ab, d)) + }) + + It("apply include and exclude", func() { + Expect(Try(). + Including(MustParse("a==A")). + Excluding(MustParse("c==C")). + From(items)).To(ConsistOf(ab)) + }) + }) + + Describe("when two steps are provided", func() { + It("should return first step results if not empty", func() { + Expect(Try(). + Including(MustParse("a==A")). + Then(). + Including(MustParse("b==B")). + From(items)). + To(ConsistOf(ab, abc)) + }) + It("should return second step results if first is empty but second is not empty", func() { + Expect(Try(). + Including(MustParse("a==C")). + Then(). + Including(MustParse("c==C")). + From(items)). + To(ConsistOf(abc, c)) + }) + It("should return first step where result falls within desired count at low end", func() { + Expect(Try(). + Including(MustParse("d==D")). + Then(). + Including(MustParse("a==A")). + ToGet(2, 3). + From(items)). + To(ConsistOf(ab, abc)) + }) + It("should return first step where result falls within desired count at high end", func() { + Expect(Try(). + Including(MustParse("d==D")). + Then(). + Including(MustParse("a==A"), MustParse("c==C")). + ToGet(2, 3). + From(items)). + To(ConsistOf(ab, abc, c)) + }) + + It("should return first step which meets exact count", func() { + Expect(Try(). + Including(MustParse("a==A")). + Then(). + Including(MustParse("d==D")). + ToGetExactly(1). + From(items)). + To(ConsistOf(d)) + }) + + It("should error if no step which meets requested count", func() { + _, err := Try().Including(MustParse("d==D")). + ToGetExactly(2). + From(items) + Expect(err).To(HaveOccurred()) + }) + }) + +}) diff --git a/pkg/filter/filter.go b/pkg/filter/filter.go new file mode 100644 index 0000000..4a92260 --- /dev/null +++ b/pkg/filter/filter.go @@ -0,0 +1,103 @@ +package filter + +import ( + "fmt" + "reflect" +) + +type Filter interface { + fmt.Stringer + IsMatch(l Labels) bool +} + +type Labelled interface { + GetLabels() Labels +} + +type filterFunc struct { + label string + fn func(l Labels) bool +} + +func FilterFunc(label string, fn func(l Labels) bool) Filter { + return filterFunc{label, fn} +} + +func (f filterFunc) String() string { + return f.label +} + +func (f filterFunc) IsMatch(l Labels) bool { + return f.fn(l) +} + +func FilterMatchAll() Filter { + return FilterFunc("all", func(l Labels) bool { return true }) +} + +func Include(from interface{}, filters ...Filter) interface{} { + return applyFilters(newFilterable(from), filters, true).val.Interface() +} + +func Exclude(from interface{}, filters ...Filter) interface{} { + return applyFilters(newFilterable(from), filters, false).val.Interface() +} + +func applyFilters(f filterable, filters []Filter, include bool) filterable { + var after filterable + switch f.val.Kind() { + case reflect.Map: + after = f.cloneEmpty() + keys := f.val.MapKeys() + for _, key := range keys { + value := f.val.MapIndex(key) + labelled, ok := value.Interface().(Labelled) + if !ok { + panic(fmt.Sprintf("values must implement the filter.Labelled interface")) + } + var matched bool + labels := labelled.GetLabels() + for _, filter := range filters { + if ok { + matched = MatchFilter(labels, filter) + if matched { + break + } + } + } + if matched == include { + after.val.SetMapIndex(key, value) + } + } + case reflect.Slice: + length := f.val.Len() + after = f.cloneEmpty() + for i := 0; i < length; i++ { + value := f.val.Index(i) + labelled, ok := value.Interface().(Labelled) + if !ok { + panic(fmt.Sprintf("values must implement the filter.Labelled interface")) + } + labels := labelled.GetLabels() + var matched bool + for _, filter := range filters { + if ok { + matched = MatchFilter(labels, filter) + if matched { + break + } + } + } + if matched == include { + after.val = reflect.Append(after.val, value) + } + } + } + + return after +} + +func MatchFilter(labels Labels, filter Filter) bool { + matched := filter.IsMatch(labels) + return matched +} diff --git a/pkg/filter/filter_suite_test.go b/pkg/filter/filter_suite_test.go new file mode 100644 index 0000000..6013545 --- /dev/null +++ b/pkg/filter/filter_suite_test.go @@ -0,0 +1,23 @@ +package filter_test + +import ( + . "github.com/naveego/bosun/pkg/filter" + "testing" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +func TestFilter(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Filter Suite") +} + +type Item struct { + Name string + Labels map[string]string +} + +func (i Item) GetLabels() Labels { + return LabelsFromMap(i.Labels) +} diff --git a/pkg/filter/filter_test.go b/pkg/filter/filter_test.go new file mode 100644 index 0000000..f416bb7 --- /dev/null +++ b/pkg/filter/filter_test.go @@ -0,0 +1,54 @@ +package filter_test + +import ( + . "github.com/naveego/bosun/pkg/filter" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var _ = Describe("Filter", func() { + ab := Item{ + Name: "ab", + Labels: map[string]string{ + "a": "A", + "b": "B", + }, + } + abc := Item{ + Name: "abc", + Labels: map[string]string{ + "a": "A", + "b": "B", + "c": "C", + }, + } + c := Item{ + Name: "c", + Labels: map[string]string{ + "c": "C", + }, + } + d := Item{ + Name: "d", + Labels: map[string]string{ + "d": "D", + }, + } + items := []Item{ + ab, + abc, + c, + d, + } + + Describe("include", func() { + It("should include matched", func() { + Expect(Include(items, MustParse("a==A"))).To(ConsistOf(ab, abc)) + }) + }) + Describe("exclude", func() { + It("should include unmatched", func() { + Expect(Exclude(items, MustParse("c==C"))).To(ConsistOf(ab, d)) + }) + }) +}) diff --git a/pkg/filter/labels.go b/pkg/filter/labels.go new file mode 100644 index 0000000..c20fe11 --- /dev/null +++ b/pkg/filter/labels.go @@ -0,0 +1,62 @@ +package filter + +type LabelFunc func() string + +func (l LabelFunc) Value() string { return l() } + +type LabelString string + +func (l LabelString) Value() string { return string(l) } + +type LabelValue interface { + Value() string +} + +type Labels map[string]LabelValue + +func (l Labels) MarshalYAML() (interface{}, error) { + if l == nil { + return nil, nil + } + + m := make(map[string]string) + for k, v := range l { + if s, ok := v.(LabelString); ok { + m[k] = string(s) + } + } + return m, nil +} + +func (l *Labels) UnmarshalYAML(unmarshal func(interface{}) error) error { + var arr []string + err := unmarshal(&arr) + proxy := map[string]string{} + if err == nil { + for _, name := range arr { + proxy[name] = "true" + } + } else { + err = unmarshal(proxy) + } + out := Labels{} + + for k, v := range proxy { + out[k] = LabelString(v) + } + *l = out + return err +} + +// Labels implements Labelled. +func (l Labels) GetLabels() Labels { + return l +} + +func LabelsFromMap(m map[string]string) Labels { + out := Labels{} + for k, v := range m { + out[k] = LabelString(v) + } + return out +} diff --git a/pkg/filter/reflect.go b/pkg/filter/reflect.go new file mode 100644 index 0000000..d6d04c8 --- /dev/null +++ b/pkg/filter/reflect.go @@ -0,0 +1,35 @@ +package filter + +import ( + "fmt" + "reflect" +) + +type filterable struct { + val reflect.Value +} + +func newFilterable(mapOrSlice interface{}) filterable { + fromValue := reflect.ValueOf(mapOrSlice) + switch fromValue.Kind() { + case reflect.Map, + reflect.Slice: + return filterable{val: fromValue} + } + panic(fmt.Sprintf("invalid type %T, must be a map or slice", mapOrSlice)) +} + +func (f filterable) len() int { + return f.val.Len() +} + +func (f filterable) cloneEmpty() filterable { + switch f.val.Kind() { + case reflect.Map: + return filterable{val: reflect.MakeMap(f.val.Type())} + case reflect.Slice: + return filterable{val: reflect.MakeSlice(f.val.Type(), 0, f.val.Len())} + default: + panic(fmt.Sprintf("invalid type, must be a map or slice")) + } +} diff --git a/pkg/filter/simple_filter.go b/pkg/filter/simple_filter.go new file mode 100644 index 0000000..e2e2772 --- /dev/null +++ b/pkg/filter/simple_filter.go @@ -0,0 +1,127 @@ +package filter + +import ( + "fmt" + "github.com/pkg/errors" + "regexp" +) + +type Operator func(v string) bool + +const ( + OperatorKey = "" + OperatorEqual = "==" + OperatorNotEqual = "!=" + OperatorRegex = "?=" +) + +type OperatorFactory func(key, value string) (Operator, error) + +var Operators = map[string]OperatorFactory{ + "==": func(key, value string) (Operator, error) { + return func(v string) bool { + return value == v + }, nil + }, + "!=": func(key, value string) (Operator, error) { + return func(v string) bool { + return value != v + }, nil + }, + "?=": func(key, value string) (operator Operator, e error) { + re, err := regexp.Compile(value) + if err != nil { + return nil, errors.Errorf("bad regex in %s?=%s: %s", key, value, err) + } + return func(value string) bool { + return re.MatchString(value) + }, nil + }, +} + +type SimpleFilter struct { + Raw string + Key string + Value string + Operator Operator +} + +func (s SimpleFilter) String() string { + return s.Raw +} + +func (s SimpleFilter) IsMatch(l Labels) bool { + if label, ok := l[s.Key]; ok { + labelValue := label.Value() + return s.Operator(labelValue) + } + return false +} + +// FilterFromOperator creates a new filter with the given operator. +func FilterFromOperator(label, key string, operator Operator) Filter { + return SimpleFilter{ + Raw: label, + Key: key, + Operator: operator, + } +} + +// MustParse is like Parse but panics if parse fails. +func MustParse(parts ...string) Filter { + f, err := Parse(parts...) + if err != nil { + panic(err) + } + return f +} + +// Parse returns a filter based on key, value, and operator +// Argument patterns: +// `"key"` - Will match if the key is found +// `"keyOPvalue"` - Where OP is one or more none-word characters, will check the value of key against the provided value using the operator +// `"key", "op", "value"` - Will check the value at key against the value using the operator +func Parse(parts ...string) (Filter, error) { + switch len(parts) { + case 0: + return nil, errors.New("at least one part required") + case 1: + matches := simpleFilterParseRE.FindStringSubmatch(parts[0]) + if len(matches) == 4 { + return newFilterFromOperator(matches[1], matches[2], matches[3]) + } + return SimpleFilter{ + Raw: parts[0], + Key: parts[0], + Operator: func(value string) bool { + return true + }, + }, nil + + case 3: + return newFilterFromOperator(parts[0], parts[1], parts[2]) + default: + return nil, errors.Errorf("invalid parts %#v", parts) + } +} + +func newFilterFromOperator(k, op, v string) (Filter, error) { + + if factory, ok := Operators[op]; ok { + fn, err := factory(k, v) + if err != nil { + return nil, err + } + return SimpleFilter{ + Raw: fmt.Sprintf("%s%s%s", k, op, v), + Key: k, + Value: v, + Operator: fn, + }, nil + } + + return nil, errors.Errorf("no operator factory registered for operator %q", op) + +} + +var simpleFilterParseRE = regexp.MustCompile(`(\w+)(\W{1,2})(.*)`) diff --git a/pkg/filter/simple_filter_test.go b/pkg/filter/simple_filter_test.go new file mode 100644 index 0000000..d2c75d1 --- /dev/null +++ b/pkg/filter/simple_filter_test.go @@ -0,0 +1,34 @@ +package filter_test + +import ( + . "github.com/naveego/bosun/pkg/filter" + . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/extensions/table" + . "github.com/onsi/gomega" +) + +var _ = Describe("Simple filter", func() { + + labels := LabelsFromMap(map[string]string{ + "a": "A", + "long": "string-with-data", + "key": "", + }) + + DescribeTable("parsing", func(input string, expected bool) { + f, err := Parse(input) + Expect(err).ToNot(HaveOccurred()) + + Expect(f.IsMatch(labels)).To(Equal(expected)) + }, + Entry("key match", "key", true), + Entry("key mismatch", "crow", false), + Entry("equality match", "a==A", true), + Entry("equality mismatch", "a==B", false), + Entry("inequality match", "a!=B", true), + Entry("inequality mismatch", "a!=A", false), + Entry("regex match", "long?=.*-with", true), + Entry("regex mismatch", "long?=.*-wrong", false), + ) + +}) diff --git a/pkg/git/strings.go b/pkg/git/strings.go new file mode 100644 index 0000000..bcc7cf3 --- /dev/null +++ b/pkg/git/strings.go @@ -0,0 +1,32 @@ +package git + +import ( + "github.com/pkg/errors" + "regexp" +) + +type BranchName string + +var ReleasePattern = regexp.MustCompile("^release/(.*)") +var MasterPattern = regexp.MustCompile("^master$") + +func (b BranchName) Release() (string, error) { + if !b.IsRelease() { + return "", errors.Errorf("branch %q is not a release branch", b) + } + + m := ReleasePattern.FindStringSubmatch(string(b)) + return m[1], nil +} + +func (b BranchName) IsRelease() bool { + return ReleasePattern.MatchString(string(b)) +} + +func (b BranchName) IsMaster() bool { + return MasterPattern.MatchString(string(b)) +} + +func (b BranchName) String() string { + return string(b) +} diff --git a/pkg/git/wrapper.go b/pkg/git/wrapper.go index e460e8d..d77dc97 100644 --- a/pkg/git/wrapper.go +++ b/pkg/git/wrapper.go @@ -20,6 +20,17 @@ func NewGitWrapper(pathHint string) (GitWrapper, error) { }, nil } +func (g GitWrapper) ExecLines(args ...string) ([]string, error) { + text, err := g.Exec(args...) + if err != nil { + return nil, err + } + if len(text) == 0 { + return nil, nil + } + return strings.Split(text, "\n"), nil +} + func (g GitWrapper) Exec(args ...string) (string, error) { args = append([]string{"-C", g.dir}, args...) @@ -37,7 +48,7 @@ func (g GitWrapper) Branch() string { func (g GitWrapper) Commit() string { o, _ := pkg.NewCommand("git", "-C", g.dir, "log", "--pretty=format:'%h'", "-n", "1").RunOut() - return o + return strings.Trim(o, "'") } func (g GitWrapper) Tag() string { @@ -69,7 +80,6 @@ func (g GitWrapper) IsDirty() bool { return false } - func (g GitWrapper) Log(args ...string) ([]string, error) { args = append([]string{"-C", g.dir, "log"}, args...) out, err := pkg.NewCommand("git", args...).RunOut() diff --git a/pkg/helm/helpers.go b/pkg/helm/helpers.go index b3b4321..80f490f 100644 --- a/pkg/helm/helpers.go +++ b/pkg/helm/helpers.go @@ -1,6 +1,7 @@ package helm import ( + "fmt" "github.com/naveego/bosun/pkg" "github.com/pkg/errors" "os" @@ -9,16 +10,38 @@ import ( "strings" ) -func PublishChart(chart string, force bool) error { - stat, err := os.Stat(chart) +type ChartHandle string + +func (c ChartHandle) String() string { + return string(c) +} + +func (c ChartHandle) HasRepo() bool { + return strings.Contains(string(c), "/") +} +func (c ChartHandle) WithRepo(repo string) ChartHandle { + segs := strings.Split(string(c), "/") + switch len(segs) { + case 1: + return ChartHandle(fmt.Sprintf("%s/%s", repo, segs[0])) + case 2: + return ChartHandle(fmt.Sprintf("%s/%s", repo, segs[1])) + } + panic(fmt.Sprintf("invalid chart %q", string(c))) +} + +// PublishChart publishes the chart at path using qualified name. +// If force is true, an existing version of the chart will be overwritten. +func PublishChart(qualifiedName, path string, force bool) error { + stat, err := os.Stat(path) if !stat.IsDir() { - return errors.Errorf("%q is not a directory", chart) + return errors.Errorf("%q is not a directory", path) } - chartName := filepath.Base(chart) - log := pkg.Log.WithField("chart", chart).WithField("@chart", chartName) + chartName := filepath.Base(path) + log := pkg.Log.WithField("chart", path).WithField("@chart", chartName) - chartText, err := new(pkg.Command).WithExe("helm").WithArgs("inspect", "chart", chart).RunOut() + chartText, err := new(pkg.Command).WithExe("helm").WithArgs("inspect", "chart", path).RunOut() if err != nil { return errors.Wrap(err, "Could not inspect chart") @@ -30,7 +53,6 @@ func PublishChart(chart string, force bool) error { thisVersion := thisVersionMatch[1] log = log.WithField("@version", thisVersion) - qualifiedName := "helm.n5o.black/" + chartName repoContent, err := new(pkg.Command).WithExe("helm").WithEnvValue("AWS_DEFAULT_PROFILE", "black").WithArgs("search", qualifiedName, "--versions").RunOut() if err != nil { @@ -50,7 +72,7 @@ func PublishChart(chart string, force bool) error { return errors.New("version already exists (use --force to overwrite)") } - out, err := pkg.NewCommand("helm", "package", chart).RunOut() + out, err := pkg.NewCommand("helm", "package", path).RunOut() if err != nil { return errors.Wrap(err, "could not create package") } diff --git a/pkg/mirror/apply.go b/pkg/mirror/apply.go new file mode 100644 index 0000000..2f4d33c --- /dev/null +++ b/pkg/mirror/apply.go @@ -0,0 +1,37 @@ +package mirror + +import ( + "reflect" +) + +func ApplyFuncRecursively(target interface{}, fn interface{}) { + + fnVal := reflect.ValueOf(fn) + if fnVal.Kind() != reflect.Func { + panic("fn must be a function") + } + if fnVal.Type().NumIn() != 1 { + panic("fn must be a function with one parameter") + } + + argType := fnVal.Type().In(0) + + val := reflect.ValueOf(target) + if val.Kind() == reflect.Ptr { + val = val.Elem() + } + numFields := val.NumField() + for i := 0; i < numFields; i++ { + field := val.Field(i) + if field.Kind() != reflect.Slice { + continue + } + entryCount := field.Len() + for j := 0; j < entryCount; j++ { + entry := field.Index(j) + if entry.Type().AssignableTo(argType) || entry.Type().Implements(argType) { + fnVal.Call([]reflect.Value{entry}) + } + } + } +} diff --git a/pkg/mirror/apply_test.go b/pkg/mirror/apply_test.go new file mode 100644 index 0000000..4295586 --- /dev/null +++ b/pkg/mirror/apply_test.go @@ -0,0 +1,37 @@ +package mirror_test + +import ( + . "github.com/naveego/bosun/pkg/mirror" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +type Target struct { + Values []ValueString +} + +type ValueString string + +func (v ValueString) Value() string { + return string(v) +} + +type Valuer interface { + Value() string +} + +var _ = Describe("Reflect", func() { + It("should call function", func() { + target := Target{ + Values: []ValueString{"x", "y"}, + } + var out []string + + ApplyFuncRecursively(target, func(x Valuer) { + out = append(out, x.Value()) + }) + + Expect(out).To(ConsistOf("x", "y")) + + }) +}) diff --git a/pkg/mirror/mirror_suite_test.go b/pkg/mirror/mirror_suite_test.go new file mode 100644 index 0000000..47beca0 --- /dev/null +++ b/pkg/mirror/mirror_suite_test.go @@ -0,0 +1,13 @@ +package mirror_test + +import ( + "testing" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +func TestMirror(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Mirror Suite") +} diff --git a/pkg/mirror/sort.go b/pkg/mirror/sort.go new file mode 100644 index 0000000..45aecd4 --- /dev/null +++ b/pkg/mirror/sort.go @@ -0,0 +1,62 @@ +package mirror + +import ( + "reflect" + "sort" +) + +// Sort sorts target by calling less. +// target must be a slice []T. +// less must be a function of the form func(a, b T) bool +func Sort(target interface{}, less interface{}) { + + lessVal := reflect.ValueOf(less) + lessType := lessVal.Type() + if lessVal.Kind() != reflect.Func { + panic("fn must be a function") + } + if lessType.NumIn() != 2 { + panic("fn must be a function with 2 parameters") + } + if lessType.NumOut() != 1 || lessType.Out(0).Kind() != reflect.Bool { + panic("fn must be a function with 1 bool return") + } + + val := reflect.ValueOf(target) + if val.Kind() != reflect.Slice { + panic("target must be a slice") + } + + s := sorter{ + t: val, + less: lessVal, + } + + sort.Sort(s) +} + +type sorter struct { + t reflect.Value + less reflect.Value +} + +func (s sorter) Len() int { + return s.t.Len() +} + +func (s sorter) Less(i, j int) bool { + ix := s.t.Index(i) + jx := s.t.Index(j) + rv := s.less.Call([]reflect.Value{ix, jx}) + bv := rv[0] + return bv.Bool() +} + +func (s sorter) Swap(i, j int) { + ix := s.t.Index(i) + jx := s.t.Index(j) + iv := ix.Interface() + jv := jx.Interface() + ix.Set(reflect.ValueOf(jv)) + jx.Set(reflect.ValueOf(iv)) +} diff --git a/pkg/mirror/sort_test.go b/pkg/mirror/sort_test.go new file mode 100644 index 0000000..77cb525 --- /dev/null +++ b/pkg/mirror/sort_test.go @@ -0,0 +1,20 @@ +package mirror_test + +import ( + . "github.com/naveego/bosun/pkg/mirror" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var _ = Describe("Sort", func() { + It("should call function", func() { + sut := []string{"c", "a", "b"} + + Sort(sut, func(a, b string) bool { + return a < b + }) + + Expect(sut).To(BeEquivalentTo([]string{"a", "b", "c"})) + + }) +}) diff --git a/pkg/semver/semver.go b/pkg/semver/semver.go new file mode 100644 index 0000000..6968eec --- /dev/null +++ b/pkg/semver/semver.go @@ -0,0 +1,292 @@ +// Copyright 2013-2015 CoreOS, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// This code was copied into bosun because of a few missing features +// in the semver library: lack of yaml marshalling and annoying pointer inconsistencies. + +// Semantic Versions http://semver.org +package semver + +import ( + "bytes" + "errors" + "fmt" + "strconv" + "strings" +) + +type Version struct { + Major int64 + Minor int64 + Patch int64 + PreRelease PreRelease + Metadata string +} + +type PreRelease string + +func splitOff(input *string, delim string) (val string) { + parts := strings.SplitN(*input, delim, 2) + + if len(parts) == 2 { + *input = parts[0] + val = parts[1] + } + + return val +} + +func New(version string) Version { + return Must(NewVersion(version)) +} + +func NewVersion(version string) (Version, error) { + v := Version{} + err := v.Set(version) + return v, err +} + +// Must is a helper for wrapping NewVersion and will panic if err is not nil. +func Must(v Version, err error) Version { + if err != nil { + panic(err) + } + return v +} + +// Set parses and updates v from the given version string. Implements flag.Value +func (v *Version) Set(version string) error { + metadata := splitOff(&version, "+") + preRelease := PreRelease(splitOff(&version, "-")) + dotParts := strings.SplitN(version, ".", 3) + + if len(dotParts) != 3 { + return fmt.Errorf("%s is not in dotted-tri format", version) + } + + parsed := make([]int64, 3, 3) + + for i, v := range dotParts[:3] { + val, err := strconv.ParseInt(v, 10, 64) + parsed[i] = val + if err != nil { + return err + } + } + + v.Metadata = metadata + v.PreRelease = preRelease + v.Major = parsed[0] + v.Minor = parsed[1] + v.Patch = parsed[2] + return nil +} + +func (v Version) String() string { + var buffer bytes.Buffer + + fmt.Fprintf(&buffer, "%d.%d.%d", v.Major, v.Minor, v.Patch) + + if v.PreRelease != "" { + fmt.Fprintf(&buffer, "-%s", v.PreRelease) + } + + if v.Metadata != "" { + fmt.Fprintf(&buffer, "+%s", v.Metadata) + } + + return buffer.String() +} + +func (v *Version) UnmarshalYAML(unmarshal func(interface{}) error) error { + var data string + if err := unmarshal(&data); err != nil { + return err + } + return v.Set(data) +} + +func (v Version) MarshalYAML() (interface{}, error) { + return v.String(), nil +} + +func (v Version) MarshalJSON() ([]byte, error) { + return []byte(`"` + v.String() + `"`), nil +} + +func (v *Version) UnmarshalJSON(data []byte) error { + l := len(data) + if l == 0 || string(data) == `""` { + return nil + } + if l < 2 || data[0] != '"' || data[l-1] != '"' { + return errors.New("invalid semver string") + } + return v.Set(string(data[1 : l-1])) +} + +// Compare tests if v is less than, equal to, or greater than versionB, +// returning -1, 0, or +1 respectively. +func (v Version) Compare(versionB Version) int { + if cmp := recursiveCompare(v.Slice(), versionB.Slice()); cmp != 0 { + return cmp + } + return preReleaseCompare(v, versionB) +} + +// Equal tests if v is equal to versionB. +func (v Version) Equal(versionB Version) bool { + return v.Compare(versionB) == 0 +} + +// LessThan tests if v is less than versionB. +func (v Version) LessThan(versionB Version) bool { + return v.Compare(versionB) < 0 +} + +// Slice converts the comparable parts of the semver into a slice of integers. +func (v Version) Slice() []int64 { + return []int64{v.Major, v.Minor, v.Patch} +} + +func (p PreRelease) Slice() []string { + preRelease := string(p) + return strings.Split(preRelease, ".") +} + +func preReleaseCompare(versionA Version, versionB Version) int { + a := versionA.PreRelease + b := versionB.PreRelease + + /* Handle the case where if two versions are otherwise equal it is the + * one without a PreRelease that is greater */ + if len(a) == 0 && (len(b) > 0) { + return 1 + } else if len(b) == 0 && (len(a) > 0) { + return -1 + } + + // If there is a prerelease, check and compare each part. + return recursivePreReleaseCompare(a.Slice(), b.Slice()) +} + +func recursiveCompare(versionA []int64, versionB []int64) int { + if len(versionA) == 0 { + return 0 + } + + a := versionA[0] + b := versionB[0] + + if a > b { + return 1 + } else if a < b { + return -1 + } + + return recursiveCompare(versionA[1:], versionB[1:]) +} + +func recursivePreReleaseCompare(versionA []string, versionB []string) int { + // A larger set of pre-release fields has a higher precedence than a smaller set, + // if all of the preceding identifiers are equal. + if len(versionA) == 0 { + if len(versionB) > 0 { + return -1 + } + return 0 + } else if len(versionB) == 0 { + // We're longer than versionB so return 1. + return 1 + } + + a := versionA[0] + b := versionB[0] + + aInt := false + bInt := false + + aI, err := strconv.Atoi(versionA[0]) + if err == nil { + aInt = true + } + + bI, err := strconv.Atoi(versionB[0]) + if err == nil { + bInt = true + } + + // Handle Integer Comparison + if aInt && bInt { + if aI > bI { + return 1 + } else if aI < bI { + return -1 + } + } + + // Handle String Comparison + if a > b { + return 1 + } else if a < b { + return -1 + } + + return recursivePreReleaseCompare(versionA[1:], versionB[1:]) +} + +// BumpMajor returns a copy of the version after it increments the Major field by 1 and resets all other fields to their default values +func (v Version) BumpMajor() Version { + v.Major += 1 + v.Minor = 0 + v.Patch = 0 + v.PreRelease = PreRelease("") + v.Metadata = "" + return v +} + +// BumpMinor returns a copy of the version after it increments the Minor field by 1 and resets all other fields to their default values +func (v Version) BumpMinor() Version { + v.Minor += 1 + v.Patch = 0 + v.PreRelease = PreRelease("") + v.Metadata = "" + return v +} + +// BumpPatch returns a copy of the version after it increments the Patch field by 1 and resets all other fields to their default values +func (v Version) BumpPatch() Version { + v.Patch += 1 + v.PreRelease = PreRelease("") + v.Metadata = "" + return v +} + +func (v Version) Empty() bool { + return v.Major == 0 && v.Minor == 0 && v.Patch == 0 && v.Metadata == "" && v.PreRelease == "" +} + +func (v Version) Bump(bump string) (Version, error) { + switch strings.ToLower(bump) { + case "major": + return v.BumpMajor(), nil + case "minor": + return v.BumpMinor(), nil + case "patch": + return v.BumpPatch(), nil + default: + return Version{}, fmt.Errorf("want 'major', 'minor', or 'patch', got '%s'", bump) + } + +} diff --git a/pkg/semver/semver_test.go b/pkg/semver/semver_test.go new file mode 100644 index 0000000..876c68e --- /dev/null +++ b/pkg/semver/semver_test.go @@ -0,0 +1,370 @@ +// Copyright 2013-2015 CoreOS, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package semver + +import ( + "bytes" + "encoding/json" + "errors" + "flag" + "fmt" + "math/rand" + "reflect" + "testing" + "time" + + "gopkg.in/yaml.v2" +) + +type fixture struct { + GreaterVersion string + LesserVersion string +} + +var fixtures = []fixture{ + fixture{"0.0.0", "0.0.0-foo"}, + fixture{"0.0.1", "0.0.0"}, + fixture{"1.0.0", "0.9.9"}, + fixture{"0.10.0", "0.9.0"}, + fixture{"0.99.0", "0.10.0"}, + fixture{"2.0.0", "1.2.3"}, + fixture{"0.0.0", "0.0.0-foo"}, + fixture{"0.0.1", "0.0.0"}, + fixture{"1.0.0", "0.9.9"}, + fixture{"0.10.0", "0.9.0"}, + fixture{"0.99.0", "0.10.0"}, + fixture{"2.0.0", "1.2.3"}, + fixture{"0.0.0", "0.0.0-foo"}, + fixture{"0.0.1", "0.0.0"}, + fixture{"1.0.0", "0.9.9"}, + fixture{"0.10.0", "0.9.0"}, + fixture{"0.99.0", "0.10.0"}, + fixture{"2.0.0", "1.2.3"}, + fixture{"1.2.3", "1.2.3-asdf"}, + fixture{"1.2.3", "1.2.3-4"}, + fixture{"1.2.3", "1.2.3-4-foo"}, + fixture{"1.2.3-5-foo", "1.2.3-5"}, + fixture{"1.2.3-5", "1.2.3-4"}, + fixture{"1.2.3-5-foo", "1.2.3-5-Foo"}, + fixture{"3.0.0", "2.7.2+asdf"}, + fixture{"3.0.0+foobar", "2.7.2"}, + fixture{"1.2.3-a.10", "1.2.3-a.5"}, + fixture{"1.2.3-a.b", "1.2.3-a.5"}, + fixture{"1.2.3-a.b", "1.2.3-a"}, + fixture{"1.2.3-a.b.c.10.d.5", "1.2.3-a.b.c.5.d.100"}, + fixture{"1.0.0", "1.0.0-rc.1"}, + fixture{"1.0.0-rc.2", "1.0.0-rc.1"}, + fixture{"1.0.0-rc.1", "1.0.0-beta.11"}, + fixture{"1.0.0-beta.11", "1.0.0-beta.2"}, + fixture{"1.0.0-beta.2", "1.0.0-beta"}, + fixture{"1.0.0-beta", "1.0.0-alpha.beta"}, + fixture{"1.0.0-alpha.beta", "1.0.0-alpha.1"}, + fixture{"1.0.0-alpha.1", "1.0.0-alpha"}, +} + +func TestCompare(t *testing.T) { + for _, v := range fixtures { + gt, err := NewVersion(v.GreaterVersion) + if err != nil { + t.Error(err) + } + + lt, err := NewVersion(v.LesserVersion) + if err != nil { + t.Error(err) + } + + if gt.LessThan(*lt) { + t.Errorf("%s should not be less than %s", gt, lt) + } + if gt.Equal(*lt) { + t.Errorf("%s should not be equal to %s", gt, lt) + } + if gt.Compare(*lt) <= 0 { + t.Errorf("%s should be greater than %s", gt, lt) + } + if !lt.LessThan(*gt) { + t.Errorf("%s should be less than %s", lt, gt) + } + if !lt.Equal(*lt) { + t.Errorf("%s should be equal to %s", lt, lt) + } + if lt.Compare(*gt) > 0 { + t.Errorf("%s should not be greater than %s", lt, gt) + } + } +} + +func testString(t *testing.T, orig string, version *Version) { + if orig != version.String() { + t.Errorf("%s != %s", orig, version) + } +} + +func TestString(t *testing.T) { + for _, v := range fixtures { + gt, err := NewVersion(v.GreaterVersion) + if err != nil { + t.Error(err) + } + testString(t, v.GreaterVersion, gt) + + lt, err := NewVersion(v.LesserVersion) + if err != nil { + t.Error(err) + } + testString(t, v.LesserVersion, lt) + } +} + +func shuffleStringSlice(src []string) []string { + dest := make([]string, len(src)) + rand.Seed(time.Now().Unix()) + perm := rand.Perm(len(src)) + for i, v := range perm { + dest[v] = src[i] + } + return dest +} + +func TestSort(t *testing.T) { + sortedVersions := []string{"1.0.0", "1.0.2", "1.2.0", "3.1.1"} + unsortedVersions := shuffleStringSlice(sortedVersions) + + semvers := []*Version{} + for _, v := range unsortedVersions { + sv, err := NewVersion(v) + if err != nil { + t.Fatal(err) + } + semvers = append(semvers, sv) + } + + Sort(semvers) + + for idx, sv := range semvers { + if sv.String() != sortedVersions[idx] { + t.Fatalf("incorrect sort at index %v", idx) + } + } +} + +func TestBumpMajor(t *testing.T) { + version, _ := NewVersion("1.0.0") + version.BumpMajor() + if version.Major != 2 { + t.Fatalf("bumping major on 1.0.0 resulted in %v", version) + } + + version, _ = NewVersion("1.5.2") + version.BumpMajor() + if version.Minor != 0 && version.Patch != 0 { + t.Fatalf("bumping major on 1.5.2 resulted in %v", version) + } + + version, _ = NewVersion("1.0.0+build.1-alpha.1") + version.BumpMajor() + if version.PreRelease != "" && version.PreRelease != "" { + t.Fatalf("bumping major on 1.0.0+build.1-alpha.1 resulted in %v", version) + } +} + +func TestBumpMinor(t *testing.T) { + version, _ := NewVersion("1.0.0") + version.BumpMinor() + + if version.Major != 1 { + t.Fatalf("bumping minor on 1.0.0 resulted in %v", version) + } + + if version.Minor != 1 { + t.Fatalf("bumping major on 1.0.0 resulted in %v", version) + } + + version, _ = NewVersion("1.0.0+build.1-alpha.1") + version.BumpMinor() + if version.PreRelease != "" && version.PreRelease != "" { + t.Fatalf("bumping major on 1.0.0+build.1-alpha.1 resulted in %v", version) + } +} + +func TestBumpPatch(t *testing.T) { + version, _ := NewVersion("1.0.0") + version.BumpPatch() + + if version.Major != 1 { + t.Fatalf("bumping minor on 1.0.0 resulted in %v", version) + } + + if version.Minor != 0 { + t.Fatalf("bumping major on 1.0.0 resulted in %v", version) + } + + if version.Patch != 1 { + t.Fatalf("bumping major on 1.0.0 resulted in %v", version) + } + + version, _ = NewVersion("1.0.0+build.1-alpha.1") + version.BumpPatch() + if version.PreRelease != "" && version.PreRelease != "" { + t.Fatalf("bumping major on 1.0.0+build.1-alpha.1 resulted in %v", version) + } +} + +func TestMust(t *testing.T) { + tests := []struct { + versionStr string + + version *Version + recov interface{} + }{ + { + versionStr: "1.0.0", + version: &Version{Major: 1}, + }, + { + versionStr: "version number", + recov: errors.New("version number is not in dotted-tri format"), + }, + } + + for _, tt := range tests { + func() { + defer func() { + recov := recover() + if !reflect.DeepEqual(tt.recov, recov) { + t.Fatalf("incorrect panic for %q: want %v, got %v", tt.versionStr, tt.recov, recov) + } + }() + + version := Must(NewVersion(tt.versionStr)) + if !reflect.DeepEqual(tt.version, version) { + t.Fatalf("incorrect version for %q: want %+v, got %+v", tt.versionStr, tt.version, version) + } + }() + } +} + +type fixtureJSON struct { + GreaterVersion *Version + LesserVersion *Version +} + +func TestJSON(t *testing.T) { + fj := make([]fixtureJSON, len(fixtures)) + for i, v := range fixtures { + var err error + fj[i].GreaterVersion, err = NewVersion(v.GreaterVersion) + if err != nil { + t.Fatal(err) + } + fj[i].LesserVersion, err = NewVersion(v.LesserVersion) + if err != nil { + t.Fatal(err) + } + } + + fromStrings, err := json.Marshal(fixtures) + if err != nil { + t.Fatal(err) + } + fromVersions, err := json.Marshal(fj) + if err != nil { + t.Fatal(err) + } + if !bytes.Equal(fromStrings, fromVersions) { + t.Errorf("Expected: %s", fromStrings) + t.Errorf("Unexpected: %s", fromVersions) + } + + fromJson := make([]fixtureJSON, 0, len(fj)) + err = json.Unmarshal(fromStrings, &fromJson) + if err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(fromJson, fj) { + t.Error("Expected: ", fj) + t.Error("Unexpected: ", fromJson) + } +} + +func TestYAML(t *testing.T) { + document, err := yaml.Marshal(fixtures) + if err != nil { + t.Fatal(err) + } + + expected := make([]fixtureJSON, len(fixtures)) + for i, v := range fixtures { + var err error + expected[i].GreaterVersion, err = NewVersion(v.GreaterVersion) + if err != nil { + t.Fatal(err) + } + expected[i].LesserVersion, err = NewVersion(v.LesserVersion) + if err != nil { + t.Fatal(err) + } + } + + fromYAML := make([]fixtureJSON, 0, len(fixtures)) + err = yaml.Unmarshal(document, &fromYAML) + if err != nil { + t.Fatal(err) + } + + if !reflect.DeepEqual(fromYAML, expected) { + t.Error("Expected: ", expected) + t.Error("Unexpected: ", fromYAML) + } +} + +func TestBadInput(t *testing.T) { + bad := []string{ + "1.2", + "1.2.3x", + "0x1.3.4", + "-1.2.3", + "1.2.3.4", + } + for _, b := range bad { + if _, err := NewVersion(b); err == nil { + t.Error("Improperly accepted value: ", b) + } + } +} + +func TestFlag(t *testing.T) { + v := Version{} + f := flag.NewFlagSet("version", flag.ContinueOnError) + f.Var(&v, "version", "set version") + + if err := f.Set("version", "1.2.3"); err != nil { + t.Fatal(err) + } + + if v.String() != "1.2.3" { + t.Errorf("Set wrong value %q", v) + } +} + +func ExampleVersion_LessThan() { + vA := New("1.2.3") + vB := New("3.2.1") + + fmt.Printf("%s < %s == %t\n", vA, vB, vA.LessThan(*vB)) + // Output: + // 1.2.3 < 3.2.1 == true +} diff --git a/pkg/semver/sort.go b/pkg/semver/sort.go new file mode 100644 index 0000000..e256b41 --- /dev/null +++ b/pkg/semver/sort.go @@ -0,0 +1,38 @@ +// Copyright 2013-2015 CoreOS, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package semver + +import ( + "sort" +) + +type Versions []*Version + +func (s Versions) Len() int { + return len(s) +} + +func (s Versions) Swap(i, j int) { + s[i], s[j] = s[j], s[i] +} + +func (s Versions) Less(i, j int) bool { + return s[i].LessThan(*s[j]) +} + +// Sort sorts the given slice of Version +func Sort(versions []*Version) { + sort.Sort(Versions(versions)) +} diff --git a/pkg/util/error.go b/pkg/util/error.go new file mode 100644 index 0000000..ce80953 --- /dev/null +++ b/pkg/util/error.go @@ -0,0 +1,56 @@ +package util + +import ( + "github.com/pkg/errors" + "runtime/debug" + "strings" +) + +type multiError struct { + errs []error +} + +func (m multiError) Error() string { + w := new(strings.Builder) + first := true + for _, err := range m.errs { + if !first { + w.WriteString("\n") + } + w.WriteString(err.Error()) + first = false + } + return w.String() +} + +func MultiErr(errs ...error) error { + var acc []error + for _, err := range errs { + if err != nil { + acc = append(acc, err) + } + } + + if len(acc) == 0 { + return nil + } + + return multiError{errs: acc} + +} + +func TryCatch(label string, fn func() error) (err error) { + defer func() { + if r := recover(); r != nil { + var ok bool + err, ok = r.(error) + if ok { + err = errors.Errorf("%s: panicked with error: %s\n%s", label, err, debug.Stack()) + } else { + err = errors.Errorf("%s: panicked: %v\n%s", label, r, debug.Stack()) + } + } + }() + + return fn() +} diff --git a/pkg/util/strings.go b/pkg/util/strings.go new file mode 100644 index 0000000..2c138d6 --- /dev/null +++ b/pkg/util/strings.go @@ -0,0 +1,75 @@ +package util + +import ( + "crypto/sha256" + "fmt" + "gopkg.in/yaml.v2" + "reflect" + "sort" +) + +// StringSliceToMap converts +// []string{"a","A", "b", "B"} to +// map[string]string{"a":"A", "b":"B"} +func StringSliceToMap(ss ...string) map[string]string { + out := map[string]string{} + for i := 0; i+1 < len(ss); i += 2 { + out[ss[i]] = ss[i+1] + } + return out +} + +func ConcatStrings(stringsOrSlices ...interface{}) []string { + var out []string + for i, x := range stringsOrSlices { + switch v := x.(type) { + case string: + out = append(out, v) + case []string: + out = append(out, v...) + default: + panic(fmt.Sprintf("want string or []string, got %v (%T) at %d", x, x, i)) + } + } + return out +} + +func HashToStringViaYaml(i interface{}) (string, error) { + y, err := yaml.Marshal(i) + if err != nil { + return "", err + } + + h := sha256.New() + _, err = h.Write(y) + if err != nil { + return "", err + } + o := h.Sum(nil) + + return fmt.Sprintf("%x", o), nil +} + +func SortedKeys(i interface{}) []string { + if i == nil { + return nil + } + v := reflect.ValueOf(i) + if v.Kind() != reflect.Map { + panic(fmt.Sprintf("need a map, got a %T", i)) + } + + keyValues := v.MapKeys() + var keyStrings []string + + for _, kv := range keyValues { + if s, ok := kv.Interface().(string); !ok { + panic(fmt.Sprintf("map keys must strings, got key of %s", kv.Type())) + } else { + keyStrings = append(keyStrings, s) + } + } + + sort.Strings(keyStrings) + return keyStrings +} diff --git a/pkg/util/strings_test.go b/pkg/util/strings_test.go new file mode 100644 index 0000000..c4f855c --- /dev/null +++ b/pkg/util/strings_test.go @@ -0,0 +1,40 @@ +package util + +import ( + "reflect" + "testing" +) + +func TestStringSliceToMap(t *testing.T) { + type args struct { + ss []string + } + tests := []struct { + name string + args args + want map[string]string + }{ + { + name: "combine", + args: args{ss: []string{"a", "A", "b", "B"}}, + want: map[string]string{ + "a": "A", + "b": "B", + }, + }, { + name: "combine with extra", + args: args{ss: []string{"a", "A", "b", "B", "c"}}, + want: map[string]string{ + "a": "A", + "b": "B", + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := StringSliceToMap(tt.args.ss...); !reflect.DeepEqual(got, tt.want) { + t.Errorf("StringSliceToMap() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/pkg/util/table.go b/pkg/util/table.go new file mode 100644 index 0000000..cd6a492 --- /dev/null +++ b/pkg/util/table.go @@ -0,0 +1,7 @@ +package util + +// Tabler implementations can be rendered as a table. +type Tabler interface { + Headers() []string + Rows() [][]string +} diff --git a/pkg/util/util.go b/pkg/util/util.go index 479d6bb..4b6086e 100644 --- a/pkg/util/util.go +++ b/pkg/util/util.go @@ -11,11 +11,8 @@ type RetriableError struct { Err error } - func (r RetriableError) Error() string { return "Temporary Error: " + r.Err.Error() } - - type MultiError struct { Errors []error } @@ -38,7 +35,6 @@ func (m MultiError) ToError() error { return errors.New(strings.Join(errStrings, "\n")) } - func Retry(attempts int, callback func() error) (err error) { return RetryAfter(attempts, callback, 0) } @@ -62,4 +58,16 @@ func RetryAfter(attempts int, callback func() error, d time.Duration) (err error time.Sleep(d) } return m.ToError() -} \ No newline at end of file +} + +func DistinctStrings(strs []string) []string { + var out []string + m := map[string]struct{}{} + for _, s := range strs { + m[s] = struct{}{} + } + for k := range m { + out = append(out, k) + } + return out +} diff --git a/pkg/vault_helpers.go b/pkg/vault_helpers.go index 74de361..2ffae3d 100644 --- a/pkg/vault_helpers.go +++ b/pkg/vault_helpers.go @@ -7,6 +7,7 @@ import ( "github.com/hashicorp/go-uuid" "github.com/hashicorp/vault/api" "github.com/imdario/mergo" + "github.com/naveego/bosun/pkg/util" "github.com/pkg/errors" "github.com/sirupsen/logrus" "golang.org/x/crypto/ssh/terminal" @@ -22,7 +23,6 @@ import ( "time" ) - type VaultLayout struct { Auth map[string]map[string]interface{} Mounts map[string]map[string]interface{} @@ -49,7 +49,7 @@ type TemplateValues struct { func NewTemplateValues(args ...string) (TemplateValues, error) { t := TemplateValues{} - for _, kv := range args{ + for _, kv := range args { segs := strings.Split(kv, "=") if len(segs) != 2 { return t, errors.Errorf("invalid values flag value: %q (should be Key=value)", kv) @@ -124,7 +124,7 @@ func LoadVaultLayoutFromBytes(label string, data []byte, templateArgs TemplateVa return nil, err } - return vl, nil + return vl, nil } var lineExtractor = regexp.MustCompile(`line (\d+):`) @@ -151,7 +151,11 @@ func mergeMaps(left, right map[string]map[string]interface{}) map[string]map[str return m } -func (v VaultLayout) Apply(client *api.Client) error { +// Apply applies the vault layout to vault, first checking if +// it has changed since the last time it was applied based on the +// hashKey. If hashKey is empty, or force is true, the change detection +// step is skipped. +func (v VaultLayout) Apply(hashKey string, force bool, client *api.Client) error { hadErrors := false @@ -162,6 +166,21 @@ func (v VaultLayout) Apply(client *api.Client) error { hadErrors = true } + // create change detection hash + hashPath := fmt.Sprintf("naveego-secrets/bosun-vault/%s", hashKey) + hash, _ := util.HashToStringViaYaml(v) + + if !force && hashKey != "" { + previousHashSecret, err := client.Logical().Read(hashPath) + if err == nil && previousHashSecret != nil && previousHashSecret.Data != nil { + previousHash := previousHashSecret.Data["hash"].(string) + if previousHash == hash { + Log.Warnf("Hash of vault layout %q has not changed since last applied. Use the --force flag to force it to be applied again.", hashKey) + return nil + } + } + } + for path, data := range v.Auth { log := Log.WithField("@type", "Auth").WithField("path", path) mounts, err := client.Sys().ListAuth() @@ -251,7 +270,7 @@ func (v VaultLayout) Apply(client *api.Client) error { for path, data := range v.Policies { log := Log.WithField("@type", "Policy").WithField("Path", path) var policy string - switch d := data.(type){ + switch d := data.(type) { case string: policy = d default: @@ -275,6 +294,16 @@ func (v VaultLayout) Apply(client *api.Client) error { return errors.New("Vault apply failed. See log for errors.") } + if hashKey != "" { + // Store the change detection hash + _, err := client.Logical().Write(hashPath, map[string]interface{}{ + "hash": hash, + }) + if err != nil { + Log.WithError(err).Warn("Could not store change detection hash in Vault.") + } + } + return nil } @@ -317,7 +346,7 @@ func NewVaultLowlevelClient(token, vaultAddr string) (*api.Client, error) { vaultConfig := api.DefaultConfig() vaultConfig.Address = vaultAddr - vaultConfig.Timeout = 60*time.Second + vaultConfig.Timeout = 60 * time.Second vaultConfig.MaxRetries = 6 err := vaultConfig.ReadEnvironment() @@ -478,8 +507,7 @@ func CreateWrappedAppRoleToken(vaultClient *api.Client, appRole string) (string, return wrappedToken, nil } - -func cleanUpMap(m interface{}) interface{}{ +func cleanUpMap(m interface{}) interface{} { switch t := m.(type) { case map[string]interface{}: for k, child := range t { @@ -500,4 +528,4 @@ func cleanUpMap(m interface{}) interface{}{ default: return m } -} \ No newline at end of file +} diff --git a/pkg/vault_init.go b/pkg/vault_init.go index f705ba6..3fb1546 100644 --- a/pkg/vault_init.go +++ b/pkg/vault_init.go @@ -41,22 +41,21 @@ func (v VaultInitializer) InitNonProd() error { } - -func (v VaultInitializer) installPlugin()error { +func (v VaultInitializer) installPlugin() error { vaultClient := v.Client Log.Debug("Getting hash for JOSE...") - joseSHA, err := NewCommand("kubectl exec vault-dev-0 cat /vault/plugins/jose-plugin.sha").RunOut() + joseSHA, err := NewCommand("kubectl exec -n default vault-dev-0 cat /vault/plugins/jose-plugin.sha").RunOut() if err != nil { return err } Log.Debug("Registering JOSE...") err = vaultClient.Sys().RegisterPlugin(&api.RegisterPluginInput{ - Name:"jose", - SHA256:joseSHA, - Command:"jose-plugin", + Name: "jose", + SHA256: joseSHA, + Command: "jose-plugin", }) if err != nil { @@ -84,7 +83,7 @@ func (v VaultInitializer) Unseal(path string) error { var keys []string if path == "" { - secretYaml, err := NewCommand("kubectl get secret vault-unseal-keys -o yaml").RunOut() + secretYaml, err := NewCommand("kubectl get secret -n default vault-unseal-keys -o yaml").RunOut() if err != nil { return err } @@ -99,7 +98,7 @@ func (v VaultInitializer) Unseal(path string) error { keys = append(keys, string(shard)) } } else { - files, _ := filepath.Glob(path +"/Key*") + files, _ := filepath.Glob(path + "/Key*") Log.WithField("files", files).Debug("Found Key files.") for _, file := range files { key, _ := ioutil.ReadFile(file) @@ -107,7 +106,6 @@ func (v VaultInitializer) Unseal(path string) error { } } - for k, v := range keys { fmt.Printf("Unsealing with Key %v: %q\n", k, v) _, err = vaultClient.Sys().Unseal(v) @@ -122,21 +120,21 @@ func (v VaultInitializer) Unseal(path string) error { func (v VaultInitializer) initialize() (keys []string, rootToken string, err error) { vaultClient := v.Client - err = NewCommand("kubectl delete secret vault-unseal-keys --ignore-not-found=true").RunE() - err = NewCommand("kubectl delete secret vault-root-token --ignore-not-found=true").RunE() + err = NewCommand("kubectl delete secret -n default vault-unseal-keys --ignore-not-found=true").RunE() + err = NewCommand("kubectl delete secret -n default vault-root-token --ignore-not-found=true").RunE() if err != nil { return nil, "", err } initResp, err := vaultClient.Sys().Init(&api.InitRequest{ - SecretShares:1, - SecretThreshold:1, + SecretShares: 1, + SecretThreshold: 1, }) if err != nil { return nil, "", err } - err = NewCommand("kubectl", "create", "secret", "generic", "vault-root-token", fmt.Sprintf("--from-literal=root=%s", initResp.RootToken)).RunE() + err = NewCommand("kubectl", "create", "-n", "default", "secret", "generic", "vault-root-token", fmt.Sprintf("--from-literal=root=%s", initResp.RootToken)).RunE() if err != nil { return nil, "", err } @@ -144,7 +142,7 @@ func (v VaultInitializer) initialize() (keys []string, rootToken string, err err for i, key := range initResp.Keys { fmt.Printf("Seal Key %d: %q", i, key) - err = NewCommand("kubectl", "create", "secret", "generic", "vault-unseal-keys", fmt.Sprintf("--from-literal=Key%d=%s", i, key)).RunE() + err = NewCommand("kubectl", "create", "-n", "default", "secret", "generic", "vault-unseal-keys", fmt.Sprintf("--from-literal=Key%d=%s", i, key)).RunE() if err != nil { return nil, "", err } @@ -157,10 +155,9 @@ func (v VaultInitializer) initialize() (keys []string, rootToken string, err err vaultClient.SetToken(root) _, err = vaultClient.Auth().Token().Create(&api.TokenCreateRequest{ - ID:"root", - Policies:[]string{"root"}, + ID: "root", + Policies: []string{"root"}, }) - return initResp.Keys, initResp.RootToken, err }