Skip to content

Commit

Permalink
feat: avoid importing kubectl deps when using ApplysetApplier
Browse files Browse the repository at this point in the history
  • Loading branch information
yuwenma committed May 4, 2023
1 parent c3c2bb8 commit b05572d
Show file tree
Hide file tree
Showing 13 changed files with 253 additions and 193 deletions.
1 change: 1 addition & 0 deletions dev/test
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ CGO_ENABLED=0 go test -count=1 -v ./...
popd

CGO_ENABLED=0 go test -count=1 -v ./...
CGO_ENABLED=0 go test -tags without_exec_applier,without_direct_applier -count=1 -v ./...

pushd examples/guestbook-operator
CGO_ENABLED=0 go test -count=1 -v ./...
Expand Down
3 changes: 3 additions & 0 deletions pkg/patterns/declarative/metrics_test.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
//go:build without_exec_applier && without_direct_applier
// +build without_exec_applier,without_direct_applier

/*
Copyright 2020 The Kubernetes Authors.
Expand Down
14 changes: 3 additions & 11 deletions pkg/patterns/declarative/pkg/applier/applylib.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
//go:build without_exec_applier && without_direct_applier
// +build without_exec_applier,without_direct_applier

package applier

import (
Expand All @@ -7,7 +10,6 @@ import (

"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/dynamic"
"k8s.io/klog/v2"
"sigs.k8s.io/kubebuilder-declarative-pattern/applylib/applyset"
Expand Down Expand Up @@ -123,13 +125,3 @@ func (a *ApplySetApplier) Apply(ctx context.Context, opt ApplierOptions) error {

return nil
}

// NewParentRef maps a declarative object's information to the ParentRef defined in the applyset library.
func NewParentRef(restMapper meta.RESTMapper, object runtime.Object, name, namespace string) (applyset.Parent, error) {
gvk := object.GetObjectKind().GroupVersionKind()
restMapping, err := restMapper.RESTMapping(gvk.GroupKind(), gvk.Version)
if err != nil {
return nil, err
}
return applyset.NewParentRef(object, name, namespace, restMapping), nil
}
152 changes: 3 additions & 149 deletions pkg/patterns/declarative/pkg/applier/applylib_test.go
Original file line number Diff line number Diff line change
@@ -1,162 +1,16 @@
//go:build without_exec_applier && without_direct_applier
// +build without_exec_applier,without_direct_applier

package applier

import (
"bytes"
"context"
"io"
"net/http"
"path/filepath"
"testing"

"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/rest"
"k8s.io/klog/v2"
"sigs.k8s.io/controller-runtime/pkg/client/fake"
"sigs.k8s.io/kubebuilder-declarative-pattern/applylib/applyset"
"sigs.k8s.io/kubebuilder-declarative-pattern/mockkubeapiserver"
"sigs.k8s.io/kubebuilder-declarative-pattern/pkg/patterns/declarative/pkg/manifest"
"sigs.k8s.io/kubebuilder-declarative-pattern/pkg/restmapper"
"sigs.k8s.io/kubebuilder-declarative-pattern/pkg/test/httprecorder"
"sigs.k8s.io/kubebuilder-declarative-pattern/pkg/test/testharness"
)

func fakeParent() runtime.Object {
parent := &unstructured.Unstructured{}
parent.SetKind("ConfigMap")
parent.SetAPIVersion("v1")
parent.SetName("test")
parent.SetNamespace("default")
return parent
}

func TestApplySetApplier(t *testing.T) {
patchOptions := metav1.PatchOptions{FieldManager: "kdp-test"}
applier := NewApplySetApplier(patchOptions, metav1.DeleteOptions{}, ApplysetOptions{})
runApplierGoldenTests(t, "testdata/applylib", false, applier)
}

func runApplierGoldenTests(t *testing.T, testDir string, interceptHTTPServer bool, applier Applier) {
testharness.RunGoldenTests(t, testDir, func(h *testharness.Harness, testdir string) {
ctx := context.Background()

k8s, err := mockkubeapiserver.NewMockKubeAPIServer(":0")
if err != nil {
t.Fatalf("error building mock kube-apiserver: %v", err)
}

k8s.RegisterType(schema.GroupVersionKind{Group: "", Version: "v1", Kind: "Namespace"}, "namespaces", meta.RESTScopeRoot)

defer func() {
if err := k8s.Stop(); err != nil {
t.Fatalf("error closing mock kube-apiserver: %v", err)
}
}()

addr, err := k8s.StartServing()
if err != nil {
t.Errorf("error starting mock kube-apiserver: %v", err)
}

klog.Infof("mock kubeapiserver will listen on %v", addr)

var requestLog httprecorder.RequestLog
wrapTransport := func(rt http.RoundTripper) http.RoundTripper {
return httprecorder.NewRecorder(rt, &requestLog)
}
restConfig := &rest.Config{
Host: addr.String(),
WrapTransport: wrapTransport,
}

var apiserverRequestLog httprecorder.RequestLog
if interceptHTTPServer {
k8s.AddHook(&logKubeRequestsHook{log: &apiserverRequestLog})
}

if h.FileExists(filepath.Join(testdir, "before.yaml")) {
before := string(h.MustReadFile(filepath.Join(testdir, "before.yaml")))
if err := k8s.AddObjectsFromManifest(before); err != nil {
t.Fatalf("error precreating objects: %v", err)
}
}
p := filepath.Join(testdir, "manifest.yaml")
manifestYAML := string(h.MustReadFile(p))
objects, err := manifest.ParseObjects(ctx, manifestYAML)
if err != nil {
t.Errorf("error parsing manifest %q: %v", p, err)
}

restMapper, err := restmapper.NewControllerRESTMapper(restConfig)
if err != nil {
t.Fatalf("error from controllerrestmapper.NewControllerRESTMapper: %v", err)
}

parent := fakeParent()
gvk := parent.GetObjectKind().GroupVersionKind()
restmapping, err := restMapper.RESTMapping(gvk.GroupKind(), gvk.Version)
if err != nil {
t.Errorf("error getting restmapping for parent %v", err)
}

client := fake.NewClientBuilder().WithRuntimeObjects(parent).Build()
options := ApplierOptions{
Objects: objects.GetItems(),
RESTConfig: restConfig,
RESTMapper: restMapper,
ParentRef: applyset.NewParentRef(parent, "test", "default", restmapping),
Client: client,
}
if err := applier.Apply(ctx, options); err != nil {
t.Fatalf("error from applier.Apply: %v", err)
}

t.Logf("replacing old url prefix %q", "http://"+restConfig.Host)
requestLog.ReplaceURLPrefix("http://"+restConfig.Host, "http://kube-apiserver")
requestLog.RemoveUserAgent()
requestLog.SortGETs()

requests := requestLog.FormatHTTP()
h.CompareGoldenFile(filepath.Join(testdir, "expected.yaml"), requests)

if interceptHTTPServer {
t.Logf("replacing old url prefix %q", "http://"+restConfig.Host)
apiserverRequestLog.ReplaceURLPrefix("http://"+restConfig.Host, "http://kube-apiserver")
apiserverRequestLog.RemoveUserAgent()
apiserverRequestLog.SortGETs()
apiserverRequestLog.RemoveHeader("Kubectl-Session")
apiserverRequests := apiserverRequestLog.FormatHTTP()
h.CompareGoldenFile(filepath.Join(testdir, "expected-apiserver.yaml"), apiserverRequests)
}
})
}

// logKubeRequestsHook is a hook to record mock-kubeapiserver requests to a RequestLog
type logKubeRequestsHook struct {
log *httprecorder.RequestLog
}

var _ mockkubeapiserver.BeforeHTTPOperation = &logKubeRequestsHook{}

func (h *logKubeRequestsHook) BeforeHTTPOperation(op *mockkubeapiserver.HTTPOperation) {
req := op.Request
entry := &httprecorder.LogEntry{}
entry.Request = httprecorder.Request{
Method: req.Method,
URL: req.URL.String(),
Header: req.Header,
}

if req.Body != nil {
requestBody, err := io.ReadAll(req.Body)
if err != nil {
panic("failed to read request body")
}
entry.Request.Body = string(requestBody)
req.Body = io.NopCloser(bytes.NewReader(requestBody))
}
h.log.AddEntry(entry)
}
6 changes: 4 additions & 2 deletions pkg/patterns/declarative/pkg/applier/direct.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
//go:build !without_exec_applier || !without_direct_applier
// +build !without_exec_applier !without_direct_applier

package applier

import (
Expand All @@ -6,8 +9,6 @@ import (
"os"
"strings"

"k8s.io/kubectl/pkg/util/prune"

"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
utilerrors "k8s.io/apimachinery/pkg/util/errors"
Expand All @@ -20,6 +21,7 @@ import (
"k8s.io/kubectl/pkg/cmd/apply"
cmdDelete "k8s.io/kubectl/pkg/cmd/delete"
cmdutil "k8s.io/kubectl/pkg/cmd/util"
"k8s.io/kubectl/pkg/util/prune"
"sigs.k8s.io/kubebuilder-declarative-pattern/pkg/patterns/declarative/pkg/manifest"
)

Expand Down
3 changes: 3 additions & 0 deletions pkg/patterns/declarative/pkg/applier/exec.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
//go:build without_direct_applier
// +build without_direct_applier

/*
Copyright 2018 The Kubernetes Authors.
Expand Down
16 changes: 16 additions & 0 deletions pkg/patterns/declarative/pkg/applier/global.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
//go:build !without_exec_applier || !without_direct_applier
// +build !without_exec_applier !without_direct_applier

package applier

import (
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/runtime"
"sigs.k8s.io/kubebuilder-declarative-pattern/applylib/applyset"
)

var DefaultApplier = NewDirectApplier()

var NewParentRef = func(meta.RESTMapper, runtime.Object, string, string) (applyset.Parent, error) {
return nil, nil
}
23 changes: 23 additions & 0 deletions pkg/patterns/declarative/pkg/applier/global_without_kubectl.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
//go:build without_exec_applier && without_direct_applier
// +build without_exec_applier,without_direct_applier

package applier

import (
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"sigs.k8s.io/kubebuilder-declarative-pattern/applylib/applyset"
)

var DefaultApplier = NewApplySetApplier(metav1.PatchOptions{}, metav1.DeleteOptions{}, ApplysetOptions{})

// NewParentRef maps a declarative object's information to the ParentRef defined in the applyset library.
func NewParentRef(restMapper meta.RESTMapper, object runtime.Object, name, namespace string) (applyset.Parent, error) {
gvk := object.GetObjectKind().GroupVersionKind()
restMapping, err := restMapper.RESTMapping(gvk.GroupKind(), gvk.Version)
if err != nil {
return nil, err
}
return applyset.NewParentRef(object, name, namespace, restMapping), nil
}
Loading

0 comments on commit b05572d

Please sign in to comment.