Skip to content

Commit

Permalink
Profile auto-activation
Browse files Browse the repository at this point in the history
Fix #1271

Signed-off-by: David Gageot <[email protected]>
  • Loading branch information
dgageot committed Jan 29, 2019
1 parent 6d8c95f commit 7155e2a
Show file tree
Hide file tree
Showing 11 changed files with 213 additions and 8 deletions.
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
2 changes: 1 addition & 1 deletion cmd/skaffold/app/cmd/runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ func newRunner(opts *config.SkaffoldOptions) (*runner.SkaffoldRunner, *latest.Sk

config := parsed.(*latest.SkaffoldPipeline)

err = schema.ApplyProfiles(config, opts.Profiles)
err = schema.ApplyProfiles(config, opts)
if err != nil {
return nil, nil, errors.Wrap(err, "applying profiles")
}
Expand Down
11 changes: 11 additions & 0 deletions integration/examples/annotated-skaffold.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -225,3 +225,14 @@ profiles:
patches:
- 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
# kubeContect is `ctx1`
#- env: DEBUG=true
# kubeContext: ctx1
# Also auto-activate `skaffold dev` is run
# - command: run
# Also auto-activate is the kubeContext is NOT `ctx2`
# - 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: 13 additions & 5 deletions pkg/skaffold/schema/latest/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -242,11 +242,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"`
Patches yamlpatch.Patch `yaml:"patches,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
103 changes: 102 additions & 1 deletion pkg/skaffold/schema/profile_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,14 @@ limitations under the License.
package schema

import (
"os"
"testing"

cfg "github.com/GoogleContainerTools/skaffold/pkg/skaffold/config"
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest"
"github.com/GoogleContainerTools/skaffold/testutil"
yamlpatch "github.com/krishicks/yaml-patch"
"k8s.io/client-go/tools/clientcmd/api"
)

func TestApplyProfiles(t *testing.T) {
Expand Down Expand Up @@ -201,7 +204,9 @@ func TestApplyProfiles(t *testing.T) {

for _, test := range tests {
t.Run(test.description, func(t *testing.T) {
err := ApplyProfiles(test.config, []string{test.profile})
err := ApplyProfiles(test.config, &cfg.SkaffoldOptions{
Profiles: []string{test.profile},
})

if test.shouldErr {
testutil.CheckError(t, test.shouldErr, err)
Expand All @@ -212,6 +217,102 @@ func TestApplyProfiles(t *testing.T) {
}
}

func TestActivatedProfiles(t *testing.T) {
tests := []struct {
description string
profiles []latest.Profile
opts *cfg.SkaffoldOptions
expected []string
shouldErr bool
}{
{
description: "Selected on the command line",
opts: &cfg.SkaffoldOptions{
Command: "dev",
Profiles: []string{"activated", "also-activated"},
},
profiles: []latest.Profile{
{Name: "activated"},
{Name: "not-activated"},
{Name: "also-activated"},
},
expected: []string{"activated", "also-activated"},
}, {
description: "Auto-activated by command",
opts: &cfg.SkaffoldOptions{
Command: "dev",
},
profiles: []latest.Profile{
{Name: "run-profile", Activation: []latest.Activation{{Command: "run"}}},
{Name: "dev-profile", Activation: []latest.Activation{{Command: "dev"}}},
{Name: "non-run-profile", Activation: []latest.Activation{{Command: "!run"}}},
},
expected: []string{"dev-profile", "non-run-profile"},
}, {
description: "Auto-activated by env variable",
opts: &cfg.SkaffoldOptions{},
profiles: []latest.Profile{
{Name: "activated", Activation: []latest.Activation{{Env: "KEY=VALUE"}}},
{Name: "not-activated", Activation: []latest.Activation{{Env: "KEY=OTHER"}}},
{Name: "also-activated", Activation: []latest.Activation{{Env: "KEY=!OTHER"}}},
},
expected: []string{"activated", "also-activated"},
}, {
description: "Invalid env variable",
opts: &cfg.SkaffoldOptions{},
profiles: []latest.Profile{
{Name: "activated", Activation: []latest.Activation{{Env: "KEY:VALUE"}}},
},
shouldErr: true,
}, {
description: "Auto-activated by kube context",
opts: &cfg.SkaffoldOptions{},
profiles: []latest.Profile{
{Name: "activated", Activation: []latest.Activation{{KubeContext: "prod-context"}}},
{Name: "not-activated", Activation: []latest.Activation{{KubeContext: "dev-context"}}},
{Name: "also-activated", Activation: []latest.Activation{{KubeContext: "!dev-context"}}},
},
expected: []string{"activated", "also-activated"},
}, {
description: "Auto-activated by combination",
opts: &cfg.SkaffoldOptions{
Command: "dev",
},
profiles: []latest.Profile{
{
Name: "activated", Activation: []latest.Activation{{
Env: "KEY=VALUE",
KubeContext: "prod-context",
Command: "dev",
}},
},
{
Name: "not-activated", Activation: []latest.Activation{{
Env: "KEY=VALUE",
KubeContext: "prod-context",
Command: "build",
}},
},
},
expected: []string{"activated"},
},
}

os.Setenv("KEY", "VALUE")
restore := testutil.SetupFakeKubernetesContext(t, api.Config{CurrentContext: "prod-context"})
defer restore()

for _, test := range tests {
t.Run(test.description, func(t *testing.T) {

activated, err := activatedProfiles(test.profiles, test.opts)

testutil.CheckErrorAndDeepEqual(t, test.shouldErr, err, test.expected, activated)
})
}

}

func str(value string) *interface{} {
var v interface{} = value
return &v
Expand Down
81 changes: 80 additions & 1 deletion pkg/skaffold/schema/profiles.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,12 @@ 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"
Expand All @@ -29,9 +32,14 @@ import (

// 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 {
Expand All @@ -46,6 +54,77 @@ func ApplyProfiles(c *latest.SkaffoldPipeline, profiles []string) error {
return nil
}

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 equalValue(value, os.Getenv(key)), nil
}

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

return equalValue(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 equalValue(kubeContext, currentKubeContext), nil
}

func equalValue(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)

Expand Down

0 comments on commit 7155e2a

Please sign in to comment.