Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Better profiles #1541

Merged
merged 4 commits into from
Feb 1, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions Gopkg.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions cmd/skaffold/app/cmd/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ func NewCmdBuild(out io.Writer) *cobra.Command {
Short: "Builds the artifacts",
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
opts.Command = "build"
return runBuild(out)
},
}
Expand Down
1 change: 1 addition & 0 deletions cmd/skaffold/app/cmd/delete.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ func NewCmdDelete(out io.Writer) *cobra.Command {
Short: "Delete the deployed resources",
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
opts.Command = "delete"
return delete(out)
},
}
Expand Down
1 change: 1 addition & 0 deletions cmd/skaffold/app/cmd/deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ func NewCmdDeploy(out io.Writer) *cobra.Command {
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
// Same actions as `skaffold run`, but with pre-built images.
opts.Command = "deploy"
return run(out)
},
}
Expand Down
1 change: 1 addition & 0 deletions cmd/skaffold/app/cmd/dev.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ func NewCmdDev(out io.Writer) *cobra.Command {
Short: "Runs a pipeline file in development mode",
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
opts.Command = "dev"
return dev(out)
},
}
Expand Down
1 change: 1 addition & 0 deletions cmd/skaffold/app/cmd/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ func NewCmdRun(out io.Writer) *cobra.Command {
Short: "Runs a pipeline file",
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
opts.Command = "run"
err := run(out)
if err == nil {
tips.PrintForRun(out, opts)
Expand Down
9 changes: 5 additions & 4 deletions cmd/skaffold/app/cmd/runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,15 +35,16 @@ func newRunner(opts *config.SkaffoldOptions) (*runner.SkaffoldRunner, *latest.Sk
}

config := parsed.(*latest.SkaffoldPipeline)
if err := defaults.Set(config); err != nil {
return nil, nil, errors.Wrap(err, "setting default values")
}

err = schema.ApplyProfiles(config, opts.Profiles)
err = schema.ApplyProfiles(config, opts)
if err != nil {
return nil, nil, errors.Wrap(err, "applying profiles")
}

if err := defaults.Set(config); err != nil {
return nil, nil, errors.Wrap(err, "setting default values")
}

defaultRepo, err := configutil.GetDefaultRepo(opts.DefaultRepo)
if err != nil {
return nil, nil, errors.Wrap(err, "getting default repo")
Expand Down
15 changes: 15 additions & 0 deletions integration/examples/annotated-skaffold.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -222,3 +222,18 @@ profiles:
build:
googleCloudBuild:
projectId: k8s-skaffold
- name: other
# profiles can also patch some values using standard JSON patch notation
patches:
# This profile will replace the `dockerfile` value of the first artifact by `Dockerfile.DEV`
- path: /build/artifacts/0/docker/dockerfile
value: Dockerfile.DEV
# profiles can be auto-activated by external factors, like environment variables
# kubeContext value or depending on which skaffold command is run.
activation:
# Auto-activate this profile if the DEBUG env variable is set to `true` AND the kubeContext is `ctx1`
# - env: DEBUG=true
# kubeContext: ctx1
# Auto-activate if the skaffold command is `skaffold run` OR if the kubeContext is NOT `ctx2`
# - command: run
# - kubeContext: "!ctx2"
1 change: 1 addition & 0 deletions pkg/skaffold/config/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ type SkaffoldOptions struct {
WatchPollInterval int
DefaultRepo string
PreBuiltImages []string
Command string
}

// Labels returns a map of labels to be applied to all deployed
Expand Down
18 changes: 14 additions & 4 deletions pkg/skaffold/schema/latest/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package latest

import (
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/util"
yamlpatch "github.com/krishicks/yaml-patch"
)

const Version string = "skaffold/v1beta4"
Expand Down Expand Up @@ -242,10 +243,19 @@ type Artifact struct {
// Profile is additional configuration that overrides default
// configuration when it is activated.
type Profile struct {
Name string `yaml:"name,omitempty"`
Build BuildConfig `yaml:"build,omitempty"`
Test TestConfig `yaml:"test,omitempty"`
Deploy DeployConfig `yaml:"deploy,omitempty"`
Name string `yaml:"name,omitempty"`
Build BuildConfig `yaml:"build,omitempty"`
Test TestConfig `yaml:"test,omitempty"`
Deploy DeployConfig `yaml:"deploy,omitempty"`
Patches yamlpatch.Patch `yaml:"patches,omitempty"`
Activation []Activation `yaml:"activation,omitempty"`
}

// Activation defines criteria to auto-activate a profile.
type Activation struct {
Env string `yaml:"env,omitempty"`
KubeContext string `yaml:"kubeContext,omitempty"`
Command string `yaml:"command,omitempty"`
}

type ArtifactType struct {
Expand Down
121 changes: 112 additions & 9 deletions pkg/skaffold/schema/profiles.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,36 +18,114 @@ package schema

import (
"fmt"
"os"
"reflect"
"strings"

cfg "github.com/GoogleContainerTools/skaffold/pkg/skaffold/config"
kubectx "github.com/GoogleContainerTools/skaffold/pkg/skaffold/kubernetes/context"
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"

"github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/defaults"
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest"
yaml "gopkg.in/yaml.v2"
)

// ApplyProfiles returns configuration modified by the application
// of a list of profiles.
func ApplyProfiles(c *latest.SkaffoldPipeline, profiles []string) error {
func ApplyProfiles(c *latest.SkaffoldPipeline, opts *cfg.SkaffoldOptions) error {
byName := profilesByName(c.Profiles)

profiles, err := activatedProfiles(c.Profiles, opts)
if err != nil {
return errors.Wrap(err, "finding auto-activated profiles")
}

for _, name := range profiles {
profile, present := byName[name]
if !present {
return fmt.Errorf("couldn't find profile %s", name)
}

applyProfile(c, profile)
}
if err := defaults.Set(c); err != nil {
return errors.Wrap(err, "applying default values")
if err := applyProfile(c, profile); err != nil {
return errors.Wrapf(err, "appying profile %s", name)
}
}

return nil
}

func applyProfile(config *latest.SkaffoldPipeline, profile latest.Profile) {
func activatedProfiles(profiles []latest.Profile, opts *cfg.SkaffoldOptions) ([]string, error) {
activated := opts.Profiles

// Auto-activated profiles
for _, profile := range profiles {
for _, cond := range profile.Activation {
command := isCommand(cond.Command, opts)

env, err := isEnv(cond.Env)
if err != nil {
return nil, err
}

kubeContext, err := isKubeContext(cond.KubeContext)
if err != nil {
return nil, err
}

if command && env && kubeContext {
activated = append(activated, profile.Name)
}
}
}

return activated, nil
}

func isEnv(env string) (bool, error) {
if env == "" {
return true, nil
}

keyValue := strings.SplitN(env, "=", 2)
if len(keyValue) != 2 {
return false, fmt.Errorf("invalid env variable format: %s, should be KEY=VALUE", env)
}

key := keyValue[0]
value := keyValue[1]

return satisfies(value, os.Getenv(key)), nil
}

func isCommand(command string, opts *cfg.SkaffoldOptions) bool {
if command == "" {
return true
}

return satisfies(command, opts.Command)
}

func isKubeContext(kubeContext string) (bool, error) {
if kubeContext == "" {
return true, nil
}

currentKubeContext, err := kubectx.CurrentContext()
if err != nil {
return false, errors.Wrap(err, "getting current cluster context")
}

return satisfies(kubeContext, currentKubeContext), nil
}

func satisfies(expected, actual string) bool {
if strings.HasPrefix(expected, "!") {
return actual != expected[1:]
}
return actual == expected
}

func applyProfile(config *latest.SkaffoldPipeline, profile latest.Profile) error {
logrus.Infof("applying profile: %s", profile.Name)

// this intentionally removes the Profiles field from the returned config
Expand All @@ -58,6 +136,31 @@ func applyProfile(config *latest.SkaffoldPipeline, profile latest.Profile) {
Deploy: overlayProfileField(config.Deploy, profile.Deploy).(latest.DeployConfig),
Test: overlayProfileField(config.Test, profile.Test).(latest.TestConfig),
}

if len(profile.Patches) == 0 {
return nil
}

// Default patch operation to `replace`
for i, p := range profile.Patches {
if p.Op == "" {
p.Op = "replace"
profile.Patches[i] = p
}
}

// Apply profile patches
buf, err := yaml.Marshal(*config)
if err != nil {
return err
}

buf, err = profile.Patches.Apply(buf)
if err != nil {
return err
}

return yaml.Unmarshal(buf, config)
}

func profilesByName(profiles []latest.Profile) map[string]latest.Profile {
Expand Down
Loading