Skip to content

Commit

Permalink
feat: support templating in diagnose command (#9393)
Browse files Browse the repository at this point in the history
  • Loading branch information
ericzzzzzzz committed May 3, 2024
1 parent a0687ef commit c0bf1b6
Show file tree
Hide file tree
Showing 9 changed files with 659 additions and 21 deletions.
10 changes: 10 additions & 0 deletions cmd/skaffold/app/cmd/diagnose.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import (
"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/runner/runcontext"
"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/schema/latest"
schemaUtil "github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/schema/util"
"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/tags"
"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/util"
"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/version"
"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/yaml"
Expand All @@ -38,6 +39,8 @@ import (
var (
yamlOnly bool
outputFile string

enableTemplating bool
// for testing
getRunContext = runcontext.GetRunContext
getCfgs = parser.GetAllConfigs
Expand All @@ -52,6 +55,7 @@ func NewCmdDiagnose() *cobra.Command {
WithCommonFlags().
WithFlags([]*Flag{
{Value: &yamlOnly, Name: "yaml-only", DefValue: false, Usage: "Only prints the effective skaffold.yaml configuration"},
{Value: &enableTemplating, Name: "enable-templating", DefValue: false, Usage: "Render supported templated fields with golang template engine"},
{Value: &outputFile, Name: "output", Shorthand: "o", DefValue: "", Usage: "File to write diagnose result"},
}).
NoArgs(doDiagnose)
Expand Down Expand Up @@ -82,10 +86,16 @@ func doDiagnose(ctx context.Context, out io.Writer) error {
for i := range configs {
configs[i].(*latest.SkaffoldConfig).Dependencies = nil
}
if enableTemplating {
if err := tags.ApplyTemplates(configs); err != nil {
return err
}
}
buf, err := yaml.MarshalWithSeparator(configs)
if err != nil {
return fmt.Errorf("marshalling configuration: %w", err)
}

out.Write(buf)

return nil
Expand Down
2 changes: 2 additions & 0 deletions docs-v2/content/en/docs/references/cli/_index.md
Original file line number Diff line number Diff line change
Expand Up @@ -903,6 +903,7 @@ Examples:
Options:
--assume-yes=false: If true, skaffold will skip yes/no confirmation from the user and default to yes
-c, --config='': File for global configurations (defaults to $HOME/.skaffold/config)
--enable-templating=false: Render supported templated fields with golang template engine
-f, --filename='skaffold.yaml': Path or URL to the Skaffold config file
-m, --module=[]: Filter Skaffold configs to only the provided named modules
-o, --output='': File to write diagnose result
Expand All @@ -924,6 +925,7 @@ Env vars:

* `SKAFFOLD_ASSUME_YES` (same as `--assume-yes`)
* `SKAFFOLD_CONFIG` (same as `--config`)
* `SKAFFOLD_ENABLE_TEMPLATING` (same as `--enable-templating`)
* `SKAFFOLD_FILENAME` (same as `--filename`)
* `SKAFFOLD_MODULE` (same as `--module`)
* `SKAFFOLD_OUTPUT` (same as `--output`)
Expand Down
22 changes: 19 additions & 3 deletions integration/diagnose_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import (
"github.com/GoogleContainerTools/skaffold/v2/testutil"
)

func TestDiagnose(t *testing.T) {
func TestDiagnoseExamples(t *testing.T) {
examples, err := folders("examples")
failNowIfError(t, err)
if len(examples) == 0 {
Expand All @@ -49,28 +49,44 @@ func TestDiagnose(t *testing.T) {
}
}

func TestDiagnoseOutputFile(t *testing.T) {
func TestDiagnose(t *testing.T) {
tests := []struct {
description string
dir string
outputFile string
args []string
envs map[string]string
}{
{
description: "single skaffold.yaml outside of source dir",
dir: "testdata/diagnose/temp-config",
outputFile: "abc.txt",
},
{
description: "apply replacements to templates in skaffold.yaml",
dir: "testdata/diagnose/direct-templates",
outputFile: "abc.txt",
args: []string{"--enable-templating"},
envs: map[string]string{"AAA": "aaa"},
},
}

for _, test := range tests {
MarkIntegrationTest(t, CanRunWithoutGcp)
testutil.Run(t, test.description, func(t *testutil.T) {
if test.envs != nil {
for k, v := range test.envs {
t.Setenv(k, v)
}
}
tmpDir := testutil.NewTempDir(t.T)
configContents, err := os.ReadFile(filepath.Join(test.dir, "skaffold.yaml"))
t.CheckNoError(err)
templ, err := os.ReadFile(filepath.Join(test.dir, "diagnose.tmpl"))
tmpDir.Write("skaffold.yaml", string(configContents))
skaffold.Diagnose("--yaml-only", "--output", tmpDir.Path(test.outputFile), "-f", tmpDir.Path("skaffold.yaml")).
args := []string{"--yaml-only", "--output", tmpDir.Path(test.outputFile), "-f", tmpDir.Path("skaffold.yaml")}
args = append(args, test.args...)
skaffold.Diagnose(args...).
InDir(test.dir).RunOrFail(t.T)
t.CheckNoError(err)
outTemplate := template.Must(template.New("tmpl").Parse(string(templ)))
Expand Down
52 changes: 52 additions & 0 deletions integration/testdata/diagnose/direct-templates/diagnose.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
apiVersion: skaffold/v4beta11
kind: Config
build:
artifacts:
- image: skaffold-helm
context: {{.Root}}
docker:
dockerfile: Dockerfile
buildArgs:
key1: aaa
- image: skaffold-ko
context: {{.Root}}
ko:
fromImage: gcr.io/distroless/static-debian11:nonroot
dependencies:
paths:
- '**/*.go'
- go.*
env:
- first=aaa
labels:
xxx: aaa
tagPolicy:
gitCommit: {}
local:
concurrency: 1
manifests:
kustomize:
paths:
- {{.Root}}/aaa
- {{.Root}}/aaa
helm:
releases:
- name: aaa
chartPath: {{.Root}}/aaa
valuesFiles:
- {{.Root}}/aaa
namespace: aaa
setValues:
aaa: aaa
bbb: aaa
setValueTemplates:
image.tag: aaa
deploy:
kubectl:
defaultNamespace: aaa
logs:
prefix: container
portForward:
- resourceName: aaa
namespace: aaa
address: 127.0.0.1
44 changes: 44 additions & 0 deletions integration/testdata/diagnose/direct-templates/skaffold.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
apiVersion: skaffold/v4beta11
kind: Config
build:
artifacts:
- image: skaffold-helm
docker:
buildArgs:
"key1": "{{.AAA}}"
- image: skaffold-ko
ko:
dependencies:
paths:
- "**/*.go"
- go.*
fromImage: gcr.io/distroless/static-debian11:nonroot
env:
- "first={{.AAA}}"
labels:
xxx: "{{.AAA}}"

manifests:
kustomize:
paths:
- "{{.AAA}}"
- "{{.AAA}}"
helm:
releases:
- name: "{{.AAA}}"
chartPath: "{{.AAA}}"
valuesFiles:
- "{{.AAA}}"
namespace: "{{.AAA}}"
setValues:
aaa: "{{.AAA}}"
bbb: "{{.AAA}}"
setValueTemplates:
image:
tag: "{{.AAA}}"
deploy:
kubectl:
defaultNamespace: "{{.AAA}}"
portForward:
- namespace: "{{.AAA}}"
resourceName: "{{.AAA}}"
34 changes: 17 additions & 17 deletions pkg/skaffold/schema/latest/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -191,10 +191,10 @@ type PortForwardResource struct {
Type ResourceType `yaml:"resourceType,omitempty"`

// Name is the name of the Kubernetes resource or local container to port forward.
Name string `yaml:"resourceName,omitempty"`
Name string `yaml:"resourceName,omitempty" skaffold:"template"`

// Namespace is the namespace of the resource to port forward. Does not apply to local containers.
Namespace string `yaml:"namespace,omitempty"`
Namespace string `yaml:"namespace,omitempty" skaffold:"template"`

// Port is the resource port that will be forwarded.
Port util.IntOrString `yaml:"port,omitempty"`
Expand Down Expand Up @@ -294,7 +294,7 @@ type EnvTemplateTagger struct {
// The template is executed against the current environment,
// with those variables injected.
// For example: `{{.RELEASE}}`.
Template string `yaml:"template,omitempty" yamltags:"required"`
Template string `yaml:"template,omitempty" yamltags:"required" skaffold:"template"`
}

// DateTimeTagger *beta* tags images with the build timestamp.
Expand Down Expand Up @@ -790,7 +790,7 @@ type RemoteManifest struct {
type Kustomize struct {
// Paths is the path to Kustomization files.
// Defaults to `["."]`.
Paths []string `yaml:"paths,omitempty" skaffold:"filepath"`
Paths []string `yaml:"paths,omitempty" skaffold:"filepath,template"`

// BuildArgs are additional args passed to `kustomize build`.
BuildArgs []string `yaml:"buildArgs,omitempty"`
Expand Down Expand Up @@ -935,7 +935,7 @@ type KubectlDeploy struct {
RemoteManifests []string `yaml:"remoteManifests,omitempty"`

// DefaultNamespace is the default namespace passed to kubectl on deployment if no other override is given.
DefaultNamespace *string `yaml:"defaultNamespace,omitempty"`
DefaultNamespace *string `yaml:"defaultNamespace,omitempty" skaffold:"template"`

// LifecycleHooks describes a set of lifecycle hooks that are executed before and after every deploy.
LifecycleHooks DeployHooks `yaml:"hooks,omitempty"`
Expand Down Expand Up @@ -989,32 +989,32 @@ type HelmDeployFlags struct {
type HelmRelease struct {
// Name is the name of the Helm release.
// It accepts environment variables via the go template syntax.
Name string `yaml:"name,omitempty" yamltags:"required"`
Name string `yaml:"name,omitempty" yamltags:"required" skaffold:"template"`

// ChartPath is the local path to a packaged Helm chart or an unpacked Helm chart directory.
ChartPath string `yaml:"chartPath,omitempty" yamltags:"oneOf=chartSource" skaffold:"filepath"`
ChartPath string `yaml:"chartPath,omitempty" yamltags:"oneOf=chartSource" skaffold:"filepath,template"`

// RemoteChart refers to a remote Helm chart reference or URL.
RemoteChart string `yaml:"remoteChart,omitempty" yamltags:"oneOf=chartSource"`

// ValuesFiles are the paths to the Helm `values` files.
ValuesFiles []string `yaml:"valuesFiles,omitempty" skaffold:"filepath"`
ValuesFiles []string `yaml:"valuesFiles,omitempty" skaffold:"filepath,template"`

// Namespace is the Kubernetes namespace.
Namespace string `yaml:"namespace,omitempty"`
Namespace string `yaml:"namespace,omitempty" skaffold:"template"`

// Version is the version of the chart.
Version string `yaml:"version,omitempty"`
Version string `yaml:"version,omitempty" skaffold:"template"`

// SetValues are key-value pairs.
// If present, Skaffold will send `--set` flag to Helm CLI and append all pairs after the flag.
SetValues util.FlatMap `yaml:"setValues,omitempty"`
SetValues util.FlatMap `yaml:"setValues,omitempty" skaffold:"template"`

// SetValueTemplates are key-value pairs.
// If present, Skaffold will try to parse the value part of each key-value pair using
// environment variables in the system, then send `--set` flag to Helm CLI and append
// all parsed pairs after the flag.
SetValueTemplates util.FlatMap `yaml:"setValueTemplates,omitempty"`
SetValueTemplates util.FlatMap `yaml:"setValueTemplates,omitempty" skaffold:"template"`

// SetFiles are key-value pairs.
// If present, Skaffold will send `--set-file` flag to Helm CLI and append all pairs after the flag.
Expand Down Expand Up @@ -1547,7 +1547,7 @@ type DockerArtifact struct {

// BuildArgs are arguments passed to the docker build.
// For example: `{"key1": "value1", "key2": "{{ .ENV_VAR }}"}`.
BuildArgs map[string]*string `yaml:"buildArgs,omitempty"`
BuildArgs map[string]*string `yaml:"buildArgs,omitempty" skaffold:"template"`

// NetworkMode is passed through to docker and overrides the
// network configuration of docker builder. If unset, use whatever
Expand Down Expand Up @@ -1634,19 +1634,19 @@ type KoArtifact struct {
// These environment variables are only used at build time.
// They are _not_ set in the resulting container image.
// For example: `["GOPRIVATE=git.example.com", "GOCACHE=/workspace/.gocache"]`.
Env []string `yaml:"env,omitempty"`
Env []string `yaml:"env,omitempty" skaffold:"template"`

// Flags are additional build flags passed to `go build`.
// For example: `["-trimpath", "-v"]`.
Flags []string `yaml:"flags,omitempty"`
Flags []string `yaml:"flags,omitempty" skaffold:"template"`

// Labels are key-value string pairs to add to the image config.
// For example: `{"foo":"bar"}`.
Labels map[string]string `yaml:"labels,omitempty"`
Labels map[string]string `yaml:"labels,omitempty" skaffold:"template"`

// Ldflags are linker flags passed to the builder.
// For example: `["-buildid=", "-s", "-w"]`.
Ldflags []string `yaml:"ldflags,omitempty"`
Ldflags []string `yaml:"ldflags,omitempty" skaffold:"template"`

// Main is the location of the main package. It is the pattern passed to `go build`.
// If main is specified as a relative path, it is relative to the `context` directory.
Expand Down
5 changes: 4 additions & 1 deletion pkg/skaffold/tags/paths.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"fmt"
"path/filepath"
"reflect"
"slices"
"strings"

"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/output/log"
Expand Down Expand Up @@ -122,5 +123,7 @@ func filepathTagExists(f reflect.StructField) bool {
if !ok {
return false
}
return t == "filepath"
split := strings.Split(t, ",")

return slices.Contains(split, "filepath")
}

0 comments on commit c0bf1b6

Please sign in to comment.