Skip to content

Commit

Permalink
use preprocessing to handle the first time apply with inventorypolicy (
Browse files Browse the repository at this point in the history
…#1382)

* use preprocess function to handle the first time apply with inventory policy

* update dependency

* add unit test for pre processing function
  • Loading branch information
Liujingfang1 committed Feb 1, 2021
1 parent 8a1032f commit 8695bbb
Show file tree
Hide file tree
Showing 8 changed files with 260 additions and 15 deletions.
16 changes: 12 additions & 4 deletions commands/applycmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,15 @@ import (

"github.com/GoogleContainerTools/kpt/internal/util/setters"
"github.com/GoogleContainerTools/kpt/pkg/live"
"github.com/GoogleContainerTools/kpt/pkg/live/preprocess"
"github.com/spf13/cobra"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/klog"
cmdutil "k8s.io/kubectl/pkg/cmd/util"
"sigs.k8s.io/cli-utils/cmd/apply"
"sigs.k8s.io/cli-utils/cmd/flagutils"
"sigs.k8s.io/cli-utils/pkg/common"
"sigs.k8s.io/cli-utils/pkg/inventory"
"sigs.k8s.io/cli-utils/pkg/manifestreader"
"sigs.k8s.io/cli-utils/pkg/provider"
)
Expand All @@ -24,7 +27,7 @@ func GetApplyRunner(provider provider.Provider, loader manifestreader.ManifestLo
applyRunner := apply.GetApplyRunner(provider, loader, ioStreams)
w := &ApplyRunnerWrapper{
applyRunner: applyRunner,
factory: provider.Factory(),
provider: provider,
}
// Set the wrapper run to be the RunE function for the wrapped command.
applyRunner.Command.RunE = w.RunE
Expand All @@ -36,7 +39,7 @@ func GetApplyRunner(provider provider.Provider, loader manifestreader.ManifestLo
// as structures necessary to run.
type ApplyRunnerWrapper struct {
applyRunner *apply.ApplyRunner
factory cmdutil.Factory
provider provider.Provider
}

// Command returns the wrapped ApplyRunner cobraCommand structure.
Expand All @@ -60,11 +63,16 @@ func (w *ApplyRunnerWrapper) PreRunE(_ *cobra.Command, args []string) error {
func (w *ApplyRunnerWrapper) RunE(cmd *cobra.Command, args []string) error {
if _, exists := os.LookupEnv(resourceGroupEnv); exists {
klog.V(4).Infoln("wrapper applyRunner detected environment variable")
err := live.ApplyResourceGroupCRD(w.factory)
err := live.ApplyResourceGroupCRD(w.provider.Factory())
if err != nil && !apierrors.IsAlreadyExists(err) {
return err
}
}
klog.V(4).Infoln("wrapper applyRunner run...")
if w.Command().Flag(flagutils.InventoryPolicyFlag).Value.String() == flagutils.InventoryPolicyStrict {
w.applyRunner.PreProcess = func(inv inventory.InventoryInfo, strategy common.DryRunStrategy) (inventory.InventoryPolicy, error) {
return preprocess.PreProcess(w.provider, inv, strategy)
}
}
return w.applyRunner.RunE(cmd, args)
}
51 changes: 51 additions & 0 deletions commands/destroycmd.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// Copyright 2020 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0

package commands

import (
"github.com/GoogleContainerTools/kpt/pkg/live/preprocess"
"github.com/spf13/cobra"
"k8s.io/cli-runtime/pkg/genericclioptions"
"sigs.k8s.io/cli-utils/cmd/destroy"
"sigs.k8s.io/cli-utils/cmd/flagutils"
"sigs.k8s.io/cli-utils/pkg/common"
"sigs.k8s.io/cli-utils/pkg/inventory"
"sigs.k8s.io/cli-utils/pkg/manifestreader"
"sigs.k8s.io/cli-utils/pkg/provider"
)

// GetDestroyRunner returns a wrapper around the cli-utils destroy command DestroyRunner. Sets
// up the Run on this wrapped runner to be the DestroyRunnerWrapper run.
func GetDestroyRunner(provider provider.Provider, loader manifestreader.ManifestLoader, ioStreams genericclioptions.IOStreams) *DestroyRunnerWrapper {
destroyRunner := destroy.GetDestroyRunner(provider, loader, ioStreams)
w := &DestroyRunnerWrapper{
destroyRunner: destroyRunner,
provider: provider,
}
// Set the wrapper run to be the RunE function for the wrapped command.
destroyRunner.Command.RunE = w.RunE
return w
}

// DestroyRunnerWrapper encapsulates the cli-utils destroy command DestroyRunner as well
// as structures necessary to run.
type DestroyRunnerWrapper struct {
destroyRunner *destroy.DestroyRunner
provider provider.Provider
}

// Command returns the wrapped DestroyRunner cobraCommand structure.
func (w *DestroyRunnerWrapper) Command() *cobra.Command {
return w.destroyRunner.Command
}

// RunE wraps the destroyRunner.RunE with the pre-processing for inventory policy.
func (w *DestroyRunnerWrapper) RunE(cmd *cobra.Command, args []string) error {
if w.Command().Flag(flagutils.InventoryPolicyFlag).Value.String() == flagutils.InventoryPolicyStrict {
w.destroyRunner.PreProcess = func(inv inventory.InventoryInfo, strategy common.DryRunStrategy) (inventory.InventoryPolicy, error) {
return preprocess.PreProcess(w.provider, inv, strategy)
}
}
return w.destroyRunner.RunE(cmd, args)
}
3 changes: 1 addition & 2 deletions commands/livecmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ import (
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/klog"
"k8s.io/kubectl/pkg/cmd/util"
"sigs.k8s.io/cli-utils/cmd/destroy"
"sigs.k8s.io/cli-utils/cmd/diff"
"sigs.k8s.io/cli-utils/cmd/initcmd"
"sigs.k8s.io/cli-utils/cmd/status"
Expand Down Expand Up @@ -99,7 +98,7 @@ func GetLiveCommand(name string, f util.Factory) *cobra.Command {
diffCmd.Long = livedocs.DiffShort + "\n" + livedocs.DiffLong
diffCmd.Example = livedocs.DiffExamples

destroyCmd := destroy.GetDestroyRunner(p, l, ioStreams).Command
destroyCmd := GetDestroyRunner(p, l, ioStreams).Command()
destroyCmd.Short = livedocs.DestroyShort
destroyCmd.Long = livedocs.DestroyShort + "\n" + livedocs.DestroyLong
destroyCmd.Example = livedocs.DestroyExamples
Expand Down
14 changes: 11 additions & 3 deletions commands/previewcmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,13 @@ package commands

import (
"github.com/GoogleContainerTools/kpt/internal/util/setters"
"github.com/GoogleContainerTools/kpt/pkg/live/preprocess"
"github.com/spf13/cobra"
"k8s.io/cli-runtime/pkg/genericclioptions"
cmdutil "k8s.io/kubectl/pkg/cmd/util"
"sigs.k8s.io/cli-utils/cmd/flagutils"
"sigs.k8s.io/cli-utils/cmd/preview"
"sigs.k8s.io/cli-utils/pkg/common"
"sigs.k8s.io/cli-utils/pkg/inventory"
"sigs.k8s.io/cli-utils/pkg/manifestreader"
"sigs.k8s.io/cli-utils/pkg/provider"
)
Expand All @@ -30,7 +33,7 @@ func GetPreviewRunner(provider provider.Provider, loader manifestreader.Manifest
previewRunner := preview.GetPreviewRunner(provider, loader, ioStreams)
w := &PreviewRunnerWrapper{
previewRunner: previewRunner,
factory: provider.Factory(),
provider: provider,
}
// Set the wrapper run to be the RunE function for the wrapped command.
previewRunner.Command.RunE = w.RunE
Expand All @@ -42,7 +45,7 @@ func GetPreviewRunner(provider provider.Provider, loader manifestreader.Manifest
// as structures necessary to run.
type PreviewRunnerWrapper struct {
previewRunner *preview.PreviewRunner
factory cmdutil.Factory
provider provider.Provider
}

// Command returns the wrapped PreviewRunner cobraCommand structure.
Expand All @@ -63,5 +66,10 @@ func (w *PreviewRunnerWrapper) PreRunE(_ *cobra.Command, args []string) error {
// exists in the package path. Then the wrapped PreviewRunner is
// invoked. Returns an error if one happened.
func (w *PreviewRunnerWrapper) RunE(cmd *cobra.Command, args []string) error {
if w.Command().Flag(flagutils.InventoryPolicyFlag).Value.String() == flagutils.InventoryPolicyStrict {
w.previewRunner.PreProcess = func(inv inventory.InventoryInfo, strategy common.DryRunStrategy) (inventory.InventoryPolicy, error) {
return preprocess.PreProcess(w.provider, inv, strategy)
}
}
return w.previewRunner.RunE(cmd, args)
}
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ require (
k8s.io/client-go v0.18.10
k8s.io/klog v1.0.0
k8s.io/kubectl v0.18.10
sigs.k8s.io/cli-utils v0.22.4-0.20210108175429-beb6f88a4384
sigs.k8s.io/cli-utils v0.22.5-0.20210127192708-27cfaa675296
sigs.k8s.io/kustomize/cmd/config v0.8.7-0.20201211170716-cc43a2d732d1
sigs.k8s.io/kustomize/kyaml v0.10.5
sigs.k8s.io/kustomize/kyaml v0.10.6
)
8 changes: 4 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -685,8 +685,8 @@ k8s.io/utils v0.0.0-20200324210504-a9aa75ae1b89 h1:d4vVOjXm687F1iLSP2q3lyPPuyvTU
k8s.io/utils v0.0.0-20200324210504-a9aa75ae1b89/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.7/go.mod h1:PHgbrJT7lCHcxMU+mDHEm+nx46H4zuuHZkDP6icnhu0=
sigs.k8s.io/cli-utils v0.22.4-0.20210108175429-beb6f88a4384 h1:JepLxW87TlpfpA2i7KiN86ei1cS6dbCLAebkSxz7FNw=
sigs.k8s.io/cli-utils v0.22.4-0.20210108175429-beb6f88a4384/go.mod h1:iWbsn/CCh80/SeoecTqkAso1a+ifiHQX0x5PlZ6zjD8=
sigs.k8s.io/cli-utils v0.22.5-0.20210127192708-27cfaa675296 h1:GcA9Uu6GfyLw7X+djhGV2h0bbcouZdVy0NIvlv7LTaU=
sigs.k8s.io/cli-utils v0.22.5-0.20210127192708-27cfaa675296/go.mod h1:J32lRfzTB7eeQQGnXQQThd5BlhmYZuYuAAdFTQ4oSNc=
sigs.k8s.io/controller-runtime v0.6.0 h1:Fzna3DY7c4BIP6KwfSlrfnj20DJ+SeMBK8HSFvOk9NM=
sigs.k8s.io/controller-runtime v0.6.0/go.mod h1:CpYf5pdNY/B352A1TFLAS2JVSlnGQ5O2cftPHndTroo=
sigs.k8s.io/kustomize v2.0.3+incompatible h1:JUufWFNlI44MdtnjUqVnvh29rR37PQFzPbLXqhyOyX0=
Expand All @@ -695,8 +695,8 @@ sigs.k8s.io/kustomize/cmd/config v0.8.7-0.20201211170716-cc43a2d732d1 h1:hn0F38X
sigs.k8s.io/kustomize/cmd/config v0.8.7-0.20201211170716-cc43a2d732d1/go.mod h1:e4PgdLUNnkf+Iapvjyb6gTG9DZQkDZIR6uS1Bv4YA6s=
sigs.k8s.io/kustomize/kyaml v0.10.3 h1:ARSJUMN/c3k31DYxRfZ+vp/UepUQjg9zCwny7Oj908I=
sigs.k8s.io/kustomize/kyaml v0.10.3/go.mod h1:RA+iCHA2wPCOfv6uG6TfXXWhYsHpgErq/AljxWKuxtg=
sigs.k8s.io/kustomize/kyaml v0.10.5 h1:PbJcsZsEM7O3hHtUWTR+4WkHVbQRW9crSy75or1gRbI=
sigs.k8s.io/kustomize/kyaml v0.10.5/go.mod h1:P6Oy/ah/GZMKzJMIJA2a3/bc8YrBkuL5kJji13PSIzY=
sigs.k8s.io/kustomize/kyaml v0.10.6 h1:xUJxc/k8JoWqHUahaB8DTqY0KwEPxTbTGStvW8TOcDc=
sigs.k8s.io/kustomize/kyaml v0.10.6/go.mod h1:K9yg1k/HB/6xNOf5VH3LhTo1DK9/5ykSZO5uIv+Y/1k=
sigs.k8s.io/structured-merge-diff/v3 v3.0.0-20200116222232-67a7b8c61874/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw=
sigs.k8s.io/structured-merge-diff/v3 v3.0.0 h1:dOmIZBMfhcHS09XZkMyUgkq5trg3/jRyJYFZUiaOp8E=
sigs.k8s.io/structured-merge-diff/v3 v3.0.0/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw=
Expand Down
48 changes: 48 additions & 0 deletions pkg/live/preprocess/process.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// Copyright 2021 Google LLC.
// SPDX-License-Identifier: Apache-2.0

package preprocess

import (
"fmt"

apierrors "k8s.io/apimachinery/pkg/api/errors"
"sigs.k8s.io/cli-utils/pkg/common"
"sigs.k8s.io/cli-utils/pkg/inventory"
"sigs.k8s.io/cli-utils/pkg/provider"
)

func PreProcess(p provider.Provider, inv inventory.InventoryInfo, strategy common.DryRunStrategy) (inventory.InventoryPolicy, error) {
invClient, err := p.InventoryClient()
if err != nil {
return inventory.InventoryPolicyMustMatch, err
}
obj, err := invClient.GetClusterInventoryInfo(inv)
if err != nil {
if apierrors.IsNotFound(err) {
return inventory.InventoryPolicyMustMatch, nil
}
return inventory.InventoryPolicyMustMatch, err
}

if obj == nil {
return inventory.InventoryPolicyMustMatch, nil
}

managedByKey := "apps.kubernetes.io/managed-by"
managedByVal := "kpt"
labels := obj.GetLabels()
val, found := labels[managedByKey]
if found {
if val != managedByVal {
return inventory.InventoryPolicyMustMatch, fmt.Errorf("can't apply the current package since it is managed by %s", val)
}
return inventory.InventoryPolicyMustMatch, nil
}
labels[managedByKey] = managedByVal
if strategy.ClientOrServerDryRun() {
return inventory.AdoptIfNoInventory, nil
}
err = invClient.UpdateLabels(inv, labels)
return inventory.AdoptIfNoInventory, err
}
131 changes: 131 additions & 0 deletions pkg/live/preprocess/process_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
// Copyright 2021 Google LLC.
// SPDX-License-Identifier: Apache-2.0

package preprocess

import (
"testing"

"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/kubectl/pkg/cmd/util"
"sigs.k8s.io/cli-utils/pkg/common"
"sigs.k8s.io/cli-utils/pkg/inventory"
"sigs.k8s.io/cli-utils/pkg/object"
)

func TestPreProcess(t *testing.T) {
testcases := []struct {
name string
inventoryObject *unstructured.Unstructured
expected inventory.InventoryPolicy
}{
{
name: "nil cluster inventory object",
inventoryObject: nil,
expected: inventory.InventoryPolicyMustMatch,
},
{
name: "existing cluster inventory object without managed-by label",
inventoryObject: &unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": "v1",
"kind": "ConfigMap",
"metadata": map[string]interface{}{
"name": "test",
"namespace": "test",
"labels": map[string]interface{}{
common.InventoryLabel: "test",
},
},
},
},
expected: inventory.AdoptIfNoInventory,
},
{
name: "existing cluster inventory object with managed-by label",
inventoryObject: &unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": "v1",
"kind": "ConfigMap",
"metadata": map[string]interface{}{
"name": "test",
"namespace": "test",
"labels": map[string]interface{}{
common.InventoryLabel: "test",
"apps.kubernetes.io/managed-by": "kpt",
},
},
},
},
expected: inventory.InventoryPolicyMustMatch,
},
}
for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
invClient := &fakeInventoryClient{inventory: tc.inventoryObject}
p := &fakeProvider{invClient: invClient}
actual, err := PreProcess(p, nil, common.DryRunNone)
if err != nil {
t.Fatalf("unexpected error %v", err)
}
if actual != tc.expected {
t.Fatalf("expected %v but got %v", tc.expected, actual)
}
})
}
}

type fakeProvider struct {
factory util.Factory
invClient inventory.InventoryClient
}

func (p *fakeProvider) Factory() util.Factory {
return p.factory
}

func (p *fakeProvider) InventoryClient() (inventory.InventoryClient, error) {
return p.invClient, nil
}

type fakeInventoryClient struct {
inventory *unstructured.Unstructured
}

func (f *fakeInventoryClient) GetClusterObjs(inv inventory.InventoryInfo) ([]object.ObjMetadata, error) {
return nil, nil
}

func (f *fakeInventoryClient) Merge(inv inventory.InventoryInfo, objs []object.ObjMetadata) ([]object.ObjMetadata, error) {
return nil, nil
}

// Replace replaces the set of objects stored in the inventory
// object with the passed set of objects, or an error if one occurs.
func (f *fakeInventoryClient) Replace(inv inventory.InventoryInfo, objs []object.ObjMetadata) error {
return nil
}

// DeleteInventoryObj deletes the passed inventory object from the APIServer.
func (f *fakeInventoryClient) DeleteInventoryObj(inv inventory.InventoryInfo) error {
return nil
}

// SetDryRunStrategy sets the dry run strategy on whether this we actually mutate.
func (f *fakeInventoryClient) SetDryRunStrategy(drs common.DryRunStrategy) {}

// ApplyInventoryNamespace applies the Namespace that the inventory object should be in.
func (f *fakeInventoryClient) ApplyInventoryNamespace(invNamespace *unstructured.Unstructured) error {
return nil
}

// GetClusterInventoryInfo returns the cluster inventory object.
func (f *fakeInventoryClient) GetClusterInventoryInfo(inv inventory.InventoryInfo) (*unstructured.Unstructured, error) {
return f.inventory, nil
}

// UpdateLabels updates the labels of the cluster inventory object if it exists.
func (f *fakeInventoryClient) UpdateLabels(inv inventory.InventoryInfo, labels map[string]string) error {
f.inventory.SetLabels(labels)
return nil
}

0 comments on commit 8695bbb

Please sign in to comment.