mirror of https://github.com/fluxcd/cli-utils.git
add support for declarative deletion
This commit is contained in:
parent
acf35fb267
commit
a9fab7c07d
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
)
|
||||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
Loading…
Reference in New Issue