Skip to content

Commit

Permalink
[kpt deployer] Add "local-config" annotation to kpt fn configs. (#4803)
Browse files Browse the repository at this point in the history
* [kpt deployer] Add "local-config" annotation to kpt fn resource.

The `config.kubernetes.io/local-config:true` annotation will skip the kpt
 fn resource from deploying to the cluster in `kpt live apply`.

* fix linter
  • Loading branch information
yuwenma authored Sep 19, 2020
1 parent 77244b8 commit e1339dd
Show file tree
Hide file tree
Showing 4 changed files with 154 additions and 3 deletions.
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -80,9 +80,10 @@ require (
gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c
k8s.io/api v0.18.1
k8s.io/apiextensions-apiserver v0.18.1 // indirect
k8s.io/apimachinery v0.18.1
k8s.io/apimachinery v0.19.2
k8s.io/client-go v0.18.1
k8s.io/kubectl v0.0.0-20190831163037-3b58a944563f
k8s.io/utils v0.0.0-20200324210504-a9aa75ae1b89
knative.dev/pkg v0.0.0-20200416021448-f68639f04b39 // indirect
sigs.k8s.io/yaml v1.2.0
)
52 changes: 51 additions & 1 deletion pkg/skaffold/deploy/kpt.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ import (
"regexp"
"strings"

"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
k8syaml "sigs.k8s.io/yaml"

"github.com/GoogleContainerTools/skaffold/pkg/skaffold/build"
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/config"
deploy "github.com/GoogleContainerTools/skaffold/pkg/skaffold/deploy/kubectl"
Expand All @@ -36,10 +39,12 @@ import (
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/util"
)

var (
const (
inventoryTemplate = "inventory-template.yaml"
kptHydrated = ".kpt-hydrated"
pipeline = ".pipeline"
kptFnAnnotation = "config.kubernetes.io/function"
kptFnLocalConfig = "config.kubernetes.io/local-config"
)

// KptDeployer deploys workflows with kpt CLI
Expand Down Expand Up @@ -186,6 +191,12 @@ func (k *KptDeployer) renderManifests(ctx context.Context, _ io.Writer, builds [
return nil, nil
}

// exclude the kpt function from the manipulated resources.
manifests, err = k.excludeKptFn(manifests)
if err != nil {
return nil, fmt.Errorf("exclude kpt fn from manipulated resources: %w", err)
}

manifests, err = manifests.ReplaceImages(builds)
if err != nil {
return nil, fmt.Errorf("replacing images in manifests: %w", err)
Expand Down Expand Up @@ -270,6 +281,45 @@ func (k *KptDeployer) kptFnRun(ctx context.Context) (deploy.ManifestList, error)
return manifests, nil
}

// excludeKptFn adds an annotation "config.kubernetes.io/local-config: 'true'" to kpt function.
// This will exclude kpt functions from deployed to the cluster in kpt live apply.
func (k *KptDeployer) excludeKptFn(manifest deploy.ManifestList) (deploy.ManifestList, error) {
var newManifest deploy.ManifestList
for _, yByte := range manifest {
// Convert yaml byte config to unstructured.Unstructured
jByte, _ := k8syaml.YAMLToJSON(yByte)
var obj unstructured.Unstructured
if err := obj.UnmarshalJSON(jByte); err != nil {
return nil, fmt.Errorf("unmarshaling config: %w", err)
}
// skip if the resource is not kpt fn config.
if _, ok := obj.GetAnnotations()[kptFnAnnotation]; !ok {
newManifest = append(newManifest, yByte)
continue
}
// skip if the kpt fn has local-config annotation specified.
if _, ok := obj.GetAnnotations()[kptFnLocalConfig]; ok {
newManifest = append(newManifest, yByte)
continue
}

// Add "local-config" annotation to kpt fn config.
anns := obj.GetAnnotations()
anns[kptFnLocalConfig] = "true"
obj.SetAnnotations(anns)
jByte, err := obj.MarshalJSON()
if err != nil {
return nil, fmt.Errorf("marshaling to json: %w", err)
}
newYByte, err := k8syaml.JSONToYAML(jByte)
if err != nil {
return nil, fmt.Errorf("converting json to yaml: %w", err)
}
newManifest.Append(newYByte)
}
return newManifest, nil
}

// getApplyDir returns the path to applyDir if specified by the user. Otherwise, getApplyDir
// creates a hidden directory named .kpt-hydrated in place of applyDir.
func (k *KptDeployer) getApplyDir(ctx context.Context) (string, error) {
Expand Down
99 changes: 99 additions & 0 deletions pkg/skaffold/deploy/kpt_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import (
"testing"

"github.com/GoogleContainerTools/skaffold/pkg/skaffold/build"
deploy "github.com/GoogleContainerTools/skaffold/pkg/skaffold/deploy/kubectl"
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/runner/runcontext"
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest"
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/util"
Expand Down Expand Up @@ -889,6 +890,104 @@ func TestKpt_KptCommandArgs(t *testing.T) {
}
}

// TestKpt_ExcludeKptFn checks the declarative kpt fn has expected annotations added.
func TestKpt_ExcludeKptFn(t *testing.T) {
// A declarative fn.
testFn1 := []byte(`apiVersion: v1
data:
annotation_name: k1
annotation_value: v1
kind: ConfigMap
metadata:
annotations:
config.kubernetes.io/function: fake`)
// A declarative fn which has `local-config` annotation specified.
testFn2 := []byte(`apiVersion: v1
kind: ConfigMap
metadata:
annotations:
config.kubernetes.io/function: fake
config.kubernetes.io/local-config: "false"
data:
annotation_name: k2
annotation_value: v2`)
testPod := []byte(`apiVersion: v1
kind: Pod
metadata:
namespace: default
spec:
containers:
- image: gcr.io/project/image1
name: image1`)
tests := []struct {
description string
manifests deploy.ManifestList
expected deploy.ManifestList
}{
{
description: "Add `local-config` annotation to kpt fn",
manifests: deploy.ManifestList{testFn1},
expected: deploy.ManifestList{[]byte(`apiVersion: v1
data:
annotation_name: k1
annotation_value: v1
kind: ConfigMap
metadata:
annotations:
config.kubernetes.io/function: fake
config.kubernetes.io/local-config: "true"`)},
},
{
description: "Skip preset `local-config` annotation",
manifests: deploy.ManifestList{testFn2},
expected: deploy.ManifestList{[]byte(`apiVersion: v1
kind: ConfigMap
metadata:
annotations:
config.kubernetes.io/function: fake
config.kubernetes.io/local-config: "false"
data:
annotation_name: k2
annotation_value: v2`)},
},
{
description: "Valid in kpt fn pipeline.",
manifests: deploy.ManifestList{testFn1, testFn2, testPod},
expected: deploy.ManifestList{[]byte(`apiVersion: v1
data:
annotation_name: k1
annotation_value: v1
kind: ConfigMap
metadata:
annotations:
config.kubernetes.io/function: fake
config.kubernetes.io/local-config: "true"`), []byte(`apiVersion: v1
kind: ConfigMap
metadata:
annotations:
config.kubernetes.io/function: fake
config.kubernetes.io/local-config: "false"
data:
annotation_name: k2
annotation_value: v2`), []byte(`apiVersion: v1
kind: Pod
metadata:
namespace: default
spec:
containers:
- image: gcr.io/project/image1
name: image1`)},
},
}
for _, test := range tests {
testutil.Run(t, test.description, func(t *testutil.T) {
k := NewKptDeployer(&kptConfig{}, nil)
actualManifest, err := k.excludeKptFn(test.manifests)
t.CheckErrorAndDeepEqual(false, err, test.expected.String(), actualManifest.String())
})
}
}

type kptConfig struct {
runcontext.RunContext // Embedded to provide the default values.
workingDir string
Expand Down
3 changes: 2 additions & 1 deletion vendor/modules.txt
Original file line number Diff line number Diff line change
Expand Up @@ -837,7 +837,7 @@ k8s.io/api/storage/v1alpha1
k8s.io/api/storage/v1beta1
# k8s.io/apiextensions-apiserver v0.18.1
## explicit
# k8s.io/apimachinery v0.18.1 => k8s.io/apimachinery v0.17.4
# k8s.io/apimachinery v0.19.2 => k8s.io/apimachinery v0.17.4
## explicit
k8s.io/apimachinery/pkg/api/equality
k8s.io/apimachinery/pkg/api/errors
Expand Down Expand Up @@ -1131,6 +1131,7 @@ knative.dev/pkg/kmeta
knative.dev/pkg/kmp
knative.dev/pkg/tracker
# sigs.k8s.io/yaml v1.2.0
## explicit
sigs.k8s.io/yaml
# github.com/Azure/go-autorest => github.com/Azure/go-autorest v13.0.1+incompatible
# github.com/containerd/containerd => github.com/containerd/containerd v1.3.4
Expand Down

0 comments on commit e1339dd

Please sign in to comment.