Skip to content
This repository has been archived by the owner on May 12, 2022. It is now read-only.

Commit

Permalink
Merge pull request #63 from appnexus/v2.0.0-beta.5
Browse files Browse the repository at this point in the history
V2.0.0 beta.5
  • Loading branch information
esmet authored Jan 30, 2019
2 parents 8804a5e + 872384b commit fb6f516
Show file tree
Hide file tree
Showing 7 changed files with 157 additions and 124 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -44,5 +44,5 @@ cover-html: cover-generate
@$(GOCMD) tool cover -html=$(REPOROOT)/coverage.txt

.PHONY: test
test:
test:
$(GOTEST) -v ./...
16 changes: 8 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ charts:
- name: theserver
namespace: bar
version: 0.0.1
- name: myservice
namespace: foo
version: 1.0.0
Expand Down Expand Up @@ -118,7 +118,7 @@ kubectl

### Contexts

**Ankh** configs are driven by *contexts*, like kubectl.
**Ankh** configs are driven by *contexts*, like kubectl.

```
$ cat ~/.ankh/config
Expand Down Expand Up @@ -161,7 +161,7 @@ include:

#### Context-aware yaml config

One of the primary features of Ankh is the ability to write context-aware yaml configuration for Helm charts. Often, it's necessary to have separate values for classes of operating environments, like `dev` and `production`. For example, we may want to set the log level or
One of the primary features of Ankh is the ability to write context-aware yaml configuration for Helm charts. Often, it's necessary to have separate values for classes of operating environments, like `dev` and `production`. For example, we may want to set the log level or

##### In a Helm chart:

Expand Down Expand Up @@ -214,13 +214,13 @@ charts:
- name: theserver
namespace: bar
version: 0.0.1
- name: myservice
namespace: foo
version: 1.0.0
```

When invoked, Ankh will operate over both the `haste-server` and `myservice` charts.
When invoked, Ankh will operate over both the `haste-server` and `myservice` charts.

## YAML schemas

Expand Down Expand Up @@ -266,7 +266,7 @@ When invoked, Ankh will operate over both the `haste-server` and `myservice` cha

##### `Slack Message Variables`
| Variable | Description
| ------------- | :---:
| ------------- | :---:
| `%USER%` | Current username |
| `%CHART%` | Current chart being used |
| `%VERSION%` | Version of the primary container |
Expand All @@ -284,7 +284,7 @@ Example format: `format: "_%USER%_ is releasing *%CHART%@%VERSION%* to *%TARGET%
| ------------- | :---: | :-------------: |
| kube-context | string | The kube context to use. This must be a valid context name present in your kube config (tyipcally ~/.kube/config or $KUBECONFIG). Prefer `kube-server` instead, which is less dependent on local configuration. |
| kube-server | string | The kube server to use. This must be a valid Kubernetes API server. Similar to the `server` field in kubectl's `cluster` object. This can be used in place of `kube-context`, and should be preferred. |
| environment-class | string | Optional. The environment class to use. |
| environment-class | string | Optional. The environment class to use. |
| resource-profile | string | Optional. The resource profile to use. |
| release | string | Optional. The release name to use. This is passed to Helm as --release |
| helm-registry-url | string | Optional. The URL to the Helm chart repo to use. Overrides the global Helm registry. Either this or the global registry must be defined. |
Expand All @@ -301,7 +301,7 @@ Example format: `format: "_%USER%_ is releasing *%CHART%@%VERSION%* to *%TARGET%
| Field | Type | Description |
| ------------- | :---: | :-------------: |
| name | string | The chart name. Must be the name of a chart in a Helm registry |
| namespace | string | The namespace to use when running `helm` and `kubectl`. Overrides `namespace` in an Ankh file. |
| namespace | string | The namespace to use when running `helm` and `kubectl`. Overrides `namespace` in an Ankh file. |
| version | string | Optional. The chart version, if pulling from a Helm registry. |
| path | string | Optional. The path to a local chart directory. Can be used instead of a remote `version` in a Helm registry. |
| default-values | RawYaml | Optional. Values to use in all contexts. |
Expand Down
116 changes: 61 additions & 55 deletions ankh/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package main
import (
"fmt"
"io/ioutil"
"math/rand"
"os"
"os/signal"
"path"
Expand Down Expand Up @@ -78,67 +79,28 @@ func printContexts(ankhConfig *ankh.AnkhConfig) {
}
}

func promptForMissingConfigs(ctx *ankh.ExecutionContext, ankhFile *ankh.AnkhFile) error {
if ctx.NoPrompt {
for i := 0; i < len(ankhFile.Charts); i++ {
chart := &ankhFile.Charts[i]

// Fetch and merge chart metadata
meta, err := helm.FetchChartMeta(ctx, chart)
if err != nil {
return fmt.Errorf("Error fetching chart \"%v\": %v", chart.Name, err)
}
mergo.Merge(&chart.ChartMeta, meta)

// This logic is unfortunately duplicated in this function.
// The gist is that if ctx.Namespace is set then we have a
// command line override, which we'll use later. If the namespace
// is set on the ChartMeta, then we'll prioritize using that.
if ctx.Namespace == nil && chart.ChartMeta.Namespace == nil {
return fmt.Errorf("Missing namespace for chart \"%v\". To use this chart "+
"without a namespace, use `ankh --namespace \"\" ...`",
chart.Name)
}
}
return nil
}

func reconcileMissingConfigs(ctx *ankh.ExecutionContext, ankhFile *ankh.AnkhFile) error {
// Make sure that we don't use the tag argument for more than one Chart.
// When this happens, it is almost always an error, because a tag value
// is typically only valid/intended for a single chart.
tagArgumentUsedForChart := ""

// Prompt for a chart argument if there is no chart argument and
// there is no AnkhFile present on the filesystem.
if len(ankhFile.Charts) == 0 {
if _, err := os.Stat(ctx.AnkhFilePath); os.IsNotExist(err) {
ctx.Logger.Infof("No chart specified as an argument, and no `charts` found in an Ankh file")
charts, err := helm.GetChartNames(ctx)
if err != nil {
return err
}

selectedChart, err := util.PromptForSelection(charts, "Select a chart")
if err != nil {
return err
}

ankhFile.Charts = []ankh.Chart{ankh.Chart{Name: selectedChart}}
ctx.Logger.Infof("Using chart \"%v\" based on prompt selection", selectedChart)
}
}

// Prompt for chart versions if any are missing
for i := 0; i < len(ankhFile.Charts); i++ {
chart := &ankhFile.Charts[i]

if chart.Path == "" && chart.Version == "" {
ctx.Logger.Infof("Found chart \"%v\" without a version", chart.Name)
if ctx.NoPrompt {
ctx.Logger.Fatalf("Chart \"%v\" missing version (and no 'path' set either, not prompting due to --no-prompt)",
chart.Name)
}

versions, err := helm.ListVersions(ctx, chart.Name, true)
if err != nil {
return err
}

ctx.Logger.Infof("Found chart \"%v\" without a version", chart.Name)
selectedVersion, err := util.PromptForSelection(strings.Split(strings.Trim(versions, "\n "), "\n"),
fmt.Sprintf("Select a version for chart \"%v\"", chart.Name))
if err != nil {
Expand All @@ -161,14 +123,22 @@ func promptForMissingConfigs(ctx *ankh.ExecutionContext, ankhFile *ankh.AnkhFile
// If namespace is set on the command line, we'll use that as an
// override later during executeChartsOnNamespace, so don't check
// for anything here.
// - command line override, ankh file, chart meta.
if ctx.Namespace == nil {
if ankhFile.Namespace != nil && chart.ChartMeta.Namespace == nil {
if ankhFile.Namespace != nil {
extraLog := ""
if chart.ChartMeta.Namespace == nil {
extraLog = " (overriding namespace \"%v\" from ankh.yaml present in the chart)"
}
ctx.Logger.Infof("Using namespace \"%v\" from Ankh file "+
"for chart \"%v\" which has no explicit namespace set",
*ankhFile.Namespace, chart.Name)
"for chart \"%v\"%v",
*ankhFile.Namespace, chart.Name, extraLog)
chart.ChartMeta.Namespace = ankhFile.Namespace
} else if chart.ChartMeta.Namespace == nil {
ctx.Logger.Infof("Found chart \"%v\" without a namespace", chart.Name)
if ctx.NoPrompt {
ctx.Logger.Fatalf("Chart \"%v\" missing namespace (not prompting due to --no-prompt)", chart.Name)
}
if len(ctx.AnkhConfig.Namespaces) > 0 {
selectedNamespace, err := util.PromptForSelection(ctx.AnkhConfig.Namespaces,
fmt.Sprintf("Select a namespace for chart '%v' (or re-run with -n/--namespace to provide your own)", chart.Name))
Expand Down Expand Up @@ -236,6 +206,20 @@ func promptForMissingConfigs(ctx *ankh.ExecutionContext, ankhFile *ankh.AnkhFile
}
}

// Treat any existing `tag` in `default-values` for this chart as the next-most authoritative
for k, v := range chart.DefaultValues {
if k == tagKey {
ctx.Logger.Infof("Using tag value \"%v=%s\" based on default-values present in the Ankh file", tagKey, v)
t, ok := v.(string)
if !ok {
ctx.Logger.Fatalf("Could not use value '%+v' from default-values in chart %v "+
"as a string value for tagKey '%v'", v, chart.Name, tagKey)
}
chart.Tag = &t
break
}
}

// For certain operations, we can assume a safe `unset` value for tagKey
// for the sole purpose of templating the Helm chart. The value won't be used
// meaningfully (like it would be with apply), so we choose this method instead
Expand Down Expand Up @@ -266,6 +250,11 @@ func promptForMissingConfigs(ctx *ankh.ExecutionContext, ankhFile *ankh.AnkhFile

// If we stil don't have a chart.Tag value, prompt.
if chart.Tag == nil {
if ctx.NoPrompt {
ctx.Logger.Fatalf("Chart \"%v\" missing value for `tagKey` (configured to be '%v', not prompting due to --no-prompt)",
tagKey, chart.Name)
}

image := ""
if chart.ChartMeta.TagImage != "" {
// No need to prompt for an image name if we already have one in the chart metdata
Expand Down Expand Up @@ -297,10 +286,10 @@ func promptForMissingConfigs(ctx *ankh.ExecutionContext, ankhFile *ankh.AnkhFile
ctx.Logger.Infof("Using implicit \"--set tag %v=%s\" based on prompt selection", tagKey, tag)
chart.Tag = &tag
} else if image != "" {
complaint := fmt.Sprintf("Could not determine a tag value, and we check for this because `tagKey` is configured to be `%v`. "+
complaint := fmt.Sprintf("Chart \"%v\" missing value for `tagKey` (configured to be `%v`). "+
"You may want to try passing a tag value explicitly using `ankh --set %v=... `, or simply ignore "+
"this error entirely using `ankh --ignore-config-errors ...` (not recommended)",
tagKey, tagKey)
chart.Name, tagKey, tagKey)
if ctx.IgnoreConfigErrors {
ctx.Logger.Warnf("%v", complaint)
} else {
Expand Down Expand Up @@ -520,7 +509,7 @@ func executeChartsOnNamespace(ctx *ankh.ExecutionContext, ankhFile *ankh.AnkhFil
}

func executeAnkhFile(ctx *ankh.ExecutionContext, ankhFile *ankh.AnkhFile) {
err := promptForMissingConfigs(ctx, ankhFile)
err := reconcileMissingConfigs(ctx, ankhFile)
check(err)

logExecuteAnkhFile(ctx, ankhFile)
Expand Down Expand Up @@ -594,15 +583,32 @@ func executeContext(ctx *ankh.ExecutionContext, rootAnkhFile *ankh.AnkhFile) {
}
check(err)

ctx.WorkingPath = path.Dir(ankhFilePath)
executeAnkhFile(ctx, &ankhFile)
ctx.WorkingPath = ""

log.Infof("Finished satisfying dependency: %v", dep)
}

if len(rootAnkhFile.Charts) > 0 {
executeAnkhFile(ctx, rootAnkhFile)
} else if len(dependencies) == 0 {
ctx.Logger.Fatalf("No charts nor dependencies provided, nothing to do")
if ctx.NoPrompt {
ctx.Logger.Fatalf("No charts nor dependencies provided, nothing to do")
} else if ctx.AnkhConfig.Helm.Registry != "" {
// Prompt for a chart
ctx.Logger.Infof("No chart specified as an argument, and no `charts` found in an Ankh file")
charts, err := helm.GetChartNames(ctx)
check(err)

selectedChart, err := util.PromptForSelection(charts, "Select a chart")
check(err)

rootAnkhFile.Charts = []ankh.Chart{ankh.Chart{Name: selectedChart}}
ctx.Logger.Infof("Using chart \"%v\" based on prompt selection", selectedChart)

executeAnkhFile(ctx, rootAnkhFile)
}
}
}

Expand Down Expand Up @@ -688,7 +694,7 @@ func main() {
})
datadir = app.String(cli.StringOpt{
Name: "datadir",
Value: path.Join(os.Getenv("HOME"), ".ankh", "data"),
Value: path.Join("/tmp", ".ankh", "data"),
Desc: "The data directory for Ankh template history",
EnvVar: "ANKHDATADIR",
})
Expand Down Expand Up @@ -743,7 +749,7 @@ func main() {
Environment: *environment,
Namespace: namespaceOpt,
Tag: tagOpt,
DataDir: path.Join(*datadir, fmt.Sprintf("%v", time.Now().Unix())),
DataDir: path.Join(*datadir, fmt.Sprintf("%v-%v", time.Now().Unix(), rand.Intn(100000))),
Logger: log,
HelmSetValues: helmVars,
IgnoreContextAndEnv: ctx.IgnoreContextAndEnv,
Expand Down
61 changes: 20 additions & 41 deletions config/testdata/testconfig.yaml
Original file line number Diff line number Diff line change
@@ -1,26 +1,18 @@
# current-context defines which context you currently have "selected". It's
# common for this value to change frequently as you move around to different
# clusters.
current-context: minikube

# supported-environment-classes define a list of your supported environment classes. Used for
# validation of ankh files. It's best practice to keep this value in sync with
# the rest of your teams so there isn't drift.
supported-environment-classes:
- dev
- production

# supported-resource-profiles define a list of your supported profiles. Used for
# validation of ankh files. Resource profiles are typically used for values that relate
# to how beefy your app needs to be. If you're running a production cluster that
# customers can hit, you'll probably want to support a value like "natural".
# Alternatively if you want your app to run on a resource strapped local
# cluster, you might consider also supporting something like "constrained". It's
# best practice to keep this value in sync with the rest of your teams so there
# isn't drift.
supported-resource-profiles:
- natural
- constrained
# the helm registry instructs ankh where to pull charts from
helm:
registry: https://kubernetes-charts.storage.googleapis.com/

docker:
registry: https://registry.docker.io/

# enables sending of release message to specified slack team and channel.
slack:
token: foobar123
username: random-foobar
icon-url: foobar.com/myimage.jpg
format: "_%USER%_ is releasing *%CHART%@%VERSION%* to *%TARGET%*"
rollbackFormat: "_%USER%_ is rolling back *%CHART%* in *%TARGET%*"
pretext: Release notification

# contexts are the different ways in which your ankh files can be deployed to
# kubernetes clusters. Each key in this object is a context and the names can be
Expand All @@ -30,32 +22,19 @@ contexts:
# kube-context ties this context to a `kubectl` context
kube-context: minikube

# ...or use kube-server to simply use a URL for accessing Kubernetes
#kube-server: some-kube-server.coolcompany.net

# environment-class should be one of your `supported-environment-classes` defined above
environment-class: dev

# resource-profile should be one of your `supported-resource-profiles` defined above
resource-profile: natural

# release name provided to helm - empty by default
release: ""

# helm-registry-url instructs ankh where to pull charts from
helm-registry-url: https://kubernetes-charts.storage.googleapis.com/

# cluster-admin controls if the `admin-dependencies` in an ankh file are
# executed before the rest of the `dependencies`
cluster-admin: true
# release name provided to helm
release: minikube

# global can be any nested objects with values that need to be passed to
# every chart. Arrays are not supported within `global`.
global:
foo: bar

# enables sending of release message to specified slack team and channel.
slack:
token: foobar123
username: random-foobar
icon-url: foobar.com/myimage.jpg
format: "_%USER%_ is releasing *%CHART%@%VERSION%* to *%TARGET%*"
rollbackFormat: "_%USER%_ is rolling back *%CHART%* in *%TARGET%*"
pretext: Release notification
Loading

0 comments on commit fb6f516

Please sign in to comment.