Skip to content

Commit

Permalink
feat(deploy): add cluster app exclusions and protection
Browse files Browse the repository at this point in the history
  • Loading branch information
SteveRuble committed Dec 11, 2020
1 parent 3345809 commit 5d63691
Show file tree
Hide file tree
Showing 6 changed files with 161 additions and 79 deletions.
73 changes: 47 additions & 26 deletions cmd/deploy_execute.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"github.com/fatih/color"
"github.com/naveego/bosun/pkg/bosun"
"github.com/naveego/bosun/pkg/cli"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"path/filepath"
Expand All @@ -29,10 +30,10 @@ func init() {
}

var _ = addCommand(deployCmd, &cobra.Command{
Use: "execute {path | release} [apps...]",
Use: "execute {path | {release|stable|unstable}} [apps...]",
Args: cobra.MinimumNArgs(1),
Short: "Executes a deployment against the current environment.",
Long:"If apps are provided, only those apps will be deployed.",
Long: "If apps are provided, only those apps will be deployed.",
SilenceUsage: true,
RunE: func(cmd *cobra.Command, args []string) error {
b := MustGetBosun()
Expand All @@ -44,39 +45,42 @@ var _ = addCommand(deployCmd, &cobra.Command{
return err
}
req := bosun.ExecuteDeploymentPlanRequest{
Validate: !viper.GetBool(argDeployExecuteSkipValidate),
DiffOnly: viper.GetBool(argDeployExecuteDiffOnly),
Validate: !viper.GetBool(argDeployExecuteSkipValidate),
DiffOnly: viper.GetBool(argDeployExecuteDiffOnly),
PreviewOnly: viper.GetBool(argDeployExecuteValuesOnly),
UseSudo:viper.GetBool(ArgGlobalSudo),
UseSudo: viper.GetBool(ArgGlobalSudo),
Clusters: map[string]bool{},
}

pathOrSlot := args[0]
if pathOrSlot == "release" {
r, getReleaseErr := p.GetCurrentRelease()
if getReleaseErr != nil {
return getReleaseErr
switch pathOrSlot{
case "release","current", bosun.SlotStable, bosun.SlotUnstable:
r, folder, resolveReleaseErr := getReleaseAndPlanFolderName(b, pathOrSlot)
if resolveReleaseErr != nil {
return resolveReleaseErr
}
expectedReleaseHash, hashErr := r.GetChangeDetectionHash()

if hashErr != nil {
return hashErr
}

req.Path = filepath.Join(p.GetDeploymentsDir(), fmt.Sprintf("%s/plan.yaml", r.Version.String()))
req.Plan, err = bosun.LoadDeploymentPlanFromFile(req.Path)
if err != nil {
return err
req.Path = filepath.Join(p.GetDeploymentsDir(), fmt.Sprintf("%s/plan.yaml", folder))
req.Plan, resolveReleaseErr = bosun.LoadDeploymentPlanFromFile(req.Path)
if resolveReleaseErr != nil {
return resolveReleaseErr
}

if req.Plan.BasedOnHash != "" && req.Plan.BasedOnHash != expectedReleaseHash {
confirmed := cli.RequestConfirmFromUser("The release has changed since this plan was created, are you sure you want to continue?")
if !confirmed{
if !confirmed {

color.Yellow("You may want to run `bosun deploy plan release` to update the deployment plan\n")
return nil
}
}

} else {
break
default:
req.Path = pathOrSlot
}

Expand All @@ -86,29 +90,46 @@ var _ = addCommand(deployCmd, &cobra.Command{

clusters := viper.GetStringSlice(argDeployExecuteClusters)
if len(clusters) > 0 {
req.Clusters = map[string]bool{}
for _, cluster := range clusters {
req.Clusters[cluster] = true
}
} else {
for _, cluster := range b.GetCurrentEnvironment().Clusters {
if cluster.Protected {
req.Clusters[cluster.Name] = cli.RequestConfirmFromUser("This deployment will affect protected cluster %s, are you sure you want to do this?", cluster.Name)
} else {
req.Clusters[cluster.Name] = true
}
}
}

var atLeastOneCluster = false
for _, enabled := range req.Clusters {
if enabled {
atLeastOneCluster = true
break
}
}
if !atLeastOneCluster {
return errors.Errorf("no clusters confirmed")
}

executor := bosun.NewDeploymentPlanExecutor(b, p)

_, err = executor.Execute(req)


return err
},
}, func(cmd *cobra.Command) {
cmd.Flags().Bool(argDeployExecuteSkipValidate, false, "Skip validation" )
cmd.Flags().Bool(argDeployExecuteDiffOnly, false, "Display the diffs for the deploy, but do not actually execute." )
cmd.Flags().Bool(argDeployExecuteValuesOnly, false, "Display the values which would be used for the deploy, but do not actually execute." )
cmd.Flags().StringSlice(argDeployExecuteClusters, []string{}, "Clusters to deploy to, defaults to all." )
cmd.Flags().Bool(argDeployExecuteSkipValidate, false, "Skip validation")
cmd.Flags().Bool(argDeployExecuteDiffOnly, false, "Display the diffs for the deploy, but do not actually execute.")
cmd.Flags().Bool(argDeployExecuteValuesOnly, false, "Display the values which would be used for the deploy, but do not actually execute.")
cmd.Flags().StringSlice(argDeployExecuteClusters, []string{}, "Clusters to deploy to, defaults to all.")
})

const (
argDeployExecuteSkipValidate = "skip-validation"
argDeployExecuteDiffOnly = "diff-only"
argDeployExecuteValuesOnly = "values-only"
argDeployExecuteClusters = "clusters"
)
argDeployExecuteDiffOnly = "diff-only"
argDeployExecuteValuesOnly = "values-only"
argDeployExecuteClusters = "clusters"
)
51 changes: 44 additions & 7 deletions cmd/deploy_plan.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,17 @@ func init() {
}

var deployPlanCmd = addCommand(deployCmd, &cobra.Command{
Use: "plan [release]",
Use: "plan [release|stable|unstable]",
Short: "Plans a deployment, optionally of an existing release.",
Args: cobra.RangeArgs(0, 1),
SilenceUsage: true,
RunE: func(cmd *cobra.Command, args []string) error {

if len(args) > 0 && args[0] == "release" {
return releaseDeployPlan()
if len(args) > 0 {
switch args[0] {
case "release", "current", "stable", "unstable":
return releaseDeployPlan(args[0])
}
}

b := MustGetBosun()
Expand Down Expand Up @@ -156,23 +159,57 @@ var deployReleasePlanCmd = addCommand(deployPlanCmd, &cobra.Command{
SilenceUsage: true,
RunE: func(cmd *cobra.Command, args []string) error {

return releaseDeployPlan()
return releaseDeployPlan("release")
},
})

func releaseDeployPlan() error {
func getReleaseAndPlanFolderName(b *bosun.Bosun, slotDescription string) (*bosun.ReleaseManifest, string, error){

p, err := b.GetCurrentPlatform()
if err != nil {
return nil, "", err
}

var folder string

var r *bosun.ReleaseManifest
switch slotDescription {
case "release":
case "current":
r, err = p.GetCurrentRelease()
if err == nil {
folder = r.Version.String()
}
break
case bosun.SlotStable:
r, err = p.GetStableRelease()
folder = bosun.SlotStable
break
case bosun.SlotUnstable:
r, err = p.GetUnstableRelease()
folder = bosun.SlotUnstable
break
default:
err = errors.Errorf("unsupported release slot description %q", slotDescription)
}
return r, folder, nil
}

func releaseDeployPlan(slotDescription string) error {

b := MustGetBosun()
p, err := b.GetCurrentPlatform()
if err != nil {
return err
}
r, err := p.GetCurrentRelease()


r, folder, err := getReleaseAndPlanFolderName(b, slotDescription)
if err != nil {
return err
}

deploymentPlanPath := filepath.Join(p.GetDeploymentsDir(), fmt.Sprintf("%s/plan.yaml", r.Version.String()))
deploymentPlanPath := filepath.Join(p.GetDeploymentsDir(), fmt.Sprintf("%s/plan.yaml", folder))

previousPlan, _ := bosun.LoadDeploymentPlanFromFile(deploymentPlanPath)
basedOnHash, err := r.GetChangeDetectionHash()
Expand Down
16 changes: 15 additions & 1 deletion pkg/bosun/deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,14 @@ func NewDeploy(ctx BosunContext, settings DeploySettings) (*Deploy, error) {
continue
}

clusterAppOverrides, hasClusterAppOverrides := cluster.Apps[app.Name]
if hasClusterAppOverrides{
if clusterAppOverrides.Disabled {
log.Infof("Skipping deploy to cluster %s because the cluster apps list marks this app as disabled.", cluster.Name)
continue
}
}

log = log.WithField("cluster", cluster.Name)

if deployedForClusterRole, ok := deployedToClusterForRole[cluster.Name]; ok {
Expand Down Expand Up @@ -407,11 +415,17 @@ func NewDeploy(ctx BosunContext, settings DeploySettings) (*Deploy, error) {
app = app.WithValueSet(envOverrides)
}

if hasClusterAppOverrides {
clusterAppOverridesValueSet := clusterAppOverrides.ExtractValueSet(values.ExtractValueSetArgs{
ExactMatch: appCtx.GetMatchMapArgs(),
})
app = app.WithValueSet(clusterAppOverridesValueSet)
}

deploy.AppDeploys = append(deploy.AppDeploys, app)
}
}
}

}

return deploy, nil
Expand Down
58 changes: 30 additions & 28 deletions pkg/kube/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,9 @@ func (k ConfigDefinitions) HandleConfigureKubeContextRequest(req ConfigureKubeCo
var konfigs []*ClusterConfig

if req.Name != "" {
konfig, err := k.GetKubeConfigDefinitionByName(req.Name)
if err != nil {
return err
konfig, kubeConfigErr := k.GetKubeConfigDefinitionByName(req.Name)
if kubeConfigErr != nil {
return kubeConfigErr
}
konfigs = []*ClusterConfig{konfig}
} else {
Expand Down Expand Up @@ -114,18 +114,20 @@ func (k ConfigDefinitions) GetKubeConfigDefinitionsByRole(role core.ClusterRole)

type ClusterConfig struct {
core.ConfigShared `yaml:",inline"`
Provider string `yaml:"-"`
EnvironmentAlias string `yaml:"environmentAlias,omitempty"`
Roles core.ClusterRoles `yaml:"roles,flow"`
Variables []*environmentvariables.Variable `yaml:"variables,omitempty"`
ValueOverrides *values.ValueSetCollection `yaml:"valueOverrides,omitempty"`
Oracle *OracleClusterConfig `yaml:"oracle,omitempty"`
Minikube *MinikubeConfig `yaml:"minikube,omitempty"`
Microk8s *Microk8sConfig `yaml:"microk8s,omitempty"`
Amazon *AmazonClusterConfig `yaml:"amazon,omitempty"`
Rancher *RancherClusterConfig `yaml:"rancher,omitempty"`
ExternalCluster *ExternalClusterConfig `yaml:"externalCluster,omitempty"`
Namespaces NamespaceConfigs `yaml:"namespaces"`
Provider string `yaml:"-"`
EnvironmentAlias string `yaml:"environmentAlias,omitempty"`
Roles core.ClusterRoles `yaml:"roles,flow"`
Protected bool `yaml:"protected"`
Variables []*environmentvariables.Variable `yaml:"variables,omitempty"`
ValueOverrides *values.ValueSetCollection `yaml:"valueOverrides,omitempty"`
Oracle *OracleClusterConfig `yaml:"oracle,omitempty"`
Minikube *MinikubeConfig `yaml:"minikube,omitempty"`
Microk8s *Microk8sConfig `yaml:"microk8s,omitempty"`
Amazon *AmazonClusterConfig `yaml:"amazon,omitempty"`
Rancher *RancherClusterConfig `yaml:"rancher,omitempty"`
ExternalCluster *ExternalClusterConfig `yaml:"externalCluster,omitempty"`
Namespaces NamespaceConfigs `yaml:"namespaces"`
Apps map[string]values.ValueSetCollection `yaml:"apps"`
}

type PullSecret struct {
Expand Down Expand Up @@ -308,7 +310,7 @@ func (k ClusterConfig) configureKubernetes(req ConfigureKubeContextRequest) erro
}

func CreateOrUpdatePullSecret(ctx command.ExecutionContext, clusterName, namespaceName string, pullSecret PullSecret) error {
//req.Log.Infof("Creating or updating pull secret %q in namespace %q...", pullSecret.Name, ns.Name)
// req.Log.Infof("Creating or updating pull secret %q in namespace %q...", pullSecret.Name, ns.Name)

var password string
var username string
Expand All @@ -322,14 +324,14 @@ func CreateOrUpdatePullSecret(ctx command.ExecutionContext, clusterName, namespa
if !ok {
dockerConfigPath = os.ExpandEnv("$HOME/.docker/config.json")
}
data, err := ioutil.ReadFile(dockerConfigPath)
if err != nil {
return errors.Errorf("error reading docker config from %q: %s", dockerConfigPath, err)
data, dockerFileErr := ioutil.ReadFile(dockerConfigPath)
if dockerFileErr != nil {
return errors.Errorf("error reading docker config from %q: %s", dockerConfigPath, dockerFileErr)
}

err = json.Unmarshal(data, &dockerConfig)
if err != nil {
return errors.Errorf("error docker config from %q, file was invalid: %s", dockerConfigPath, err)
dockerFileErr = json.Unmarshal(data, &dockerConfig)
if dockerFileErr != nil {
return errors.Errorf("error docker config from %q, file was invalid: %s", dockerConfigPath, dockerFileErr)
}

auths, ok := dockerConfig["auths"].(map[string]interface{})
Expand All @@ -339,9 +341,9 @@ func CreateOrUpdatePullSecret(ctx command.ExecutionContext, clusterName, namespa
return errors.Errorf("no %q entry in docker config, you should docker login first", pullSecret.Domain)
}
authBase64, _ := entry["auth"].(string)
auth, err := base64.StdEncoding.DecodeString(authBase64)
if err != nil {
return errors.Errorf("invalid %q entry in docker config, you should docker login first: %s", pullSecret.Domain, err)
auth, dockerFileErr := base64.StdEncoding.DecodeString(authBase64)
if dockerFileErr != nil {
return errors.Errorf("invalid %q entry in docker config, you should docker login first: %s", pullSecret.Domain, dockerFileErr)
}
segs := strings.Split(string(auth), ":")
username, password = segs[0], segs[1]
Expand All @@ -350,7 +352,7 @@ func CreateOrUpdatePullSecret(ctx command.ExecutionContext, clusterName, namespa
username = pullSecret.Username
password, err = pullSecret.Password.Resolve(ctx)
if err != nil {
//req.Log.Errorf("Could not resolve password for pull secret %q in namespace %q: %s", pullSecret.Name, ns.Name, err)
// req.Log.Errorf("Could not resolve password for pull secret %q in namespace %q: %s", pullSecret.Name, ns.Name, err)
return err
}
}
Expand Down Expand Up @@ -385,14 +387,14 @@ func CreateOrUpdatePullSecret(ctx command.ExecutionContext, clusterName, namespa
}
_, err = client.CoreV1().Secrets(namespaceName).Create(secret)
if kerrors.IsAlreadyExists(err) {
//req.Log.Infof("Pull secret already exists, updating...")
// req.Log.Infof("Pull secret already exists, updating...")
_, err = client.CoreV1().Secrets(namespaceName).Update(secret)
}
if err != nil {
return errors.Wrapf(err, "create or update pull secret %q in namespace %q", pullSecret.Name, namespaceName)
}

//req.Log.Infof("Done creating or updating pull secret %q in namespace %q.", pullSecret.Name, ns.Name)
// req.Log.Infof("Done creating or updating pull secret %q in namespace %q.", pullSecret.Name, ns.Name)
return nil
}

Expand Down
8 changes: 4 additions & 4 deletions pkg/values/value_set.go
Original file line number Diff line number Diff line change
Expand Up @@ -270,10 +270,10 @@ func (v ValueSet) WithDynamicValuesResolved(ctx command.ExecutionContext) (Value
// Merge the existing values into it so they can be used to format themselves
templateValues.Values = Values(templateValues.Values).Merge(v.Static).ToMapStringInterface()

rendered, err := templateValues.RenderInto(y)
if err != nil {
return v, err
}
rendered, err := templateValues.RenderInto(y)
if err != nil {
return v, err
}

var out ValueSet
err = yaml.UnmarshalString(rendered, &out)
Expand Down
Loading

0 comments on commit 5d63691

Please sign in to comment.