Skip to content

Commit

Permalink
feat(ko): Enable templating of labels and env
Browse files Browse the repository at this point in the history
This change enables `ko` builder users to substitute environment
variable values in the `labels` and `env` config fields.

These fields are used for image labels and build-time environment
variables, respectively.

Envvar templating of image labels can be used to add information such as
the Git commit SHA to the image, see GoogleContainerTools#6916.

Also, environment variable expansion of `flags` and `ldflags` in the
`ko` builder configuration now supports Skaffold's templating syntax,
for consistency.

For backwards compatibility, `ko`'s templating syntax still works with
`flags` and `ldflags`:

Skaffold: `{{.FOO}}`

`ko`: `{{.Env.FOO}}`

Tracking: GoogleContainerTools#6041
Fixes: GoogleContainerTools#6916
  • Loading branch information
halvards committed Dec 9, 2021
1 parent 4b0579c commit fd5150f
Show file tree
Hide file tree
Showing 4 changed files with 177 additions and 124 deletions.
1 change: 1 addition & 0 deletions docs/content/en/docs/environment/templating.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ will be `gcr.io/k8s-skaffold/example:v1`.
List of fields that support templating:

* `build.artifacts.[].docker.buildArgs` (see [builders]({{< relref "/docs/pipeline-stages/builders" >}}))
* `build.artifacts.[].ko.{env,flags,labels,ldflags}` (see [`ko` builder]({{< relref "/docs/pipeline-stages/builders/ko" >}}))
* `build.tagPolicy.envTemplate.template` (see [envTemplate tagger]({{< relref "/docs/pipeline-stages/taggers#envtemplate-using-values-of-environment-variables-as-tags)" >}}))
* `deploy.helm.releases.setValueTemplates` (see [Deploying with helm]({{< relref "/docs/pipeline-stages/deployers#deploying-with-helm)" >}}))
* `deploy.helm.releases.name` (see [Deploying with helm]({{< relref "/docs/pipeline-stages/deployers#deploying-with-helm)" >}}))
Expand Down
39 changes: 32 additions & 7 deletions docs/content/en/docs/pipeline-stages/builders/ko.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,12 +88,15 @@ is `linux/amd64`, but you can configure a list of platforms using the
You can also supply `["all"]` as the value of `platforms`. `all` means that the
ko builder builds images for all platforms supported by the base image.

### Labels / annotations
### Image labels

Use the `labels` configuration field to add
[annotations](https://github.com/opencontainers/image-spec/blob/main/annotations.md)
[image labels](https://github.com/opencontainers/image-spec/blob/main/config.md#properties)
(a.k.a. [`Dockerfile` `LABEL`s](https://docs.docker.com/engine/reference/builder/#label)),
e.g.:

For example, you can add labels based on the
[pre-defined annotations keys](https://github.com/opencontainers/image-spec/blob/main/annotations.md#pre-defined-annotation-keys)
from the Open Container Initiative (OCI) Image Format Specification:

```yaml
ko:
Expand All @@ -102,6 +105,16 @@ e.g.:
org.opencontainers.image.source: https://github.com/GoogleContainerTools/skaffold
```

The `labels` section supports templating of values based on environment
variables, e.g.:

```yaml
ko:
labels:
org.opencontainers.image.revision: "{{.GITHUB_SHA}}"
org.opencontainers.image.source: "{{.GITHUB_SERVER_URL}}/{{.GITHUB_REPOSITORY}}"
```

### Build time environment variables

Use the `env` configuration field to specify build-time environment variables.
Expand All @@ -115,6 +128,15 @@ Example:
- GOPRIVATE=git.internal.example.com,source.developers.google.com
```

The `env` field supports templating of values using environment variables, for
example:

```yaml
ko:
env:
- GOPROXY={{.GOPROXY}}
```

### Dependencies

The `dependencies` section configures what files Skaffold should watch for
Expand Down Expand Up @@ -168,17 +190,20 @@ Use the `ldflags` configuration field to provide linker flag arguments, e.g.:
- -w
```

`ko` supports templating of `flags` and `ldflags` using environment variables,
The `flags` and `ldflags` fields support templating using environment
variables,
e.g.:

```yaml
ko:
ldflags:
- -X main.version={{.Env.VERSION}}
- -X main.version={{.VERSION}}
```

These templates are passed through to `ko` and are expanded using
[`ko`'s template expansion implementation](https://github.com/google/ko/blob/v0.9.3/pkg/build/gobuild.go#L632-L660).
These templates are evaluated by Skaffold. Note that the syntax is slightly
different to
[`ko`'s template expansion](https://github.com/google/ko/blob/v0.9.3/pkg/build/gobuild.go#L632-L660),
specifically, there's no `.Env` prefix.

### Source file locations

Expand Down
78 changes: 59 additions & 19 deletions pkg/skaffold/build/ko/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import (

"github.com/GoogleContainerTools/skaffold/pkg/skaffold/config"
latestV1 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest/v1"
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/util"
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/version"
)

Expand All @@ -44,12 +45,16 @@ func buildOptions(a *latestV1.Artifact, runMode config.RunMode) (*options.BuildO
if err != nil {
return nil, fmt.Errorf("could not create ko build config: %v", err)
}
imageLabels, err := labels(a)
if err != nil {
return nil, fmt.Errorf("could not expand image labels: %v", err)
}
return &options.BuildOptions{
BaseImage: a.KoArtifact.BaseImage,
BuildConfigs: buildconfig,
ConcurrentBuilds: 1, // we could plug in Skaffold's max builds here, but it'd be incorrect if users build more than one artifact
DisableOptimizations: runMode == config.RunModes.Debug,
Labels: labels(a),
Labels: imageLabels,
Platform: strings.Join(a.KoArtifact.Platforms, ","),
Trimpath: runMode != config.RunModes.Debug,
UserAgent: version.UserAgentWithClient(),
Expand All @@ -63,20 +68,33 @@ func buildOptions(a *latestV1.Artifact, runMode config.RunMode) (*options.BuildO
// In this case, ko falls back to build configs provided in `.ko.yaml`, or to the default zero config.
func buildConfig(a *latestV1.Artifact) (map[string]build.Config, error) {
buildconfigs := map[string]build.Config{}
if koArtifactSpecifiesBuildConfig(*a.KoArtifact) {
koImportpath, err := getImportPath(a)
if err != nil {
return nil, fmt.Errorf("could not determine import path of image %s: %v", a.ImageName, err)
}
importpath := strings.TrimPrefix(koImportpath, build.StrictScheme)
buildconfigs[importpath] = build.Config{
ID: a.ImageName,
Dir: ".",
Env: a.KoArtifact.Env,
Flags: a.KoArtifact.Flags,
Ldflags: a.KoArtifact.Ldflags,
Main: a.KoArtifact.Main,
}
if !koArtifactSpecifiesBuildConfig(*a.KoArtifact) {
return buildconfigs, nil
}
koImportpath, err := getImportPath(a)
if err != nil {
return nil, fmt.Errorf("could not determine import path of image %s: %v", a.ImageName, err)
}
env, err := expand(a.KoArtifact.Env)
if err != nil {
return nil, fmt.Errorf("could not expand env: %v", err)
}
flags, err := expand(a.KoArtifact.Flags)
if err != nil {
return nil, fmt.Errorf("could not expand build flags: %v", err)
}
ldflags, err := expand(a.KoArtifact.Ldflags)
if err != nil {
return nil, fmt.Errorf("could not expand linker flags: %v", err)
}
importpath := strings.TrimPrefix(koImportpath, build.StrictScheme)
buildconfigs[importpath] = build.Config{
ID: a.ImageName,
Dir: ".",
Env: env,
Flags: flags,
Ldflags: ldflags,
Main: a.KoArtifact.Main,
}
return buildconfigs, nil
}
Expand All @@ -100,10 +118,32 @@ func koArtifactSpecifiesBuildConfig(k latestV1.KoArtifact) bool {
return false
}

func labels(a *latestV1.Artifact) []string {
var labels []string
func labels(a *latestV1.Artifact) ([]string, error) {
rawLabels := map[string]*string{}
for k, v := range a.KoArtifact.Labels {
labels = append(labels, fmt.Sprintf("%s=%s", k, v))
rawLabels[k] = util.StringPtr(v)
}
expandedLabels, err := util.EvaluateEnvTemplateMapWithEnv(rawLabels, nil)
if err != nil {
return nil, fmt.Errorf("unable to expand image labels: %w", err)
}
var labels []string
for k, v := range expandedLabels {
labels = append(labels, fmt.Sprintf("%s=%s", k, *v))
}
return labels, nil
}

func expand(dryValues []string) ([]string, error) {
var expandedValues []string
for _, rawValue := range dryValues {
// support ko-style envvar templating syntax, see https://github.com/GoogleContainerTools/skaffold/issues/6916
rawValue = strings.ReplaceAll(rawValue, "{{.Env.", "{{.")
expandedValue, err := util.ExpandEnvTemplate(rawValue, nil)
if err != nil {
return nil, fmt.Errorf("could not expand %s", rawValue)
}
expandedValues = append(expandedValues, expandedValue)
}
return labels
return expandedValues, nil
}
Loading

0 comments on commit fd5150f

Please sign in to comment.