Skip to content

Commit

Permalink
Squashed commit of the following: (#1318)
Browse files Browse the repository at this point in the history
commit 3422bf620ace313c5b806986579d498d48404f04
Author: Jingfang Liu <jingfangliu@google.com>
Date:   Mon Dec 28 13:55:32 2020 -0800

    change the order of updating owning-inventory annotation and creating ResourceGorup CR

commit be90e247a8729aa7321bd3b23541894805f26cc4
Author: Jingfang Liu <jingfangliu@google.com>
Date:   Tue Dec 22 16:16:46 2020 -0800

    fix lint error

commit e9c9c3869268424184eb07647c1f47790c468e28
Author: Jingfang Liu <jingfangliu@google.com>
Date:   Tue Dec 22 14:44:52 2020 -0800

    update the last-applied-configuration properly

commit 901528a3ff3b89f4fc163d09d75dc02ac04cf4e0
Author: Jingfang Liu <jingfangliu@google.com>
Date:   Tue Dec 22 14:16:41 2020 -0800

    update owning-inventory annotation during migration
  • Loading branch information
Liujingfang1 committed Jan 6, 2021
1 parent 76810ac commit 8201c64
Show file tree
Hide file tree
Showing 7 changed files with 263 additions and 18 deletions.
45 changes: 43 additions & 2 deletions commands/migratecmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,17 @@ package commands

import (
"bytes"
"context"
"fmt"
"io"
"io/ioutil"
"os"

"github.com/GoogleContainerTools/kpt/pkg/client"
"github.com/GoogleContainerTools/kpt/pkg/live"
"github.com/spf13/cobra"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/klog"
Expand Down Expand Up @@ -139,7 +142,7 @@ func (mr *MigrateRunner) Run(reader io.Reader, args []string) error {
}
if len(cmObjs) > 0 {
// Migrate the ConfigMap inventory objects to a ResourceGroup custom resource.
if err = mr.migrateObjs(cmObjs, bytes.NewReader(stdinBytes), args); err != nil {
if err = mr.migrateObjs(cmObjs, cmInvObj.ID(), bytes.NewReader(stdinBytes), args); err != nil {
return err
}
// Delete the old ConfigMap inventory object.
Expand Down Expand Up @@ -232,7 +235,7 @@ func (mr *MigrateRunner) retrieveInvObjs(invObj inventory.InventoryInfo) ([]obje
// migrateObjs stores the passed objects in the ResourceGroup inventory
// object and applies the inventory object to the cluster. Returns
// an error if one occurred.
func (mr *MigrateRunner) migrateObjs(cmObjs []object.ObjMetadata, reader io.Reader, args []string) error {
func (mr *MigrateRunner) migrateObjs(cmObjs []object.ObjMetadata, oldID string, reader io.Reader, args []string) error {
fmt.Fprint(mr.ioStreams.Out, " migrate inventory to ResourceGroup...")
if len(cmObjs) == 0 {
fmt.Fprintln(mr.ioStreams.Out, "no inventory objects found")
Expand Down Expand Up @@ -260,8 +263,13 @@ func (mr *MigrateRunner) migrateObjs(cmObjs []object.ObjMetadata, reader io.Read
return err
}
inv := live.WrapInventoryInfoObj(rgInv)
err = updateOwningInventoryAnnotation(mr.rgProvider.Factory(), cmObjs, oldID, inv.ID())
if err != nil {
return err
}
_, err = rgInvClient.Merge(inv, cmObjs)
if err != nil {
fmt.Fprintln(mr.ioStreams.Out, "failed", err.Error())
return err
}
fmt.Fprintln(mr.ioStreams.Out, "success")
Expand Down Expand Up @@ -327,3 +335,36 @@ func findResourceGroupInv(objs []*unstructured.Unstructured) (*unstructured.Unst
}
return nil, fmt.Errorf("resource group inventory object not found")
}

func updateOwningInventoryAnnotation(f cmdutil.Factory, objMetas []object.ObjMetadata, old, new string) error {
d, err := f.DynamicClient()
if err != nil {
return err
}
mapper, err := f.ToRESTMapper()
if err != nil {
return err
}
c := client.NewClient(d, mapper)
for _, meta := range objMetas {
obj, err := c.Get(context.TODO(), meta)
if err != nil {
if apierrors.IsNotFound(err) {
continue
}
return err
}
changed, err := client.UpdateAnnotation(obj, old, new)
if err != nil {
return err
}
if !changed {
continue
}
err = c.Update(context.TODO(), meta, obj, &metav1.UpdateOptions{})
if err != nil {
return err
}
}
return nil
}
2 changes: 1 addition & 1 deletion commands/migratecmd_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,7 @@ func TestKptMigrate_migrateObjs(t *testing.T) {
cmLoader := manifestreader.NewManifestLoader(tf)
rgLoader := live.NewResourceGroupManifestLoader(tf)
migrateRunner := GetMigrateRunner(cmProvider, rgProvider, cmLoader, rgLoader, ioStreams)
err := migrateRunner.migrateObjs(tc.objs, strings.NewReader(tc.invObj), []string{})
err := migrateRunner.migrateObjs(tc.objs, "", strings.NewReader(tc.invObj), []string{})
// Check if there should be an error
if tc.isError {
if err == nil {
Expand Down
14 changes: 14 additions & 0 deletions commands/previewcmd.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,17 @@
// Copyright 2019 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package commands

import (
Expand Down
30 changes: 15 additions & 15 deletions e2e/live/end-to-end-test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -427,8 +427,8 @@ assertContains "namespace/test-namespace created (preview)"
assertContains "pod/pod-a created (preview)"
assertContains "pod/pod-b created (preview)"
assertContains "pod/pod-c created (preview)"
assertContains "4 resource(s) applied. 4 created, 0 unchanged, 0 configured"
assertContains "0 resource(s) pruned, 0 skipped"
assertContains "4 resource(s) applied. 4 created, 0 unchanged, 0 configured, 0 failed"
assertContains "0 resource(s) pruned, 0 skipped, 0 failed"
printResult

# Test 3: Basic kpt live apply
Expand All @@ -442,8 +442,8 @@ assertContains "namespace/test-namespace"
assertContains "pod/pod-a created"
assertContains "pod/pod-b created"
assertContains "pod/pod-c created"
assertContains "4 resource(s) applied. 3 created, 1 unchanged, 0 configured"
assertContains "0 resource(s) pruned, 0 skipped"
assertContains "4 resource(s) applied. 3 created, 1 unchanged, 0 configured, 0 failed"
assertContains "0 resource(s) pruned, 0 skipped, 0 failed"
wait 2
# Validate resources in the cluster
# ConfigMap inventory with four inventory items.
Expand All @@ -461,9 +461,9 @@ assertContains "namespace/test-namespace configured (preview)"
assertContains "pod/pod-b configured (preview)"
assertContains "pod/pod-c configured (preview)"
assertContains "pod/pod-d created (preview)"
assertContains "4 resource(s) applied. 1 created, 0 unchanged, 3 configured (preview)"
assertContains "4 resource(s) applied. 1 created, 0 unchanged, 3 configured, 0 failed (preview)"
assertContains "pod/pod-a pruned (preview)"
assertContains "1 resource(s) pruned, 0 skipped (preview)"
assertContains "1 resource(s) pruned, 0 skipped, 0 failed (preview)"
wait 2
# Validate resources in the cluster
# ConfigMap inventory with four inventory items.
Expand All @@ -482,9 +482,9 @@ assertContains "namespace/test-namespace unchanged"
assertContains "pod/pod-b unchanged"
assertContains "pod/pod-c unchanged"
assertContains "pod/pod-d created"
assertContains "4 resource(s) applied. 1 created, 3 unchanged, 0 configured"
assertContains "4 resource(s) applied. 1 created, 3 unchanged, 0 configured, 0 failed"
assertContains "pod/pod-a pruned"
assertContains "1 resource(s) pruned, 0 skipped"
assertContains "1 resource(s) pruned, 0 skipped, 0 failed"
wait 2
# Validate resources in the cluster
# ConfigMap inventory with four inventory items.
Expand Down Expand Up @@ -552,8 +552,8 @@ assertContains "namespace/test-rg-namespace unchanged"
assertContains "pod/pod-a created"
assertContains "pod/pod-b created"
assertContains "pod/pod-c created"
assertContains "4 resource(s) applied. 3 created, 1 unchanged, 0 configured"
assertContains "0 resource(s) pruned, 0 skipped"
assertContains "4 resource(s) applied. 3 created, 1 unchanged, 0 configured, 0 failed"
assertContains "0 resource(s) pruned, 0 skipped, 0 failed"
# Validate resources in the cluster
assertCMInventory "test-rg-namespace" "4"
assertPodExists "pod-a" "test-rg-namespace"
Expand Down Expand Up @@ -613,8 +613,8 @@ assertContains "namespace/test-rg-namespace configured (preview)"
assertContains "pod/pod-a configured (preview)"
assertContains "pod/pod-b configured (preview)"
assertContains "pod/pod-c configured (preview)"
assertContains "4 resource(s) applied. 0 created, 0 unchanged, 4 configured (preview)"
assertContains "0 resource(s) pruned, 0 skipped (preview)"
assertContains "4 resource(s) applied. 0 created, 0 unchanged, 4 configured, 0 failed (preview)"
assertContains "0 resource(s) pruned, 0 skipped, 0 failed (preview)"
# Validate resources in the cluster
assertRGInventory "test-rg-namespace"
assertPodExists "pod-a" "test-rg-namespace"
Expand All @@ -634,8 +634,8 @@ assertContains "pod/pod-a pruned"
assertContains "pod/pod-b unchanged"
assertContains "pod/pod-c unchanged"
assertContains "pod/pod-d created"
assertContains "4 resource(s) applied. 1 created, 3 unchanged, 0 configured"
assertContains "1 resource(s) pruned, 0 skipped"
assertContains "4 resource(s) applied. 1 created, 3 unchanged, 0 configured, 0 failed"
assertContains "1 resource(s) pruned, 0 skipped, 0 failed"
# Validate resources in the cluster
assertRGInventory "test-rg-namespace"
assertPodExists "pod-b" "test-rg-namespace"
Expand Down Expand Up @@ -664,7 +664,7 @@ cp -f e2e/live/testdata/Kptfile e2e/live/testdata/migrate-error
echo "Testing kpt live init for Kptfile (ResourceGroup inventory)"
echo "kpt live init e2e/live/testdata/migrate-error"
${BIN_DIR}/kpt live init e2e/live/testdata/migrate-error > $OUTPUT_DIR/status 2>&1
assertContains "namespace: test-rg-namespace is used for inventory object"
assertContains "namespace: test-namespace-migrate-error is used for inventory object"
assertContains "Initialized: "
assertContains "Kptfile"
# Difference in Kptfile should have inventory data
Expand Down
14 changes: 14 additions & 0 deletions internal/testutil/setup_manager.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,17 @@
// Copyright 2019 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package testutil

import (
Expand Down
103 changes: 103 additions & 0 deletions pkg/client/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
// Copyright 2020 Google LLC.
// SPDX-License-Identifier: Apache-2.0

package client

import (
"context"
"encoding/json"

v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/client-go/dynamic"
"k8s.io/kubectl/pkg/scheme"
"k8s.io/kubectl/pkg/util"
"sigs.k8s.io/cli-utils/pkg/object"
)

// client is the client to update object in the API server.
type client struct {
client dynamic.Interface
restMapper meta.RESTMapper
}

func NewClient(d dynamic.Interface, mapper meta.RESTMapper) *client {
return &client{
client: d,
restMapper: mapper,
}
}

// Update updates an object using dynamic client
func (uc *client) Update(ctx context.Context, meta object.ObjMetadata, obj *unstructured.Unstructured, options *metav1.UpdateOptions) error {
r, err := uc.resourceInterface(meta)
if err != nil {
return err
}
if options == nil {
options = &metav1.UpdateOptions{}
}
_, err = r.Update(ctx, obj, *options)
return err
}

// Get fetches the requested object into the input obj using dynamic client
func (uc *client) Get(ctx context.Context, meta object.ObjMetadata) (*unstructured.Unstructured, error) {
r, err := uc.resourceInterface(meta)
if err != nil {
return nil, err
}
return r.Get(ctx, meta.Name, metav1.GetOptions{})
}

func (uc *client) resourceInterface(meta object.ObjMetadata) (dynamic.ResourceInterface, error) {
mapping, err := uc.restMapper.RESTMapping(meta.GroupKind)
if err != nil {
return nil, err
}
namespacedClient := uc.client.Resource(mapping.Resource).Namespace(meta.Namespace)
return namespacedClient, nil
}

// UpdateAnnotation updates the object owning inventory annotation
// to the new ID when the owning inventory annotation is either empty or the old ID.
// It returns if the annotation is updated.
func UpdateAnnotation(obj *unstructured.Unstructured, oldID, newID string) (bool, error) {
key := "config.k8s.io/owning-inventory"
annotations := obj.GetAnnotations()
if annotations == nil {
annotations = make(map[string]string)
}
val, found := annotations[key]
if !found || val == oldID {
annotations[key] = newID
// Since the annotation is updated, we also need to update the
// last applied configuration annotation.
u := getOriginalObj(obj)
if u != nil {
u.SetAnnotations(annotations)
err := util.CreateOrUpdateAnnotation(false, u, scheme.DefaultJSONEncoder())
obj.SetAnnotations(u.GetAnnotations())
return true, err
}
obj.SetAnnotations(annotations)
return true, nil
}
return false, nil
}

func getOriginalObj(obj *unstructured.Unstructured) *unstructured.Unstructured {
annotations := obj.GetAnnotations()
lastApplied, found := annotations[v1.LastAppliedConfigAnnotation]
if !found {
return nil
}
u := &unstructured.Unstructured{}
err := json.Unmarshal([]byte(lastApplied), u)
if err != nil {
return nil
}
return u
}
73 changes: 73 additions & 0 deletions pkg/client/client_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// Copyright 2020 Google LLC.
// SPDX-License-Identifier: Apache-2.0

package client

import (
"testing"

"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)

func TestUpdateAnnotation(t *testing.T) {
testcases := []struct {
name string
annotations map[string]string
oldID string
newID string
shouldUpdate bool
}{
{
name: "empty owning-inventory is changed to newID",
annotations: nil,
oldID: "old",
newID: "new",
shouldUpdate: true,
},
{
name: "oldID is changed to newID",
annotations: map[string]string{
"config.k8s.io/owning-inventory": "old",
},
oldID: "old",
newID: "new",
shouldUpdate: true,
},
{
name: "non empty unmatched id won't be changed to newID",
annotations: map[string]string{
"config.k8s.io/owning-inventory": "random",
},
oldID: "old",
newID: "new",
shouldUpdate: false,
},
}
deployment := &unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": map[string]interface{}{
"name": "deployment",
"namespace": "test",
},
},
}
for _, tc := range testcases {
deployment.SetAnnotations(tc.annotations)
updated, err := UpdateAnnotation(deployment, tc.oldID, tc.newID)
if err != nil {
t.Errorf("unexpected error %v", err)
}
if tc.shouldUpdate {
if !updated {
t.Errorf("owning-inventory should be updated")
}
if deployment.GetAnnotations()["config.k8s.io/owning-inventory"] != tc.newID {
t.Errorf("the owning-inventory annotation is not correctly updated")
}
} else if updated {
t.Errorf("owning-inventory shouldn't be changed")
}
}
}

0 comments on commit 8201c64

Please sign in to comment.