add support for declarative deletion

This commit is contained in:
jingfangliu 2019-06-04 16:50:11 -07:00
parent acf35fb267
commit a9fab7c07d
7 changed files with 358 additions and 39 deletions

View File

@ -25,6 +25,7 @@ import (
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/cli-experimental/internal/pkg/client"
"sigs.k8s.io/cli-experimental/internal/pkg/clik8s"
"sigs.k8s.io/cli-experimental/internal/pkg/constants"
"sigs.k8s.io/kustomize/pkg/inventory"
)
@ -67,7 +68,12 @@ func (a *Apply) Do() (Result, error) {
fmt.Fprintf(a.Out, "failed to update inventory object %v\n", err)
}
}
if presence, ok := annotation[constants.Presence]; ok {
if presence == constants.EnsureNoExist {
// not applying the resource
continue
}
}
err := a.DynamicClient.Apply(context.Background(), u)
if err != nil {
fmt.Fprintf(a.Out, "failed to apply the object: %s/%s: %v\n", u.GetKind(), u.GetName(), err)

View File

@ -15,10 +15,16 @@ package apply_test
import (
"bytes"
"context"
"io/ioutil"
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
"gopkg.in/src-d/go-git.v4/plumbing/object"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
"sigs.k8s.io/cli-experimental/internal/pkg/apply"
"sigs.k8s.io/cli-experimental/internal/pkg/clik8s"
"sigs.k8s.io/cli-experimental/internal/pkg/wirecli/wiretest"
@ -59,3 +65,137 @@ func TestApply(t *testing.T) {
assert.NoError(t, err)
assert.Equal(t, apply.Result{updatedObjects}, r)
}
func InitializeKustomizationWithPresence() ([]string, func(), error) {
f1, err := ioutil.TempDir("/tmp", "TestApply")
if err != nil {
return nil, nil, err
}
err = ioutil.WriteFile(filepath.Join(f1, "kustomization.yaml"), []byte(`apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
configMapGenerator:
- name: test-map
inventory:
type: ConfigMap
configMap:
name: inventory
namespace: default
resources:
- not-apply-service.yaml
namespace: default
`), 0644)
if err != nil {
return nil, nil, err
}
err = ioutil.WriteFile(filepath.Join(f1, "not-apply-service.yaml"), []byte(`
apiVersion: v1
kind: Service
metadata:
name: my-service
annotations:
kubectl.kubernetes.io/presence: EnsureDoesNotExist
spec:
selector:
app: MyApp
ports:
- protocol: TCP
port: 80
targetPort: 9376
`), 0644)
if err != nil {
return nil, nil, err
}
f2, err := ioutil.TempDir("/tmp", "TestApply")
if err != nil {
return nil, nil, err
}
err = ioutil.WriteFile(filepath.Join(f2, "kustomization.yaml"), []byte(`apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
configMapGenerator:
- name: test-map
literals:
- foo=bar
inventory:
type: ConfigMap
configMap:
name: inventory
namespace: default
resources:
- not-apply-service.yaml
namespace: default
`), 0644)
if err != nil {
return nil, nil, err
}
err = ioutil.WriteFile(filepath.Join(f2, "not-apply-service.yaml"), []byte(`
apiVersion: v1
kind: Service
metadata:
name: my-service
annotations:
kubectl.kubernetes.io/presence: EnsureDoesNotExist
spec:
selector:
app: MyApp
ports:
- protocol: TCP
port: 80
targetPort: 9376
`), 0644)
if err != nil {
return nil, nil, err
}
return []string{f1, f2}, func() {
os.RemoveAll(f1)
os.RemoveAll(f2)
}, nil
}
func TestApplyWithPresenceAnnotation(t *testing.T) {
buf := new(bytes.Buffer)
kp := wiretest.InitializConfigProvider()
fs, cleanup, err := InitializeKustomizationWithPresence()
defer cleanup()
assert.NoError(t, err)
assert.Equal(t, len(fs), 2)
objects, err := kp.GetConfig(fs[0])
assert.NoError(t, err)
a, done, err := wiretest.InitializeApply(objects, &object.Commit{}, buf)
defer done()
serviceList := &unstructured.UnstructuredList{}
serviceList.SetGroupVersionKind(schema.GroupVersionKind{
Kind: "ServiceList",
Version: "v1",
})
err = a.DynamicClient.List(context.Background(), serviceList, "default", nil)
defaultCount := len(serviceList.Items)
assert.NoError(t, err)
r, err := a.Do()
assert.NoError(t, err)
assert.Equal(t, apply.Result{objects}, r)
err = a.DynamicClient.List(context.Background(), serviceList, "default", nil)
assert.Equal(t, len(serviceList.Items), defaultCount)
updatedObjects, err := kp.GetConfig(fs[1])
a.Resources = updatedObjects
assert.NoError(t, err)
r, err = a.Do()
assert.NoError(t, err)
assert.Equal(t, apply.Result{updatedObjects}, r)
err = a.DynamicClient.List(context.Background(), serviceList, "default", nil)
assert.Equal(t, len(serviceList.Items), defaultCount)
}

View File

@ -0,0 +1,23 @@
/*
Copyright 2019 The Kubernetes Authors.
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 constants
const (
Presence = "kubectl.kubernetes.io/presence"
EnsureExist = "EnsureExist"
EnsureNoExist = "EnsureDoesNotExist"
)

View File

@ -18,10 +18,9 @@ import (
"fmt"
"io"
"os"
"sigs.k8s.io/cli-experimental/internal/pkg/util"
"gopkg.in/src-d/go-git.v4/plumbing/object"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
"sigs.k8s.io/cli-experimental/internal/pkg/client"
@ -65,7 +64,7 @@ func (a *Delete) Do() (Result, error) {
}
}
err := a.deleteObject(ctx, u.GroupVersionKind(), u.GetNamespace(), u.GetName())
_, err := util.DeleteObject(a.DynamicClient, ctx, u.GroupVersionKind(), u.GetNamespace(), u.GetName())
if err != nil {
fmt.Fprint(os.Stderr, err)
}
@ -94,7 +93,7 @@ func (a *Delete) handleInventroy(ctx context.Context, annotations map[string]str
Version: id.Version,
Kind: id.Kind,
}
err = a.deleteObject(ctx, gvk, id.Namespace, id.Name)
_, err = util.DeleteObject(a.DynamicClient, ctx, gvk, id.Namespace, id.Name)
if err != nil {
fmt.Fprint(os.Stderr, err)
}
@ -102,22 +101,6 @@ func (a *Delete) handleInventroy(ctx context.Context, annotations map[string]str
return nil
}
func (a *Delete) deleteObject(ctx context.Context, gvk schema.GroupVersionKind, ns, nm string) error {
obj := &unstructured.Unstructured{}
obj.SetGroupVersionKind(gvk)
obj.SetNamespace(ns)
obj.SetName(nm)
err := a.DynamicClient.Delete(ctx, obj, &metav1.DeleteOptions{})
if err != nil {
if errors.IsNotFound(err) {
return nil
}
return fmt.Errorf("failed to delete %s/%s: %v", gvk.Kind, nm, err)
}
return nil
}
// normalizeResourceOrdering move the inventory object to be the last resource
// This is to make sure the inventory object is the last object to be deleted.
func normalizeResourceOrdering(resources clik8s.ResourceConfigs) []*unstructured.Unstructured {

View File

@ -16,6 +16,9 @@ package delete_test
import (
"bytes"
"context"
"io/ioutil"
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
@ -78,3 +81,132 @@ func TestDelete(t *testing.T) {
assert.NoError(t, err)
assert.Equal(t, len(cmList.Items), 0)
}
func InitializeKustomizationWithPresence() ([]string, func(), error) {
f1, err := ioutil.TempDir("/tmp", "TestApply")
if err != nil {
return nil, nil, err
}
err = ioutil.WriteFile(filepath.Join(f1, "kustomization.yaml"), []byte(`apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
configMapGenerator:
- name: test-map
inventory:
type: ConfigMap
configMap:
name: inventory
namespace: default
resources:
- not-delete-service.yaml
namespace: default
`), 0644)
if err != nil {
return nil, nil, err
}
err = ioutil.WriteFile(filepath.Join(f1, "not-delete-service.yaml"), []byte(`
apiVersion: v1
kind: Service
metadata:
name: my-service
annotations:
kubectl.kubernetes.io/presence: EnsureExist
spec:
selector:
app: MyApp
ports:
- protocol: TCP
port: 80
targetPort: 9376
`), 0644)
if err != nil {
return nil, nil, err
}
f2, err := ioutil.TempDir("/tmp", "TestApply")
if err != nil {
return nil, nil, err
}
err = ioutil.WriteFile(filepath.Join(f2, "kustomization.yaml"), []byte(`apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
configMapGenerator:
- name: test-map
literals:
- foo=bar
inventory:
type: ConfigMap
configMap:
name: inventory
namespace: default
namespace: default
`), 0644)
if err != nil {
return nil, nil, err
}
return []string{f1, f2}, func() {
os.RemoveAll(f1)
os.RemoveAll(f2)
}, nil
}
func TestDeleteWithPresence(t *testing.T) {
buf := new(bytes.Buffer)
kp := wiretest.InitializConfigProvider()
fs, cleanup, err := InitializeKustomizationWithPresence()
defer cleanup()
assert.NoError(t, err)
assert.Equal(t, len(fs), 2)
objects, err := kp.GetConfig(fs[0])
assert.NoError(t, err)
a, donea, err := wiretest.InitializeApply(objects, &object.Commit{}, buf)
assert.NoError(t, err)
defer donea()
serviceList := &unstructured.UnstructuredList{}
serviceList.SetGroupVersionKind(schema.GroupVersionKind{
Kind: "ServiceList",
Version: "v1",
})
err = a.DynamicClient.List(context.Background(), serviceList, "default", nil)
defaultCount := len(serviceList.Items)
_, err = a.Do()
assert.NoError(t, err)
updatedObjects, err := kp.GetConfig(fs[1])
assert.NoError(t, err)
a.Resources = updatedObjects
_, err = a.Do()
assert.NoError(t, err)
cmList := &unstructured.UnstructuredList{}
cmList.SetGroupVersionKind(schema.GroupVersionKind{
Kind: "ConfigMapList",
Version: "v1",
})
err = a.DynamicClient.List(context.Background(), cmList, "default", nil)
assert.NoError(t, err)
assert.Equal(t, len(cmList.Items), 3)
d, doned, err := wiretest.InitializeDelete(updatedObjects, &object.Commit{}, buf)
defer doned()
assert.NoError(t, err)
d.DynamicClient = a.DynamicClient
_, err = d.Do()
assert.NoError(t, err)
err = d.DynamicClient.List(context.Background(), cmList, "default", nil)
assert.NoError(t, err)
assert.Equal(t, len(cmList.Items), 0)
err = a.DynamicClient.List(context.Background(), serviceList, "default", nil)
assert.NoError(t, err)
assert.Equal(t, len(serviceList.Items), defaultCount + 1)
}

View File

@ -18,12 +18,12 @@ import (
"fmt"
"io"
"os"
"sigs.k8s.io/cli-experimental/internal/pkg/util"
"k8s.io/apimachinery/pkg/types"
"gopkg.in/src-d/go-git.v4/plumbing/object"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
"sigs.k8s.io/cli-experimental/internal/pkg/client"
@ -114,7 +114,7 @@ func (o *Prune) runPrune(ctx context.Context, obj *unstructured.Unstructured) (
Version: item.Version,
Kind: item.Kind,
}
u, err := o.deleteObject(ctx, gvk, item.Namespace, item.Name)
u, err := util.DeleteObject(o.DynamicClient, ctx, gvk, item.Namespace, item.Name)
if err != nil {
return nil, nil, err
}
@ -127,19 +127,3 @@ func (o *Prune) runPrune(ctx context.Context, obj *unstructured.Unstructured) (
return obj, results, nil
}
func (o *Prune) deleteObject(ctx context.Context, gvk schema.GroupVersionKind,
ns, nm string) (*unstructured.Unstructured, error) {
obj := &unstructured.Unstructured{}
obj.SetGroupVersionKind(gvk)
obj.SetNamespace(ns)
obj.SetName(nm)
err := o.DynamicClient.Delete(context.Background(), obj, &metav1.DeleteOptions{})
if err != nil {
if errors.IsNotFound(err) {
return nil, nil
}
return nil, fmt.Errorf("failed to delete %s/%s: %v", gvk.Kind, nm, err)
}
return obj, nil
}

View File

@ -0,0 +1,51 @@
package util
import (
"context"
"fmt"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/cli-experimental/internal/pkg/client"
"sigs.k8s.io/cli-experimental/internal/pkg/constants"
)
// DeleteObject delete an object given a client and Group,Version,Kind,Name,Namespace of an object
func DeleteObject(c client.Client, ctx context.Context, gvk schema.GroupVersionKind, ns, nm string) (
*unstructured.Unstructured, error) {
obj := &unstructured.Unstructured{}
obj.SetGroupVersionKind(gvk)
obj.SetNamespace(ns)
obj.SetName(nm)
err := c.Get(ctx, types.NamespacedName{
Namespace: ns,
Name: nm,
}, obj)
if err != nil {
if errors.IsNotFound(err) {
return nil, nil
}
return nil, fmt.Errorf("failed to get %s/%s: %v", gvk.Kind, nm, err)
}
annotations := obj.GetAnnotations()
if presence, ok := annotations[constants.Presence]; ok {
if presence == constants.EnsureExist {
// not delete the resource
return nil, nil
}
}
err = c.Delete(ctx, obj, &metav1.DeleteOptions{})
if err != nil {
if errors.IsNotFound(err) {
return nil, nil
}
return nil, fmt.Errorf("failed to delete %s/%s: %v", gvk.Kind, nm, err)
}
return obj, nil
}