Skip to content

Commit e56739c

Browse files
authored
feat: add CreateResource to kubectl (#12174 and #4116) (#516)
* separating kubectl and resource ops mocks Signed-off-by: reggie <[email protected]> * separating kubectl and resource ops mocks Signed-off-by: reggie <[email protected]> * separating kubectl and resource ops mocks Signed-off-by: reggie <[email protected]> * server dry-run for MockKubectlCmd Signed-off-by: reggie <[email protected]> * server dry-run for MockKubectlCmd Signed-off-by: reggie <[email protected]> * server dry-run for MockKubectlCmd Signed-off-by: reggie <[email protected]> * mock create noop Signed-off-by: reggie <[email protected]> * ctl create resource with createOptions Signed-off-by: reggie <[email protected]> --------- Signed-off-by: reggie <[email protected]>
1 parent ad9a694 commit e56739c

File tree

4 files changed

+224
-122
lines changed

4 files changed

+224
-122
lines changed

pkg/sync/sync_context_test.go

Lines changed: 53 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,14 @@ import (
55
"encoding/json"
66
"errors"
77
"fmt"
8-
apierrors "k8s.io/apimachinery/pkg/api/errors"
9-
"k8s.io/apimachinery/pkg/runtime/schema"
108
"net/http"
119
"net/http/httptest"
1210
"reflect"
1311
"testing"
1412

13+
apierrors "k8s.io/apimachinery/pkg/api/errors"
14+
"k8s.io/apimachinery/pkg/runtime/schema"
15+
1516
"github.com/stretchr/testify/assert"
1617
"github.com/stretchr/testify/require"
1718
corev1 "k8s.io/api/core/v1"
@@ -70,11 +71,15 @@ func newTestSyncCtx(getResourceFunc *func(ctx context.Context, config *rest.Conf
7071
return nil
7172
}
7273
mockKubectl := kubetest.MockKubectlCmd{}
74+
75+
sc.kubectl = &mockKubectl
76+
mockResourceOps := kubetest.MockResourceOps{}
77+
sc.resourceOps = &mockResourceOps
7378
if getResourceFunc != nil {
7479
mockKubectl.WithGetResourceFunc(*getResourceFunc)
80+
mockResourceOps.WithGetResourceFunc(*getResourceFunc)
7581
}
76-
sc.kubectl = &mockKubectl
77-
sc.resourceOps = &mockKubectl
82+
7883
for _, opt := range opts {
7984
opt(&sc)
8085
}
@@ -94,8 +99,9 @@ func TestSyncValidate(t *testing.T) {
9499

95100
syncCtx.Sync()
96101

97-
kubectl := syncCtx.kubectl.(*kubetest.MockKubectlCmd)
98-
assert.False(t, kubectl.GetLastValidate())
102+
// kubectl := syncCtx.kubectl.(*kubetest.MockKubectlCmd)
103+
resourceOps, _ := syncCtx.resourceOps.(*kubetest.MockResourceOps)
104+
assert.False(t, resourceOps.GetLastValidate())
99105
}
100106

101107
func TestSyncNotPermittedNamespace(t *testing.T) {
@@ -307,7 +313,15 @@ func TestSyncCreateFailure(t *testing.T) {
307313
},
308314
}
309315
syncCtx.kubectl = mockKubectl
310-
syncCtx.resourceOps = mockKubectl
316+
mockResourceOps := &kubetest.MockResourceOps{
317+
Commands: map[string]kubetest.KubectlOutput{
318+
testSvc.GetName(): {
319+
Output: "",
320+
Err: fmt.Errorf("foo"),
321+
},
322+
},
323+
}
324+
syncCtx.resourceOps = mockResourceOps
311325
syncCtx.resources = groupResources(ReconciliationResult{
312326
Live: []*unstructured.Unstructured{nil},
313327
Target: []*unstructured.Unstructured{testSvc},
@@ -449,7 +463,15 @@ func TestSyncPruneFailure(t *testing.T) {
449463
},
450464
}
451465
syncCtx.kubectl = mockKubectl
452-
syncCtx.resourceOps = mockKubectl
466+
mockResourceOps := kubetest.MockResourceOps{
467+
Commands: map[string]kubetest.KubectlOutput{
468+
"test-service": {
469+
Output: "",
470+
Err: fmt.Errorf("foo"),
471+
},
472+
},
473+
}
474+
syncCtx.resourceOps = &mockResourceOps
453475
testSvc := NewService()
454476
testSvc.SetName("test-service")
455477
testSvc.SetNamespace(FakeArgoCDNamespace)
@@ -718,8 +740,9 @@ func TestSyncOptionValidate(t *testing.T) {
718740

719741
syncCtx.Sync()
720742

721-
kubectl, _ := syncCtx.kubectl.(*kubetest.MockKubectlCmd)
722-
assert.Equal(t, tt.want, kubectl.GetLastValidate())
743+
// kubectl, _ := syncCtx.kubectl.(*kubetest.MockKubectlCmd)
744+
resourceOps, _ := syncCtx.resourceOps.(*kubetest.MockResourceOps)
745+
assert.Equal(t, tt.want, resourceOps.GetLastValidate())
723746
})
724747
}
725748
}
@@ -756,8 +779,9 @@ func TestSync_Replace(t *testing.T) {
756779

757780
syncCtx.Sync()
758781

759-
kubectl, _ := syncCtx.kubectl.(*kubetest.MockKubectlCmd)
760-
assert.Equal(t, tc.commandUsed, kubectl.GetLastResourceCommand(kube.GetResourceKey(tc.target)))
782+
// kubectl, _ := syncCtx.kubectl.(*kubetest.MockKubectlCmd)
783+
resourceOps, _ := syncCtx.resourceOps.(*kubetest.MockResourceOps)
784+
assert.Equal(t, tc.commandUsed, resourceOps.GetLastResourceCommand(kube.GetResourceKey(tc.target)))
761785
})
762786
}
763787
}
@@ -806,10 +830,11 @@ func TestSync_ServerSideApply(t *testing.T) {
806830

807831
syncCtx.Sync()
808832

809-
kubectl, _ := syncCtx.kubectl.(*kubetest.MockKubectlCmd)
810-
assert.Equal(t, tc.commandUsed, kubectl.GetLastResourceCommand(kube.GetResourceKey(tc.target)))
811-
assert.Equal(t, tc.serverSideApply, kubectl.GetLastServerSideApply())
812-
assert.Equal(t, tc.manager, kubectl.GetLastServerSideApplyManager())
833+
// kubectl, _ := syncCtx.kubectl.(*kubetest.MockKubectlCmd)
834+
resourceOps, _ := syncCtx.resourceOps.(*kubetest.MockResourceOps)
835+
assert.Equal(t, tc.commandUsed, resourceOps.GetLastResourceCommand(kube.GetResourceKey(tc.target)))
836+
assert.Equal(t, tc.serverSideApply, resourceOps.GetLastServerSideApply())
837+
assert.Equal(t, tc.manager, resourceOps.GetLastServerSideApplyManager())
813838
})
814839
}
815840
}
@@ -1123,7 +1148,10 @@ func TestSyncFailureHookWithFailedSync(t *testing.T) {
11231148
Commands: map[string]kubetest.KubectlOutput{pod.GetName(): {Err: fmt.Errorf("")}},
11241149
}
11251150
syncCtx.kubectl = mockKubectl
1126-
syncCtx.resourceOps = mockKubectl
1151+
mockResourceOps := kubetest.MockResourceOps{
1152+
Commands: map[string]kubetest.KubectlOutput{pod.GetName(): {Err: fmt.Errorf("")}},
1153+
}
1154+
syncCtx.resourceOps = &mockResourceOps
11271155

11281156
syncCtx.Sync()
11291157
syncCtx.Sync()
@@ -1175,7 +1203,14 @@ func TestRunSyncFailHooksFailed(t *testing.T) {
11751203
failedSyncFailHook.GetName(): {Err: fmt.Errorf("")}},
11761204
}
11771205
syncCtx.kubectl = mockKubectl
1178-
syncCtx.resourceOps = mockKubectl
1206+
mockResourceOps := kubetest.MockResourceOps{
1207+
Commands: map[string]kubetest.KubectlOutput{
1208+
// Fail operation
1209+
pod.GetName(): {Err: fmt.Errorf("")},
1210+
// Fail a single SyncFail hook
1211+
failedSyncFailHook.GetName(): {Err: fmt.Errorf("")}},
1212+
}
1213+
syncCtx.resourceOps = &mockResourceOps
11791214

11801215
syncCtx.Sync()
11811216
syncCtx.Sync()

pkg/utils/kube/ctl.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ type Kubectl interface {
3333
ConvertToVersion(obj *unstructured.Unstructured, group, version string) (*unstructured.Unstructured, error)
3434
DeleteResource(ctx context.Context, config *rest.Config, gvk schema.GroupVersionKind, name string, namespace string, deleteOptions metav1.DeleteOptions) error
3535
GetResource(ctx context.Context, config *rest.Config, gvk schema.GroupVersionKind, name string, namespace string) (*unstructured.Unstructured, error)
36+
CreateResource(ctx context.Context, config *rest.Config, gvk schema.GroupVersionKind, name string, namespace string, obj *unstructured.Unstructured, createOptions metav1.CreateOptions, subresources ...string) (*unstructured.Unstructured, error)
3637
PatchResource(ctx context.Context, config *rest.Config, gvk schema.GroupVersionKind, name string, namespace string, patchType types.PatchType, patchBytes []byte, subresources ...string) (*unstructured.Unstructured, error)
3738
GetAPIResources(config *rest.Config, preferred bool, resourceFilter ResourceFilter) ([]APIResourceInfo, error)
3839
GetServerVersion(config *rest.Config) (string, error)
@@ -208,6 +209,29 @@ func (k *KubectlCmd) GetResource(ctx context.Context, config *rest.Config, gvk s
208209
return resourceIf.Get(ctx, name, metav1.GetOptions{})
209210
}
210211

212+
// CreateResource creates resource
213+
func (k *KubectlCmd) CreateResource(ctx context.Context, config *rest.Config, gvk schema.GroupVersionKind, name string, namespace string, obj *unstructured.Unstructured, createOptions metav1.CreateOptions, subresources ...string) (*unstructured.Unstructured, error) {
214+
span := k.Tracer.StartSpan("CreateResource")
215+
span.SetBaggageItem("kind", gvk.Kind)
216+
span.SetBaggageItem("name", name)
217+
defer span.Finish()
218+
dynamicIf, err := dynamic.NewForConfig(config)
219+
if err != nil {
220+
return nil, err
221+
}
222+
disco, err := discovery.NewDiscoveryClientForConfig(config)
223+
if err != nil {
224+
return nil, err
225+
}
226+
apiResource, err := ServerResourceForGroupVersionKind(disco, gvk, "create")
227+
if err != nil {
228+
return nil, err
229+
}
230+
resource := gvk.GroupVersion().WithResource(apiResource.Name)
231+
resourceIf := ToResourceInterface(dynamicIf, apiResource, resource, namespace)
232+
return resourceIf.Create(ctx, obj, createOptions, subresources...)
233+
}
234+
211235
// PatchResource patches resource
212236
func (k *KubectlCmd) PatchResource(ctx context.Context, config *rest.Config, gvk schema.GroupVersionKind, name string, namespace string, patchType types.PatchType, patchBytes []byte, subresources ...string) (*unstructured.Unstructured, error) {
213237
span := k.Tracer.StartSpan("PatchResource")

pkg/utils/kube/kubetest/mock.go

Lines changed: 3 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package kubetest
22

33
import (
44
"context"
5-
"sync"
65

76
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
87
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
@@ -12,7 +11,6 @@ import (
1211
"k8s.io/apimachinery/pkg/watch"
1312
"k8s.io/client-go/dynamic"
1413
"k8s.io/client-go/rest"
15-
cmdutil "k8s.io/kubectl/pkg/cmd/util"
1614
"k8s.io/kubectl/pkg/util/openapi"
1715

1816
"github.com/argoproj/gitops-engine/pkg/utils/kube"
@@ -30,13 +28,6 @@ type MockKubectlCmd struct {
3028
Version string
3129
DynamicClient dynamic.Interface
3230

33-
lastCommandPerResource map[kube.ResourceKey]string
34-
lastValidate bool
35-
serverSideApply bool
36-
serverSideApplyManager string
37-
38-
recordLock sync.RWMutex
39-
4031
convertToVersionFunc *func(obj *unstructured.Unstructured, group, version string) (*unstructured.Unstructured, error)
4132
getResourceFunc *func(ctx context.Context, config *rest.Config, gvk schema.GroupVersionKind, name string, namespace string) (*unstructured.Unstructured, error)
4233
}
@@ -53,63 +44,6 @@ func (k *MockKubectlCmd) WithGetResourceFunc(getResourcefunc func(context.Contex
5344
return k
5445
}
5546

56-
func (k *MockKubectlCmd) GetLastResourceCommand(key kube.ResourceKey) string {
57-
k.recordLock.Lock()
58-
defer k.recordLock.Unlock()
59-
if k.lastCommandPerResource == nil {
60-
return ""
61-
}
62-
return k.lastCommandPerResource[key]
63-
}
64-
65-
func (k *MockKubectlCmd) SetLastResourceCommand(key kube.ResourceKey, cmd string) {
66-
k.recordLock.Lock()
67-
if k.lastCommandPerResource == nil {
68-
k.lastCommandPerResource = map[kube.ResourceKey]string{}
69-
}
70-
k.lastCommandPerResource[key] = cmd
71-
k.recordLock.Unlock()
72-
}
73-
74-
func (k *MockKubectlCmd) SetLastValidate(validate bool) {
75-
k.recordLock.Lock()
76-
k.lastValidate = validate
77-
k.recordLock.Unlock()
78-
}
79-
80-
func (k *MockKubectlCmd) GetLastValidate() bool {
81-
k.recordLock.RLock()
82-
validate := k.lastValidate
83-
k.recordLock.RUnlock()
84-
return validate
85-
}
86-
87-
func (k *MockKubectlCmd) SetLastServerSideApply(serverSideApply bool) {
88-
k.recordLock.Lock()
89-
k.serverSideApply = serverSideApply
90-
k.recordLock.Unlock()
91-
}
92-
93-
func (k *MockKubectlCmd) SetLastServerSideApplyManager(manager string) {
94-
k.recordLock.Lock()
95-
k.serverSideApplyManager = manager
96-
k.recordLock.Unlock()
97-
}
98-
99-
func (k *MockKubectlCmd) GetLastServerSideApplyManager() string {
100-
k.recordLock.Lock()
101-
manager := k.serverSideApplyManager
102-
k.recordLock.Unlock()
103-
return manager
104-
}
105-
106-
func (k *MockKubectlCmd) GetLastServerSideApply() bool {
107-
k.recordLock.RLock()
108-
serverSideApply := k.serverSideApply
109-
k.recordLock.RUnlock()
110-
return serverSideApply
111-
}
112-
11347
func (k *MockKubectlCmd) NewDynamicClient(config *rest.Config) (dynamic.Interface, error) {
11448
return k.DynamicClient, nil
11549
}
@@ -138,43 +72,8 @@ func (k *MockKubectlCmd) DeleteResource(ctx context.Context, config *rest.Config
13872
return command.Err
13973
}
14074

141-
func (k *MockKubectlCmd) CreateResource(ctx context.Context, obj *unstructured.Unstructured, dryRunStrategy cmdutil.DryRunStrategy, validate bool) (string, error) {
142-
k.SetLastResourceCommand(kube.GetResourceKey(obj), "create")
143-
command, ok := k.Commands[obj.GetName()]
144-
if !ok {
145-
return "", nil
146-
}
147-
return command.Output, command.Err
148-
}
149-
150-
func (k *MockKubectlCmd) UpdateResource(ctx context.Context, obj *unstructured.Unstructured, dryRunStrategy cmdutil.DryRunStrategy) (*unstructured.Unstructured, error) {
151-
k.SetLastResourceCommand(kube.GetResourceKey(obj), "update")
152-
command, ok := k.Commands[obj.GetName()]
153-
if !ok {
154-
return obj, nil
155-
}
156-
return obj, command.Err
157-
}
158-
159-
func (k *MockKubectlCmd) ApplyResource(ctx context.Context, obj *unstructured.Unstructured, dryRunStrategy cmdutil.DryRunStrategy, force, validate, serverSideApply bool, manager string) (string, error) {
160-
k.SetLastValidate(validate)
161-
k.SetLastServerSideApply(serverSideApply)
162-
k.SetLastServerSideApplyManager(manager)
163-
k.SetLastResourceCommand(kube.GetResourceKey(obj), "apply")
164-
command, ok := k.Commands[obj.GetName()]
165-
if !ok {
166-
return "", nil
167-
}
168-
return command.Output, command.Err
169-
}
170-
171-
func (k *MockKubectlCmd) ReplaceResource(ctx context.Context, obj *unstructured.Unstructured, dryRunStrategy cmdutil.DryRunStrategy, force bool) (string, error) {
172-
command, ok := k.Commands[obj.GetName()]
173-
k.SetLastResourceCommand(kube.GetResourceKey(obj), "replace")
174-
if !ok {
175-
return "", nil
176-
}
177-
return command.Output, command.Err
75+
func (k *MockKubectlCmd) CreateResource(ctx context.Context, config *rest.Config, gvk schema.GroupVersionKind, name string, namespace string, obj *unstructured.Unstructured, createOptions metav1.CreateOptions, subresources ...string) (*unstructured.Unstructured, error) {
76+
return nil, nil
17877
}
17978

18079
// ConvertToVersion converts an unstructured object into the specified group/version
@@ -198,7 +97,7 @@ func (k *MockKubectlCmd) SetOnKubectlRun(onKubectlRun kube.OnKubectlRunFunc) {
19897
}
19998

20099
func (k *MockKubectlCmd) ManageResources(config *rest.Config, openAPISchema openapi.Resources) (kube.ResourceOperations, func(), error) {
201-
return k, func() {
100+
return &MockResourceOps{}, func() {
202101

203102
}, nil
204103
}

0 commit comments

Comments
 (0)