Skip to content

Commit

Permalink
add support for additional annotations and labels during env deployment
Browse files Browse the repository at this point in the history
  • Loading branch information
maorfr committed Nov 21, 2018
1 parent 1f8c3b8 commit ec2352b
Show file tree
Hide file tree
Showing 6 changed files with 96 additions and 16 deletions.
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,11 @@ Additional flags:
* Use the `-f` flag to specify different values files to use during deployment.
* Use the `-s` flag to set additional parameteres.

Additional actions:

* After the deployment is complete, orca will validate that the environment is in a healthy state.
* You can skip the validation using the `--skip-validation` flag.

#### Get the "stable" environment and deploy the same configuration to a new environment, with override(s)

Useful for creating test environments for a single service.
Expand Down
3 changes: 3 additions & 0 deletions docs/commands/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,16 +76,19 @@ Aliases:
env, environment
Flags:
--annotations strings additional environment (namespace) annotations (can specify multiple): annotation=value
-c, --charts-file string path to file with list of Helm charts to install. Overrides $ORCA_CHARTS_FILE
-x, --deploy-only-override-if-env-exists if environment exists - deploy only override(s) (support for features spanning multiple services). Overrides $ORCA_DEPLOY_ONLY_OVERRIDE_IF_ENV_EXISTS
--helm-tls-store string path to TLS certs and keys. Overrides $HELM_TLS_STORE
--inject enable injection during helm upgrade. Overrides $ORCA_INJECT (requires helm inject plugin: https://github.com/maorfr/helm-inject)
--kube-context string name of the kubeconfig context to use. Overrides $ORCA_KUBE_CONTEXT
--labels strings environment (namespace) labels (can specify multiple): label=value
-n, --name string name of environment (namespace) to deploy to. Overrides $ORCA_NAME
--override strings chart to override with different version (can specify multiple): chart=version
-p, --parallel int number of releases to act on in parallel. set this flag to 0 for full parallelism. Overrides $ORCA_PARALLEL (default 1)
--repo string chart repository (name=url). Overrides $ORCA_REPO
-s, --set strings set additional parameters
--skip-validation skip environment validation after deployment. Overrides $ORCA_SKIP_VALIDATION
--timeout int time in seconds to wait for any individual Kubernetes operation (like Jobs for hooks). Overrides $ORCA_TIMEOUT (default 300)
--tls enable TLS for request. Overrides $ORCA_TLS
-f, --values strings values file to use (packaged within the chart)
Expand Down
59 changes: 49 additions & 10 deletions pkg/orca/env.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ type envCmd struct {
deployOnlyOverrideIfEnvExists bool
parallel int
timeout int
annotations []string
labels []string
skipValidation bool

out io.Writer
}
Expand Down Expand Up @@ -130,7 +133,7 @@ func NewDeployEnvCmd(out io.Writer) *cobra.Command {
return errors.New("override has to be defined when using deploy-only-override-if-env-exists")
}
}
if circular := utils.CheckCircularDependencies(utils.InitReleasesFromChartsFile(e.chartsFile, e.name)); circular {
if e.chartsFile != "" && utils.CheckCircularDependencies(utils.InitReleasesFromChartsFile(e.chartsFile, e.name)) {
return errors.New("Circular dependency found")
}
return nil
Expand Down Expand Up @@ -162,14 +165,32 @@ func NewDeployEnvCmd(out io.Writer) *cobra.Command {
log.Fatal(err)
}

annotations := map[string]string{}
for _, a := range e.annotations {
k, v := utils.SplitInTwo(a, "=")
annotations[k] = v
}
labels := map[string]string{}
for _, a := range e.labels {
k, v := utils.SplitInTwo(a, "=")
labels[k] = v
}
if err := utils.UpdateNamespace(e.name, e.kubeContext, annotations, labels, true); err != nil {
log.Fatal(err)
}

log.Print("initializing releases to deploy")
var desiredReleases []utils.ReleaseSpec
if nsPreExists && e.deployOnlyOverrideIfEnvExists {
desiredReleases = utils.InitReleases(e.name, e.override)
} else {
desiredReleases = utils.InitReleasesFromChartsFile(e.chartsFile, e.name)
if e.chartsFile != "" {
desiredReleases = utils.InitReleasesFromChartsFile(e.chartsFile, e.name)
}
desiredReleases = utils.OverrideReleases(desiredReleases, e.override, e.name)
}

log.Print("getting currently deployed releases")
installedReleases, err := utils.GetInstalledReleases(utils.GetInstalledReleasesOptions{
KubeContext: e.kubeContext,
Namespace: e.name,
Expand All @@ -179,8 +200,10 @@ func NewDeployEnvCmd(out io.Writer) *cobra.Command {
unlockEnvironment(e.name, e.kubeContext, true)
log.Fatal(err)
}
log.Print("calculating delta between desired releases and currently deployed releases")
releasesToInstall := utils.GetReleasesDelta(desiredReleases, installedReleases)

log.Print("deploying releases")
if err := utils.DeployChartsFromRepository(utils.DeployChartsFromRepositoryOptions{
ReleasesToInstall: releasesToInstall,
KubeContext: e.kubeContext,
Expand All @@ -199,6 +222,7 @@ func NewDeployEnvCmd(out io.Writer) *cobra.Command {
}

if !e.deployOnlyOverrideIfEnvExists {
log.Print("getting currently deployed releases")
installedReleases, err := utils.GetInstalledReleases(utils.GetInstalledReleasesOptions{
KubeContext: e.kubeContext,
Namespace: e.name,
Expand All @@ -208,7 +232,9 @@ func NewDeployEnvCmd(out io.Writer) *cobra.Command {
markEnvironmentAsUnknown(e.name, e.kubeContext, true)
log.Fatal(err)
}
log.Print("calculating delta between desired releases and currently deployed releases")
releasesToDelete := utils.GetReleasesDelta(installedReleases, desiredReleases)
log.Print("deleting undesired releases")
if err := utils.DeleteReleases(utils.DeleteReleasesOptions{
ReleasesToDelete: releasesToDelete,
KubeContext: e.kubeContext,
Expand All @@ -222,14 +248,22 @@ func NewDeployEnvCmd(out io.Writer) *cobra.Command {
}
}
log.Printf("deployed environment \"%s\"", e.name)
envValid, err := utils.IsEnvValidWithLoopBackOff(e.name, e.kubeContext)

var envValid bool
if !e.skipValidation {
envValid, err = utils.IsEnvValidWithLoopBackOff(e.name, e.kubeContext)
}

unlockEnvironment(e.name, e.kubeContext, true)

if e.skipValidation {
return
}
if err != nil {
log.Fatal(err)
}

if !envValid {
markEnvironmentAsFailed(e.name, e.kubeContext, true)
log.Fatalf("environment \"%s\" validation failed!", e.name)
}
// If we have made it so far, the environment is validated
Expand All @@ -252,6 +286,9 @@ func NewDeployEnvCmd(out io.Writer) *cobra.Command {
f.BoolVarP(&e.deployOnlyOverrideIfEnvExists, "deploy-only-override-if-env-exists", "x", false, "if environment exists - deploy only override(s) (support for features spanning multiple services). Overrides $ORCA_DEPLOY_ONLY_OVERRIDE_IF_ENV_EXISTS")
f.IntVarP(&e.parallel, "parallel", "p", utils.GetIntEnvVar("ORCA_PARALLEL", 1), "number of releases to act on in parallel. set this flag to 0 for full parallelism. Overrides $ORCA_PARALLEL")
f.IntVar(&e.timeout, "timeout", utils.GetIntEnvVar("ORCA_TIMEOUT", 300), "time in seconds to wait for any individual Kubernetes operation (like Jobs for hooks). Overrides $ORCA_TIMEOUT")
f.StringSliceVar(&e.annotations, "annotations", []string{}, "additional environment (namespace) annotations (can specify multiple): annotation=value")
f.StringSliceVar(&e.labels, "labels", []string{}, "environment (namespace) labels (can specify multiple): label=value")
f.BoolVar(&e.skipValidation, "skip-validation", utils.GetBoolEnvVar("ORCA_SKIP_VALIDATION", false), "skip environment validation after deployment. Overrides $ORCA_SKIP_VALIDATION")

f.BoolVar(&e.createNS, "create-ns", utils.GetBoolEnvVar("ORCA_CREATE_NS", false), "should create new namespace. Overrides $ORCA_CREATE_NS")
f.MarkDeprecated("create-ns", "namespace will be created if it does not exist")
Expand Down Expand Up @@ -291,6 +328,7 @@ func NewDeleteEnvCmd(out io.Writer) *cobra.Command {
log.Printf("environment \"%s\" not found", e.name)
}

log.Print("getting currently deployed releases")
releases, err := utils.GetInstalledReleases(utils.GetInstalledReleasesOptions{
KubeContext: e.kubeContext,
Namespace: e.name,
Expand All @@ -299,6 +337,7 @@ func NewDeleteEnvCmd(out io.Writer) *cobra.Command {
if err != nil {
log.Fatal(err)
}
log.Print("deleting releases")
if err := utils.DeleteReleases(utils.DeleteReleasesOptions{
ReleasesToDelete: releases,
KubeContext: e.kubeContext,
Expand Down Expand Up @@ -547,7 +586,7 @@ func lockEnvironment(name, kubeContext string, print bool) error {
}
// There is a race condition here, may need to attend to it in the future
annotations := map[string]string{stateAnnotation: busyState}
err = utils.UpdateNamespace(name, kubeContext, annotations, print)
err = utils.UpdateNamespace(name, kubeContext, annotations, map[string]string{}, print)

return err
}
Expand All @@ -565,7 +604,7 @@ func unlockEnvironment(name, kubeContext string, print bool) error {
}
}
annotations := map[string]string{stateAnnotation: freeState}
err = utils.UpdateNamespace(name, kubeContext, annotations, print)
err = utils.UpdateNamespace(name, kubeContext, annotations, map[string]string{}, print)

return err
}
Expand All @@ -578,31 +617,31 @@ func markEnvironmentForDeletion(name, kubeContext string, force, print bool) err
}
}
annotations := map[string]string{stateAnnotation: deleteState}
err := utils.UpdateNamespace(name, kubeContext, annotations, print)
err := utils.UpdateNamespace(name, kubeContext, annotations, map[string]string{}, print)

return err
}

// markEnvironmentAsFailed annotates a namespace with "failed"
func markEnvironmentAsFailed(name, kubeContext string, print bool) error {
annotations := map[string]string{stateAnnotation: failedState}
err := utils.UpdateNamespace(name, kubeContext, annotations, print)
err := utils.UpdateNamespace(name, kubeContext, annotations, map[string]string{}, print)

return err
}

// markEnvironmentAsUnknown annotates a namespace with "unknown"
func markEnvironmentAsUnknown(name, kubeContext string, print bool) error {
annotations := map[string]string{stateAnnotation: unknownState}
err := utils.UpdateNamespace(name, kubeContext, annotations, print)
err := utils.UpdateNamespace(name, kubeContext, annotations, map[string]string{}, print)

return err
}

// unlockEnvironment annotates a namespace with "unknown"
func removeStateAnnotationsFromEnvironment(name, kubeContext string, print bool) error {
annotations := map[string]string{}
err := utils.UpdateNamespace(name, kubeContext, annotations, print)
err := utils.UpdateNamespace(name, kubeContext, annotations, map[string]string{}, print)

return err
}
6 changes: 3 additions & 3 deletions pkg/utils/chart.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ func GetReleasesDelta(fromReleases, toReleases []ReleaseSpec) []ReleaseSpec {

// InitReleasesFromChartsFile initializes a slice of ReleaseSpec from a yaml formatted charts file
func InitReleasesFromChartsFile(file, env string) []ReleaseSpec {
var charts []ReleaseSpec
var releases []ReleaseSpec

data, err := ioutil.ReadFile(file)
if err != nil {
Expand All @@ -70,10 +70,10 @@ func InitReleasesFromChartsFile(file, env string) []ReleaseSpec {
c.Dependencies = append(c.Dependencies, depStr)
}
}
charts = append(charts, c)
releases = append(releases, c)
}

return charts
return releases
}

// InitReleases initializes a slice of ReleaseSpec from a string slice
Expand Down
7 changes: 6 additions & 1 deletion pkg/utils/helm.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,10 @@ type DeployChartsFromRepositoryOptions struct {

// DeployChartsFromRepository deploys a list of Helm charts from a repository in parallel
func DeployChartsFromRepository(o DeployChartsFromRepositoryOptions) error {

releasesToInstall := o.ReleasesToInstall
if len(releasesToInstall) == 0 {
return nil
}
parallel := o.Parallel

totalReleases := len(releasesToInstall)
Expand Down Expand Up @@ -134,6 +136,9 @@ type DeleteReleasesOptions struct {
// DeleteReleases deletes a list of releases in parallel
func DeleteReleases(o DeleteReleasesOptions) error {
releasesToDelete := o.ReleasesToDelete
if len(releasesToDelete) == 0 {
return nil
}
parallel := o.Parallel

print := false
Expand Down
32 changes: 30 additions & 2 deletions pkg/utils/kube.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,26 +48,54 @@ func GetNamespace(name, kubeContext string) (*v1.Namespace, error) {
}

// UpdateNamespace updates a namespace
func UpdateNamespace(name, kubeContext string, annotations map[string]string, print bool) error {
func UpdateNamespace(name, kubeContext string, annotationsToUpdate, labelsToUpdate map[string]string, print bool) error {
if len(annotationsToUpdate) == 0 && len(labelsToUpdate) == 0 {
return nil
}

clientset, err := getClientSet(kubeContext)
if err != nil {
return err
}

ns, err := GetNamespace(name, kubeContext)
if err != nil {
return err
}
annotations := overrideAttributes(ns.Annotations, annotationsToUpdate)
labels := overrideAttributes(ns.Labels, labelsToUpdate)
nsSpec := &v1.Namespace{ObjectMeta: metav1.ObjectMeta{
Name: name,
Annotations: annotations,
Labels: labels,
}}
_, err = clientset.Core().Namespaces().Update(nsSpec)
if err != nil {
return err
}
if print {
log.Printf("updated namespace \"%s\" with annotations (%s)", name, annotations)
if len(annotationsToUpdate) != 0 {
log.Printf("updated namespace \"%s\" with annotations (%s)", name, annotations)
}
if len(labelsToUpdate) != 0 {
log.Printf("updated namespace \"%s\" with labels (%s)", name, labels)
}
}
return nil
}

func overrideAttributes(currentAttributes, attributesToUpdate map[string]string) map[string]string {
attributes := currentAttributes
if len(attributes) == 0 {
attributes = attributesToUpdate
} else {
for k, v := range attributesToUpdate {
attributes[k] = v
}
}
return attributes
}

// DeleteNamespace deletes a namespace
func DeleteNamespace(name, kubeContext string, print bool) error {
clientset, err := getClientSet(kubeContext)
Expand Down

0 comments on commit ec2352b

Please sign in to comment.