From 6dbbb7dc1eb7bbf1e2544dc64f2543f9b82bd6a8 Mon Sep 17 00:00:00 2001 From: Lucas Rodriguez Date: Sun, 19 May 2024 22:51:28 -0500 Subject: [PATCH 01/45] works on my machine --- src/internal/agent/hooks/pods.go | 18 +-- src/internal/agent/hooks/pods_test.go | 143 ++++++++++++++++++ src/internal/agent/hooks/test_utils.go | 45 ++++++ .../{admission.go => admission/handler.go} | 24 +-- src/internal/agent/http/server.go | 11 +- 5 files changed, 219 insertions(+), 22 deletions(-) create mode 100644 src/internal/agent/hooks/pods_test.go create mode 100644 src/internal/agent/hooks/test_utils.go rename src/internal/agent/http/{admission.go => admission/handler.go} (80%) diff --git a/src/internal/agent/hooks/pods.go b/src/internal/agent/hooks/pods.go index a7b6911991..8c48383d6d 100644 --- a/src/internal/agent/hooks/pods.go +++ b/src/internal/agent/hooks/pods.go @@ -11,20 +11,24 @@ import ( "github.com/defenseunicorns/zarf/src/config" "github.com/defenseunicorns/zarf/src/config/lang" "github.com/defenseunicorns/zarf/src/internal/agent/operations" - "github.com/defenseunicorns/zarf/src/internal/agent/state" "github.com/defenseunicorns/zarf/src/pkg/message" "github.com/defenseunicorns/zarf/src/pkg/transform" + "github.com/defenseunicorns/zarf/src/types" v1 "k8s.io/api/admission/v1" corev1 "k8s.io/api/core/v1" ) // NewPodMutationHook creates a new instance of pods mutation hook. -func NewPodMutationHook() operations.Hook { +func NewPodMutationHook(zarfState *types.ZarfState) operations.Hook { message.Debug("hooks.NewMutationHook()") return operations.Hook{ - Create: mutatePod, - Update: mutatePod, + Create: func(r *v1.AdmissionRequest) (*operations.Result, error) { + return mutatePod(r, zarfState) + }, + Update: func(r *v1.AdmissionRequest) (*operations.Result, error) { + return mutatePod(r, zarfState) + }, } } @@ -38,7 +42,7 @@ func parsePod(object []byte) (*corev1.Pod, error) { return &pod, nil } -func mutatePod(r *v1.AdmissionRequest) (*operations.Result, error) { +func mutatePod(r *v1.AdmissionRequest, zarfState *types.ZarfState) (*operations.Result, error) { message.Debugf("hooks.mutatePod()(*v1.AdmissionRequest) - %#v , %s/%s: %#v", r.Kind, r.Namespace, r.Name, r.Operation) var patchOperations []operations.PatchOperation @@ -59,10 +63,6 @@ func mutatePod(r *v1.AdmissionRequest) (*operations.Result, error) { zarfSecret := []corev1.LocalObjectReference{{Name: config.ZarfImagePullSecretName}} patchOperations = append(patchOperations, operations.ReplacePatchOperation("/spec/imagePullSecrets", zarfSecret)) - zarfState, err := state.GetZarfStateFromAgentPod() - if err != nil { - return nil, fmt.Errorf(lang.AgentErrGetState, err) - } containerRegistryURL := zarfState.RegistryInfo.Address // update the image host for each init container diff --git a/src/internal/agent/hooks/pods_test.go b/src/internal/agent/hooks/pods_test.go new file mode 100644 index 0000000000..9bd2b027f8 --- /dev/null +++ b/src/internal/agent/hooks/pods_test.go @@ -0,0 +1,143 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2021-Present The Zarf Authors + +package hooks + +import ( + "encoding/json" + "testing" + + "github.com/defenseunicorns/zarf/src/config" + "github.com/defenseunicorns/zarf/src/internal/agent/http/admission" + "github.com/defenseunicorns/zarf/src/internal/agent/operations" + "github.com/defenseunicorns/zarf/src/types" + "github.com/stretchr/testify/require" + v1 "k8s.io/api/admission/v1" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" +) + +// createPodAdmissionRequest creates an admission request for a pod. +func createPodAdmissionRequest(t *testing.T, op v1.Operation, pod *corev1.Pod) *v1.AdmissionRequest { + t.Helper() + raw, err := json.Marshal(pod) + require.NoError(t, err) + return &v1.AdmissionRequest{ + Operation: op, + Object: runtime.RawExtension{ + Raw: raw, + }, + } +} + +// TestPodMutationWebhook tests the pod mutation webhook. +func TestPodMutationWebhook(t *testing.T) { + t.Parallel() + + handler := admission.NewHandler().Serve(NewPodMutationHook(&types.ZarfState{ + RegistryInfo: types.RegistryInfo{ + Address: "127.0.0.1:31999", + }, + })) + + tests := []struct { + name string + admissionReq *v1.AdmissionRequest + expectedPatch []operations.PatchOperation + }{ + { + name: "pod with label should be mutated", + admissionReq: createPodAdmissionRequest(t, v1.Create, &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{"should-be": "mutated"}, + }, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{{Image: "nginx"}}, + InitContainers: []corev1.Container{{Image: "busybox"}}, + EphemeralContainers: []corev1.EphemeralContainer{ + { + EphemeralContainerCommon: corev1.EphemeralContainerCommon{ + Image: "alpine", + }, + }, + }, + }, + }), + expectedPatch: []operations.PatchOperation{ + operations.ReplacePatchOperation( + "/spec/imagePullSecrets", + []corev1.LocalObjectReference{{Name: config.ZarfImagePullSecretName}}, + ), + operations.ReplacePatchOperation( + "/spec/initContainers/0/image", + "127.0.0.1:31999/library/busybox:latest-zarf-2140033595", + ), + operations.ReplacePatchOperation( + "/spec/ephemeralContainers/0/image", + "127.0.0.1:31999/library/alpine:latest-zarf-1117969859", + ), + operations.ReplacePatchOperation( + "/spec/containers/0/image", + "127.0.0.1:31999/library/nginx:latest-zarf-3793515731", + ), + operations.ReplacePatchOperation( + "/metadata/labels/zarf-agent", + "patched", + ), + }, + }, + { + name: "pod with zarf-agent patched label", + admissionReq: createPodAdmissionRequest(t, v1.Create, &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{"zarf-agent": "patched"}, + }, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{{Image: "nginx"}}, + }, + }), + expectedPatch: nil, + }, + { + name: "pod with no labels", + admissionReq: createPodAdmissionRequest(t, v1.Create, &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Labels: nil, + }, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{{Image: "nginx"}}, + }, + }), + expectedPatch: []operations.PatchOperation{ + operations.ReplacePatchOperation( + "/spec/imagePullSecrets", + []corev1.LocalObjectReference{{Name: config.ZarfImagePullSecretName}}, + ), + operations.ReplacePatchOperation( + "/spec/containers/0/image", + "127.0.0.1:31999/library/nginx:latest-zarf-3793515731", + ), + operations.AddPatchOperation( + "/metadata/labels", + map[string]string{"zarf-agent": "patched"}, + ), + }, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + resp := sendAdmissionRequest(t, tt.admissionReq, handler) + if tt.expectedPatch != nil { + expectedPatchJSON, err := json.Marshal(tt.expectedPatch) + require.NoError(t, err) + require.JSONEq(t, string(expectedPatchJSON), string(resp.Patch)) + } else { + require.Empty(t, string(resp.Patch)) + } + }) + } +} diff --git a/src/internal/agent/hooks/test_utils.go b/src/internal/agent/hooks/test_utils.go new file mode 100644 index 0000000000..48ee80e8db --- /dev/null +++ b/src/internal/agent/hooks/test_utils.go @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2021-Present The Zarf Authors + +package hooks + +import ( + "bytes" + "encoding/json" + "net/http" + "net/http/httptest" + "testing" + + "github.com/stretchr/testify/require" + v1 "k8s.io/api/admission/v1" +) + +// sendAdmissionRequest sends an admission request to the handler and returns the response. +func sendAdmissionRequest(t *testing.T, admissionReq *v1.AdmissionRequest, handler http.HandlerFunc) *v1.AdmissionResponse { + t.Helper() + + b, err := json.Marshal(&v1.AdmissionReview{ + Request: admissionReq, + }) + require.NoError(t, err) + + // Note: The URL ("/test") doesn't matter here because we are directly invoking the handler. + // The handler processes the request based on the HTTP method and body content, not the URL path. + req := httptest.NewRequest(http.MethodPost, "/test", bytes.NewReader(b)) + req.Header.Set("Content-Type", "application/json") + + rr := httptest.NewRecorder() + handler.ServeHTTP(rr, req) + + require.Equal(t, http.StatusOK, rr.Code) + + var admissionReview v1.AdmissionReview + err = json.NewDecoder(rr.Body).Decode(&admissionReview) + require.NoError(t, err) + + resp := admissionReview.Response + require.NotNil(t, resp) + require.True(t, resp.Allowed) + + return resp +} diff --git a/src/internal/agent/http/admission.go b/src/internal/agent/http/admission/handler.go similarity index 80% rename from src/internal/agent/http/admission.go rename to src/internal/agent/http/admission/handler.go index fbeeb3b983..b4e3109af7 100644 --- a/src/internal/agent/http/admission.go +++ b/src/internal/agent/http/admission/handler.go @@ -1,8 +1,10 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: 2021-Present The Zarf Authors -// Package http provides a http server for the webhook and proxy. -package http +// Package admission provides an HTTP handler for a Kubernetes admission webhook. +// It includes functionality to decode incoming admission requests, execute +// the corresponding operations, and return appropriate admission responses. +package admission import ( "encoding/json" @@ -19,20 +21,20 @@ import ( "k8s.io/apimachinery/pkg/runtime/serializer" ) -// admissionHandler represents the HTTP handler for an admission webhook. -type admissionHandler struct { +// Handler represents the HTTP handler for an admission webhook. +type Handler struct { decoder runtime.Decoder } -// newAdmissionHandler returns an instance of AdmissionHandler. -func newAdmissionHandler() *admissionHandler { - return &admissionHandler{ +// NewHandler returns a new admission Handler. +func NewHandler() *Handler { + return &Handler{ decoder: serializer.NewCodecFactory(runtime.NewScheme()).UniversalDeserializer(), } } -// Serve returns a http.HandlerFunc for an admission webhook. -func (h *admissionHandler) Serve(hook operations.Hook) http.HandlerFunc { +// Serve returns an http.HandlerFunc for an admission webhook. +func (h *Handler) Serve(hook operations.Hook) http.HandlerFunc { message.Debugf("http.Serve(%#v)", hook) return func(w http.ResponseWriter, r *http.Request) { message.Debugf("http.Serve()(writer, %#v)", r.URL) @@ -67,7 +69,7 @@ func (h *admissionHandler) Serve(hook operations.Hook) http.HandlerFunc { result, err := hook.Execute(review.Request) if err != nil { - message.WarnErr(err, lang.AgentErrBindHandler) + message.Warnf("%s: %s", lang.AgentErrBindHandler, err.Error()) w.WriteHeader(http.StatusInternalServerError) return } @@ -84,7 +86,7 @@ func (h *admissionHandler) Serve(hook operations.Hook) http.HandlerFunc { }, } - // set the patch operations for mutating admission + // Set the patch operations for mutating admission if len(result.PatchOps) > 0 { jsonPatchType := v1.PatchTypeJSONPatch patchBytes, err := json.Marshal(result.PatchOps) diff --git a/src/internal/agent/http/server.go b/src/internal/agent/http/server.go index 86ff5e828f..71f560852c 100644 --- a/src/internal/agent/http/server.go +++ b/src/internal/agent/http/server.go @@ -10,6 +10,8 @@ import ( "time" "github.com/defenseunicorns/zarf/src/internal/agent/hooks" + "github.com/defenseunicorns/zarf/src/internal/agent/http/admission" + "github.com/defenseunicorns/zarf/src/internal/agent/state" "github.com/defenseunicorns/zarf/src/pkg/message" "github.com/prometheus/client_golang/prometheus/promhttp" ) @@ -18,14 +20,19 @@ import ( func NewAdmissionServer(port string) *http.Server { message.Debugf("http.NewServer(%s)", port) + zarfState, err := state.GetZarfStateFromAgentPod() + if err != nil { + message.Fatal(err, err.Error()) + } + // Instances hooks - podsMutation := hooks.NewPodMutationHook() + podsMutation := hooks.NewPodMutationHook(zarfState) fluxGitRepositoryMutation := hooks.NewGitRepositoryMutationHook() argocdApplicationMutation := hooks.NewApplicationMutationHook() argocdRepositoryMutation := hooks.NewRepositoryMutationHook() // Routers - ah := newAdmissionHandler() + ah := admission.NewHandler() mux := http.NewServeMux() mux.Handle("/healthz", healthz()) mux.Handle("/mutate/pod", ah.Serve(podsMutation)) From 13bb0da448be7bf6754860d1688f928d78cc0fa7 Mon Sep 17 00:00:00 2001 From: Lucas Rodriguez Date: Mon, 20 May 2024 10:25:32 -0500 Subject: [PATCH 02/45] move sendAdmissionRequest() utils_test.go --- src/internal/agent/hooks/{test_utils.go => utils_test.go} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/internal/agent/hooks/{test_utils.go => utils_test.go} (100%) diff --git a/src/internal/agent/hooks/test_utils.go b/src/internal/agent/hooks/utils_test.go similarity index 100% rename from src/internal/agent/hooks/test_utils.go rename to src/internal/agent/hooks/utils_test.go From 04eb2929e88a8d55237a0562f632dde44e283c27 Mon Sep 17 00:00:00 2001 From: Lucas Rodriguez Date: Mon, 20 May 2024 11:52:08 -0500 Subject: [PATCH 03/45] load state from cluster --- packages/zarf-agent/manifests/deployment.yaml | 6 ---- src/internal/agent/hooks/pods.go | 29 +++++++++++++------ src/internal/agent/hooks/pods_test.go | 25 +++++++++++++--- src/internal/agent/http/server.go | 9 +++--- 4 files changed, 46 insertions(+), 23 deletions(-) diff --git a/packages/zarf-agent/manifests/deployment.yaml b/packages/zarf-agent/manifests/deployment.yaml index 2d4767ba56..1566916e8c 100644 --- a/packages/zarf-agent/manifests/deployment.yaml +++ b/packages/zarf-agent/manifests/deployment.yaml @@ -42,9 +42,6 @@ spec: - name: tls-certs mountPath: /etc/certs readOnly: true - - name: zarf-state - mountPath: /etc/zarf-state - readOnly: true # Required for OpenShift to mount k9s vendored directories - name: config mountPath: /.config @@ -54,9 +51,6 @@ spec: - name: tls-certs secret: secretName: agent-hook-tls - - name: zarf-state - secret: - secretName: zarf-state # Required for OpenShift to mount k9s vendored directories - name: config emptyDir: {} diff --git a/src/internal/agent/hooks/pods.go b/src/internal/agent/hooks/pods.go index 8c48383d6d..b57667b1ad 100644 --- a/src/internal/agent/hooks/pods.go +++ b/src/internal/agent/hooks/pods.go @@ -5,29 +5,30 @@ package hooks import ( + "context" "encoding/json" "fmt" "github.com/defenseunicorns/zarf/src/config" "github.com/defenseunicorns/zarf/src/config/lang" "github.com/defenseunicorns/zarf/src/internal/agent/operations" + "github.com/defenseunicorns/zarf/src/pkg/cluster" "github.com/defenseunicorns/zarf/src/pkg/message" "github.com/defenseunicorns/zarf/src/pkg/transform" - "github.com/defenseunicorns/zarf/src/types" v1 "k8s.io/api/admission/v1" corev1 "k8s.io/api/core/v1" ) // NewPodMutationHook creates a new instance of pods mutation hook. -func NewPodMutationHook(zarfState *types.ZarfState) operations.Hook { +func NewPodMutationHook(ctx context.Context, cluster *cluster.Cluster) operations.Hook { message.Debug("hooks.NewMutationHook()") return operations.Hook{ Create: func(r *v1.AdmissionRequest) (*operations.Result, error) { - return mutatePod(r, zarfState) + return mutatePod(ctx, r, cluster) }, Update: func(r *v1.AdmissionRequest) (*operations.Result, error) { - return mutatePod(r, zarfState) + return mutatePod(ctx, r, cluster) }, } } @@ -42,10 +43,9 @@ func parsePod(object []byte) (*corev1.Pod, error) { return &pod, nil } -func mutatePod(r *v1.AdmissionRequest, zarfState *types.ZarfState) (*operations.Result, error) { +func mutatePod(ctx context.Context, r *v1.AdmissionRequest, cluster *cluster.Cluster) (*operations.Result, error) { message.Debugf("hooks.mutatePod()(*v1.AdmissionRequest) - %#v , %s/%s: %#v", r.Kind, r.Namespace, r.Name, r.Operation) - var patchOperations []operations.PatchOperation pod, err := parsePod(r.Object.Raw) if err != nil { return &operations.Result{Msg: err.Error()}, nil @@ -55,16 +55,27 @@ func mutatePod(r *v1.AdmissionRequest, zarfState *types.ZarfState) (*operations. // We've already played with this pod, just keep swimming 🐟 return &operations.Result{ Allowed: true, - PatchOps: patchOperations, + PatchOps: []operations.PatchOperation{}, }, nil } + state, err := cluster.LoadZarfState(ctx) + if err != nil { + message.Debugf("failed to load Zarf state: %s", err.Error()) + return &operations.Result{ + Allowed: true, + PatchOps: []operations.PatchOperation{}, + }, nil + } + + containerRegistryURL := state.RegistryInfo.Address + + var patchOperations []operations.PatchOperation + // Add the zarf secret to the podspec zarfSecret := []corev1.LocalObjectReference{{Name: config.ZarfImagePullSecretName}} patchOperations = append(patchOperations, operations.ReplacePatchOperation("/spec/imagePullSecrets", zarfSecret)) - containerRegistryURL := zarfState.RegistryInfo.Address - // update the image host for each init container for idx, container := range pod.Spec.InitContainers { path := fmt.Sprintf("/spec/initContainers/%d/image", idx) diff --git a/src/internal/agent/hooks/pods_test.go b/src/internal/agent/hooks/pods_test.go index 9bd2b027f8..71f5ccfbd2 100644 --- a/src/internal/agent/hooks/pods_test.go +++ b/src/internal/agent/hooks/pods_test.go @@ -4,18 +4,22 @@ package hooks import ( + "context" "encoding/json" "testing" "github.com/defenseunicorns/zarf/src/config" "github.com/defenseunicorns/zarf/src/internal/agent/http/admission" "github.com/defenseunicorns/zarf/src/internal/agent/operations" + "github.com/defenseunicorns/zarf/src/pkg/cluster" + "github.com/defenseunicorns/zarf/src/pkg/k8s" "github.com/defenseunicorns/zarf/src/types" "github.com/stretchr/testify/require" v1 "k8s.io/api/admission/v1" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/kubernetes/fake" ) // createPodAdmissionRequest creates an admission request for a pod. @@ -35,11 +39,24 @@ func createPodAdmissionRequest(t *testing.T, op v1.Operation, pod *corev1.Pod) * func TestPodMutationWebhook(t *testing.T) { t.Parallel() - handler := admission.NewHandler().Serve(NewPodMutationHook(&types.ZarfState{ - RegistryInfo: types.RegistryInfo{ - Address: "127.0.0.1:31999", + ctx := context.Background() + + c := &cluster.Cluster{K8s: &k8s.K8s{Clientset: fake.NewSimpleClientset()}} + handler := admission.NewHandler().Serve(NewPodMutationHook(ctx, c)) + + state, err := json.Marshal(&types.ZarfState{RegistryInfo: types.RegistryInfo{Address: "127.0.0.1:31999"}}) + require.NoError(t, err) + + secret := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: cluster.ZarfStateSecretName, + Namespace: "zarf", + }, + Data: map[string][]byte{ + cluster.ZarfStateDataKey: state, }, - })) + } + c.Clientset.CoreV1().Secrets("zarf").Create(ctx, secret, metav1.CreateOptions{}) tests := []struct { name string diff --git a/src/internal/agent/http/server.go b/src/internal/agent/http/server.go index 71f560852c..bc44f1fdf2 100644 --- a/src/internal/agent/http/server.go +++ b/src/internal/agent/http/server.go @@ -5,13 +5,14 @@ package http import ( + "context" "fmt" "net/http" "time" "github.com/defenseunicorns/zarf/src/internal/agent/hooks" "github.com/defenseunicorns/zarf/src/internal/agent/http/admission" - "github.com/defenseunicorns/zarf/src/internal/agent/state" + "github.com/defenseunicorns/zarf/src/pkg/cluster" "github.com/defenseunicorns/zarf/src/pkg/message" "github.com/prometheus/client_golang/prometheus/promhttp" ) @@ -20,13 +21,13 @@ import ( func NewAdmissionServer(port string) *http.Server { message.Debugf("http.NewServer(%s)", port) - zarfState, err := state.GetZarfStateFromAgentPod() + c, err := cluster.NewCluster() if err != nil { - message.Fatal(err, err.Error()) + message.Fatalf(err, err.Error()) } // Instances hooks - podsMutation := hooks.NewPodMutationHook(zarfState) + podsMutation := hooks.NewPodMutationHook(context.Background(), c) fluxGitRepositoryMutation := hooks.NewGitRepositoryMutationHook() argocdApplicationMutation := hooks.NewApplicationMutationHook() argocdRepositoryMutation := hooks.NewRepositoryMutationHook() From c287ca221a8be6754b7fdf984eda9af9581941be Mon Sep 17 00:00:00 2001 From: Austin Abro Date: Mon, 20 May 2024 17:00:11 +0000 Subject: [PATCH 04/45] adding clusterrole to agent --- packages/zarf-agent/manifests/clusterrole.yaml | 13 +++++++++++++ .../zarf-agent/manifests/clusterrolebinding.yaml | 12 ++++++++++++ packages/zarf-agent/manifests/serviceaccount.yaml | 5 +++++ packages/zarf-agent/zarf.yaml | 3 +++ 4 files changed, 33 insertions(+) create mode 100644 packages/zarf-agent/manifests/clusterrole.yaml create mode 100644 packages/zarf-agent/manifests/clusterrolebinding.yaml create mode 100644 packages/zarf-agent/manifests/serviceaccount.yaml diff --git a/packages/zarf-agent/manifests/clusterrole.yaml b/packages/zarf-agent/manifests/clusterrole.yaml new file mode 100644 index 0000000000..c29641bbbf --- /dev/null +++ b/packages/zarf-agent/manifests/clusterrole.yaml @@ -0,0 +1,13 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: zarf-agent +rules: +- apiGroups: + - "" + resources: + - services + - secrets + verbs: + - get + - list diff --git a/packages/zarf-agent/manifests/clusterrolebinding.yaml b/packages/zarf-agent/manifests/clusterrolebinding.yaml new file mode 100644 index 0000000000..e44d3dd946 --- /dev/null +++ b/packages/zarf-agent/manifests/clusterrolebinding.yaml @@ -0,0 +1,12 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: zarf-agent-binding +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: zarf-agent +subjects: +- kind: ServiceAccount + name: zarf + namespace: zarf diff --git a/packages/zarf-agent/manifests/serviceaccount.yaml b/packages/zarf-agent/manifests/serviceaccount.yaml new file mode 100644 index 0000000000..2f9b060094 --- /dev/null +++ b/packages/zarf-agent/manifests/serviceaccount.yaml @@ -0,0 +1,5 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: zarf + namespace: zarf diff --git a/packages/zarf-agent/zarf.yaml b/packages/zarf-agent/zarf.yaml index 86799ff03f..4f4edff6d0 100644 --- a/packages/zarf-agent/zarf.yaml +++ b/packages/zarf-agent/zarf.yaml @@ -27,6 +27,9 @@ components: - manifests/secret.yaml - manifests/deployment.yaml - manifests/webhook.yaml + - manifests/clusterrole.yaml + - manifests/clusterrolebinding.yaml + - manifests/serviceaccount.yaml actions: onCreate: before: From bcad43ed382b2f691f64d4af3bb30145c7fd1c03 Mon Sep 17 00:00:00 2001 From: Austin Abro Date: Mon, 20 May 2024 17:20:55 +0000 Subject: [PATCH 05/45] WIP flux tests --- src/config/lang/english.go | 2 +- src/internal/agent/hooks/flux.go | 29 +++++--- src/internal/agent/hooks/flux_test.go | 99 +++++++++++++++++++++++++++ src/internal/agent/http/server.go | 2 +- 4 files changed, 119 insertions(+), 13 deletions(-) create mode 100644 src/internal/agent/hooks/flux_test.go diff --git a/src/config/lang/english.go b/src/config/lang/english.go index b72ffa1461..c5deeffc0c 100644 --- a/src/config/lang/english.go +++ b/src/config/lang/english.go @@ -664,7 +664,7 @@ const ( AgentErrBadRequest = "could not read request body: %s" AgentErrBindHandler = "Unable to bind the webhook handler" AgentErrCouldNotDeserializeReq = "could not deserialize request: %s" - AgentErrGetState = "failed to load zarf state from file: %w" + AgentErrGetState = "failed to load zarf state: %w" AgentErrHostnameMatch = "failed to complete hostname matching: %w" AgentErrImageSwap = "Unable to swap the host for (%s)" AgentErrInvalidMethod = "invalid method only POST requests are allowed" diff --git a/src/internal/agent/hooks/flux.go b/src/internal/agent/hooks/flux.go index c4efce1846..d548447fbf 100644 --- a/src/internal/agent/hooks/flux.go +++ b/src/internal/agent/hooks/flux.go @@ -5,6 +5,7 @@ package hooks import ( + "context" "encoding/json" "fmt" @@ -12,10 +13,9 @@ import ( "github.com/defenseunicorns/zarf/src/config" "github.com/defenseunicorns/zarf/src/config/lang" "github.com/defenseunicorns/zarf/src/internal/agent/operations" - "github.com/defenseunicorns/zarf/src/internal/agent/state" + "github.com/defenseunicorns/zarf/src/pkg/cluster" "github.com/defenseunicorns/zarf/src/pkg/message" "github.com/defenseunicorns/zarf/src/pkg/transform" - "github.com/defenseunicorns/zarf/src/types" v1 "k8s.io/api/admission/v1" ) @@ -33,19 +33,22 @@ type GenericGitRepo struct { } // NewGitRepositoryMutationHook creates a new instance of the git repo mutation hook. -func NewGitRepositoryMutationHook() operations.Hook { +func NewGitRepositoryMutationHook(ctx context.Context, cluster *cluster.Cluster) operations.Hook { message.Debug("hooks.NewGitRepositoryMutationHook()") return operations.Hook{ - Create: mutateGitRepo, - Update: mutateGitRepo, + Create: func(r *v1.AdmissionRequest) (*operations.Result, error) { + return mutateGitRepo(ctx, r, cluster) + }, + Update: func(r *v1.AdmissionRequest) (*operations.Result, error) { + return mutateGitRepo(ctx, r, cluster) + }, } } // mutateGitRepoCreate mutates the git repository url to point to the repository URL defined in the ZarfState. -func mutateGitRepo(r *v1.AdmissionRequest) (result *operations.Result, err error) { +func mutateGitRepo(ctx context.Context, r *v1.AdmissionRequest, cluster *cluster.Cluster) (result *operations.Result, err error) { var ( - zarfState *types.ZarfState patches []operations.PatchOperation isPatched bool @@ -54,11 +57,13 @@ func mutateGitRepo(r *v1.AdmissionRequest) (result *operations.Result, err error ) // Form the zarfState.GitServer.Address from the zarfState - if zarfState, err = state.GetZarfStateFromAgentPod(); err != nil { + + state, err := cluster.LoadZarfState(ctx) + if err != nil { return nil, fmt.Errorf(lang.AgentErrGetState, err) } - message.Debugf("Using the url of (%s) to mutate the flux repository", zarfState.GitServer.Address) + message.Debugf("Using the url of (%s) to mutate the flux repository", state.GitServer.Address) // parse to simple struct to read the git url src := &GenericGitRepo{} @@ -71,7 +76,7 @@ func mutateGitRepo(r *v1.AdmissionRequest) (result *operations.Result, err error // NOTE: We mutate on updates IF AND ONLY IF the hostname in the request is different than the hostname in the zarfState // NOTE: We are checking if the hostname is different before because we do not want to potentially mutate a URL that has already been mutated. if isUpdate { - isPatched, err = helpers.DoHostnamesMatch(zarfState.GitServer.Address, src.Spec.URL) + isPatched, err = helpers.DoHostnamesMatch(state.GitServer.Address, src.Spec.URL) if err != nil { return nil, fmt.Errorf(lang.AgentErrHostnameMatch, err) } @@ -80,7 +85,7 @@ func mutateGitRepo(r *v1.AdmissionRequest) (result *operations.Result, err error // Mutate the git URL if necessary if isCreate || (isUpdate && !isPatched) { // Mutate the git URL so that the hostname matches the hostname in the Zarf state - transformedURL, err := transform.GitURL(zarfState.GitServer.Address, patchedURL, zarfState.GitServer.PushUsername) + transformedURL, err := transform.GitURL(state.GitServer.Address, patchedURL, state.GitServer.PushUsername) if err != nil { message.Warnf("Unable to transform the git url, using the original url we have: %s", patchedURL) } @@ -102,6 +107,8 @@ func populatePatchOperations(repoURL string, secretName string) []operations.Pat var patches []operations.PatchOperation patches = append(patches, operations.ReplacePatchOperation("/spec/url", repoURL)) + //TODO This logic can be simplified + // If a prior secret exists, replace it if secretName != "" { patches = append(patches, operations.ReplacePatchOperation("/spec/secretRef/name", config.ZarfGitServerSecretName)) diff --git a/src/internal/agent/hooks/flux_test.go b/src/internal/agent/hooks/flux_test.go new file mode 100644 index 0000000000..064b02360f --- /dev/null +++ b/src/internal/agent/hooks/flux_test.go @@ -0,0 +1,99 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2021-Present The Zarf Authors + +package hooks + +import ( + "context" + "encoding/json" + "testing" + + "github.com/defenseunicorns/zarf/src/internal/agent/http/admission" + "github.com/defenseunicorns/zarf/src/internal/agent/operations" + "github.com/defenseunicorns/zarf/src/pkg/cluster" + "github.com/defenseunicorns/zarf/src/pkg/k8s" + "github.com/defenseunicorns/zarf/src/types" + flux "github.com/fluxcd/source-controller/api/v1" + "github.com/stretchr/testify/require" + v1 "k8s.io/api/admission/v1" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/kubernetes/fake" +) + +// createPodAdmissionRequest creates an admission request for a pod. +func createFluxAdmissionRequest(t *testing.T, op v1.Operation, fluxGitRepo *flux.GitRepository) *v1.AdmissionRequest { + t.Helper() + raw, err := json.Marshal(fluxGitRepo) + require.NoError(t, err) + return &v1.AdmissionRequest{ + Operation: op, + Object: runtime.RawExtension{ + Raw: raw, + }, + } +} + +func TestFluxMutationWebhook(t *testing.T) { + t.Parallel() + + ctx := context.Background() + + c := &cluster.Cluster{K8s: &k8s.K8s{Clientset: fake.NewSimpleClientset()}} + handler := admission.NewHandler().Serve(NewGitRepositoryMutationHook(ctx, c)) + + state, err := json.Marshal(&types.ZarfState{GitServer: types.GitServerInfo{ + Address: "https://git-server.com", + PushUsername: "a-push-user", + }}) + require.NoError(t, err) + + secret := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: cluster.ZarfStateSecretName, + Namespace: cluster.ZarfNamespaceName, + }, + Data: map[string][]byte{ + cluster.ZarfStateDataKey: state, + }, + } + c.Clientset.CoreV1().Secrets(cluster.ZarfNamespaceName).Create(ctx, secret, metav1.CreateOptions{}) + + tests := []struct { + name string + admissionReq *v1.AdmissionRequest + expectedPatch []operations.PatchOperation + }{ + { + name: "flux object should be mutated", + admissionReq: createFluxAdmissionRequest(t, v1.Create, &flux.GitRepository{ + ObjectMeta: metav1.ObjectMeta{ + Name: "mutate-this", + }, + Spec: flux.GitRepositorySpec{ + URL: "https://github.com/stefanprodan/podinfo.git", + Reference: &flux.GitRepositoryRef{ + Tag: "6.4.0", + }, + }, + }), + expectedPatch: nil, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + resp := sendAdmissionRequest(t, tt.admissionReq, handler) + if tt.expectedPatch != nil { + expectedPatchJSON, err := json.Marshal(tt.expectedPatch) + require.NoError(t, err) + require.JSONEq(t, string(expectedPatchJSON), string(resp.Patch)) + } else { + require.Empty(t, string(resp.Patch)) + } + }) + } +} diff --git a/src/internal/agent/http/server.go b/src/internal/agent/http/server.go index bc44f1fdf2..2f25477c83 100644 --- a/src/internal/agent/http/server.go +++ b/src/internal/agent/http/server.go @@ -28,7 +28,7 @@ func NewAdmissionServer(port string) *http.Server { // Instances hooks podsMutation := hooks.NewPodMutationHook(context.Background(), c) - fluxGitRepositoryMutation := hooks.NewGitRepositoryMutationHook() + fluxGitRepositoryMutation := hooks.NewGitRepositoryMutationHook(context.Background(), c) argocdApplicationMutation := hooks.NewApplicationMutationHook() argocdRepositoryMutation := hooks.NewRepositoryMutationHook() From ddb08eb38329d02330b87a03c7b52659cdcf2bc9 Mon Sep 17 00:00:00 2001 From: Lucas Rodriguez Date: Mon, 20 May 2024 14:16:28 -0500 Subject: [PATCH 06/45] WIP flux --- src/internal/agent/hooks/flux.go | 4 +-- src/internal/agent/hooks/flux_test.go | 34 +++++++++++++++++++++++--- src/internal/agent/hooks/pods_test.go | 7 +++++- src/internal/agent/hooks/utils_test.go | 4 +-- 4 files changed, 40 insertions(+), 9 deletions(-) diff --git a/src/internal/agent/hooks/flux.go b/src/internal/agent/hooks/flux.go index d548447fbf..91f0d41a85 100644 --- a/src/internal/agent/hooks/flux.go +++ b/src/internal/agent/hooks/flux.go @@ -56,8 +56,6 @@ func mutateGitRepo(ctx context.Context, r *v1.AdmissionRequest, cluster *cluster isUpdate = r.Operation == v1.Update ) - // Form the zarfState.GitServer.Address from the zarfState - state, err := cluster.LoadZarfState(ctx) if err != nil { return nil, fmt.Errorf(lang.AgentErrGetState, err) @@ -87,7 +85,7 @@ func mutateGitRepo(ctx context.Context, r *v1.AdmissionRequest, cluster *cluster // Mutate the git URL so that the hostname matches the hostname in the Zarf state transformedURL, err := transform.GitURL(state.GitServer.Address, patchedURL, state.GitServer.PushUsername) if err != nil { - message.Warnf("Unable to transform the git url, using the original url we have: %s", patchedURL) + return nil, fmt.Errorf("unable to transform the git url: %w", err) } patchedURL = transformedURL.String() message.Debugf("original git URL of (%s) got mutated to (%s)", src.Spec.URL, patchedURL) diff --git a/src/internal/agent/hooks/flux_test.go b/src/internal/agent/hooks/flux_test.go index 064b02360f..2cce555a61 100644 --- a/src/internal/agent/hooks/flux_test.go +++ b/src/internal/agent/hooks/flux_test.go @@ -6,6 +6,7 @@ package hooks import ( "context" "encoding/json" + "net/http" "testing" "github.com/defenseunicorns/zarf/src/internal/agent/http/admission" @@ -22,7 +23,7 @@ import ( "k8s.io/client-go/kubernetes/fake" ) -// createPodAdmissionRequest creates an admission request for a pod. +// createFluxAdmissionRequest creates an admission request for a pod. func createFluxAdmissionRequest(t *testing.T, op v1.Operation, fluxGitRepo *flux.GitRepository) *v1.AdmissionRequest { t.Helper() raw, err := json.Marshal(fluxGitRepo) @@ -64,9 +65,10 @@ func TestFluxMutationWebhook(t *testing.T) { name string admissionReq *v1.AdmissionRequest expectedPatch []operations.PatchOperation + code int }{ { - name: "flux object should be mutated", + name: "flux object should be mutated on Create", admissionReq: createFluxAdmissionRequest(t, v1.Create, &flux.GitRepository{ ObjectMeta: metav1.ObjectMeta{ Name: "mutate-this", @@ -78,7 +80,33 @@ func TestFluxMutationWebhook(t *testing.T) { }, }, }), + expectedPatch: []operations.PatchOperation{ + operations.ReplacePatchOperation( + "/spec/url", + "https://git-server.com/a-push-user/podinfo-1646971829.git", + ), + operations.AddPatchOperation( + "/spec/secretRef", + map[string]string{"name": "private-git-server"}, + ), + }, + code: http.StatusOK, + }, + { + name: "flux object should be mutated on Update", + admissionReq: createFluxAdmissionRequest(t, v1.Update, &flux.GitRepository{ + ObjectMeta: metav1.ObjectMeta{ + Name: "mutate-this", + }, + Spec: flux.GitRepositorySpec{ + URL: "not-a-git-url", + Reference: &flux.GitRepositoryRef{ + Tag: "6.4.0", + }, + }, + }), expectedPatch: nil, + code: http.StatusInternalServerError, }, } @@ -86,7 +114,7 @@ func TestFluxMutationWebhook(t *testing.T) { tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() - resp := sendAdmissionRequest(t, tt.admissionReq, handler) + resp := sendAdmissionRequest(t, tt.admissionReq, handler, tt.code) if tt.expectedPatch != nil { expectedPatchJSON, err := json.Marshal(tt.expectedPatch) require.NoError(t, err) diff --git a/src/internal/agent/hooks/pods_test.go b/src/internal/agent/hooks/pods_test.go index 71f5ccfbd2..2cfe318e2d 100644 --- a/src/internal/agent/hooks/pods_test.go +++ b/src/internal/agent/hooks/pods_test.go @@ -6,6 +6,7 @@ package hooks import ( "context" "encoding/json" + "net/http" "testing" "github.com/defenseunicorns/zarf/src/config" @@ -62,6 +63,7 @@ func TestPodMutationWebhook(t *testing.T) { name string admissionReq *v1.AdmissionRequest expectedPatch []operations.PatchOperation + code int }{ { name: "pod with label should be mutated", @@ -103,6 +105,7 @@ func TestPodMutationWebhook(t *testing.T) { "patched", ), }, + code: http.StatusOK, }, { name: "pod with zarf-agent patched label", @@ -115,6 +118,7 @@ func TestPodMutationWebhook(t *testing.T) { }, }), expectedPatch: nil, + code: http.StatusOK, }, { name: "pod with no labels", @@ -140,6 +144,7 @@ func TestPodMutationWebhook(t *testing.T) { map[string]string{"zarf-agent": "patched"}, ), }, + code: http.StatusOK, }, } @@ -147,7 +152,7 @@ func TestPodMutationWebhook(t *testing.T) { tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() - resp := sendAdmissionRequest(t, tt.admissionReq, handler) + resp := sendAdmissionRequest(t, tt.admissionReq, handler, tt.code) if tt.expectedPatch != nil { expectedPatchJSON, err := json.Marshal(tt.expectedPatch) require.NoError(t, err) diff --git a/src/internal/agent/hooks/utils_test.go b/src/internal/agent/hooks/utils_test.go index 48ee80e8db..6253a7e59d 100644 --- a/src/internal/agent/hooks/utils_test.go +++ b/src/internal/agent/hooks/utils_test.go @@ -15,7 +15,7 @@ import ( ) // sendAdmissionRequest sends an admission request to the handler and returns the response. -func sendAdmissionRequest(t *testing.T, admissionReq *v1.AdmissionRequest, handler http.HandlerFunc) *v1.AdmissionResponse { +func sendAdmissionRequest(t *testing.T, admissionReq *v1.AdmissionRequest, handler http.HandlerFunc, code int) *v1.AdmissionResponse { t.Helper() b, err := json.Marshal(&v1.AdmissionReview{ @@ -31,7 +31,7 @@ func sendAdmissionRequest(t *testing.T, admissionReq *v1.AdmissionRequest, handl rr := httptest.NewRecorder() handler.ServeHTTP(rr, req) - require.Equal(t, http.StatusOK, rr.Code) + require.Equal(t, code, rr.Code) var admissionReview v1.AdmissionReview err = json.NewDecoder(rr.Body).Decode(&admissionReview) From 0070938af6dd5598aad7a56c2ff40573643182e7 Mon Sep 17 00:00:00 2001 From: Austin Abro Date: Mon, 20 May 2024 19:55:45 +0000 Subject: [PATCH 07/45] tests passing --- src/internal/agent/hooks/flux_test.go | 2 +- src/internal/agent/hooks/pods_test.go | 10 +++++++++- src/internal/agent/hooks/utils_test.go | 12 +++++------- 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/src/internal/agent/hooks/flux_test.go b/src/internal/agent/hooks/flux_test.go index 2cce555a61..02a5fa1ba7 100644 --- a/src/internal/agent/hooks/flux_test.go +++ b/src/internal/agent/hooks/flux_test.go @@ -119,7 +119,7 @@ func TestFluxMutationWebhook(t *testing.T) { expectedPatchJSON, err := json.Marshal(tt.expectedPatch) require.NoError(t, err) require.JSONEq(t, string(expectedPatchJSON), string(resp.Patch)) - } else { + } else if tt.code != http.StatusInternalServerError { require.Empty(t, string(resp.Patch)) } }) diff --git a/src/internal/agent/hooks/pods_test.go b/src/internal/agent/hooks/pods_test.go index 2cfe318e2d..ce3a1134fc 100644 --- a/src/internal/agent/hooks/pods_test.go +++ b/src/internal/agent/hooks/pods_test.go @@ -146,6 +146,11 @@ func TestPodMutationWebhook(t *testing.T) { }, code: http.StatusOK, }, + { + name: "empty pod", + admissionReq: createPodAdmissionRequest(t, v1.Create, nil), + code: 200, + }, } for _, tt := range tests { @@ -156,8 +161,11 @@ func TestPodMutationWebhook(t *testing.T) { if tt.expectedPatch != nil { expectedPatchJSON, err := json.Marshal(tt.expectedPatch) require.NoError(t, err) + require.NotNil(t, resp) + require.True(t, resp.Allowed) + require.JSONEq(t, string(expectedPatchJSON), string(resp.Patch)) - } else { + } else if tt.code != http.StatusInternalServerError { require.Empty(t, string(resp.Patch)) } }) diff --git a/src/internal/agent/hooks/utils_test.go b/src/internal/agent/hooks/utils_test.go index 6253a7e59d..862646e616 100644 --- a/src/internal/agent/hooks/utils_test.go +++ b/src/internal/agent/hooks/utils_test.go @@ -34,12 +34,10 @@ func sendAdmissionRequest(t *testing.T, admissionReq *v1.AdmissionRequest, handl require.Equal(t, code, rr.Code) var admissionReview v1.AdmissionReview - err = json.NewDecoder(rr.Body).Decode(&admissionReview) - require.NoError(t, err) - - resp := admissionReview.Response - require.NotNil(t, resp) - require.True(t, resp.Allowed) + if rr.Code == http.StatusOK { + err = json.NewDecoder(rr.Body).Decode(&admissionReview) + require.NoError(t, err) + } - return resp + return admissionReview.Response } From f2138371b9654532d08666be06777807b1681178 Mon Sep 17 00:00:00 2001 From: Lucas Rodriguez Date: Mon, 20 May 2024 16:23:18 -0500 Subject: [PATCH 08/45] flux working --- go.mod | 2 +- packages/zarf-agent/manifests/deployment.yaml | 1 + .../manifests/{clusterrole.yaml => role.yaml} | 4 +-- ...usterrolebinding.yaml => rolebinding.yaml} | 4 +-- packages/zarf-agent/zarf.yaml | 4 +-- src/internal/agent/hooks/flux.go | 35 +++++++------------ src/internal/agent/hooks/flux_test.go | 12 +++---- src/pkg/transform/git.go | 6 ++-- 8 files changed, 28 insertions(+), 40 deletions(-) rename packages/zarf-agent/manifests/{clusterrole.yaml => role.yaml} (77%) rename packages/zarf-agent/manifests/{clusterrolebinding.yaml => rolebinding.yaml} (82%) diff --git a/go.mod b/go.mod index 9fe6c7a968..71368adc16 100644 --- a/go.mod +++ b/go.mod @@ -227,7 +227,7 @@ require ( github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fluxcd/pkg/apis/acl v0.1.0 // indirect github.com/fluxcd/pkg/apis/kustomize v1.3.0 // indirect - github.com/fluxcd/pkg/apis/meta v1.3.0 // indirect + github.com/fluxcd/pkg/apis/meta v1.3.0 github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/fvbommel/sortorder v1.1.0 // indirect github.com/gabriel-vasile/mimetype v1.4.3 // indirect diff --git a/packages/zarf-agent/manifests/deployment.yaml b/packages/zarf-agent/manifests/deployment.yaml index 1566916e8c..a8e481845f 100644 --- a/packages/zarf-agent/manifests/deployment.yaml +++ b/packages/zarf-agent/manifests/deployment.yaml @@ -20,6 +20,7 @@ spec: imagePullSecrets: - name: private-registry priorityClassName: system-node-critical + serviceAccountName: zarf containers: - name: server image: "###ZARF_REGISTRY###/###ZARF_CONST_AGENT_IMAGE###:###ZARF_CONST_AGENT_IMAGE_TAG###" diff --git a/packages/zarf-agent/manifests/clusterrole.yaml b/packages/zarf-agent/manifests/role.yaml similarity index 77% rename from packages/zarf-agent/manifests/clusterrole.yaml rename to packages/zarf-agent/manifests/role.yaml index c29641bbbf..8a877760b0 100644 --- a/packages/zarf-agent/manifests/clusterrole.yaml +++ b/packages/zarf-agent/manifests/role.yaml @@ -1,13 +1,11 @@ apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRole +kind: Role metadata: name: zarf-agent rules: - apiGroups: - "" resources: - - services - secrets verbs: - get - - list diff --git a/packages/zarf-agent/manifests/clusterrolebinding.yaml b/packages/zarf-agent/manifests/rolebinding.yaml similarity index 82% rename from packages/zarf-agent/manifests/clusterrolebinding.yaml rename to packages/zarf-agent/manifests/rolebinding.yaml index e44d3dd946..e036da63fa 100644 --- a/packages/zarf-agent/manifests/clusterrolebinding.yaml +++ b/packages/zarf-agent/manifests/rolebinding.yaml @@ -1,10 +1,10 @@ apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRoleBinding +kind: RoleBinding metadata: name: zarf-agent-binding roleRef: apiGroup: rbac.authorization.k8s.io - kind: ClusterRole + kind: Role name: zarf-agent subjects: - kind: ServiceAccount diff --git a/packages/zarf-agent/zarf.yaml b/packages/zarf-agent/zarf.yaml index 4f4edff6d0..32a6c34997 100644 --- a/packages/zarf-agent/zarf.yaml +++ b/packages/zarf-agent/zarf.yaml @@ -27,8 +27,8 @@ components: - manifests/secret.yaml - manifests/deployment.yaml - manifests/webhook.yaml - - manifests/clusterrole.yaml - - manifests/clusterrolebinding.yaml + - manifests/role.yaml + - manifests/rolebinding.yaml - manifests/serviceaccount.yaml actions: onCreate: diff --git a/src/internal/agent/hooks/flux.go b/src/internal/agent/hooks/flux.go index 91f0d41a85..74af89d1a3 100644 --- a/src/internal/agent/hooks/flux.go +++ b/src/internal/agent/hooks/flux.go @@ -16,22 +16,11 @@ import ( "github.com/defenseunicorns/zarf/src/pkg/cluster" "github.com/defenseunicorns/zarf/src/pkg/message" "github.com/defenseunicorns/zarf/src/pkg/transform" + fluxmeta "github.com/fluxcd/pkg/apis/meta" + flux "github.com/fluxcd/source-controller/api/v1" v1 "k8s.io/api/admission/v1" ) -// SecretRef contains the name used to reference a git repository secret. -type SecretRef struct { - Name string `json:"name"` -} - -// GenericGitRepo contains the URL of a git repo and the secret that corresponds to it for use with Flux. -type GenericGitRepo struct { - Spec struct { - URL string `json:"url"` - SecretRef SecretRef `json:"secretRef,omitempty"` - } `json:"spec"` -} - // NewGitRepositoryMutationHook creates a new instance of the git repo mutation hook. func NewGitRepositoryMutationHook(ctx context.Context, cluster *cluster.Cluster) operations.Hook { message.Debug("hooks.NewGitRepositoryMutationHook()") @@ -63,18 +52,17 @@ func mutateGitRepo(ctx context.Context, r *v1.AdmissionRequest, cluster *cluster message.Debugf("Using the url of (%s) to mutate the flux repository", state.GitServer.Address) - // parse to simple struct to read the git url - src := &GenericGitRepo{} - if err = json.Unmarshal(r.Object.Raw, &src); err != nil { + repo := flux.GitRepository{} + if err = json.Unmarshal(r.Object.Raw, &repo); err != nil { return nil, fmt.Errorf(lang.ErrUnmarshal, err) } - patchedURL := src.Spec.URL + patchedURL := repo.Spec.URL // Check if this is an update operation and the hostname is different from what we have in the zarfState // NOTE: We mutate on updates IF AND ONLY IF the hostname in the request is different than the hostname in the zarfState // NOTE: We are checking if the hostname is different before because we do not want to potentially mutate a URL that has already been mutated. if isUpdate { - isPatched, err = helpers.DoHostnamesMatch(state.GitServer.Address, src.Spec.URL) + isPatched, err = helpers.DoHostnamesMatch(state.GitServer.Address, repo.Spec.URL) if err != nil { return nil, fmt.Errorf(lang.AgentErrHostnameMatch, err) } @@ -88,11 +76,11 @@ func mutateGitRepo(ctx context.Context, r *v1.AdmissionRequest, cluster *cluster return nil, fmt.Errorf("unable to transform the git url: %w", err) } patchedURL = transformedURL.String() - message.Debugf("original git URL of (%s) got mutated to (%s)", src.Spec.URL, patchedURL) + message.Debugf("original git URL of (%s) got mutated to (%s)", repo.Spec.URL, patchedURL) } // Patch updates of the repo spec - patches = populatePatchOperations(patchedURL, src.Spec.SecretRef.Name) + patches = populatePatchOperations(patchedURL, repo.Spec.SecretRef) return &operations.Result{ Allowed: true, @@ -101,18 +89,19 @@ func mutateGitRepo(ctx context.Context, r *v1.AdmissionRequest, cluster *cluster } // Patch updates of the repo spec. -func populatePatchOperations(repoURL string, secretName string) []operations.PatchOperation { +func populatePatchOperations(repoURL string, secretRef *fluxmeta.LocalObjectReference) []operations.PatchOperation { var patches []operations.PatchOperation patches = append(patches, operations.ReplacePatchOperation("/spec/url", repoURL)) //TODO This logic can be simplified // If a prior secret exists, replace it - if secretName != "" { + if secretRef != nil && secretRef.Name != "" { patches = append(patches, operations.ReplacePatchOperation("/spec/secretRef/name", config.ZarfGitServerSecretName)) } else { // Otherwise, add the new secret - patches = append(patches, operations.AddPatchOperation("/spec/secretRef", SecretRef{Name: config.ZarfGitServerSecretName})) + newSecretRef := fluxmeta.LocalObjectReference{Name: config.ZarfGitServerSecretName} + patches = append(patches, operations.AddPatchOperation("/spec/secretRef", newSecretRef)) } return patches diff --git a/src/internal/agent/hooks/flux_test.go b/src/internal/agent/hooks/flux_test.go index 02a5fa1ba7..585c964bef 100644 --- a/src/internal/agent/hooks/flux_test.go +++ b/src/internal/agent/hooks/flux_test.go @@ -23,8 +23,8 @@ import ( "k8s.io/client-go/kubernetes/fake" ) -// createFluxAdmissionRequest creates an admission request for a pod. -func createFluxAdmissionRequest(t *testing.T, op v1.Operation, fluxGitRepo *flux.GitRepository) *v1.AdmissionRequest { +// createFluxGitRepoAdmissionRequest creates an admission request for a Flux GitRepository. +func createFluxGitRepoAdmissionRequest(t *testing.T, op v1.Operation, fluxGitRepo *flux.GitRepository) *v1.AdmissionRequest { t.Helper() raw, err := json.Marshal(fluxGitRepo) require.NoError(t, err) @@ -68,8 +68,8 @@ func TestFluxMutationWebhook(t *testing.T) { code int }{ { - name: "flux object should be mutated on Create", - admissionReq: createFluxAdmissionRequest(t, v1.Create, &flux.GitRepository{ + name: "should be mutated", + admissionReq: createFluxGitRepoAdmissionRequest(t, v1.Create, &flux.GitRepository{ ObjectMeta: metav1.ObjectMeta{ Name: "mutate-this", }, @@ -93,8 +93,8 @@ func TestFluxMutationWebhook(t *testing.T) { code: http.StatusOK, }, { - name: "flux object should be mutated on Update", - admissionReq: createFluxAdmissionRequest(t, v1.Update, &flux.GitRepository{ + name: "should not mutate invalid git url", + admissionReq: createFluxGitRepoAdmissionRequest(t, v1.Update, &flux.GitRepository{ ObjectMeta: metav1.ObjectMeta{ Name: "mutate-this", }, diff --git a/src/pkg/transform/git.go b/src/pkg/transform/git.go index 3710579ab2..4659421ba0 100644 --- a/src/pkg/transform/git.go +++ b/src/pkg/transform/git.go @@ -34,7 +34,7 @@ func GitURLSplitRef(sourceURL string) (string, string, error) { get, err := helpers.MatchRegex(gitURLRegex, sourceURL) if err != nil { - return "", "", fmt.Errorf("unable to get extract the source url and ref from the url %s", sourceURL) + return "", "", fmt.Errorf("unable to extract the repo name from the url %q", sourceURL) } gitURLNoRef := fmt.Sprintf("%s%s/%s%s", get("proto"), get("hostPath"), get("repo"), get("git")) @@ -49,7 +49,7 @@ func GitURLtoFolderName(sourceURL string) (string, error) { if err != nil { // Unable to find a substring match for the regex - return "", fmt.Errorf("unable to get extract the folder name from the url %s", sourceURL) + return "", fmt.Errorf("unable to extract the repo name from the url %q", sourceURL) } repoName := get("repo") @@ -70,7 +70,7 @@ func GitURLtoRepoName(sourceURL string) (string, error) { if err != nil { // Unable to find a substring match for the regex - return "", fmt.Errorf("unable to get extract the repo name from the url %s", sourceURL) + return "", fmt.Errorf("unable to extract the repo name from the url %q", sourceURL) } repoName := get("repo") From 48304d4f61e5fe9a020fdf8932cf363d0eaf7aa3 Mon Sep 17 00:00:00 2001 From: Lucas Rodriguez Date: Mon, 20 May 2024 16:29:46 -0500 Subject: [PATCH 09/45] use upstream type for flux test and delete comments --- src/internal/agent/hooks/flux_test.go | 5 +++-- src/internal/agent/hooks/pods_test.go | 2 -- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/internal/agent/hooks/flux_test.go b/src/internal/agent/hooks/flux_test.go index 585c964bef..58446c7b9c 100644 --- a/src/internal/agent/hooks/flux_test.go +++ b/src/internal/agent/hooks/flux_test.go @@ -9,11 +9,13 @@ import ( "net/http" "testing" + "github.com/defenseunicorns/zarf/src/config" "github.com/defenseunicorns/zarf/src/internal/agent/http/admission" "github.com/defenseunicorns/zarf/src/internal/agent/operations" "github.com/defenseunicorns/zarf/src/pkg/cluster" "github.com/defenseunicorns/zarf/src/pkg/k8s" "github.com/defenseunicorns/zarf/src/types" + fluxmeta "github.com/fluxcd/pkg/apis/meta" flux "github.com/fluxcd/source-controller/api/v1" "github.com/stretchr/testify/require" v1 "k8s.io/api/admission/v1" @@ -23,7 +25,6 @@ import ( "k8s.io/client-go/kubernetes/fake" ) -// createFluxGitRepoAdmissionRequest creates an admission request for a Flux GitRepository. func createFluxGitRepoAdmissionRequest(t *testing.T, op v1.Operation, fluxGitRepo *flux.GitRepository) *v1.AdmissionRequest { t.Helper() raw, err := json.Marshal(fluxGitRepo) @@ -87,7 +88,7 @@ func TestFluxMutationWebhook(t *testing.T) { ), operations.AddPatchOperation( "/spec/secretRef", - map[string]string{"name": "private-git-server"}, + fluxmeta.LocalObjectReference{Name: config.ZarfGitServerSecretName}, ), }, code: http.StatusOK, diff --git a/src/internal/agent/hooks/pods_test.go b/src/internal/agent/hooks/pods_test.go index ce3a1134fc..75c48d4a82 100644 --- a/src/internal/agent/hooks/pods_test.go +++ b/src/internal/agent/hooks/pods_test.go @@ -23,7 +23,6 @@ import ( "k8s.io/client-go/kubernetes/fake" ) -// createPodAdmissionRequest creates an admission request for a pod. func createPodAdmissionRequest(t *testing.T, op v1.Operation, pod *corev1.Pod) *v1.AdmissionRequest { t.Helper() raw, err := json.Marshal(pod) @@ -36,7 +35,6 @@ func createPodAdmissionRequest(t *testing.T, op v1.Operation, pod *corev1.Pod) * } } -// TestPodMutationWebhook tests the pod mutation webhook. func TestPodMutationWebhook(t *testing.T) { t.Parallel() From 5f25efc08d7faebfa2f2e931ac9f2e5fa9867105 Mon Sep 17 00:00:00 2001 From: Lucas Rodriguez Date: Mon, 20 May 2024 16:32:10 -0500 Subject: [PATCH 10/45] beter err handling --- src/internal/agent/hooks/pods.go | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/src/internal/agent/hooks/pods.go b/src/internal/agent/hooks/pods.go index b57667b1ad..ea822f366f 100644 --- a/src/internal/agent/hooks/pods.go +++ b/src/internal/agent/hooks/pods.go @@ -61,14 +61,9 @@ func mutatePod(ctx context.Context, r *v1.AdmissionRequest, cluster *cluster.Clu state, err := cluster.LoadZarfState(ctx) if err != nil { - message.Debugf("failed to load Zarf state: %s", err.Error()) - return &operations.Result{ - Allowed: true, - PatchOps: []operations.PatchOperation{}, - }, nil + return nil, fmt.Errorf(lang.AgentErrGetState, err) } - - containerRegistryURL := state.RegistryInfo.Address + registryURL := state.RegistryInfo.Address var patchOperations []operations.PatchOperation @@ -79,7 +74,7 @@ func mutatePod(ctx context.Context, r *v1.AdmissionRequest, cluster *cluster.Clu // update the image host for each init container for idx, container := range pod.Spec.InitContainers { path := fmt.Sprintf("/spec/initContainers/%d/image", idx) - replacement, err := transform.ImageTransformHost(containerRegistryURL, container.Image) + replacement, err := transform.ImageTransformHost(registryURL, container.Image) if err != nil { message.Warnf(lang.AgentErrImageSwap, container.Image) continue // Continue, because we might as well attempt to mutate the other containers for this pod @@ -90,7 +85,7 @@ func mutatePod(ctx context.Context, r *v1.AdmissionRequest, cluster *cluster.Clu // update the image host for each ephemeral container for idx, container := range pod.Spec.EphemeralContainers { path := fmt.Sprintf("/spec/ephemeralContainers/%d/image", idx) - replacement, err := transform.ImageTransformHost(containerRegistryURL, container.Image) + replacement, err := transform.ImageTransformHost(registryURL, container.Image) if err != nil { message.Warnf(lang.AgentErrImageSwap, container.Image) continue // Continue, because we might as well attempt to mutate the other containers for this pod @@ -101,7 +96,7 @@ func mutatePod(ctx context.Context, r *v1.AdmissionRequest, cluster *cluster.Clu // update the image host for each normal container for idx, container := range pod.Spec.Containers { path := fmt.Sprintf("/spec/containers/%d/image", idx) - replacement, err := transform.ImageTransformHost(containerRegistryURL, container.Image) + replacement, err := transform.ImageTransformHost(registryURL, container.Image) if err != nil { message.Warnf(lang.AgentErrImageSwap, container.Image) continue // Continue, because we might as well attempt to mutate the other containers for this pod From 628ab54965d62408db5ebd3e48e51b2395883482 Mon Sep 17 00:00:00 2001 From: Lucas Rodriguez Date: Mon, 20 May 2024 16:36:36 -0500 Subject: [PATCH 11/45] moar stuff --- src/internal/agent/hooks/flux_test.go | 2 ++ src/internal/agent/hooks/pods_test.go | 3 +-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/internal/agent/hooks/flux_test.go b/src/internal/agent/hooks/flux_test.go index 58446c7b9c..b2a789325e 100644 --- a/src/internal/agent/hooks/flux_test.go +++ b/src/internal/agent/hooks/flux_test.go @@ -119,6 +119,8 @@ func TestFluxMutationWebhook(t *testing.T) { if tt.expectedPatch != nil { expectedPatchJSON, err := json.Marshal(tt.expectedPatch) require.NoError(t, err) + require.NotNil(t, resp) + require.True(t, resp.Allowed) require.JSONEq(t, string(expectedPatchJSON), string(resp.Patch)) } else if tt.code != http.StatusInternalServerError { require.Empty(t, string(resp.Patch)) diff --git a/src/internal/agent/hooks/pods_test.go b/src/internal/agent/hooks/pods_test.go index 75c48d4a82..258d0931a6 100644 --- a/src/internal/agent/hooks/pods_test.go +++ b/src/internal/agent/hooks/pods_test.go @@ -147,7 +147,7 @@ func TestPodMutationWebhook(t *testing.T) { { name: "empty pod", admissionReq: createPodAdmissionRequest(t, v1.Create, nil), - code: 200, + code: http.StatusOK, }, } @@ -161,7 +161,6 @@ func TestPodMutationWebhook(t *testing.T) { require.NoError(t, err) require.NotNil(t, resp) require.True(t, resp.Allowed) - require.JSONEq(t, string(expectedPatchJSON), string(resp.Patch)) } else if tt.code != http.StatusInternalServerError { require.Empty(t, string(resp.Patch)) From e253431f4ae18852d466b94dd9ada096eb2f33e0 Mon Sep 17 00:00:00 2001 From: Lucas Rodriguez Date: Mon, 20 May 2024 16:40:17 -0500 Subject: [PATCH 12/45] ctx --- src/internal/agent/http/server.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/internal/agent/http/server.go b/src/internal/agent/http/server.go index 2f25477c83..b2253d7af0 100644 --- a/src/internal/agent/http/server.go +++ b/src/internal/agent/http/server.go @@ -26,9 +26,11 @@ func NewAdmissionServer(port string) *http.Server { message.Fatalf(err, err.Error()) } + ctx := context.Background() + // Instances hooks - podsMutation := hooks.NewPodMutationHook(context.Background(), c) - fluxGitRepositoryMutation := hooks.NewGitRepositoryMutationHook(context.Background(), c) + podsMutation := hooks.NewPodMutationHook(ctx, c) + fluxGitRepositoryMutation := hooks.NewGitRepositoryMutationHook(ctx, c) argocdApplicationMutation := hooks.NewApplicationMutationHook() argocdRepositoryMutation := hooks.NewRepositoryMutationHook() From a477285f26f3726c179dcf6ff4fa1512402edad8 Mon Sep 17 00:00:00 2001 From: Lucas Rodriguez Date: Mon, 20 May 2024 17:11:22 -0500 Subject: [PATCH 13/45] add test cases for flux webhook --- src/internal/agent/hooks/flux_test.go | 53 +++++++++++++++++++++++++++ src/internal/agent/hooks/pods_test.go | 2 +- 2 files changed, 54 insertions(+), 1 deletion(-) diff --git a/src/internal/agent/hooks/flux_test.go b/src/internal/agent/hooks/flux_test.go index b2a789325e..465596908a 100644 --- a/src/internal/agent/hooks/flux_test.go +++ b/src/internal/agent/hooks/flux_test.go @@ -109,6 +109,59 @@ func TestFluxMutationWebhook(t *testing.T) { expectedPatch: nil, code: http.StatusInternalServerError, }, + { + name: "should replace existing secret", + admissionReq: createFluxGitRepoAdmissionRequest(t, v1.Create, &flux.GitRepository{ + ObjectMeta: metav1.ObjectMeta{ + Name: "replace-secret", + }, + Spec: flux.GitRepositorySpec{ + URL: "https://github.com/stefanprodan/podinfo.git", + SecretRef: &fluxmeta.LocalObjectReference{ + Name: "existing-secret", + }, + Reference: &flux.GitRepositoryRef{ + Tag: "6.4.0", + }, + }, + }), + expectedPatch: []operations.PatchOperation{ + operations.ReplacePatchOperation( + "/spec/url", + "https://git-server.com/a-push-user/podinfo-1646971829.git", + ), + operations.ReplacePatchOperation( + "/spec/secretRef/name", + config.ZarfGitServerSecretName, + ), + }, + code: http.StatusOK, + }, + { + name: "should not mutate on update if hostname matches", + admissionReq: createFluxGitRepoAdmissionRequest(t, v1.Update, &flux.GitRepository{ + ObjectMeta: metav1.ObjectMeta{ + Name: "no-mutate", + }, + Spec: flux.GitRepositorySpec{ + URL: "https://git-server.com/a-push-user/podinfo.git", + Reference: &flux.GitRepositoryRef{ + Tag: "6.4.0", + }, + }, + }), + expectedPatch: []operations.PatchOperation{ + operations.ReplacePatchOperation( + "/spec/url", + "https://git-server.com/a-push-user/podinfo.git", + ), + operations.AddPatchOperation( + "/spec/secretRef", + fluxmeta.LocalObjectReference{Name: config.ZarfGitServerSecretName}, + ), + }, + code: http.StatusOK, + }, } for _, tt := range tests { diff --git a/src/internal/agent/hooks/pods_test.go b/src/internal/agent/hooks/pods_test.go index 258d0931a6..24c96d9ab2 100644 --- a/src/internal/agent/hooks/pods_test.go +++ b/src/internal/agent/hooks/pods_test.go @@ -49,7 +49,7 @@ func TestPodMutationWebhook(t *testing.T) { secret := &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ Name: cluster.ZarfStateSecretName, - Namespace: "zarf", + Namespace: cluster.ZarfNamespaceName, }, Data: map[string][]byte{ cluster.ZarfStateDataKey: state, From 3de643579e0d26ab8dd9922a2fbd3afb32f5543a Mon Sep 17 00:00:00 2001 From: Lucas Rodriguez Date: Mon, 20 May 2024 17:46:28 -0500 Subject: [PATCH 14/45] add helpers for test setup --- src/internal/agent/hooks/flux_test.go | 22 +++------------- src/internal/agent/hooks/pods_test.go | 19 ++------------ src/internal/agent/hooks/utils_test.go | 35 ++++++++++++++++++++++++++ 3 files changed, 40 insertions(+), 36 deletions(-) diff --git a/src/internal/agent/hooks/flux_test.go b/src/internal/agent/hooks/flux_test.go index 465596908a..a05570787d 100644 --- a/src/internal/agent/hooks/flux_test.go +++ b/src/internal/agent/hooks/flux_test.go @@ -10,7 +10,6 @@ import ( "testing" "github.com/defenseunicorns/zarf/src/config" - "github.com/defenseunicorns/zarf/src/internal/agent/http/admission" "github.com/defenseunicorns/zarf/src/internal/agent/operations" "github.com/defenseunicorns/zarf/src/pkg/cluster" "github.com/defenseunicorns/zarf/src/pkg/k8s" @@ -19,7 +18,6 @@ import ( flux "github.com/fluxcd/source-controller/api/v1" "github.com/stretchr/testify/require" v1 "k8s.io/api/admission/v1" - corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/client-go/kubernetes/fake" @@ -41,26 +39,12 @@ func TestFluxMutationWebhook(t *testing.T) { t.Parallel() ctx := context.Background() - c := &cluster.Cluster{K8s: &k8s.K8s{Clientset: fake.NewSimpleClientset()}} - handler := admission.NewHandler().Serve(NewGitRepositoryMutationHook(ctx, c)) - - state, err := json.Marshal(&types.ZarfState{GitServer: types.GitServerInfo{ + state := &types.ZarfState{GitServer: types.GitServerInfo{ Address: "https://git-server.com", PushUsername: "a-push-user", - }}) - require.NoError(t, err) - - secret := &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: cluster.ZarfStateSecretName, - Namespace: cluster.ZarfNamespaceName, - }, - Data: map[string][]byte{ - cluster.ZarfStateDataKey: state, - }, - } - c.Clientset.CoreV1().Secrets(cluster.ZarfNamespaceName).Create(ctx, secret, metav1.CreateOptions{}) + }} + handler := setupWebhookTest(ctx, t, c, state, NewGitRepositoryMutationHook) tests := []struct { name string diff --git a/src/internal/agent/hooks/pods_test.go b/src/internal/agent/hooks/pods_test.go index 24c96d9ab2..93516b2360 100644 --- a/src/internal/agent/hooks/pods_test.go +++ b/src/internal/agent/hooks/pods_test.go @@ -10,7 +10,6 @@ import ( "testing" "github.com/defenseunicorns/zarf/src/config" - "github.com/defenseunicorns/zarf/src/internal/agent/http/admission" "github.com/defenseunicorns/zarf/src/internal/agent/operations" "github.com/defenseunicorns/zarf/src/pkg/cluster" "github.com/defenseunicorns/zarf/src/pkg/k8s" @@ -39,23 +38,9 @@ func TestPodMutationWebhook(t *testing.T) { t.Parallel() ctx := context.Background() - c := &cluster.Cluster{K8s: &k8s.K8s{Clientset: fake.NewSimpleClientset()}} - handler := admission.NewHandler().Serve(NewPodMutationHook(ctx, c)) - - state, err := json.Marshal(&types.ZarfState{RegistryInfo: types.RegistryInfo{Address: "127.0.0.1:31999"}}) - require.NoError(t, err) - - secret := &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: cluster.ZarfStateSecretName, - Namespace: cluster.ZarfNamespaceName, - }, - Data: map[string][]byte{ - cluster.ZarfStateDataKey: state, - }, - } - c.Clientset.CoreV1().Secrets("zarf").Create(ctx, secret, metav1.CreateOptions{}) + state := &types.ZarfState{RegistryInfo: types.RegistryInfo{Address: "127.0.0.1:31999"}} + handler := setupWebhookTest(ctx, t, c, state, NewPodMutationHook) tests := []struct { name string diff --git a/src/internal/agent/hooks/utils_test.go b/src/internal/agent/hooks/utils_test.go index 862646e616..bbcdffa718 100644 --- a/src/internal/agent/hooks/utils_test.go +++ b/src/internal/agent/hooks/utils_test.go @@ -5,15 +5,50 @@ package hooks import ( "bytes" + "context" "encoding/json" "net/http" "net/http/httptest" "testing" + "github.com/defenseunicorns/zarf/src/internal/agent/http/admission" + "github.com/defenseunicorns/zarf/src/internal/agent/operations" + "github.com/defenseunicorns/zarf/src/pkg/cluster" + "github.com/defenseunicorns/zarf/src/types" "github.com/stretchr/testify/require" v1 "k8s.io/api/admission/v1" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) +// setupWebhookTest sets up the test environment and returns the http.HandlerFunc for the provided hook. +// Cluster.K8s.Clientset should be initialized with fake.NewSimpleClientset() prior to passing into this function. +func setupWebhookTest(ctx context.Context, t *testing.T, c *cluster.Cluster, state *types.ZarfState, hookFunc func(ctx context.Context, c *cluster.Cluster) operations.Hook) http.HandlerFunc { + t.Helper() + createTestZarfStateSecret(ctx, t, c, state) + return admission.NewHandler().Serve(hookFunc(ctx, c)) +} + +// createTestZarfStateSecret creates a test zarf-state secret in the zarf namespace. +// Cluster.K8s.Clientset should be initialized with fake.NewSimpleClientset() prior to passing into this function. +func createTestZarfStateSecret(ctx context.Context, t *testing.T, c *cluster.Cluster, state *types.ZarfState) { + t.Helper() + stateData, err := json.Marshal(state) + require.NoError(t, err) + + secret := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: cluster.ZarfStateSecretName, + Namespace: cluster.ZarfNamespaceName, + }, + Data: map[string][]byte{ + cluster.ZarfStateDataKey: stateData, + }, + } + _, err = c.Clientset.CoreV1().Secrets(cluster.ZarfNamespaceName).Create(ctx, secret, metav1.CreateOptions{}) + require.NoError(t, err) +} + // sendAdmissionRequest sends an admission request to the handler and returns the response. func sendAdmissionRequest(t *testing.T, admissionReq *v1.AdmissionRequest, handler http.HandlerFunc, code int) *v1.AdmissionResponse { t.Helper() From 5218d10c88968989d230aecc81d36e1c8d8184bc Mon Sep 17 00:00:00 2001 From: Lucas Rodriguez Date: Mon, 20 May 2024 20:38:22 -0500 Subject: [PATCH 15/45] argo working --- .../agent/hooks/argocd-application.go | 32 +++++++++++-------- src/internal/agent/hooks/argocd-repository.go | 28 +++++++++------- src/internal/agent/http/server.go | 6 ++-- 3 files changed, 37 insertions(+), 29 deletions(-) diff --git a/src/internal/agent/hooks/argocd-application.go b/src/internal/agent/hooks/argocd-application.go index 260b1d53a3..c67e9f8c8b 100644 --- a/src/internal/agent/hooks/argocd-application.go +++ b/src/internal/agent/hooks/argocd-application.go @@ -5,13 +5,14 @@ package hooks import ( + "context" "encoding/json" "fmt" "github.com/defenseunicorns/pkg/helpers" "github.com/defenseunicorns/zarf/src/config/lang" "github.com/defenseunicorns/zarf/src/internal/agent/operations" - "github.com/defenseunicorns/zarf/src/internal/agent/state" + "github.com/defenseunicorns/zarf/src/pkg/cluster" "github.com/defenseunicorns/zarf/src/pkg/message" "github.com/defenseunicorns/zarf/src/pkg/transform" "github.com/defenseunicorns/zarf/src/types" @@ -32,7 +33,6 @@ type ArgoApplication struct { } var ( - zarfState *types.ZarfState patches []operations.PatchOperation isPatched bool isCreate bool @@ -40,28 +40,32 @@ var ( ) // NewApplicationMutationHook creates a new instance of the ArgoCD Application mutation hook. -func NewApplicationMutationHook() operations.Hook { +func NewApplicationMutationHook(ctx context.Context, cluster *cluster.Cluster) operations.Hook { message.Debug("hooks.NewApplicationMutationHook()") return operations.Hook{ - Create: mutateApplication, - Update: mutateApplication, + Create: func(r *v1.AdmissionRequest) (*operations.Result, error) { + return mutateApplication(ctx, r, cluster) + }, + Update: func(r *v1.AdmissionRequest) (*operations.Result, error) { + return mutateApplication(ctx, r, cluster) + }, } } // mutateApplication mutates the git repository url to point to the repository URL defined in the ZarfState. -func mutateApplication(r *v1.AdmissionRequest) (result *operations.Result, err error) { +func mutateApplication(ctx context.Context, r *v1.AdmissionRequest, cluster *cluster.Cluster) (result *operations.Result, err error) { isCreate = r.Operation == v1.Create isUpdate = r.Operation == v1.Update patches = []operations.PatchOperation{} - // Form the zarfState.GitServer.Address from the zarfState - if zarfState, err = state.GetZarfStateFromAgentPod(); err != nil { + state, err := cluster.LoadZarfState(ctx) + if err != nil { return nil, fmt.Errorf(lang.AgentErrGetState, err) } - message.Debugf("Using the url of (%s) to mutate the ArgoCD Application", zarfState.GitServer.Address) + message.Debugf("Using the url of (%s) to mutate the ArgoCD Application", state.GitServer.Address) // parse to simple struct to read the git url src := &ArgoApplication{} @@ -73,13 +77,13 @@ func mutateApplication(r *v1.AdmissionRequest) (result *operations.Result, err e message.Debugf("Data %v", string(r.Object.Raw)) if src.Spec.Source != (Source{}) { - patchedURL, _ := getPatchedRepoURL(src.Spec.Source.RepoURL) + patchedURL, _ := getPatchedRepoURL(src.Spec.Source.RepoURL, state.GitServer) patches = populateSingleSourceArgoApplicationPatchOperations(patchedURL, patches) } if len(src.Spec.Sources) > 0 { for idx, source := range src.Spec.Sources { - patchedURL, _ := getPatchedRepoURL(source.RepoURL) + patchedURL, _ := getPatchedRepoURL(source.RepoURL, state.GitServer) patches = populateMultipleSourceArgoApplicationPatchOperations(idx, patchedURL, patches) } } @@ -90,7 +94,7 @@ func mutateApplication(r *v1.AdmissionRequest) (result *operations.Result, err e }, nil } -func getPatchedRepoURL(repoURL string) (string, error) { +func getPatchedRepoURL(repoURL string, gs types.GitServerInfo) (string, error) { var err error patchedURL := repoURL @@ -98,7 +102,7 @@ func getPatchedRepoURL(repoURL string) (string, error) { // NOTE: We mutate on updates IF AND ONLY IF the hostname in the request is different from the hostname in the zarfState // NOTE: We are checking if the hostname is different before because we do not want to potentially mutate a URL that has already been mutated. if isUpdate { - isPatched, err = helpers.DoHostnamesMatch(zarfState.GitServer.Address, repoURL) + isPatched, err = helpers.DoHostnamesMatch(gs.Address, repoURL) if err != nil { return "", fmt.Errorf(lang.AgentErrHostnameMatch, err) } @@ -107,7 +111,7 @@ func getPatchedRepoURL(repoURL string) (string, error) { // Mutate the repoURL if necessary if isCreate || (isUpdate && !isPatched) { // Mutate the git URL so that the hostname matches the hostname in the Zarf state - transformedURL, err := transform.GitURL(zarfState.GitServer.Address, patchedURL, zarfState.GitServer.PushUsername) + transformedURL, err := transform.GitURL(gs.Address, patchedURL, gs.PushUsername) if err != nil { message.Warnf("Unable to transform the repoURL, using the original url we have: %s", patchedURL) } diff --git a/src/internal/agent/hooks/argocd-repository.go b/src/internal/agent/hooks/argocd-repository.go index a9b74cd72f..eb56e9d215 100644 --- a/src/internal/agent/hooks/argocd-repository.go +++ b/src/internal/agent/hooks/argocd-repository.go @@ -5,6 +5,7 @@ package hooks import ( + "context" "encoding/base64" "encoding/json" "fmt" @@ -12,7 +13,7 @@ import ( "github.com/defenseunicorns/pkg/helpers" "github.com/defenseunicorns/zarf/src/config/lang" "github.com/defenseunicorns/zarf/src/internal/agent/operations" - "github.com/defenseunicorns/zarf/src/internal/agent/state" + "github.com/defenseunicorns/zarf/src/pkg/cluster" "github.com/defenseunicorns/zarf/src/pkg/message" "github.com/defenseunicorns/zarf/src/pkg/transform" "github.com/defenseunicorns/zarf/src/types" @@ -27,19 +28,22 @@ type ArgoRepository struct { } // NewRepositoryMutationHook creates a new instance of the ArgoCD Repository mutation hook. -func NewRepositoryMutationHook() operations.Hook { +func NewRepositoryMutationHook(ctx context.Context, cluster *cluster.Cluster) operations.Hook { message.Debug("hooks.NewRepositoryMutationHook()") return operations.Hook{ - Create: mutateRepository, - Update: mutateRepository, + Create: func(r *v1.AdmissionRequest) (*operations.Result, error) { + return mutateRepository(ctx, r, cluster) + }, + Update: func(r *v1.AdmissionRequest) (*operations.Result, error) { + return mutateRepository(ctx, r, cluster) + }, } } // mutateRepository mutates the git repository URL to point to the repository URL defined in the ZarfState. -func mutateRepository(r *v1.AdmissionRequest) (result *operations.Result, err error) { +func mutateRepository(ctx context.Context, r *v1.AdmissionRequest, cluster *cluster.Cluster) (result *operations.Result, err error) { var ( - zarfState *types.ZarfState patches []operations.PatchOperation isPatched bool @@ -47,12 +51,12 @@ func mutateRepository(r *v1.AdmissionRequest) (result *operations.Result, err er isUpdate = r.Operation == v1.Update ) - // Form the zarfState.GitServer.Address from the zarfState - if zarfState, err = state.GetZarfStateFromAgentPod(); err != nil { + state, err := cluster.LoadZarfState(ctx) + if err != nil { return nil, fmt.Errorf(lang.AgentErrGetState, err) } - message.Debugf("Using the url of (%s) to mutate the ArgoCD Repository Secret", zarfState.GitServer.Address) + message.Debugf("Using the url of (%s) to mutate the ArgoCD Repository Secret", state.GitServer.Address) // parse to simple struct to read the git url src := &ArgoRepository{} @@ -71,7 +75,7 @@ func mutateRepository(r *v1.AdmissionRequest) (result *operations.Result, err er // NOTE: We mutate on updates IF AND ONLY IF the hostname in the request is different from the hostname in the zarfState // NOTE: We are checking if the hostname is different before because we do not want to potentially mutate a URL that has already been mutated. if isUpdate { - isPatched, err = helpers.DoHostnamesMatch(zarfState.GitServer.Address, src.Data.URL) + isPatched, err = helpers.DoHostnamesMatch(state.GitServer.Address, src.Data.URL) if err != nil { return nil, fmt.Errorf(lang.AgentErrHostnameMatch, err) } @@ -80,7 +84,7 @@ func mutateRepository(r *v1.AdmissionRequest) (result *operations.Result, err er // Mutate the repoURL if necessary if isCreate || (isUpdate && !isPatched) { // Mutate the git URL so that the hostname matches the hostname in the Zarf state - transformedURL, err := transform.GitURL(zarfState.GitServer.Address, patchedURL, zarfState.GitServer.PushUsername) + transformedURL, err := transform.GitURL(state.GitServer.Address, patchedURL, state.GitServer.PushUsername) if err != nil { message.Warnf("Unable to transform the url, using the original url we have: %s", patchedURL) } @@ -89,7 +93,7 @@ func mutateRepository(r *v1.AdmissionRequest) (result *operations.Result, err er } // Patch updates of the repo spec - patches = populateArgoRepositoryPatchOperations(patchedURL, zarfState.GitServer.PullPassword) + patches = populateArgoRepositoryPatchOperations(patchedURL, state.GitServer.PullPassword) return &operations.Result{ Allowed: true, diff --git a/src/internal/agent/http/server.go b/src/internal/agent/http/server.go index b2253d7af0..ca7524420c 100644 --- a/src/internal/agent/http/server.go +++ b/src/internal/agent/http/server.go @@ -19,7 +19,7 @@ import ( // NewAdmissionServer creates a http.Server for the mutating webhook admission handler. func NewAdmissionServer(port string) *http.Server { - message.Debugf("http.NewServer(%s)", port) + message.Debugf("http.NewAdmissionServer(%s)", port) c, err := cluster.NewCluster() if err != nil { @@ -31,8 +31,8 @@ func NewAdmissionServer(port string) *http.Server { // Instances hooks podsMutation := hooks.NewPodMutationHook(ctx, c) fluxGitRepositoryMutation := hooks.NewGitRepositoryMutationHook(ctx, c) - argocdApplicationMutation := hooks.NewApplicationMutationHook() - argocdRepositoryMutation := hooks.NewRepositoryMutationHook() + argocdApplicationMutation := hooks.NewApplicationMutationHook(ctx, c) + argocdRepositoryMutation := hooks.NewRepositoryMutationHook(ctx, c) // Routers ah := admission.NewHandler() From edec3716c3d2b34439fc714fbce64d42bc0cce13 Mon Sep 17 00:00:00 2001 From: Lucas Rodriguez Date: Mon, 20 May 2024 23:26:42 -0500 Subject: [PATCH 16/45] copy argo app types from upstream --- .../agent/hooks/argocd-application.go | 79 +++++++++++-------- src/internal/agent/hooks/argocd-repository.go | 2 +- 2 files changed, 46 insertions(+), 35 deletions(-) diff --git a/src/internal/agent/hooks/argocd-application.go b/src/internal/agent/hooks/argocd-application.go index c67e9f8c8b..2096a2381f 100644 --- a/src/internal/agent/hooks/argocd-application.go +++ b/src/internal/agent/hooks/argocd-application.go @@ -19,25 +19,33 @@ import ( v1 "k8s.io/api/admission/v1" ) -// Source represents a subset of the Argo Source object needed for Zarf Git URL mutations -type Source struct { - RepoURL string `json:"repoURL"` +// Application is a definition of an ArgoCD Application resource. +// The ArgoCD Application structs in this file have been copied from upstream. +// +// https://github.com/argoproj/argo-cd/blob/v2.11.0/pkg/apis/application/v1alpha1/types.go +// +// There were errors encountered when trying to import argocd as a Go package. +// +// For more information: https://argo-cd.readthedocs.io/en/stable/user-guide/import/ +type Application struct { + Spec ApplicationSpec `json:"spec"` } -// ArgoApplication represents a subset of the Argo Application object needed for Zarf Git URL mutations -type ArgoApplication struct { - Spec struct { - Source Source `json:"source"` - Sources []Source `json:"sources"` - } `json:"spec"` +// ApplicationSpec represents desired application state. Contains link to repository with application definition. +type ApplicationSpec struct { + // Source is a reference to the location of the application's manifests or chart. + Source *ApplicationSource `json:"source,omitempty"` + Sources ApplicationSources `json:"sources,omitempty"` } -var ( - patches []operations.PatchOperation - isPatched bool - isCreate bool - isUpdate bool -) +// ApplicationSource contains all required information about the source of an application. +type ApplicationSource struct { + // RepoURL is the URL to the repository (Git or Helm) that contains the application manifests. + RepoURL string `json:"repoURL"` +} + +// ApplicationSources contains list of required information about the sources of an application. +type ApplicationSources []ApplicationSource // NewApplicationMutationHook creates a new instance of the ArgoCD Application mutation hook. func NewApplicationMutationHook(ctx context.Context, cluster *cluster.Cluster) operations.Hook { @@ -54,12 +62,6 @@ func NewApplicationMutationHook(ctx context.Context, cluster *cluster.Cluster) o // mutateApplication mutates the git repository url to point to the repository URL defined in the ZarfState. func mutateApplication(ctx context.Context, r *v1.AdmissionRequest, cluster *cluster.Cluster) (result *operations.Result, err error) { - - isCreate = r.Operation == v1.Create - isUpdate = r.Operation == v1.Update - - patches = []operations.PatchOperation{} - state, err := cluster.LoadZarfState(ctx) if err != nil { return nil, fmt.Errorf(lang.AgentErrGetState, err) @@ -67,23 +69,29 @@ func mutateApplication(ctx context.Context, r *v1.AdmissionRequest, cluster *clu message.Debugf("Using the url of (%s) to mutate the ArgoCD Application", state.GitServer.Address) - // parse to simple struct to read the git url - src := &ArgoApplication{} - - if err = json.Unmarshal(r.Object.Raw, &src); err != nil { + app := Application{} + if err = json.Unmarshal(r.Object.Raw, &app); err != nil { return nil, fmt.Errorf(lang.ErrUnmarshal, err) } message.Debugf("Data %v", string(r.Object.Raw)) - if src.Spec.Source != (Source{}) { - patchedURL, _ := getPatchedRepoURL(src.Spec.Source.RepoURL, state.GitServer) + patches := []operations.PatchOperation{} + + if app.Spec.Source != nil { + patchedURL, err := getPatchedRepoURL(app.Spec.Source.RepoURL, state.GitServer, r) + if err != nil { + return nil, err + } patches = populateSingleSourceArgoApplicationPatchOperations(patchedURL, patches) } - if len(src.Spec.Sources) > 0 { - for idx, source := range src.Spec.Sources { - patchedURL, _ := getPatchedRepoURL(source.RepoURL, state.GitServer) + if len(app.Spec.Sources) > 0 { + for idx, source := range app.Spec.Sources { + patchedURL, err := getPatchedRepoURL(source.RepoURL, state.GitServer, r) + if err != nil { + return nil, err + } patches = populateMultipleSourceArgoApplicationPatchOperations(idx, patchedURL, patches) } } @@ -94,9 +102,12 @@ func mutateApplication(ctx context.Context, r *v1.AdmissionRequest, cluster *clu }, nil } -func getPatchedRepoURL(repoURL string, gs types.GitServerInfo) (string, error) { - var err error +func getPatchedRepoURL(repoURL string, gs types.GitServerInfo, r *v1.AdmissionRequest) (string, error) { + isCreate := r.Operation == v1.Create + isUpdate := r.Operation == v1.Update patchedURL := repoURL + var isPatched bool + var err error // Check if this is an update operation and the hostname is different from what we have in the zarfState // NOTE: We mutate on updates IF AND ONLY IF the hostname in the request is different from the hostname in the zarfState @@ -113,13 +124,13 @@ func getPatchedRepoURL(repoURL string, gs types.GitServerInfo) (string, error) { // Mutate the git URL so that the hostname matches the hostname in the Zarf state transformedURL, err := transform.GitURL(gs.Address, patchedURL, gs.PushUsername) if err != nil { - message.Warnf("Unable to transform the repoURL, using the original url we have: %s", patchedURL) + return "", fmt.Errorf("unable to transform the git url: %w", err) } patchedURL = transformedURL.String() message.Debugf("original repoURL of (%s) got mutated to (%s)", repoURL, patchedURL) } - return patchedURL, err + return patchedURL, nil } // Patch updates of the Argo source spec. diff --git a/src/internal/agent/hooks/argocd-repository.go b/src/internal/agent/hooks/argocd-repository.go index eb56e9d215..5701a8a5a5 100644 --- a/src/internal/agent/hooks/argocd-repository.go +++ b/src/internal/agent/hooks/argocd-repository.go @@ -86,7 +86,7 @@ func mutateRepository(ctx context.Context, r *v1.AdmissionRequest, cluster *clus // Mutate the git URL so that the hostname matches the hostname in the Zarf state transformedURL, err := transform.GitURL(state.GitServer.Address, patchedURL, state.GitServer.PushUsername) if err != nil { - message.Warnf("Unable to transform the url, using the original url we have: %s", patchedURL) + return nil, fmt.Errorf("unable to transform the git url: %w", err) } patchedURL = transformedURL.String() message.Debugf("original url of (%s) got mutated to (%s)", src.Data.URL, patchedURL) From 3ae4ec4b314c291a5b52298c80aad729b21afc94 Mon Sep 17 00:00:00 2001 From: Lucas Rodriguez Date: Tue, 21 May 2024 01:06:22 -0500 Subject: [PATCH 17/45] use upstream types for argo mutations --- .../agent/hooks/argocd-application.go | 2 +- src/internal/agent/hooks/argocd-repository.go | 70 +++++++++---------- src/internal/agent/http/server.go | 2 +- 3 files changed, 37 insertions(+), 37 deletions(-) diff --git a/src/internal/agent/hooks/argocd-application.go b/src/internal/agent/hooks/argocd-application.go index 2096a2381f..466f382e85 100644 --- a/src/internal/agent/hooks/argocd-application.go +++ b/src/internal/agent/hooks/argocd-application.go @@ -20,7 +20,7 @@ import ( ) // Application is a definition of an ArgoCD Application resource. -// The ArgoCD Application structs in this file have been copied from upstream. +// The ArgoCD Application structs in this file have been partially copied from upstream. // // https://github.com/argoproj/argo-cd/blob/v2.11.0/pkg/apis/application/v1alpha1/types.go // diff --git a/src/internal/agent/hooks/argocd-repository.go b/src/internal/agent/hooks/argocd-repository.go index 5701a8a5a5..2bb0fb7daf 100644 --- a/src/internal/agent/hooks/argocd-repository.go +++ b/src/internal/agent/hooks/argocd-repository.go @@ -18,38 +18,40 @@ import ( "github.com/defenseunicorns/zarf/src/pkg/transform" "github.com/defenseunicorns/zarf/src/types" v1 "k8s.io/api/admission/v1" + corev1 "k8s.io/api/core/v1" ) -// ArgoRepository represents a subset of the Argo Repository object needed for Zarf Git URL mutations -type ArgoRepository struct { - Data struct { - URL string `json:"url"` - } +// RepoCreds holds the definition for repository credentials. +// This has been partially copied from upstream. +// +// https://github.com/argoproj/argo-cd/blob/v2.11.0/pkg/apis/application/v1alpha1/repository_types.go +// +// There were errors encountered when trying to import argocd as a Go package. +// +// For more information: https://argo-cd.readthedocs.io/en/stable/user-guide/import/ +type RepoCreds struct { + // URL is the URL that this credential matches to. + URL string `json:"url"` } -// NewRepositoryMutationHook creates a new instance of the ArgoCD Repository mutation hook. -func NewRepositoryMutationHook(ctx context.Context, cluster *cluster.Cluster) operations.Hook { +// NewRepositorySecretMutationHook creates a new instance of the ArgoCD repository secret mutation hook. +func NewRepositorySecretMutationHook(ctx context.Context, cluster *cluster.Cluster) operations.Hook { message.Debug("hooks.NewRepositoryMutationHook()") return operations.Hook{ Create: func(r *v1.AdmissionRequest) (*operations.Result, error) { - return mutateRepository(ctx, r, cluster) + return mutateRepositorySecret(ctx, r, cluster) }, Update: func(r *v1.AdmissionRequest) (*operations.Result, error) { - return mutateRepository(ctx, r, cluster) + return mutateRepositorySecret(ctx, r, cluster) }, } } -// mutateRepository mutates the git repository URL to point to the repository URL defined in the ZarfState. -func mutateRepository(ctx context.Context, r *v1.AdmissionRequest, cluster *cluster.Cluster) (result *operations.Result, err error) { - - var ( - patches []operations.PatchOperation - isPatched bool - - isCreate = r.Operation == v1.Create - isUpdate = r.Operation == v1.Update - ) +// mutateRepositorySecret mutates the git URL in the ArgoCD repository secret to point to the repository URL defined in the ZarfState. +func mutateRepositorySecret(ctx context.Context, r *v1.AdmissionRequest, cluster *cluster.Cluster) (result *operations.Result, err error) { + isCreate := r.Operation == v1.Create + isUpdate := r.Operation == v1.Update + var isPatched bool state, err := cluster.LoadZarfState(ctx) if err != nil { @@ -58,46 +60,44 @@ func mutateRepository(ctx context.Context, r *v1.AdmissionRequest, cluster *clus message.Debugf("Using the url of (%s) to mutate the ArgoCD Repository Secret", state.GitServer.Address) - // parse to simple struct to read the git url - src := &ArgoRepository{} - - if err = json.Unmarshal(r.Object.Raw, &src); err != nil { + secret := corev1.Secret{} + if err = json.Unmarshal(r.Object.Raw, &secret); err != nil { return nil, fmt.Errorf(lang.ErrUnmarshal, err) } - decodedURL, err := base64.StdEncoding.DecodeString(src.Data.URL) - if err != nil { - message.Fatalf("Error decoding URL from Repository Secret %s", src.Data.URL) + + url, exists := secret.Data["url"] + if !exists { + return nil, fmt.Errorf("url field not found in argocd repository secret data") } - src.Data.URL = string(decodedURL) - patchedURL := src.Data.URL + + var repoCreds RepoCreds + repoCreds.URL = string(url) // Check if this is an update operation and the hostname is different from what we have in the zarfState // NOTE: We mutate on updates IF AND ONLY IF the hostname in the request is different from the hostname in the zarfState // NOTE: We are checking if the hostname is different before because we do not want to potentially mutate a URL that has already been mutated. if isUpdate { - isPatched, err = helpers.DoHostnamesMatch(state.GitServer.Address, src.Data.URL) + isPatched, err = helpers.DoHostnamesMatch(state.GitServer.Address, repoCreds.URL) if err != nil { return nil, fmt.Errorf(lang.AgentErrHostnameMatch, err) } } + patchedURL := repoCreds.URL // Mutate the repoURL if necessary if isCreate || (isUpdate && !isPatched) { // Mutate the git URL so that the hostname matches the hostname in the Zarf state - transformedURL, err := transform.GitURL(state.GitServer.Address, patchedURL, state.GitServer.PushUsername) + transformedURL, err := transform.GitURL(state.GitServer.Address, repoCreds.URL, state.GitServer.PushUsername) if err != nil { return nil, fmt.Errorf("unable to transform the git url: %w", err) } patchedURL = transformedURL.String() - message.Debugf("original url of (%s) got mutated to (%s)", src.Data.URL, patchedURL) + message.Debugf("original url of (%s) got mutated to (%s)", repoCreds.URL, patchedURL) } - // Patch updates of the repo spec - patches = populateArgoRepositoryPatchOperations(patchedURL, state.GitServer.PullPassword) - return &operations.Result{ Allowed: true, - PatchOps: patches, + PatchOps: populateArgoRepositoryPatchOperations(patchedURL, state.GitServer.PullPassword), }, nil } diff --git a/src/internal/agent/http/server.go b/src/internal/agent/http/server.go index ca7524420c..49fa5a6e7e 100644 --- a/src/internal/agent/http/server.go +++ b/src/internal/agent/http/server.go @@ -32,7 +32,7 @@ func NewAdmissionServer(port string) *http.Server { podsMutation := hooks.NewPodMutationHook(ctx, c) fluxGitRepositoryMutation := hooks.NewGitRepositoryMutationHook(ctx, c) argocdApplicationMutation := hooks.NewApplicationMutationHook(ctx, c) - argocdRepositoryMutation := hooks.NewRepositoryMutationHook(ctx, c) + argocdRepositoryMutation := hooks.NewRepositorySecretMutationHook(ctx, c) // Routers ah := admission.NewHandler() From 32f88b014d7257cdfd2e7705988168d08ffab358 Mon Sep 17 00:00:00 2001 From: Austin Abro Date: Tue, 21 May 2024 13:37:41 +0000 Subject: [PATCH 18/45] happy path argo test --- examples/podinfo-flux/podinfo-source.yaml | 2 +- src/internal/agent/hooks/argo_repo_test.go | 105 ++++++++++++++++++ src/internal/agent/hooks/argocd-repository.go | 4 +- src/internal/agent/hooks/flux.go | 3 +- src/internal/agent/hooks/flux_test.go | 2 +- 5 files changed, 111 insertions(+), 5 deletions(-) create mode 100644 src/internal/agent/hooks/argo_repo_test.go diff --git a/examples/podinfo-flux/podinfo-source.yaml b/examples/podinfo-flux/podinfo-source.yaml index e42f85dce2..f0d84ff887 100644 --- a/examples/podinfo-flux/podinfo-source.yaml +++ b/examples/podinfo-flux/podinfo-source.yaml @@ -9,4 +9,4 @@ spec: ref: tag: 6.3.3 # Currently the Zarf Agent can only mutate urls that are proper URIs (i.e. scheme://host/repo) - url: https://github.com/stefanprodan/podinfo.git + url: http://bad-url diff --git a/src/internal/agent/hooks/argo_repo_test.go b/src/internal/agent/hooks/argo_repo_test.go new file mode 100644 index 0000000000..f108510282 --- /dev/null +++ b/src/internal/agent/hooks/argo_repo_test.go @@ -0,0 +1,105 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2021-Present The Zarf Authors + +package hooks + +import ( + "context" + b64 "encoding/base64" + "encoding/json" + "net/http" + "testing" + + "github.com/defenseunicorns/zarf/src/internal/agent/operations" + "github.com/defenseunicorns/zarf/src/pkg/cluster" + "github.com/defenseunicorns/zarf/src/pkg/k8s" + "github.com/defenseunicorns/zarf/src/types" + "github.com/stretchr/testify/require" + v1 "k8s.io/api/admission/v1" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/kubernetes/fake" +) + +func createArgoRepoAdmissionRequest(t *testing.T, op v1.Operation, argoRepo *corev1.Secret) *v1.AdmissionRequest { + t.Helper() + raw, err := json.Marshal(argoRepo) + require.NoError(t, err) + return &v1.AdmissionRequest{ + Operation: op, + Object: runtime.RawExtension{ + Raw: raw, + }, + } +} + +func TestArgoRepoWebhook(t *testing.T) { + t.Parallel() + + ctx := context.Background() + c := &cluster.Cluster{K8s: &k8s.K8s{Clientset: fake.NewSimpleClientset()}} + state := &types.ZarfState{GitServer: types.GitServerInfo{ + Address: "https://git-server.com", + PushUsername: "a-push-user", + PullUsername: "a-pull-password", + }} + handler := setupWebhookTest(ctx, t, c, state, NewRepositorySecretMutationHook) + + tests := []struct { + name string + admissionReq *v1.AdmissionRequest + expectedPatch []operations.PatchOperation + code int + }{ + { + name: "should be mutated", + admissionReq: createArgoRepoAdmissionRequest(t, v1.Create, &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{ + "argocd.argoproj.io/secret-type": "repository", + }, + Name: "argo-repo-secret", + Namespace: "argo", + }, + Data: map[string][]byte{ + "url": []byte("https://diff-git-server.com/podinfo"), + }, + }), + expectedPatch: []operations.PatchOperation{ + operations.ReplacePatchOperation( + "/data/url", + b64.StdEncoding.EncodeToString([]byte("https://git-server.com/a-push-user/podinfo-1868163476")), + ), + operations.ReplacePatchOperation( + "/data/username", + //TODO this should be different + b64.StdEncoding.EncodeToString([]byte("zarf-git-read-user")), + ), + operations.ReplacePatchOperation( + "/data/password", + //TODO this should be different + b64.StdEncoding.EncodeToString([]byte(state.GitServer.PullPassword)), + ), + }, + code: http.StatusOK, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + resp := sendAdmissionRequest(t, tt.admissionReq, handler, tt.code) + if tt.expectedPatch != nil { + expectedPatchJSON, err := json.Marshal(tt.expectedPatch) + require.NoError(t, err) + require.NotNil(t, resp) + require.True(t, resp.Allowed) + require.JSONEq(t, string(expectedPatchJSON), string(resp.Patch)) + } else if tt.code != http.StatusInternalServerError { + require.Empty(t, string(resp.Patch)) + } + }) + } +} diff --git a/src/internal/agent/hooks/argocd-repository.go b/src/internal/agent/hooks/argocd-repository.go index 2bb0fb7daf..7a2dcc154a 100644 --- a/src/internal/agent/hooks/argocd-repository.go +++ b/src/internal/agent/hooks/argocd-repository.go @@ -58,7 +58,7 @@ func mutateRepositorySecret(ctx context.Context, r *v1.AdmissionRequest, cluster return nil, fmt.Errorf(lang.AgentErrGetState, err) } - message.Debugf("Using the url of (%s) to mutate the ArgoCD Repository Secret", state.GitServer.Address) + message.Infof("Using the url of (%s) to mutate the ArgoCD Repository Secret", state.GitServer.Address) secret := corev1.Secret{} if err = json.Unmarshal(r.Object.Raw, &secret); err != nil { @@ -89,7 +89,7 @@ func mutateRepositorySecret(ctx context.Context, r *v1.AdmissionRequest, cluster // Mutate the git URL so that the hostname matches the hostname in the Zarf state transformedURL, err := transform.GitURL(state.GitServer.Address, repoCreds.URL, state.GitServer.PushUsername) if err != nil { - return nil, fmt.Errorf("unable to transform the git url: %w", err) + return nil, fmt.Errorf("unable the git url: %w", err) } patchedURL = transformedURL.String() message.Debugf("original url of (%s) got mutated to (%s)", repoCreds.URL, patchedURL) diff --git a/src/internal/agent/hooks/flux.go b/src/internal/agent/hooks/flux.go index 74af89d1a3..4f2500ad41 100644 --- a/src/internal/agent/hooks/flux.go +++ b/src/internal/agent/hooks/flux.go @@ -56,7 +56,6 @@ func mutateGitRepo(ctx context.Context, r *v1.AdmissionRequest, cluster *cluster if err = json.Unmarshal(r.Object.Raw, &repo); err != nil { return nil, fmt.Errorf(lang.ErrUnmarshal, err) } - patchedURL := repo.Spec.URL // Check if this is an update operation and the hostname is different from what we have in the zarfState // NOTE: We mutate on updates IF AND ONLY IF the hostname in the request is different than the hostname in the zarfState @@ -68,6 +67,8 @@ func mutateGitRepo(ctx context.Context, r *v1.AdmissionRequest, cluster *cluster } } + patchedURL := repo.Spec.URL + // Mutate the git URL if necessary if isCreate || (isUpdate && !isPatched) { // Mutate the git URL so that the hostname matches the hostname in the Zarf state diff --git a/src/internal/agent/hooks/flux_test.go b/src/internal/agent/hooks/flux_test.go index a05570787d..e9bd957457 100644 --- a/src/internal/agent/hooks/flux_test.go +++ b/src/internal/agent/hooks/flux_test.go @@ -122,7 +122,7 @@ func TestFluxMutationWebhook(t *testing.T) { code: http.StatusOK, }, { - name: "should not mutate on update if hostname matches", + name: "should patch to same url and update secret if hostname matches", admissionReq: createFluxGitRepoAdmissionRequest(t, v1.Update, &flux.GitRepository{ ObjectMeta: metav1.ObjectMeta{ Name: "no-mutate", From 1985894174c0260ca60eb9aac7e69502a69c21a2 Mon Sep 17 00:00:00 2001 From: Austin Abro Date: Tue, 21 May 2024 13:37:52 +0000 Subject: [PATCH 19/45] happy path argo test --- src/internal/agent/hooks/argo_repo_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/src/internal/agent/hooks/argo_repo_test.go b/src/internal/agent/hooks/argo_repo_test.go index f108510282..4a89687725 100644 --- a/src/internal/agent/hooks/argo_repo_test.go +++ b/src/internal/agent/hooks/argo_repo_test.go @@ -78,7 +78,6 @@ func TestArgoRepoWebhook(t *testing.T) { ), operations.ReplacePatchOperation( "/data/password", - //TODO this should be different b64.StdEncoding.EncodeToString([]byte(state.GitServer.PullPassword)), ), }, From 83b0bfc753bf2720aebd0d650c9b645086328eb5 Mon Sep 17 00:00:00 2001 From: Austin Abro Date: Tue, 21 May 2024 13:41:46 +0000 Subject: [PATCH 20/45] separating PR --- packages/zarf-agent/manifests/deployment.yaml | 6 + src/internal/agent/hooks/argo_repo_test.go | 104 ----------- src/internal/agent/hooks/flux_test.go | 167 ------------------ src/pkg/cluster/zarf_test.go | 7 +- 4 files changed, 9 insertions(+), 275 deletions(-) delete mode 100644 src/internal/agent/hooks/argo_repo_test.go delete mode 100644 src/internal/agent/hooks/flux_test.go diff --git a/packages/zarf-agent/manifests/deployment.yaml b/packages/zarf-agent/manifests/deployment.yaml index a8e481845f..a4113812f9 100644 --- a/packages/zarf-agent/manifests/deployment.yaml +++ b/packages/zarf-agent/manifests/deployment.yaml @@ -43,6 +43,9 @@ spec: - name: tls-certs mountPath: /etc/certs readOnly: true + - name: zarf-state + mountPath: /etc/zarf-state + readOnly: true # Required for OpenShift to mount k9s vendored directories - name: config mountPath: /.config @@ -52,6 +55,9 @@ spec: - name: tls-certs secret: secretName: agent-hook-tls + - name: zarf-state + secret: + secretName: zarf-state # Required for OpenShift to mount k9s vendored directories - name: config emptyDir: {} diff --git a/src/internal/agent/hooks/argo_repo_test.go b/src/internal/agent/hooks/argo_repo_test.go deleted file mode 100644 index 4a89687725..0000000000 --- a/src/internal/agent/hooks/argo_repo_test.go +++ /dev/null @@ -1,104 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// SPDX-FileCopyrightText: 2021-Present The Zarf Authors - -package hooks - -import ( - "context" - b64 "encoding/base64" - "encoding/json" - "net/http" - "testing" - - "github.com/defenseunicorns/zarf/src/internal/agent/operations" - "github.com/defenseunicorns/zarf/src/pkg/cluster" - "github.com/defenseunicorns/zarf/src/pkg/k8s" - "github.com/defenseunicorns/zarf/src/types" - "github.com/stretchr/testify/require" - v1 "k8s.io/api/admission/v1" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/client-go/kubernetes/fake" -) - -func createArgoRepoAdmissionRequest(t *testing.T, op v1.Operation, argoRepo *corev1.Secret) *v1.AdmissionRequest { - t.Helper() - raw, err := json.Marshal(argoRepo) - require.NoError(t, err) - return &v1.AdmissionRequest{ - Operation: op, - Object: runtime.RawExtension{ - Raw: raw, - }, - } -} - -func TestArgoRepoWebhook(t *testing.T) { - t.Parallel() - - ctx := context.Background() - c := &cluster.Cluster{K8s: &k8s.K8s{Clientset: fake.NewSimpleClientset()}} - state := &types.ZarfState{GitServer: types.GitServerInfo{ - Address: "https://git-server.com", - PushUsername: "a-push-user", - PullUsername: "a-pull-password", - }} - handler := setupWebhookTest(ctx, t, c, state, NewRepositorySecretMutationHook) - - tests := []struct { - name string - admissionReq *v1.AdmissionRequest - expectedPatch []operations.PatchOperation - code int - }{ - { - name: "should be mutated", - admissionReq: createArgoRepoAdmissionRequest(t, v1.Create, &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{ - "argocd.argoproj.io/secret-type": "repository", - }, - Name: "argo-repo-secret", - Namespace: "argo", - }, - Data: map[string][]byte{ - "url": []byte("https://diff-git-server.com/podinfo"), - }, - }), - expectedPatch: []operations.PatchOperation{ - operations.ReplacePatchOperation( - "/data/url", - b64.StdEncoding.EncodeToString([]byte("https://git-server.com/a-push-user/podinfo-1868163476")), - ), - operations.ReplacePatchOperation( - "/data/username", - //TODO this should be different - b64.StdEncoding.EncodeToString([]byte("zarf-git-read-user")), - ), - operations.ReplacePatchOperation( - "/data/password", - b64.StdEncoding.EncodeToString([]byte(state.GitServer.PullPassword)), - ), - }, - code: http.StatusOK, - }, - } - - for _, tt := range tests { - tt := tt - t.Run(tt.name, func(t *testing.T) { - t.Parallel() - resp := sendAdmissionRequest(t, tt.admissionReq, handler, tt.code) - if tt.expectedPatch != nil { - expectedPatchJSON, err := json.Marshal(tt.expectedPatch) - require.NoError(t, err) - require.NotNil(t, resp) - require.True(t, resp.Allowed) - require.JSONEq(t, string(expectedPatchJSON), string(resp.Patch)) - } else if tt.code != http.StatusInternalServerError { - require.Empty(t, string(resp.Patch)) - } - }) - } -} diff --git a/src/internal/agent/hooks/flux_test.go b/src/internal/agent/hooks/flux_test.go deleted file mode 100644 index e9bd957457..0000000000 --- a/src/internal/agent/hooks/flux_test.go +++ /dev/null @@ -1,167 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// SPDX-FileCopyrightText: 2021-Present The Zarf Authors - -package hooks - -import ( - "context" - "encoding/json" - "net/http" - "testing" - - "github.com/defenseunicorns/zarf/src/config" - "github.com/defenseunicorns/zarf/src/internal/agent/operations" - "github.com/defenseunicorns/zarf/src/pkg/cluster" - "github.com/defenseunicorns/zarf/src/pkg/k8s" - "github.com/defenseunicorns/zarf/src/types" - fluxmeta "github.com/fluxcd/pkg/apis/meta" - flux "github.com/fluxcd/source-controller/api/v1" - "github.com/stretchr/testify/require" - v1 "k8s.io/api/admission/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/client-go/kubernetes/fake" -) - -func createFluxGitRepoAdmissionRequest(t *testing.T, op v1.Operation, fluxGitRepo *flux.GitRepository) *v1.AdmissionRequest { - t.Helper() - raw, err := json.Marshal(fluxGitRepo) - require.NoError(t, err) - return &v1.AdmissionRequest{ - Operation: op, - Object: runtime.RawExtension{ - Raw: raw, - }, - } -} - -func TestFluxMutationWebhook(t *testing.T) { - t.Parallel() - - ctx := context.Background() - c := &cluster.Cluster{K8s: &k8s.K8s{Clientset: fake.NewSimpleClientset()}} - state := &types.ZarfState{GitServer: types.GitServerInfo{ - Address: "https://git-server.com", - PushUsername: "a-push-user", - }} - handler := setupWebhookTest(ctx, t, c, state, NewGitRepositoryMutationHook) - - tests := []struct { - name string - admissionReq *v1.AdmissionRequest - expectedPatch []operations.PatchOperation - code int - }{ - { - name: "should be mutated", - admissionReq: createFluxGitRepoAdmissionRequest(t, v1.Create, &flux.GitRepository{ - ObjectMeta: metav1.ObjectMeta{ - Name: "mutate-this", - }, - Spec: flux.GitRepositorySpec{ - URL: "https://github.com/stefanprodan/podinfo.git", - Reference: &flux.GitRepositoryRef{ - Tag: "6.4.0", - }, - }, - }), - expectedPatch: []operations.PatchOperation{ - operations.ReplacePatchOperation( - "/spec/url", - "https://git-server.com/a-push-user/podinfo-1646971829.git", - ), - operations.AddPatchOperation( - "/spec/secretRef", - fluxmeta.LocalObjectReference{Name: config.ZarfGitServerSecretName}, - ), - }, - code: http.StatusOK, - }, - { - name: "should not mutate invalid git url", - admissionReq: createFluxGitRepoAdmissionRequest(t, v1.Update, &flux.GitRepository{ - ObjectMeta: metav1.ObjectMeta{ - Name: "mutate-this", - }, - Spec: flux.GitRepositorySpec{ - URL: "not-a-git-url", - Reference: &flux.GitRepositoryRef{ - Tag: "6.4.0", - }, - }, - }), - expectedPatch: nil, - code: http.StatusInternalServerError, - }, - { - name: "should replace existing secret", - admissionReq: createFluxGitRepoAdmissionRequest(t, v1.Create, &flux.GitRepository{ - ObjectMeta: metav1.ObjectMeta{ - Name: "replace-secret", - }, - Spec: flux.GitRepositorySpec{ - URL: "https://github.com/stefanprodan/podinfo.git", - SecretRef: &fluxmeta.LocalObjectReference{ - Name: "existing-secret", - }, - Reference: &flux.GitRepositoryRef{ - Tag: "6.4.0", - }, - }, - }), - expectedPatch: []operations.PatchOperation{ - operations.ReplacePatchOperation( - "/spec/url", - "https://git-server.com/a-push-user/podinfo-1646971829.git", - ), - operations.ReplacePatchOperation( - "/spec/secretRef/name", - config.ZarfGitServerSecretName, - ), - }, - code: http.StatusOK, - }, - { - name: "should patch to same url and update secret if hostname matches", - admissionReq: createFluxGitRepoAdmissionRequest(t, v1.Update, &flux.GitRepository{ - ObjectMeta: metav1.ObjectMeta{ - Name: "no-mutate", - }, - Spec: flux.GitRepositorySpec{ - URL: "https://git-server.com/a-push-user/podinfo.git", - Reference: &flux.GitRepositoryRef{ - Tag: "6.4.0", - }, - }, - }), - expectedPatch: []operations.PatchOperation{ - operations.ReplacePatchOperation( - "/spec/url", - "https://git-server.com/a-push-user/podinfo.git", - ), - operations.AddPatchOperation( - "/spec/secretRef", - fluxmeta.LocalObjectReference{Name: config.ZarfGitServerSecretName}, - ), - }, - code: http.StatusOK, - }, - } - - for _, tt := range tests { - tt := tt - t.Run(tt.name, func(t *testing.T) { - t.Parallel() - resp := sendAdmissionRequest(t, tt.admissionReq, handler, tt.code) - if tt.expectedPatch != nil { - expectedPatchJSON, err := json.Marshal(tt.expectedPatch) - require.NoError(t, err) - require.NotNil(t, resp) - require.True(t, resp.Allowed) - require.JSONEq(t, string(expectedPatchJSON), string(resp.Patch)) - } else if tt.code != http.StatusInternalServerError { - require.Empty(t, string(resp.Patch)) - } - }) - } -} diff --git a/src/pkg/cluster/zarf_test.go b/src/pkg/cluster/zarf_test.go index b9451f3e72..0d40f58bfa 100644 --- a/src/pkg/cluster/zarf_test.go +++ b/src/pkg/cluster/zarf_test.go @@ -213,9 +213,6 @@ func TestGetDeployedPackage(t *testing.T) { for _, p := range packages { b, err := json.Marshal(p) require.NoError(t, err) - data := map[string][]byte{ - "data": b, - } secret := corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ Name: strings.Join([]string{config.ZarfPackagePrefix, p.Name}, ""), @@ -224,7 +221,9 @@ func TestGetDeployedPackage(t *testing.T) { ZarfPackageInfoLabel: p.Name, }, }, - Data: data, + Data: map[string][]byte{ + "data": b, + }, } c.Clientset.CoreV1().Secrets("zarf").Create(ctx, &secret, metav1.CreateOptions{}) actual, err := c.GetDeployedPackage(ctx, p.Name) From 0940c58677d81a71fd5ef313c30828708bd85481 Mon Sep 17 00:00:00 2001 From: Austin Abro Date: Tue, 21 May 2024 13:49:11 +0000 Subject: [PATCH 21/45] pod pod pod --- .../agent/hooks/argocd-application.go | 105 ++++++++---------- src/internal/agent/hooks/argocd-repository.go | 86 +++++++------- src/internal/agent/hooks/flux.go | 65 ++++++----- src/internal/agent/http/server.go | 6 +- src/pkg/transform/git.go | 6 +- 5 files changed, 127 insertions(+), 141 deletions(-) diff --git a/src/internal/agent/hooks/argocd-application.go b/src/internal/agent/hooks/argocd-application.go index 466f382e85..260b1d53a3 100644 --- a/src/internal/agent/hooks/argocd-application.go +++ b/src/internal/agent/hooks/argocd-application.go @@ -5,93 +5,81 @@ package hooks import ( - "context" "encoding/json" "fmt" "github.com/defenseunicorns/pkg/helpers" "github.com/defenseunicorns/zarf/src/config/lang" "github.com/defenseunicorns/zarf/src/internal/agent/operations" - "github.com/defenseunicorns/zarf/src/pkg/cluster" + "github.com/defenseunicorns/zarf/src/internal/agent/state" "github.com/defenseunicorns/zarf/src/pkg/message" "github.com/defenseunicorns/zarf/src/pkg/transform" "github.com/defenseunicorns/zarf/src/types" v1 "k8s.io/api/admission/v1" ) -// Application is a definition of an ArgoCD Application resource. -// The ArgoCD Application structs in this file have been partially copied from upstream. -// -// https://github.com/argoproj/argo-cd/blob/v2.11.0/pkg/apis/application/v1alpha1/types.go -// -// There were errors encountered when trying to import argocd as a Go package. -// -// For more information: https://argo-cd.readthedocs.io/en/stable/user-guide/import/ -type Application struct { - Spec ApplicationSpec `json:"spec"` -} - -// ApplicationSpec represents desired application state. Contains link to repository with application definition. -type ApplicationSpec struct { - // Source is a reference to the location of the application's manifests or chart. - Source *ApplicationSource `json:"source,omitempty"` - Sources ApplicationSources `json:"sources,omitempty"` +// Source represents a subset of the Argo Source object needed for Zarf Git URL mutations +type Source struct { + RepoURL string `json:"repoURL"` } -// ApplicationSource contains all required information about the source of an application. -type ApplicationSource struct { - // RepoURL is the URL to the repository (Git or Helm) that contains the application manifests. - RepoURL string `json:"repoURL"` +// ArgoApplication represents a subset of the Argo Application object needed for Zarf Git URL mutations +type ArgoApplication struct { + Spec struct { + Source Source `json:"source"` + Sources []Source `json:"sources"` + } `json:"spec"` } -// ApplicationSources contains list of required information about the sources of an application. -type ApplicationSources []ApplicationSource +var ( + zarfState *types.ZarfState + patches []operations.PatchOperation + isPatched bool + isCreate bool + isUpdate bool +) // NewApplicationMutationHook creates a new instance of the ArgoCD Application mutation hook. -func NewApplicationMutationHook(ctx context.Context, cluster *cluster.Cluster) operations.Hook { +func NewApplicationMutationHook() operations.Hook { message.Debug("hooks.NewApplicationMutationHook()") return operations.Hook{ - Create: func(r *v1.AdmissionRequest) (*operations.Result, error) { - return mutateApplication(ctx, r, cluster) - }, - Update: func(r *v1.AdmissionRequest) (*operations.Result, error) { - return mutateApplication(ctx, r, cluster) - }, + Create: mutateApplication, + Update: mutateApplication, } } // mutateApplication mutates the git repository url to point to the repository URL defined in the ZarfState. -func mutateApplication(ctx context.Context, r *v1.AdmissionRequest, cluster *cluster.Cluster) (result *operations.Result, err error) { - state, err := cluster.LoadZarfState(ctx) - if err != nil { +func mutateApplication(r *v1.AdmissionRequest) (result *operations.Result, err error) { + + isCreate = r.Operation == v1.Create + isUpdate = r.Operation == v1.Update + + patches = []operations.PatchOperation{} + + // Form the zarfState.GitServer.Address from the zarfState + if zarfState, err = state.GetZarfStateFromAgentPod(); err != nil { return nil, fmt.Errorf(lang.AgentErrGetState, err) } - message.Debugf("Using the url of (%s) to mutate the ArgoCD Application", state.GitServer.Address) + message.Debugf("Using the url of (%s) to mutate the ArgoCD Application", zarfState.GitServer.Address) + + // parse to simple struct to read the git url + src := &ArgoApplication{} - app := Application{} - if err = json.Unmarshal(r.Object.Raw, &app); err != nil { + if err = json.Unmarshal(r.Object.Raw, &src); err != nil { return nil, fmt.Errorf(lang.ErrUnmarshal, err) } message.Debugf("Data %v", string(r.Object.Raw)) - patches := []operations.PatchOperation{} - - if app.Spec.Source != nil { - patchedURL, err := getPatchedRepoURL(app.Spec.Source.RepoURL, state.GitServer, r) - if err != nil { - return nil, err - } + if src.Spec.Source != (Source{}) { + patchedURL, _ := getPatchedRepoURL(src.Spec.Source.RepoURL) patches = populateSingleSourceArgoApplicationPatchOperations(patchedURL, patches) } - if len(app.Spec.Sources) > 0 { - for idx, source := range app.Spec.Sources { - patchedURL, err := getPatchedRepoURL(source.RepoURL, state.GitServer, r) - if err != nil { - return nil, err - } + if len(src.Spec.Sources) > 0 { + for idx, source := range src.Spec.Sources { + patchedURL, _ := getPatchedRepoURL(source.RepoURL) patches = populateMultipleSourceArgoApplicationPatchOperations(idx, patchedURL, patches) } } @@ -102,18 +90,15 @@ func mutateApplication(ctx context.Context, r *v1.AdmissionRequest, cluster *clu }, nil } -func getPatchedRepoURL(repoURL string, gs types.GitServerInfo, r *v1.AdmissionRequest) (string, error) { - isCreate := r.Operation == v1.Create - isUpdate := r.Operation == v1.Update - patchedURL := repoURL - var isPatched bool +func getPatchedRepoURL(repoURL string) (string, error) { var err error + patchedURL := repoURL // Check if this is an update operation and the hostname is different from what we have in the zarfState // NOTE: We mutate on updates IF AND ONLY IF the hostname in the request is different from the hostname in the zarfState // NOTE: We are checking if the hostname is different before because we do not want to potentially mutate a URL that has already been mutated. if isUpdate { - isPatched, err = helpers.DoHostnamesMatch(gs.Address, repoURL) + isPatched, err = helpers.DoHostnamesMatch(zarfState.GitServer.Address, repoURL) if err != nil { return "", fmt.Errorf(lang.AgentErrHostnameMatch, err) } @@ -122,15 +107,15 @@ func getPatchedRepoURL(repoURL string, gs types.GitServerInfo, r *v1.AdmissionRe // Mutate the repoURL if necessary if isCreate || (isUpdate && !isPatched) { // Mutate the git URL so that the hostname matches the hostname in the Zarf state - transformedURL, err := transform.GitURL(gs.Address, patchedURL, gs.PushUsername) + transformedURL, err := transform.GitURL(zarfState.GitServer.Address, patchedURL, zarfState.GitServer.PushUsername) if err != nil { - return "", fmt.Errorf("unable to transform the git url: %w", err) + message.Warnf("Unable to transform the repoURL, using the original url we have: %s", patchedURL) } patchedURL = transformedURL.String() message.Debugf("original repoURL of (%s) got mutated to (%s)", repoURL, patchedURL) } - return patchedURL, nil + return patchedURL, err } // Patch updates of the Argo source spec. diff --git a/src/internal/agent/hooks/argocd-repository.go b/src/internal/agent/hooks/argocd-repository.go index 7a2dcc154a..a9b74cd72f 100644 --- a/src/internal/agent/hooks/argocd-repository.go +++ b/src/internal/agent/hooks/argocd-repository.go @@ -5,7 +5,6 @@ package hooks import ( - "context" "encoding/base64" "encoding/json" "fmt" @@ -13,91 +12,88 @@ import ( "github.com/defenseunicorns/pkg/helpers" "github.com/defenseunicorns/zarf/src/config/lang" "github.com/defenseunicorns/zarf/src/internal/agent/operations" - "github.com/defenseunicorns/zarf/src/pkg/cluster" + "github.com/defenseunicorns/zarf/src/internal/agent/state" "github.com/defenseunicorns/zarf/src/pkg/message" "github.com/defenseunicorns/zarf/src/pkg/transform" "github.com/defenseunicorns/zarf/src/types" v1 "k8s.io/api/admission/v1" - corev1 "k8s.io/api/core/v1" ) -// RepoCreds holds the definition for repository credentials. -// This has been partially copied from upstream. -// -// https://github.com/argoproj/argo-cd/blob/v2.11.0/pkg/apis/application/v1alpha1/repository_types.go -// -// There were errors encountered when trying to import argocd as a Go package. -// -// For more information: https://argo-cd.readthedocs.io/en/stable/user-guide/import/ -type RepoCreds struct { - // URL is the URL that this credential matches to. - URL string `json:"url"` +// ArgoRepository represents a subset of the Argo Repository object needed for Zarf Git URL mutations +type ArgoRepository struct { + Data struct { + URL string `json:"url"` + } } -// NewRepositorySecretMutationHook creates a new instance of the ArgoCD repository secret mutation hook. -func NewRepositorySecretMutationHook(ctx context.Context, cluster *cluster.Cluster) operations.Hook { +// NewRepositoryMutationHook creates a new instance of the ArgoCD Repository mutation hook. +func NewRepositoryMutationHook() operations.Hook { message.Debug("hooks.NewRepositoryMutationHook()") return operations.Hook{ - Create: func(r *v1.AdmissionRequest) (*operations.Result, error) { - return mutateRepositorySecret(ctx, r, cluster) - }, - Update: func(r *v1.AdmissionRequest) (*operations.Result, error) { - return mutateRepositorySecret(ctx, r, cluster) - }, + Create: mutateRepository, + Update: mutateRepository, } } -// mutateRepositorySecret mutates the git URL in the ArgoCD repository secret to point to the repository URL defined in the ZarfState. -func mutateRepositorySecret(ctx context.Context, r *v1.AdmissionRequest, cluster *cluster.Cluster) (result *operations.Result, err error) { - isCreate := r.Operation == v1.Create - isUpdate := r.Operation == v1.Update - var isPatched bool +// mutateRepository mutates the git repository URL to point to the repository URL defined in the ZarfState. +func mutateRepository(r *v1.AdmissionRequest) (result *operations.Result, err error) { - state, err := cluster.LoadZarfState(ctx) - if err != nil { + var ( + zarfState *types.ZarfState + patches []operations.PatchOperation + isPatched bool + + isCreate = r.Operation == v1.Create + isUpdate = r.Operation == v1.Update + ) + + // Form the zarfState.GitServer.Address from the zarfState + if zarfState, err = state.GetZarfStateFromAgentPod(); err != nil { return nil, fmt.Errorf(lang.AgentErrGetState, err) } - message.Infof("Using the url of (%s) to mutate the ArgoCD Repository Secret", state.GitServer.Address) + message.Debugf("Using the url of (%s) to mutate the ArgoCD Repository Secret", zarfState.GitServer.Address) + + // parse to simple struct to read the git url + src := &ArgoRepository{} - secret := corev1.Secret{} - if err = json.Unmarshal(r.Object.Raw, &secret); err != nil { + if err = json.Unmarshal(r.Object.Raw, &src); err != nil { return nil, fmt.Errorf(lang.ErrUnmarshal, err) } - - url, exists := secret.Data["url"] - if !exists { - return nil, fmt.Errorf("url field not found in argocd repository secret data") + decodedURL, err := base64.StdEncoding.DecodeString(src.Data.URL) + if err != nil { + message.Fatalf("Error decoding URL from Repository Secret %s", src.Data.URL) } - - var repoCreds RepoCreds - repoCreds.URL = string(url) + src.Data.URL = string(decodedURL) + patchedURL := src.Data.URL // Check if this is an update operation and the hostname is different from what we have in the zarfState // NOTE: We mutate on updates IF AND ONLY IF the hostname in the request is different from the hostname in the zarfState // NOTE: We are checking if the hostname is different before because we do not want to potentially mutate a URL that has already been mutated. if isUpdate { - isPatched, err = helpers.DoHostnamesMatch(state.GitServer.Address, repoCreds.URL) + isPatched, err = helpers.DoHostnamesMatch(zarfState.GitServer.Address, src.Data.URL) if err != nil { return nil, fmt.Errorf(lang.AgentErrHostnameMatch, err) } } - patchedURL := repoCreds.URL // Mutate the repoURL if necessary if isCreate || (isUpdate && !isPatched) { // Mutate the git URL so that the hostname matches the hostname in the Zarf state - transformedURL, err := transform.GitURL(state.GitServer.Address, repoCreds.URL, state.GitServer.PushUsername) + transformedURL, err := transform.GitURL(zarfState.GitServer.Address, patchedURL, zarfState.GitServer.PushUsername) if err != nil { - return nil, fmt.Errorf("unable the git url: %w", err) + message.Warnf("Unable to transform the url, using the original url we have: %s", patchedURL) } patchedURL = transformedURL.String() - message.Debugf("original url of (%s) got mutated to (%s)", repoCreds.URL, patchedURL) + message.Debugf("original url of (%s) got mutated to (%s)", src.Data.URL, patchedURL) } + // Patch updates of the repo spec + patches = populateArgoRepositoryPatchOperations(patchedURL, zarfState.GitServer.PullPassword) + return &operations.Result{ Allowed: true, - PatchOps: populateArgoRepositoryPatchOperations(patchedURL, state.GitServer.PullPassword), + PatchOps: patches, }, nil } diff --git a/src/internal/agent/hooks/flux.go b/src/internal/agent/hooks/flux.go index 4f2500ad41..c4efce1846 100644 --- a/src/internal/agent/hooks/flux.go +++ b/src/internal/agent/hooks/flux.go @@ -5,7 +5,6 @@ package hooks import ( - "context" "encoding/json" "fmt" @@ -13,31 +12,40 @@ import ( "github.com/defenseunicorns/zarf/src/config" "github.com/defenseunicorns/zarf/src/config/lang" "github.com/defenseunicorns/zarf/src/internal/agent/operations" - "github.com/defenseunicorns/zarf/src/pkg/cluster" + "github.com/defenseunicorns/zarf/src/internal/agent/state" "github.com/defenseunicorns/zarf/src/pkg/message" "github.com/defenseunicorns/zarf/src/pkg/transform" - fluxmeta "github.com/fluxcd/pkg/apis/meta" - flux "github.com/fluxcd/source-controller/api/v1" + "github.com/defenseunicorns/zarf/src/types" v1 "k8s.io/api/admission/v1" ) +// SecretRef contains the name used to reference a git repository secret. +type SecretRef struct { + Name string `json:"name"` +} + +// GenericGitRepo contains the URL of a git repo and the secret that corresponds to it for use with Flux. +type GenericGitRepo struct { + Spec struct { + URL string `json:"url"` + SecretRef SecretRef `json:"secretRef,omitempty"` + } `json:"spec"` +} + // NewGitRepositoryMutationHook creates a new instance of the git repo mutation hook. -func NewGitRepositoryMutationHook(ctx context.Context, cluster *cluster.Cluster) operations.Hook { +func NewGitRepositoryMutationHook() operations.Hook { message.Debug("hooks.NewGitRepositoryMutationHook()") return operations.Hook{ - Create: func(r *v1.AdmissionRequest) (*operations.Result, error) { - return mutateGitRepo(ctx, r, cluster) - }, - Update: func(r *v1.AdmissionRequest) (*operations.Result, error) { - return mutateGitRepo(ctx, r, cluster) - }, + Create: mutateGitRepo, + Update: mutateGitRepo, } } // mutateGitRepoCreate mutates the git repository url to point to the repository URL defined in the ZarfState. -func mutateGitRepo(ctx context.Context, r *v1.AdmissionRequest, cluster *cluster.Cluster) (result *operations.Result, err error) { +func mutateGitRepo(r *v1.AdmissionRequest) (result *operations.Result, err error) { var ( + zarfState *types.ZarfState patches []operations.PatchOperation isPatched bool @@ -45,43 +53,43 @@ func mutateGitRepo(ctx context.Context, r *v1.AdmissionRequest, cluster *cluster isUpdate = r.Operation == v1.Update ) - state, err := cluster.LoadZarfState(ctx) - if err != nil { + // Form the zarfState.GitServer.Address from the zarfState + if zarfState, err = state.GetZarfStateFromAgentPod(); err != nil { return nil, fmt.Errorf(lang.AgentErrGetState, err) } - message.Debugf("Using the url of (%s) to mutate the flux repository", state.GitServer.Address) + message.Debugf("Using the url of (%s) to mutate the flux repository", zarfState.GitServer.Address) - repo := flux.GitRepository{} - if err = json.Unmarshal(r.Object.Raw, &repo); err != nil { + // parse to simple struct to read the git url + src := &GenericGitRepo{} + if err = json.Unmarshal(r.Object.Raw, &src); err != nil { return nil, fmt.Errorf(lang.ErrUnmarshal, err) } + patchedURL := src.Spec.URL // Check if this is an update operation and the hostname is different from what we have in the zarfState // NOTE: We mutate on updates IF AND ONLY IF the hostname in the request is different than the hostname in the zarfState // NOTE: We are checking if the hostname is different before because we do not want to potentially mutate a URL that has already been mutated. if isUpdate { - isPatched, err = helpers.DoHostnamesMatch(state.GitServer.Address, repo.Spec.URL) + isPatched, err = helpers.DoHostnamesMatch(zarfState.GitServer.Address, src.Spec.URL) if err != nil { return nil, fmt.Errorf(lang.AgentErrHostnameMatch, err) } } - patchedURL := repo.Spec.URL - // Mutate the git URL if necessary if isCreate || (isUpdate && !isPatched) { // Mutate the git URL so that the hostname matches the hostname in the Zarf state - transformedURL, err := transform.GitURL(state.GitServer.Address, patchedURL, state.GitServer.PushUsername) + transformedURL, err := transform.GitURL(zarfState.GitServer.Address, patchedURL, zarfState.GitServer.PushUsername) if err != nil { - return nil, fmt.Errorf("unable to transform the git url: %w", err) + message.Warnf("Unable to transform the git url, using the original url we have: %s", patchedURL) } patchedURL = transformedURL.String() - message.Debugf("original git URL of (%s) got mutated to (%s)", repo.Spec.URL, patchedURL) + message.Debugf("original git URL of (%s) got mutated to (%s)", src.Spec.URL, patchedURL) } // Patch updates of the repo spec - patches = populatePatchOperations(patchedURL, repo.Spec.SecretRef) + patches = populatePatchOperations(patchedURL, src.Spec.SecretRef.Name) return &operations.Result{ Allowed: true, @@ -90,19 +98,16 @@ func mutateGitRepo(ctx context.Context, r *v1.AdmissionRequest, cluster *cluster } // Patch updates of the repo spec. -func populatePatchOperations(repoURL string, secretRef *fluxmeta.LocalObjectReference) []operations.PatchOperation { +func populatePatchOperations(repoURL string, secretName string) []operations.PatchOperation { var patches []operations.PatchOperation patches = append(patches, operations.ReplacePatchOperation("/spec/url", repoURL)) - //TODO This logic can be simplified - // If a prior secret exists, replace it - if secretRef != nil && secretRef.Name != "" { + if secretName != "" { patches = append(patches, operations.ReplacePatchOperation("/spec/secretRef/name", config.ZarfGitServerSecretName)) } else { // Otherwise, add the new secret - newSecretRef := fluxmeta.LocalObjectReference{Name: config.ZarfGitServerSecretName} - patches = append(patches, operations.AddPatchOperation("/spec/secretRef", newSecretRef)) + patches = append(patches, operations.AddPatchOperation("/spec/secretRef", SecretRef{Name: config.ZarfGitServerSecretName})) } return patches diff --git a/src/internal/agent/http/server.go b/src/internal/agent/http/server.go index 49fa5a6e7e..7b07b1445d 100644 --- a/src/internal/agent/http/server.go +++ b/src/internal/agent/http/server.go @@ -30,9 +30,9 @@ func NewAdmissionServer(port string) *http.Server { // Instances hooks podsMutation := hooks.NewPodMutationHook(ctx, c) - fluxGitRepositoryMutation := hooks.NewGitRepositoryMutationHook(ctx, c) - argocdApplicationMutation := hooks.NewApplicationMutationHook(ctx, c) - argocdRepositoryMutation := hooks.NewRepositorySecretMutationHook(ctx, c) + fluxGitRepositoryMutation := hooks.NewGitRepositoryMutationHook() + argocdApplicationMutation := hooks.NewApplicationMutationHook() + argocdRepositoryMutation := hooks.NewRepositoryMutationHook() // Routers ah := admission.NewHandler() diff --git a/src/pkg/transform/git.go b/src/pkg/transform/git.go index 4659421ba0..3710579ab2 100644 --- a/src/pkg/transform/git.go +++ b/src/pkg/transform/git.go @@ -34,7 +34,7 @@ func GitURLSplitRef(sourceURL string) (string, string, error) { get, err := helpers.MatchRegex(gitURLRegex, sourceURL) if err != nil { - return "", "", fmt.Errorf("unable to extract the repo name from the url %q", sourceURL) + return "", "", fmt.Errorf("unable to get extract the source url and ref from the url %s", sourceURL) } gitURLNoRef := fmt.Sprintf("%s%s/%s%s", get("proto"), get("hostPath"), get("repo"), get("git")) @@ -49,7 +49,7 @@ func GitURLtoFolderName(sourceURL string) (string, error) { if err != nil { // Unable to find a substring match for the regex - return "", fmt.Errorf("unable to extract the repo name from the url %q", sourceURL) + return "", fmt.Errorf("unable to get extract the folder name from the url %s", sourceURL) } repoName := get("repo") @@ -70,7 +70,7 @@ func GitURLtoRepoName(sourceURL string) (string, error) { if err != nil { // Unable to find a substring match for the regex - return "", fmt.Errorf("unable to extract the repo name from the url %q", sourceURL) + return "", fmt.Errorf("unable to get extract the repo name from the url %s", sourceURL) } repoName := get("repo") From 093bd368501549643dea04cba0826adf377bc5a7 Mon Sep 17 00:00:00 2001 From: Austin Abro Date: Tue, 21 May 2024 13:54:35 +0000 Subject: [PATCH 22/45] refactor --- go.mod | 2 +- src/internal/agent/hooks/pods_test.go | 7 +------ 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/go.mod b/go.mod index 71368adc16..9fe6c7a968 100644 --- a/go.mod +++ b/go.mod @@ -227,7 +227,7 @@ require ( github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fluxcd/pkg/apis/acl v0.1.0 // indirect github.com/fluxcd/pkg/apis/kustomize v1.3.0 // indirect - github.com/fluxcd/pkg/apis/meta v1.3.0 + github.com/fluxcd/pkg/apis/meta v1.3.0 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/fvbommel/sortorder v1.1.0 // indirect github.com/gabriel-vasile/mimetype v1.4.3 // indirect diff --git a/src/internal/agent/hooks/pods_test.go b/src/internal/agent/hooks/pods_test.go index 93516b2360..6d9468ce18 100644 --- a/src/internal/agent/hooks/pods_test.go +++ b/src/internal/agent/hooks/pods_test.go @@ -129,11 +129,6 @@ func TestPodMutationWebhook(t *testing.T) { }, code: http.StatusOK, }, - { - name: "empty pod", - admissionReq: createPodAdmissionRequest(t, v1.Create, nil), - code: http.StatusOK, - }, } for _, tt := range tests { @@ -147,7 +142,7 @@ func TestPodMutationWebhook(t *testing.T) { require.NotNil(t, resp) require.True(t, resp.Allowed) require.JSONEq(t, string(expectedPatchJSON), string(resp.Patch)) - } else if tt.code != http.StatusInternalServerError { + } else { require.Empty(t, string(resp.Patch)) } }) From 73eb95078ed23a4975ff4d1c037f849ee1586618 Mon Sep 17 00:00:00 2001 From: Austin Abro Date: Tue, 21 May 2024 13:56:01 +0000 Subject: [PATCH 23/45] fix source --- examples/podinfo-flux/podinfo-source.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/podinfo-flux/podinfo-source.yaml b/examples/podinfo-flux/podinfo-source.yaml index f0d84ff887..e42f85dce2 100644 --- a/examples/podinfo-flux/podinfo-source.yaml +++ b/examples/podinfo-flux/podinfo-source.yaml @@ -9,4 +9,4 @@ spec: ref: tag: 6.3.3 # Currently the Zarf Agent can only mutate urls that are proper URIs (i.e. scheme://host/repo) - url: http://bad-url + url: https://github.com/stefanprodan/podinfo.git From 70644ad14b8b39ff9cb0301943fe5449cbccca5f Mon Sep 17 00:00:00 2001 From: Austin Abro Date: Tue, 21 May 2024 14:03:51 +0000 Subject: [PATCH 24/45] test --- src/internal/agent/hooks/pods_test.go | 9 ++++----- src/internal/agent/hooks/utils_test.go | 18 +++++------------- 2 files changed, 9 insertions(+), 18 deletions(-) diff --git a/src/internal/agent/hooks/pods_test.go b/src/internal/agent/hooks/pods_test.go index 6d9468ce18..1e76f0dec5 100644 --- a/src/internal/agent/hooks/pods_test.go +++ b/src/internal/agent/hooks/pods_test.go @@ -10,16 +10,14 @@ import ( "testing" "github.com/defenseunicorns/zarf/src/config" + "github.com/defenseunicorns/zarf/src/internal/agent/http/admission" "github.com/defenseunicorns/zarf/src/internal/agent/operations" - "github.com/defenseunicorns/zarf/src/pkg/cluster" - "github.com/defenseunicorns/zarf/src/pkg/k8s" "github.com/defenseunicorns/zarf/src/types" "github.com/stretchr/testify/require" v1 "k8s.io/api/admission/v1" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" - "k8s.io/client-go/kubernetes/fake" ) func createPodAdmissionRequest(t *testing.T, op v1.Operation, pod *corev1.Pod) *v1.AdmissionRequest { @@ -38,9 +36,10 @@ func TestPodMutationWebhook(t *testing.T) { t.Parallel() ctx := context.Background() - c := &cluster.Cluster{K8s: &k8s.K8s{Clientset: fake.NewSimpleClientset()}} + state := &types.ZarfState{RegistryInfo: types.RegistryInfo{Address: "127.0.0.1:31999"}} - handler := setupWebhookTest(ctx, t, c, state, NewPodMutationHook) + c := createTestClientWithZarfState(ctx, t, state) + handler := admission.NewHandler().Serve(NewPodMutationHook(ctx, c)) tests := []struct { name string diff --git a/src/internal/agent/hooks/utils_test.go b/src/internal/agent/hooks/utils_test.go index bbcdffa718..909cd1f301 100644 --- a/src/internal/agent/hooks/utils_test.go +++ b/src/internal/agent/hooks/utils_test.go @@ -11,28 +11,19 @@ import ( "net/http/httptest" "testing" - "github.com/defenseunicorns/zarf/src/internal/agent/http/admission" - "github.com/defenseunicorns/zarf/src/internal/agent/operations" "github.com/defenseunicorns/zarf/src/pkg/cluster" + "github.com/defenseunicorns/zarf/src/pkg/k8s" "github.com/defenseunicorns/zarf/src/types" "github.com/stretchr/testify/require" v1 "k8s.io/api/admission/v1" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes/fake" ) -// setupWebhookTest sets up the test environment and returns the http.HandlerFunc for the provided hook. -// Cluster.K8s.Clientset should be initialized with fake.NewSimpleClientset() prior to passing into this function. -func setupWebhookTest(ctx context.Context, t *testing.T, c *cluster.Cluster, state *types.ZarfState, hookFunc func(ctx context.Context, c *cluster.Cluster) operations.Hook) http.HandlerFunc { - t.Helper() - createTestZarfStateSecret(ctx, t, c, state) - return admission.NewHandler().Serve(hookFunc(ctx, c)) -} - -// createTestZarfStateSecret creates a test zarf-state secret in the zarf namespace. -// Cluster.K8s.Clientset should be initialized with fake.NewSimpleClientset() prior to passing into this function. -func createTestZarfStateSecret(ctx context.Context, t *testing.T, c *cluster.Cluster, state *types.ZarfState) { +func createTestClientWithZarfState(ctx context.Context, t *testing.T, state *types.ZarfState) *cluster.Cluster { t.Helper() + c := &cluster.Cluster{K8s: &k8s.K8s{Clientset: fake.NewSimpleClientset()}} stateData, err := json.Marshal(state) require.NoError(t, err) @@ -47,6 +38,7 @@ func createTestZarfStateSecret(ctx context.Context, t *testing.T, c *cluster.Clu } _, err = c.Clientset.CoreV1().Secrets(cluster.ZarfNamespaceName).Create(ctx, secret, metav1.CreateOptions{}) require.NoError(t, err) + return c } // sendAdmissionRequest sends an admission request to the handler and returns the response. From 554ecf5a7388097269efef8806b03c069f84a967 Mon Sep 17 00:00:00 2001 From: Austin Abro Date: Tue, 21 May 2024 14:04:52 +0000 Subject: [PATCH 25/45] messaging --- src/internal/agent/hooks/pods_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/internal/agent/hooks/pods_test.go b/src/internal/agent/hooks/pods_test.go index 1e76f0dec5..3b35142072 100644 --- a/src/internal/agent/hooks/pods_test.go +++ b/src/internal/agent/hooks/pods_test.go @@ -90,7 +90,7 @@ func TestPodMutationWebhook(t *testing.T) { code: http.StatusOK, }, { - name: "pod with zarf-agent patched label", + name: "pod with zarf-agent patched label should not be mutated", admissionReq: createPodAdmissionRequest(t, v1.Create, &corev1.Pod{ ObjectMeta: metav1.ObjectMeta{ Labels: map[string]string{"zarf-agent": "patched"}, @@ -103,7 +103,7 @@ func TestPodMutationWebhook(t *testing.T) { code: http.StatusOK, }, { - name: "pod with no labels", + name: "pod with no labels should not error", admissionReq: createPodAdmissionRequest(t, v1.Create, &corev1.Pod{ ObjectMeta: metav1.ObjectMeta{ Labels: nil, From c86b62ffcc92a59904e848ba40626c4326d1d332 Mon Sep 17 00:00:00 2001 From: Austin Abro Date: Tue, 21 May 2024 14:22:53 +0000 Subject: [PATCH 26/45] uno reverse --- src/internal/agent/hooks/pods_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/internal/agent/hooks/pods_test.go b/src/internal/agent/hooks/pods_test.go index 3b35142072..eb3480b415 100644 --- a/src/internal/agent/hooks/pods_test.go +++ b/src/internal/agent/hooks/pods_test.go @@ -135,14 +135,14 @@ func TestPodMutationWebhook(t *testing.T) { t.Run(tt.name, func(t *testing.T) { t.Parallel() resp := sendAdmissionRequest(t, tt.admissionReq, handler, tt.code) - if tt.expectedPatch != nil { + if tt.expectedPatch == nil { + require.Empty(t, string(resp.Patch)) + } else { expectedPatchJSON, err := json.Marshal(tt.expectedPatch) require.NoError(t, err) require.NotNil(t, resp) require.True(t, resp.Allowed) require.JSONEq(t, string(expectedPatchJSON), string(resp.Patch)) - } else { - require.Empty(t, string(resp.Patch)) } }) } From 0202eff567cc3aa5c9713eb7afa625e2f3d91208 Mon Sep 17 00:00:00 2001 From: Austin Abro Date: Tue, 21 May 2024 15:24:23 +0000 Subject: [PATCH 27/45] extrememlely very much wip wip wip --- src/internal/agent/hooks/flux.go | 65 ++++--- src/internal/agent/hooks/flux_test.go | 171 +++++++++++++++++++ src/internal/agent/hooks/pods_test.go | 21 +-- src/internal/agent/hooks/utils_test.go | 32 +++- src/internal/agent/http/admission/handler.go | 18 ++ src/internal/agent/http/server.go | 2 +- 6 files changed, 257 insertions(+), 52 deletions(-) create mode 100644 src/internal/agent/hooks/flux_test.go diff --git a/src/internal/agent/hooks/flux.go b/src/internal/agent/hooks/flux.go index c4efce1846..4f2500ad41 100644 --- a/src/internal/agent/hooks/flux.go +++ b/src/internal/agent/hooks/flux.go @@ -5,6 +5,7 @@ package hooks import ( + "context" "encoding/json" "fmt" @@ -12,40 +13,31 @@ import ( "github.com/defenseunicorns/zarf/src/config" "github.com/defenseunicorns/zarf/src/config/lang" "github.com/defenseunicorns/zarf/src/internal/agent/operations" - "github.com/defenseunicorns/zarf/src/internal/agent/state" + "github.com/defenseunicorns/zarf/src/pkg/cluster" "github.com/defenseunicorns/zarf/src/pkg/message" "github.com/defenseunicorns/zarf/src/pkg/transform" - "github.com/defenseunicorns/zarf/src/types" + fluxmeta "github.com/fluxcd/pkg/apis/meta" + flux "github.com/fluxcd/source-controller/api/v1" v1 "k8s.io/api/admission/v1" ) -// SecretRef contains the name used to reference a git repository secret. -type SecretRef struct { - Name string `json:"name"` -} - -// GenericGitRepo contains the URL of a git repo and the secret that corresponds to it for use with Flux. -type GenericGitRepo struct { - Spec struct { - URL string `json:"url"` - SecretRef SecretRef `json:"secretRef,omitempty"` - } `json:"spec"` -} - // NewGitRepositoryMutationHook creates a new instance of the git repo mutation hook. -func NewGitRepositoryMutationHook() operations.Hook { +func NewGitRepositoryMutationHook(ctx context.Context, cluster *cluster.Cluster) operations.Hook { message.Debug("hooks.NewGitRepositoryMutationHook()") return operations.Hook{ - Create: mutateGitRepo, - Update: mutateGitRepo, + Create: func(r *v1.AdmissionRequest) (*operations.Result, error) { + return mutateGitRepo(ctx, r, cluster) + }, + Update: func(r *v1.AdmissionRequest) (*operations.Result, error) { + return mutateGitRepo(ctx, r, cluster) + }, } } // mutateGitRepoCreate mutates the git repository url to point to the repository URL defined in the ZarfState. -func mutateGitRepo(r *v1.AdmissionRequest) (result *operations.Result, err error) { +func mutateGitRepo(ctx context.Context, r *v1.AdmissionRequest, cluster *cluster.Cluster) (result *operations.Result, err error) { var ( - zarfState *types.ZarfState patches []operations.PatchOperation isPatched bool @@ -53,43 +45,43 @@ func mutateGitRepo(r *v1.AdmissionRequest) (result *operations.Result, err error isUpdate = r.Operation == v1.Update ) - // Form the zarfState.GitServer.Address from the zarfState - if zarfState, err = state.GetZarfStateFromAgentPod(); err != nil { + state, err := cluster.LoadZarfState(ctx) + if err != nil { return nil, fmt.Errorf(lang.AgentErrGetState, err) } - message.Debugf("Using the url of (%s) to mutate the flux repository", zarfState.GitServer.Address) + message.Debugf("Using the url of (%s) to mutate the flux repository", state.GitServer.Address) - // parse to simple struct to read the git url - src := &GenericGitRepo{} - if err = json.Unmarshal(r.Object.Raw, &src); err != nil { + repo := flux.GitRepository{} + if err = json.Unmarshal(r.Object.Raw, &repo); err != nil { return nil, fmt.Errorf(lang.ErrUnmarshal, err) } - patchedURL := src.Spec.URL // Check if this is an update operation and the hostname is different from what we have in the zarfState // NOTE: We mutate on updates IF AND ONLY IF the hostname in the request is different than the hostname in the zarfState // NOTE: We are checking if the hostname is different before because we do not want to potentially mutate a URL that has already been mutated. if isUpdate { - isPatched, err = helpers.DoHostnamesMatch(zarfState.GitServer.Address, src.Spec.URL) + isPatched, err = helpers.DoHostnamesMatch(state.GitServer.Address, repo.Spec.URL) if err != nil { return nil, fmt.Errorf(lang.AgentErrHostnameMatch, err) } } + patchedURL := repo.Spec.URL + // Mutate the git URL if necessary if isCreate || (isUpdate && !isPatched) { // Mutate the git URL so that the hostname matches the hostname in the Zarf state - transformedURL, err := transform.GitURL(zarfState.GitServer.Address, patchedURL, zarfState.GitServer.PushUsername) + transformedURL, err := transform.GitURL(state.GitServer.Address, patchedURL, state.GitServer.PushUsername) if err != nil { - message.Warnf("Unable to transform the git url, using the original url we have: %s", patchedURL) + return nil, fmt.Errorf("unable to transform the git url: %w", err) } patchedURL = transformedURL.String() - message.Debugf("original git URL of (%s) got mutated to (%s)", src.Spec.URL, patchedURL) + message.Debugf("original git URL of (%s) got mutated to (%s)", repo.Spec.URL, patchedURL) } // Patch updates of the repo spec - patches = populatePatchOperations(patchedURL, src.Spec.SecretRef.Name) + patches = populatePatchOperations(patchedURL, repo.Spec.SecretRef) return &operations.Result{ Allowed: true, @@ -98,16 +90,19 @@ func mutateGitRepo(r *v1.AdmissionRequest) (result *operations.Result, err error } // Patch updates of the repo spec. -func populatePatchOperations(repoURL string, secretName string) []operations.PatchOperation { +func populatePatchOperations(repoURL string, secretRef *fluxmeta.LocalObjectReference) []operations.PatchOperation { var patches []operations.PatchOperation patches = append(patches, operations.ReplacePatchOperation("/spec/url", repoURL)) + //TODO This logic can be simplified + // If a prior secret exists, replace it - if secretName != "" { + if secretRef != nil && secretRef.Name != "" { patches = append(patches, operations.ReplacePatchOperation("/spec/secretRef/name", config.ZarfGitServerSecretName)) } else { // Otherwise, add the new secret - patches = append(patches, operations.AddPatchOperation("/spec/secretRef", SecretRef{Name: config.ZarfGitServerSecretName})) + newSecretRef := fluxmeta.LocalObjectReference{Name: config.ZarfGitServerSecretName} + patches = append(patches, operations.AddPatchOperation("/spec/secretRef", newSecretRef)) } return patches diff --git a/src/internal/agent/hooks/flux_test.go b/src/internal/agent/hooks/flux_test.go new file mode 100644 index 0000000000..a159679555 --- /dev/null +++ b/src/internal/agent/hooks/flux_test.go @@ -0,0 +1,171 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2021-Present The Zarf Authors + +package hooks + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "testing" + + "github.com/defenseunicorns/zarf/src/config" + "github.com/defenseunicorns/zarf/src/internal/agent/http/admission" + "github.com/defenseunicorns/zarf/src/internal/agent/operations" + "github.com/defenseunicorns/zarf/src/types" + fluxmeta "github.com/fluxcd/pkg/apis/meta" + flux "github.com/fluxcd/source-controller/api/v1" + "github.com/stretchr/testify/require" + v1 "k8s.io/api/admission/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" +) + +func createFluxGitRepoAdmissionRequest(t *testing.T, op v1.Operation, fluxGitRepo *flux.GitRepository) *v1.AdmissionRequest { + t.Helper() + raw, err := json.Marshal(fluxGitRepo) + require.NoError(t, err) + return &v1.AdmissionRequest{ + Operation: op, + Object: runtime.RawExtension{ + Raw: raw, + }, + } +} + +func TestFluxMutationWebhook(t *testing.T) { + t.Parallel() + + ctx := context.Background() + state := &types.ZarfState{GitServer: types.GitServerInfo{ + Address: "https://git-server.com", + PushUsername: "a-push-user", + }} + c := createTestClientWithZarfState(ctx, t, state) + handler := admission.NewHandler().Serve(NewGitRepositoryMutationHook(ctx, c)) + + tests := []struct { + name string + admissionReq *v1.AdmissionRequest + expectedPatch []operations.PatchOperation + code int + err error + }{ + { + name: "should be mutated", + admissionReq: createFluxGitRepoAdmissionRequest(t, v1.Create, &flux.GitRepository{ + ObjectMeta: metav1.ObjectMeta{ + Name: "mutate-this", + }, + Spec: flux.GitRepositorySpec{ + URL: "https://github.com/stefanprodan/podinfo.git", + Reference: &flux.GitRepositoryRef{ + Tag: "6.4.0", + }, + }, + }), + expectedPatch: []operations.PatchOperation{ + operations.ReplacePatchOperation( + "/spec/url", + "https://git-server.com/a-push-user/podinfo-1646971829.git", + ), + operations.AddPatchOperation( + "/spec/secretRef", + fluxmeta.LocalObjectReference{Name: config.ZarfGitServerSecretName}, + ), + }, + code: http.StatusOK, + }, + { + name: "should not mutate invalid git url", + admissionReq: createFluxGitRepoAdmissionRequest(t, v1.Update, &flux.GitRepository{ + ObjectMeta: metav1.ObjectMeta{ + Name: "mutate-this", + }, + Spec: flux.GitRepositorySpec{ + URL: "not-a-git-url", + Reference: &flux.GitRepositoryRef{ + Tag: "6.4.0", + }, + }, + }), + expectedPatch: nil, + code: http.StatusInternalServerError, + err: fmt.Errorf("unable to transform the git url:"), + }, + { + name: "should replace existing secret", + admissionReq: createFluxGitRepoAdmissionRequest(t, v1.Create, &flux.GitRepository{ + ObjectMeta: metav1.ObjectMeta{ + Name: "replace-secret", + }, + Spec: flux.GitRepositorySpec{ + URL: "https://github.com/stefanprodan/podinfo.git", + SecretRef: &fluxmeta.LocalObjectReference{ + Name: "existing-secret", + }, + Reference: &flux.GitRepositoryRef{ + Tag: "6.4.0", + }, + }, + }), + expectedPatch: []operations.PatchOperation{ + operations.ReplacePatchOperation( + "/spec/url", + "https://git-server.com/a-push-user/podinfo-1646971829.git", + ), + operations.ReplacePatchOperation( + "/spec/secretRef/name", + config.ZarfGitServerSecretName, + ), + }, + code: http.StatusOK, + }, + { + name: "should patch to same url and update secret if hostname matches", + admissionReq: createFluxGitRepoAdmissionRequest(t, v1.Update, &flux.GitRepository{ + ObjectMeta: metav1.ObjectMeta{ + Name: "no-mutate", + }, + Spec: flux.GitRepositorySpec{ + URL: "https://git-server.com/a-push-user/podinfo.git", + Reference: &flux.GitRepositoryRef{ + Tag: "6.4.0", + }, + }, + }), + expectedPatch: []operations.PatchOperation{ + operations.ReplacePatchOperation( + "/spec/url", + "https://git-server.com/a-push-user/podinfo.git", + ), + operations.AddPatchOperation( + "/spec/secretRef", + fluxmeta.LocalObjectReference{Name: config.ZarfGitServerSecretName}, + ), + }, + code: http.StatusOK, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + rr := sendAdmissionRequest(t, tt.admissionReq, handler, tt.code) + verifyAdmission(t, rr, tt.code, tt.expectedPatch, tt.err) + // if tt.err != nil { + // resp.Body + // } else if tt.expectedPatch == nil { + // require.Empty(t, string(resp.Patch)) + // } else { + // expectedPatchJSON, err := json.Marshal(tt.expectedPatch) + // require.NoError(t, err) + // require.NotNil(t, resp) + // require.True(t, resp.Allowed) + // require.JSONEq(t, string(expectedPatchJSON), string(resp.Patch)) + // } + }) + } +} diff --git a/src/internal/agent/hooks/pods_test.go b/src/internal/agent/hooks/pods_test.go index eb3480b415..2aeb8e2bab 100644 --- a/src/internal/agent/hooks/pods_test.go +++ b/src/internal/agent/hooks/pods_test.go @@ -134,16 +134,17 @@ func TestPodMutationWebhook(t *testing.T) { tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() - resp := sendAdmissionRequest(t, tt.admissionReq, handler, tt.code) - if tt.expectedPatch == nil { - require.Empty(t, string(resp.Patch)) - } else { - expectedPatchJSON, err := json.Marshal(tt.expectedPatch) - require.NoError(t, err) - require.NotNil(t, resp) - require.True(t, resp.Allowed) - require.JSONEq(t, string(expectedPatchJSON), string(resp.Patch)) - } + rr := sendAdmissionRequest(t, tt.admissionReq, handler, tt.code) + verifyAdmission(t, rr, tt.code, tt.expectedPatch, nil) + // if tt.expectedPatch == nil { + // require.Empty(t, string(resp.Patch)) + // } else { + // expectedPatchJSON, err := json.Marshal(tt.expectedPatch) + // require.NoError(t, err) + // require.NotNil(t, resp) + // require.True(t, resp.Allowed) + // require.JSONEq(t, string(expectedPatchJSON), string(resp.Patch)) + // } }) } } diff --git a/src/internal/agent/hooks/utils_test.go b/src/internal/agent/hooks/utils_test.go index 909cd1f301..2a1c394935 100644 --- a/src/internal/agent/hooks/utils_test.go +++ b/src/internal/agent/hooks/utils_test.go @@ -11,6 +11,7 @@ import ( "net/http/httptest" "testing" + "github.com/defenseunicorns/zarf/src/internal/agent/operations" "github.com/defenseunicorns/zarf/src/pkg/cluster" "github.com/defenseunicorns/zarf/src/pkg/k8s" "github.com/defenseunicorns/zarf/src/types" @@ -42,7 +43,7 @@ func createTestClientWithZarfState(ctx context.Context, t *testing.T, state *typ } // sendAdmissionRequest sends an admission request to the handler and returns the response. -func sendAdmissionRequest(t *testing.T, admissionReq *v1.AdmissionRequest, handler http.HandlerFunc, code int) *v1.AdmissionResponse { +func sendAdmissionRequest(t *testing.T, admissionReq *v1.AdmissionRequest, handler http.HandlerFunc, code int) *httptest.ResponseRecorder { t.Helper() b, err := json.Marshal(&v1.AdmissionReview{ @@ -58,13 +59,32 @@ func sendAdmissionRequest(t *testing.T, admissionReq *v1.AdmissionRequest, handl rr := httptest.NewRecorder() handler.ServeHTTP(rr, req) - require.Equal(t, code, rr.Code) + return rr +} + +func verifyAdmission(t *testing.T, rr *httptest.ResponseRecorder, expectedCode int, expectedPatch []operations.PatchOperation, expectedErr error){ + t.Helper() + + require.Equal(t, expectedCode, rr.Code) + + if expectedErr != nil { + require.Contains(t, rr.Body.String(), expectedErr.Error() ) + return + } + var admissionReview v1.AdmissionReview - if rr.Code == http.StatusOK { - err = json.NewDecoder(rr.Body).Decode(&admissionReview) + + err := json.NewDecoder(rr.Body).Decode(&admissionReview) + resp := admissionReview.Response + require.NoError(t, err) + if expectedPatch == nil { + require.Empty(t, string(resp.Patch)) + } else { + expectedPatchJSON, err := json.Marshal(expectedPatch) require.NoError(t, err) + require.NotNil(t, resp) + require.True(t, resp.Allowed) + require.JSONEq(t, string(expectedPatchJSON), string(resp.Patch)) } - - return admissionReview.Response } diff --git a/src/internal/agent/http/admission/handler.go b/src/internal/agent/http/admission/handler.go index b4e3109af7..7cca183b05 100644 --- a/src/internal/agent/http/admission/handler.go +++ b/src/internal/agent/http/admission/handler.go @@ -33,6 +33,10 @@ func NewHandler() *Handler { } } +type errorResponse struct { + err string `json:"error"` +} + // Serve returns an http.HandlerFunc for an admission webhook. func (h *Handler) Serve(hook operations.Hook) http.HandlerFunc { message.Debugf("http.Serve(%#v)", hook) @@ -70,7 +74,21 @@ func (h *Handler) Serve(hook operations.Hook) http.HandlerFunc { result, err := hook.Execute(review.Request) if err != nil { message.Warnf("%s: %s", lang.AgentErrBindHandler, err.Error()) + + // // Preparing the error response + errorResponse := errorResponse{ + err: err.Error(), + } + responseBytes, err := json.Marshal(err) w.WriteHeader(http.StatusInternalServerError) + if err != nil { + message.Warnf("Error marshaling the error response: %s", errorResponse) + w.WriteHeader(800) // Fallback if response cannot be marshaled + return + } + w.Header().Set("Content-Type", "application/json") + + w.Write(responseBytes) return } diff --git a/src/internal/agent/http/server.go b/src/internal/agent/http/server.go index 7b07b1445d..2c56428fd7 100644 --- a/src/internal/agent/http/server.go +++ b/src/internal/agent/http/server.go @@ -30,7 +30,7 @@ func NewAdmissionServer(port string) *http.Server { // Instances hooks podsMutation := hooks.NewPodMutationHook(ctx, c) - fluxGitRepositoryMutation := hooks.NewGitRepositoryMutationHook() + fluxGitRepositoryMutation := hooks.NewGitRepositoryMutationHook(ctx, c) argocdApplicationMutation := hooks.NewApplicationMutationHook() argocdRepositoryMutation := hooks.NewRepositoryMutationHook() From 053d5daf5a70778ceabbe246c96264733cbb5bb9 Mon Sep 17 00:00:00 2001 From: Austin Abro Date: Tue, 21 May 2024 17:21:24 +0000 Subject: [PATCH 28/45] unit tests passing --- src/config/lang/english.go | 2 ++ src/internal/agent/hooks/flux.go | 2 +- src/internal/agent/hooks/flux_test.go | 21 ++++----------- src/internal/agent/hooks/pods.go | 2 +- src/internal/agent/hooks/pods_test.go | 14 +++------- src/internal/agent/hooks/utils_test.go | 9 +++---- src/internal/agent/http/admission/handler.go | 27 +++++++++----------- 7 files changed, 28 insertions(+), 49 deletions(-) diff --git a/src/config/lang/english.go b/src/config/lang/english.go index c5deeffc0c..1400d5df6e 100644 --- a/src/config/lang/english.go +++ b/src/config/lang/english.go @@ -665,10 +665,12 @@ const ( AgentErrBindHandler = "Unable to bind the webhook handler" AgentErrCouldNotDeserializeReq = "could not deserialize request: %s" AgentErrGetState = "failed to load zarf state: %w" + AgentErrParsePod = "failed to parse pod: %w" AgentErrHostnameMatch = "failed to complete hostname matching: %w" AgentErrImageSwap = "Unable to swap the host for (%s)" AgentErrInvalidMethod = "invalid method only POST requests are allowed" AgentErrInvalidOp = "invalid operation: %s" + AgentErrTransformGitURL = "unable to transform the git url" AgentErrInvalidType = "only content type 'application/json' is supported" AgentErrMarshallJSONPatch = "unable to marshall the json patch" AgentErrMarshalResponse = "unable to marshal the response" diff --git a/src/internal/agent/hooks/flux.go b/src/internal/agent/hooks/flux.go index 4f2500ad41..afa9bc7f10 100644 --- a/src/internal/agent/hooks/flux.go +++ b/src/internal/agent/hooks/flux.go @@ -74,7 +74,7 @@ func mutateGitRepo(ctx context.Context, r *v1.AdmissionRequest, cluster *cluster // Mutate the git URL so that the hostname matches the hostname in the Zarf state transformedURL, err := transform.GitURL(state.GitServer.Address, patchedURL, state.GitServer.PushUsername) if err != nil { - return nil, fmt.Errorf("unable to transform the git url: %w", err) + return nil, fmt.Errorf("%s: %w", lang.AgentErrTransformGitURL, err) } patchedURL = transformedURL.String() message.Debugf("original git URL of (%s) got mutated to (%s)", repo.Spec.URL, patchedURL) diff --git a/src/internal/agent/hooks/flux_test.go b/src/internal/agent/hooks/flux_test.go index a159679555..6170f4f250 100644 --- a/src/internal/agent/hooks/flux_test.go +++ b/src/internal/agent/hooks/flux_test.go @@ -6,11 +6,11 @@ package hooks import ( "context" "encoding/json" - "fmt" "net/http" "testing" "github.com/defenseunicorns/zarf/src/config" + "github.com/defenseunicorns/zarf/src/config/lang" "github.com/defenseunicorns/zarf/src/internal/agent/http/admission" "github.com/defenseunicorns/zarf/src/internal/agent/operations" "github.com/defenseunicorns/zarf/src/types" @@ -50,7 +50,7 @@ func TestFluxMutationWebhook(t *testing.T) { admissionReq *v1.AdmissionRequest expectedPatch []operations.PatchOperation code int - err error + errContains string }{ { name: "should be mutated", @@ -92,7 +92,7 @@ func TestFluxMutationWebhook(t *testing.T) { }), expectedPatch: nil, code: http.StatusInternalServerError, - err: fmt.Errorf("unable to transform the git url:"), + errContains: lang.AgentErrTransformGitURL, }, { name: "should replace existing secret", @@ -153,19 +153,8 @@ func TestFluxMutationWebhook(t *testing.T) { tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() - rr := sendAdmissionRequest(t, tt.admissionReq, handler, tt.code) - verifyAdmission(t, rr, tt.code, tt.expectedPatch, tt.err) - // if tt.err != nil { - // resp.Body - // } else if tt.expectedPatch == nil { - // require.Empty(t, string(resp.Patch)) - // } else { - // expectedPatchJSON, err := json.Marshal(tt.expectedPatch) - // require.NoError(t, err) - // require.NotNil(t, resp) - // require.True(t, resp.Allowed) - // require.JSONEq(t, string(expectedPatchJSON), string(resp.Patch)) - // } + rr := sendAdmissionRequest(t, tt.admissionReq, handler) + verifyAdmission(t, rr, tt.code, tt.expectedPatch, tt.errContains) }) } } diff --git a/src/internal/agent/hooks/pods.go b/src/internal/agent/hooks/pods.go index ea822f366f..fc268f3c4f 100644 --- a/src/internal/agent/hooks/pods.go +++ b/src/internal/agent/hooks/pods.go @@ -48,7 +48,7 @@ func mutatePod(ctx context.Context, r *v1.AdmissionRequest, cluster *cluster.Clu pod, err := parsePod(r.Object.Raw) if err != nil { - return &operations.Result{Msg: err.Error()}, nil + return nil, fmt.Errorf(lang.AgentErrParsePod, err) } if pod.Labels != nil && pod.Labels["zarf-agent"] == "patched" { diff --git a/src/internal/agent/hooks/pods_test.go b/src/internal/agent/hooks/pods_test.go index 2aeb8e2bab..65ca864e8e 100644 --- a/src/internal/agent/hooks/pods_test.go +++ b/src/internal/agent/hooks/pods_test.go @@ -46,6 +46,7 @@ func TestPodMutationWebhook(t *testing.T) { admissionReq *v1.AdmissionRequest expectedPatch []operations.PatchOperation code int + expectedErr string }{ { name: "pod with label should be mutated", @@ -134,17 +135,8 @@ func TestPodMutationWebhook(t *testing.T) { tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() - rr := sendAdmissionRequest(t, tt.admissionReq, handler, tt.code) - verifyAdmission(t, rr, tt.code, tt.expectedPatch, nil) - // if tt.expectedPatch == nil { - // require.Empty(t, string(resp.Patch)) - // } else { - // expectedPatchJSON, err := json.Marshal(tt.expectedPatch) - // require.NoError(t, err) - // require.NotNil(t, resp) - // require.True(t, resp.Allowed) - // require.JSONEq(t, string(expectedPatchJSON), string(resp.Patch)) - // } + rr := sendAdmissionRequest(t, tt.admissionReq, handler) + verifyAdmission(t, rr, tt.code, tt.expectedPatch, tt.expectedErr) }) } } diff --git a/src/internal/agent/hooks/utils_test.go b/src/internal/agent/hooks/utils_test.go index 2a1c394935..e1789df63e 100644 --- a/src/internal/agent/hooks/utils_test.go +++ b/src/internal/agent/hooks/utils_test.go @@ -43,7 +43,7 @@ func createTestClientWithZarfState(ctx context.Context, t *testing.T, state *typ } // sendAdmissionRequest sends an admission request to the handler and returns the response. -func sendAdmissionRequest(t *testing.T, admissionReq *v1.AdmissionRequest, handler http.HandlerFunc, code int) *httptest.ResponseRecorder { +func sendAdmissionRequest(t *testing.T, admissionReq *v1.AdmissionRequest, handler http.HandlerFunc) *httptest.ResponseRecorder { t.Helper() b, err := json.Marshal(&v1.AdmissionReview{ @@ -62,17 +62,16 @@ func sendAdmissionRequest(t *testing.T, admissionReq *v1.AdmissionRequest, handl return rr } -func verifyAdmission(t *testing.T, rr *httptest.ResponseRecorder, expectedCode int, expectedPatch []operations.PatchOperation, expectedErr error){ +func verifyAdmission(t *testing.T, rr *httptest.ResponseRecorder, expectedCode int, expectedPatch []operations.PatchOperation, expectedErrContains string) { t.Helper() require.Equal(t, expectedCode, rr.Code) - if expectedErr != nil { - require.Contains(t, rr.Body.String(), expectedErr.Error() ) + if expectedErrContains != "" { + require.Contains(t, rr.Body.String(), expectedErrContains) return } - var admissionReview v1.AdmissionReview err := json.NewDecoder(rr.Body).Decode(&admissionReview) diff --git a/src/internal/agent/http/admission/handler.go b/src/internal/agent/http/admission/handler.go index 7cca183b05..b0cb5ba3ab 100644 --- a/src/internal/agent/http/admission/handler.go +++ b/src/internal/agent/http/admission/handler.go @@ -33,10 +33,6 @@ func NewHandler() *Handler { } } -type errorResponse struct { - err string `json:"error"` -} - // Serve returns an http.HandlerFunc for an admission webhook. func (h *Handler) Serve(hook operations.Hook) http.HandlerFunc { message.Debugf("http.Serve(%#v)", hook) @@ -76,19 +72,20 @@ func (h *Handler) Serve(hook operations.Hook) http.HandlerFunc { message.Warnf("%s: %s", lang.AgentErrBindHandler, err.Error()) // // Preparing the error response - errorResponse := errorResponse{ - err: err.Error(), - } - responseBytes, err := json.Marshal(err) + // errorResponse := errorResponse{ + // err: err.Error(), + // } + // responseBytes, err := json.Marshal(err) + + // if err != nil { + // message.Warnf("Error marshaling the error response: %s", errorResponse) + // w.WriteHeader(800) // Fallback if response cannot be marshaled + // return + // } w.WriteHeader(http.StatusInternalServerError) - if err != nil { - message.Warnf("Error marshaling the error response: %s", errorResponse) - w.WriteHeader(800) // Fallback if response cannot be marshaled - return - } - w.Header().Set("Content-Type", "application/json") - w.Write(responseBytes) + // TODO look at how/if this gets propagated to the agent + w.Write([]byte(err.Error())) return } From 6847125d57f2a7baae076e09608922581bc62e59 Mon Sep 17 00:00:00 2001 From: Austin Abro Date: Tue, 21 May 2024 17:27:31 +0000 Subject: [PATCH 29/45] refactor tests --- go.mod | 2 +- src/internal/agent/hooks/flux_test.go | 22 ++++++++-------------- src/internal/agent/hooks/pods_test.go | 18 ++++++------------ src/internal/agent/hooks/utils_test.go | 20 ++++++++++++++------ 4 files changed, 29 insertions(+), 33 deletions(-) diff --git a/go.mod b/go.mod index 9fe6c7a968..71368adc16 100644 --- a/go.mod +++ b/go.mod @@ -227,7 +227,7 @@ require ( github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fluxcd/pkg/apis/acl v0.1.0 // indirect github.com/fluxcd/pkg/apis/kustomize v1.3.0 // indirect - github.com/fluxcd/pkg/apis/meta v1.3.0 // indirect + github.com/fluxcd/pkg/apis/meta v1.3.0 github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/fvbommel/sortorder v1.1.0 // indirect github.com/gabriel-vasile/mimetype v1.4.3 // indirect diff --git a/src/internal/agent/hooks/flux_test.go b/src/internal/agent/hooks/flux_test.go index 6170f4f250..eef08a075f 100644 --- a/src/internal/agent/hooks/flux_test.go +++ b/src/internal/agent/hooks/flux_test.go @@ -45,13 +45,7 @@ func TestFluxMutationWebhook(t *testing.T) { c := createTestClientWithZarfState(ctx, t, state) handler := admission.NewHandler().Serve(NewGitRepositoryMutationHook(ctx, c)) - tests := []struct { - name string - admissionReq *v1.AdmissionRequest - expectedPatch []operations.PatchOperation - code int - errContains string - }{ + tests := []admissionTest{ { name: "should be mutated", admissionReq: createFluxGitRepoAdmissionRequest(t, v1.Create, &flux.GitRepository{ @@ -65,7 +59,7 @@ func TestFluxMutationWebhook(t *testing.T) { }, }, }), - expectedPatch: []operations.PatchOperation{ + patch: []operations.PatchOperation{ operations.ReplacePatchOperation( "/spec/url", "https://git-server.com/a-push-user/podinfo-1646971829.git", @@ -90,9 +84,9 @@ func TestFluxMutationWebhook(t *testing.T) { }, }, }), - expectedPatch: nil, - code: http.StatusInternalServerError, - errContains: lang.AgentErrTransformGitURL, + patch: nil, + code: http.StatusInternalServerError, + errContains: lang.AgentErrTransformGitURL, }, { name: "should replace existing secret", @@ -110,7 +104,7 @@ func TestFluxMutationWebhook(t *testing.T) { }, }, }), - expectedPatch: []operations.PatchOperation{ + patch: []operations.PatchOperation{ operations.ReplacePatchOperation( "/spec/url", "https://git-server.com/a-push-user/podinfo-1646971829.git", @@ -135,7 +129,7 @@ func TestFluxMutationWebhook(t *testing.T) { }, }, }), - expectedPatch: []operations.PatchOperation{ + patch: []operations.PatchOperation{ operations.ReplacePatchOperation( "/spec/url", "https://git-server.com/a-push-user/podinfo.git", @@ -154,7 +148,7 @@ func TestFluxMutationWebhook(t *testing.T) { t.Run(tt.name, func(t *testing.T) { t.Parallel() rr := sendAdmissionRequest(t, tt.admissionReq, handler) - verifyAdmission(t, rr, tt.code, tt.expectedPatch, tt.errContains) + verifyAdmission(t, rr, tt) }) } } diff --git a/src/internal/agent/hooks/pods_test.go b/src/internal/agent/hooks/pods_test.go index 65ca864e8e..8eca704f59 100644 --- a/src/internal/agent/hooks/pods_test.go +++ b/src/internal/agent/hooks/pods_test.go @@ -41,13 +41,7 @@ func TestPodMutationWebhook(t *testing.T) { c := createTestClientWithZarfState(ctx, t, state) handler := admission.NewHandler().Serve(NewPodMutationHook(ctx, c)) - tests := []struct { - name string - admissionReq *v1.AdmissionRequest - expectedPatch []operations.PatchOperation - code int - expectedErr string - }{ + tests := []admissionTest{ { name: "pod with label should be mutated", admissionReq: createPodAdmissionRequest(t, v1.Create, &corev1.Pod{ @@ -66,7 +60,7 @@ func TestPodMutationWebhook(t *testing.T) { }, }, }), - expectedPatch: []operations.PatchOperation{ + patch: []operations.PatchOperation{ operations.ReplacePatchOperation( "/spec/imagePullSecrets", []corev1.LocalObjectReference{{Name: config.ZarfImagePullSecretName}}, @@ -100,8 +94,8 @@ func TestPodMutationWebhook(t *testing.T) { Containers: []corev1.Container{{Image: "nginx"}}, }, }), - expectedPatch: nil, - code: http.StatusOK, + patch: nil, + code: http.StatusOK, }, { name: "pod with no labels should not error", @@ -113,7 +107,7 @@ func TestPodMutationWebhook(t *testing.T) { Containers: []corev1.Container{{Image: "nginx"}}, }, }), - expectedPatch: []operations.PatchOperation{ + patch: []operations.PatchOperation{ operations.ReplacePatchOperation( "/spec/imagePullSecrets", []corev1.LocalObjectReference{{Name: config.ZarfImagePullSecretName}}, @@ -136,7 +130,7 @@ func TestPodMutationWebhook(t *testing.T) { t.Run(tt.name, func(t *testing.T) { t.Parallel() rr := sendAdmissionRequest(t, tt.admissionReq, handler) - verifyAdmission(t, rr, tt.code, tt.expectedPatch, tt.expectedErr) + verifyAdmission(t, rr, tt) }) } } diff --git a/src/internal/agent/hooks/utils_test.go b/src/internal/agent/hooks/utils_test.go index e1789df63e..887779fb5e 100644 --- a/src/internal/agent/hooks/utils_test.go +++ b/src/internal/agent/hooks/utils_test.go @@ -22,6 +22,14 @@ import ( "k8s.io/client-go/kubernetes/fake" ) +type admissionTest struct { + name string + admissionReq *v1.AdmissionRequest + patch []operations.PatchOperation + code int + errContains string +} + func createTestClientWithZarfState(ctx context.Context, t *testing.T, state *types.ZarfState) *cluster.Cluster { t.Helper() c := &cluster.Cluster{K8s: &k8s.K8s{Clientset: fake.NewSimpleClientset()}} @@ -62,13 +70,13 @@ func sendAdmissionRequest(t *testing.T, admissionReq *v1.AdmissionRequest, handl return rr } -func verifyAdmission(t *testing.T, rr *httptest.ResponseRecorder, expectedCode int, expectedPatch []operations.PatchOperation, expectedErrContains string) { +func verifyAdmission(t *testing.T, rr *httptest.ResponseRecorder, expected admissionTest) { t.Helper() - require.Equal(t, expectedCode, rr.Code) + require.Equal(t, expected.code, rr.Code) - if expectedErrContains != "" { - require.Contains(t, rr.Body.String(), expectedErrContains) + if expected.errContains != "" { + require.Contains(t, rr.Body.String(), expected.errContains) return } @@ -77,10 +85,10 @@ func verifyAdmission(t *testing.T, rr *httptest.ResponseRecorder, expectedCode i err := json.NewDecoder(rr.Body).Decode(&admissionReview) resp := admissionReview.Response require.NoError(t, err) - if expectedPatch == nil { + if expected.patch == nil { require.Empty(t, string(resp.Patch)) } else { - expectedPatchJSON, err := json.Marshal(expectedPatch) + expectedPatchJSON, err := json.Marshal(expected.patch) require.NoError(t, err) require.NotNil(t, resp) require.True(t, resp.Allowed) From 1aa5b07c7727da7ebdd23bc0c84af720d0cda75c Mon Sep 17 00:00:00 2001 From: Austin Abro Date: Wed, 22 May 2024 13:58:58 +0000 Subject: [PATCH 30/45] changing error we return --- src/internal/agent/http/admission/handler.go | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/src/internal/agent/http/admission/handler.go b/src/internal/agent/http/admission/handler.go index b0cb5ba3ab..8651b2183d 100644 --- a/src/internal/agent/http/admission/handler.go +++ b/src/internal/agent/http/admission/handler.go @@ -70,21 +70,7 @@ func (h *Handler) Serve(hook operations.Hook) http.HandlerFunc { result, err := hook.Execute(review.Request) if err != nil { message.Warnf("%s: %s", lang.AgentErrBindHandler, err.Error()) - - // // Preparing the error response - // errorResponse := errorResponse{ - // err: err.Error(), - // } - // responseBytes, err := json.Marshal(err) - - // if err != nil { - // message.Warnf("Error marshaling the error response: %s", errorResponse) - // w.WriteHeader(800) // Fallback if response cannot be marshaled - // return - // } w.WriteHeader(http.StatusInternalServerError) - - // TODO look at how/if this gets propagated to the agent w.Write([]byte(err.Error())) return } From 63452c39cfb3e3fa2cd18803a53a814a3aee16fa Mon Sep 17 00:00:00 2001 From: Austin Abro Date: Wed, 22 May 2024 14:11:44 +0000 Subject: [PATCH 31/45] simplify --- src/internal/agent/hooks/flux.go | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/src/internal/agent/hooks/flux.go b/src/internal/agent/hooks/flux.go index afa9bc7f10..95889a1088 100644 --- a/src/internal/agent/hooks/flux.go +++ b/src/internal/agent/hooks/flux.go @@ -81,7 +81,7 @@ func mutateGitRepo(ctx context.Context, r *v1.AdmissionRequest, cluster *cluster } // Patch updates of the repo spec - patches = populatePatchOperations(patchedURL, repo.Spec.SecretRef) + patches = populatePatchOperations(patchedURL) return &operations.Result{ Allowed: true, @@ -90,20 +90,12 @@ func mutateGitRepo(ctx context.Context, r *v1.AdmissionRequest, cluster *cluster } // Patch updates of the repo spec. -func populatePatchOperations(repoURL string, secretRef *fluxmeta.LocalObjectReference) []operations.PatchOperation { +func populatePatchOperations(repoURL string) []operations.PatchOperation { var patches []operations.PatchOperation patches = append(patches, operations.ReplacePatchOperation("/spec/url", repoURL)) - //TODO This logic can be simplified - - // If a prior secret exists, replace it - if secretRef != nil && secretRef.Name != "" { - patches = append(patches, operations.ReplacePatchOperation("/spec/secretRef/name", config.ZarfGitServerSecretName)) - } else { - // Otherwise, add the new secret - newSecretRef := fluxmeta.LocalObjectReference{Name: config.ZarfGitServerSecretName} - patches = append(patches, operations.AddPatchOperation("/spec/secretRef", newSecretRef)) - } + newSecretRef := fluxmeta.LocalObjectReference{Name: config.ZarfGitServerSecretName} + patches = append(patches, operations.AddPatchOperation("/spec/secretRef", newSecretRef)) return patches } From ad3244440be7aa693b56811a377f3c4eb82e1b7c Mon Sep 17 00:00:00 2001 From: Austin Abro Date: Wed, 22 May 2024 14:18:04 +0000 Subject: [PATCH 32/45] cleaning up test --- src/internal/agent/hooks/flux_test.go | 37 --------------------------- 1 file changed, 37 deletions(-) diff --git a/src/internal/agent/hooks/flux_test.go b/src/internal/agent/hooks/flux_test.go index eef08a075f..3538fdff37 100644 --- a/src/internal/agent/hooks/flux_test.go +++ b/src/internal/agent/hooks/flux_test.go @@ -54,9 +54,6 @@ func TestFluxMutationWebhook(t *testing.T) { }, Spec: flux.GitRepositorySpec{ URL: "https://github.com/stefanprodan/podinfo.git", - Reference: &flux.GitRepositoryRef{ - Tag: "6.4.0", - }, }, }), patch: []operations.PatchOperation{ @@ -79,43 +76,12 @@ func TestFluxMutationWebhook(t *testing.T) { }, Spec: flux.GitRepositorySpec{ URL: "not-a-git-url", - Reference: &flux.GitRepositoryRef{ - Tag: "6.4.0", - }, }, }), patch: nil, code: http.StatusInternalServerError, errContains: lang.AgentErrTransformGitURL, }, - { - name: "should replace existing secret", - admissionReq: createFluxGitRepoAdmissionRequest(t, v1.Create, &flux.GitRepository{ - ObjectMeta: metav1.ObjectMeta{ - Name: "replace-secret", - }, - Spec: flux.GitRepositorySpec{ - URL: "https://github.com/stefanprodan/podinfo.git", - SecretRef: &fluxmeta.LocalObjectReference{ - Name: "existing-secret", - }, - Reference: &flux.GitRepositoryRef{ - Tag: "6.4.0", - }, - }, - }), - patch: []operations.PatchOperation{ - operations.ReplacePatchOperation( - "/spec/url", - "https://git-server.com/a-push-user/podinfo-1646971829.git", - ), - operations.ReplacePatchOperation( - "/spec/secretRef/name", - config.ZarfGitServerSecretName, - ), - }, - code: http.StatusOK, - }, { name: "should patch to same url and update secret if hostname matches", admissionReq: createFluxGitRepoAdmissionRequest(t, v1.Update, &flux.GitRepository{ @@ -124,9 +90,6 @@ func TestFluxMutationWebhook(t *testing.T) { }, Spec: flux.GitRepositorySpec{ URL: "https://git-server.com/a-push-user/podinfo.git", - Reference: &flux.GitRepositoryRef{ - Tag: "6.4.0", - }, }, }), patch: []operations.PatchOperation{ From d68265daac01e8a59791bf29f67989ab75701d27 Mon Sep 17 00:00:00 2001 From: Austin Abro Date: Wed, 22 May 2024 15:37:32 +0000 Subject: [PATCH 33/45] adding admission response --- src/internal/agent/hooks/utils_test.go | 9 +++--- src/internal/agent/http/admission/handler.go | 31 ++++++++++++-------- 2 files changed, 24 insertions(+), 16 deletions(-) diff --git a/src/internal/agent/hooks/utils_test.go b/src/internal/agent/hooks/utils_test.go index 887779fb5e..7cc1c912a6 100644 --- a/src/internal/agent/hooks/utils_test.go +++ b/src/internal/agent/hooks/utils_test.go @@ -75,14 +75,15 @@ func verifyAdmission(t *testing.T, rr *httptest.ResponseRecorder, expected admis require.Equal(t, expected.code, rr.Code) + var admissionReview v1.AdmissionReview + + err := json.NewDecoder(rr.Body).Decode(&admissionReview) + if expected.errContains != "" { - require.Contains(t, rr.Body.String(), expected.errContains) + require.Contains(t, admissionReview.Response.Result.Message, expected.errContains) return } - var admissionReview v1.AdmissionReview - - err := json.NewDecoder(rr.Body).Decode(&admissionReview) resp := admissionReview.Response require.NoError(t, err) if expected.patch == nil { diff --git a/src/internal/agent/http/admission/handler.go b/src/internal/agent/http/admission/handler.go index 8651b2183d..1811fdf226 100644 --- a/src/internal/agent/http/admission/handler.go +++ b/src/internal/agent/http/admission/handler.go @@ -15,8 +15,8 @@ import ( "github.com/defenseunicorns/zarf/src/config/lang" "github.com/defenseunicorns/zarf/src/internal/agent/operations" "github.com/defenseunicorns/zarf/src/pkg/message" - v1 "k8s.io/api/admission/v1" - meta "k8s.io/apimachinery/pkg/apis/meta/v1" + corev1 "k8s.io/api/admission/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/serializer" ) @@ -56,7 +56,7 @@ func (h *Handler) Serve(hook operations.Hook) http.HandlerFunc { return } - var review v1.AdmissionReview + var review corev1.AdmissionReview if _, _, err := h.decoder.Decode(body, nil, &review); err != nil { http.Error(w, fmt.Sprintf(lang.AgentErrCouldNotDeserializeReq, err), http.StatusBadRequest) return @@ -70,26 +70,33 @@ func (h *Handler) Serve(hook operations.Hook) http.HandlerFunc { result, err := hook.Execute(review.Request) if err != nil { message.Warnf("%s: %s", lang.AgentErrBindHandler, err.Error()) + admissionResponse := corev1.AdmissionReview{ + Response: &corev1.AdmissionResponse{ + Result: &metav1.Status{Message: err.Error(), Status: string(metav1.StatusReasonInternalError)}, + }, + } + jsonResponse, err := json.Marshal(admissionResponse) + if err != nil { + message.WarnErr(err, lang.AgentErrMarshalResponse) + http.Error(w, lang.AgentErrMarshalResponse, http.StatusInternalServerError) + return + } w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte(err.Error())) + w.Write(jsonResponse) return } - admissionResponse := v1.AdmissionReview{ - TypeMeta: meta.TypeMeta{ - APIVersion: v1.SchemeGroupVersion.String(), - Kind: "AdmissionReview", - }, - Response: &v1.AdmissionResponse{ + admissionResponse := corev1.AdmissionReview{ + Response: &corev1.AdmissionResponse{ UID: review.Request.UID, Allowed: result.Allowed, - Result: &meta.Status{Message: result.Msg}, + Result: &metav1.Status{Message: result.Msg}, }, } // Set the patch operations for mutating admission if len(result.PatchOps) > 0 { - jsonPatchType := v1.PatchTypeJSONPatch + jsonPatchType := corev1.PatchTypeJSONPatch patchBytes, err := json.Marshal(result.PatchOps) if err != nil { message.WarnErr(err, lang.AgentErrMarshallJSONPatch) From 824489453754882da4e04320e93a95c54820c220 Mon Sep 17 00:00:00 2001 From: Austin Abro Date: Wed, 22 May 2024 15:55:05 +0000 Subject: [PATCH 34/45] admission metadata is required? :/ --- src/internal/agent/http/admission/handler.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/internal/agent/http/admission/handler.go b/src/internal/agent/http/admission/handler.go index 1811fdf226..4839073038 100644 --- a/src/internal/agent/http/admission/handler.go +++ b/src/internal/agent/http/admission/handler.go @@ -68,9 +68,14 @@ func (h *Handler) Serve(hook operations.Hook) http.HandlerFunc { } result, err := hook.Execute(review.Request) + admissionMeta := metav1.TypeMeta{ + APIVersion: corev1.SchemeGroupVersion.String(), + Kind: "AdmissionReview", + } if err != nil { message.Warnf("%s: %s", lang.AgentErrBindHandler, err.Error()) admissionResponse := corev1.AdmissionReview{ + TypeMeta: admissionMeta, Response: &corev1.AdmissionResponse{ Result: &metav1.Status{Message: err.Error(), Status: string(metav1.StatusReasonInternalError)}, }, @@ -87,6 +92,7 @@ func (h *Handler) Serve(hook operations.Hook) http.HandlerFunc { } admissionResponse := corev1.AdmissionReview{ + TypeMeta: admissionMeta, Response: &corev1.AdmissionResponse{ UID: review.Request.UID, Allowed: result.Allowed, From 3f27d95df2e916a8562fc8e977e9c0054fe84c81 Mon Sep 17 00:00:00 2001 From: Austin Abro Date: Wed, 22 May 2024 16:44:02 +0000 Subject: [PATCH 35/45] wip --- src/internal/agent/hooks/argo_repo_test.go | 102 +++++++++++++++++ .../agent/hooks/argocd-application.go | 105 ++++++++++-------- src/internal/agent/hooks/argocd-repository.go | 86 +++++++------- src/internal/agent/hooks/utils_test.go | 5 +- src/internal/agent/http/server.go | 4 +- 5 files changed, 211 insertions(+), 91 deletions(-) create mode 100644 src/internal/agent/hooks/argo_repo_test.go diff --git a/src/internal/agent/hooks/argo_repo_test.go b/src/internal/agent/hooks/argo_repo_test.go new file mode 100644 index 0000000000..6b9d92decd --- /dev/null +++ b/src/internal/agent/hooks/argo_repo_test.go @@ -0,0 +1,102 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2021-Present The Zarf Authors + +package hooks + +import ( + "context" + b64 "encoding/base64" + "encoding/json" + "net/http" + "testing" + + "github.com/defenseunicorns/zarf/src/internal/agent/http/admission" + "github.com/defenseunicorns/zarf/src/internal/agent/operations" + "github.com/defenseunicorns/zarf/src/types" + "github.com/stretchr/testify/require" + v1 "k8s.io/api/admission/v1" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" +) + +func createArgoRepoAdmissionRequest(t *testing.T, op v1.Operation, argoRepo *corev1.Secret) *v1.AdmissionRequest { + t.Helper() + raw, err := json.Marshal(argoRepo) + require.NoError(t, err) + return &v1.AdmissionRequest{ + Operation: op, + Object: runtime.RawExtension{ + Raw: raw, + }, + } +} + +func TestArgoRepoWebhook(t *testing.T) { + t.Parallel() + + ctx := context.Background() + state := &types.ZarfState{GitServer: types.GitServerInfo{ + Address: "https://git-server.com", + PushUsername: "a-push-user", + }} + c := createTestClientWithZarfState(ctx, t, state) + handler := admission.NewHandler().Serve(NewGitRepositoryMutationHook(ctx, c)) + + tests := []struct { + name string + admissionReq *v1.AdmissionRequest + expectedPatch []operations.PatchOperation + code int + }{ + { + name: "should be mutated", + admissionReq: createArgoRepoAdmissionRequest(t, v1.Create, &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{ + "argocd.argoproj.io/secret-type": "repository", + }, + Name: "argo-repo-secret", + Namespace: "argo", + }, + Data: map[string][]byte{ + "url": []byte("https://diff-git-server.com/podinfo"), + }, + }), + expectedPatch: []operations.PatchOperation{ + operations.ReplacePatchOperation( + "/data/url", + b64.StdEncoding.EncodeToString([]byte("https://git-server.com/a-push-user/podinfo-1868163476")), + ), + operations.ReplacePatchOperation( + "/data/username", + //TODO this should be different + b64.StdEncoding.EncodeToString([]byte("zarf-git-read-user")), + ), + operations.ReplacePatchOperation( + "/data/password", + b64.StdEncoding.EncodeToString([]byte(state.GitServer.PullPassword)), + ), + }, + code: http.StatusOK, + }, + } + + for _, tt := range tests { + // fmt.Println(tt) + // tt := tt + // t.Run(tt.name, func(t *testing.T) { + // t.Parallel() + sendAdmissionRequest(t, tt.admissionReq, handler, tt.code) + // if tt.expectedPatch != nil { + // expectedPatchJSON, err := json.Marshal(tt.expectedPatch) + // require.NoError(t, err) + // require.NotNil(t, resp) + // require.True(t, resp.Allowed) + // require.JSONEq(t, string(expectedPatchJSON), string(resp.Patch)) + // } else if tt.code != http.StatusInternalServerError { + // require.Empty(t, string(resp.Patch)) + // } + // }) + } +} diff --git a/src/internal/agent/hooks/argocd-application.go b/src/internal/agent/hooks/argocd-application.go index 260b1d53a3..466f382e85 100644 --- a/src/internal/agent/hooks/argocd-application.go +++ b/src/internal/agent/hooks/argocd-application.go @@ -5,81 +5,93 @@ package hooks import ( + "context" "encoding/json" "fmt" "github.com/defenseunicorns/pkg/helpers" "github.com/defenseunicorns/zarf/src/config/lang" "github.com/defenseunicorns/zarf/src/internal/agent/operations" - "github.com/defenseunicorns/zarf/src/internal/agent/state" + "github.com/defenseunicorns/zarf/src/pkg/cluster" "github.com/defenseunicorns/zarf/src/pkg/message" "github.com/defenseunicorns/zarf/src/pkg/transform" "github.com/defenseunicorns/zarf/src/types" v1 "k8s.io/api/admission/v1" ) -// Source represents a subset of the Argo Source object needed for Zarf Git URL mutations -type Source struct { - RepoURL string `json:"repoURL"` +// Application is a definition of an ArgoCD Application resource. +// The ArgoCD Application structs in this file have been partially copied from upstream. +// +// https://github.com/argoproj/argo-cd/blob/v2.11.0/pkg/apis/application/v1alpha1/types.go +// +// There were errors encountered when trying to import argocd as a Go package. +// +// For more information: https://argo-cd.readthedocs.io/en/stable/user-guide/import/ +type Application struct { + Spec ApplicationSpec `json:"spec"` } -// ArgoApplication represents a subset of the Argo Application object needed for Zarf Git URL mutations -type ArgoApplication struct { - Spec struct { - Source Source `json:"source"` - Sources []Source `json:"sources"` - } `json:"spec"` +// ApplicationSpec represents desired application state. Contains link to repository with application definition. +type ApplicationSpec struct { + // Source is a reference to the location of the application's manifests or chart. + Source *ApplicationSource `json:"source,omitempty"` + Sources ApplicationSources `json:"sources,omitempty"` } -var ( - zarfState *types.ZarfState - patches []operations.PatchOperation - isPatched bool - isCreate bool - isUpdate bool -) +// ApplicationSource contains all required information about the source of an application. +type ApplicationSource struct { + // RepoURL is the URL to the repository (Git or Helm) that contains the application manifests. + RepoURL string `json:"repoURL"` +} + +// ApplicationSources contains list of required information about the sources of an application. +type ApplicationSources []ApplicationSource // NewApplicationMutationHook creates a new instance of the ArgoCD Application mutation hook. -func NewApplicationMutationHook() operations.Hook { +func NewApplicationMutationHook(ctx context.Context, cluster *cluster.Cluster) operations.Hook { message.Debug("hooks.NewApplicationMutationHook()") return operations.Hook{ - Create: mutateApplication, - Update: mutateApplication, + Create: func(r *v1.AdmissionRequest) (*operations.Result, error) { + return mutateApplication(ctx, r, cluster) + }, + Update: func(r *v1.AdmissionRequest) (*operations.Result, error) { + return mutateApplication(ctx, r, cluster) + }, } } // mutateApplication mutates the git repository url to point to the repository URL defined in the ZarfState. -func mutateApplication(r *v1.AdmissionRequest) (result *operations.Result, err error) { - - isCreate = r.Operation == v1.Create - isUpdate = r.Operation == v1.Update - - patches = []operations.PatchOperation{} - - // Form the zarfState.GitServer.Address from the zarfState - if zarfState, err = state.GetZarfStateFromAgentPod(); err != nil { +func mutateApplication(ctx context.Context, r *v1.AdmissionRequest, cluster *cluster.Cluster) (result *operations.Result, err error) { + state, err := cluster.LoadZarfState(ctx) + if err != nil { return nil, fmt.Errorf(lang.AgentErrGetState, err) } - message.Debugf("Using the url of (%s) to mutate the ArgoCD Application", zarfState.GitServer.Address) - - // parse to simple struct to read the git url - src := &ArgoApplication{} + message.Debugf("Using the url of (%s) to mutate the ArgoCD Application", state.GitServer.Address) - if err = json.Unmarshal(r.Object.Raw, &src); err != nil { + app := Application{} + if err = json.Unmarshal(r.Object.Raw, &app); err != nil { return nil, fmt.Errorf(lang.ErrUnmarshal, err) } message.Debugf("Data %v", string(r.Object.Raw)) - if src.Spec.Source != (Source{}) { - patchedURL, _ := getPatchedRepoURL(src.Spec.Source.RepoURL) + patches := []operations.PatchOperation{} + + if app.Spec.Source != nil { + patchedURL, err := getPatchedRepoURL(app.Spec.Source.RepoURL, state.GitServer, r) + if err != nil { + return nil, err + } patches = populateSingleSourceArgoApplicationPatchOperations(patchedURL, patches) } - if len(src.Spec.Sources) > 0 { - for idx, source := range src.Spec.Sources { - patchedURL, _ := getPatchedRepoURL(source.RepoURL) + if len(app.Spec.Sources) > 0 { + for idx, source := range app.Spec.Sources { + patchedURL, err := getPatchedRepoURL(source.RepoURL, state.GitServer, r) + if err != nil { + return nil, err + } patches = populateMultipleSourceArgoApplicationPatchOperations(idx, patchedURL, patches) } } @@ -90,15 +102,18 @@ func mutateApplication(r *v1.AdmissionRequest) (result *operations.Result, err e }, nil } -func getPatchedRepoURL(repoURL string) (string, error) { - var err error +func getPatchedRepoURL(repoURL string, gs types.GitServerInfo, r *v1.AdmissionRequest) (string, error) { + isCreate := r.Operation == v1.Create + isUpdate := r.Operation == v1.Update patchedURL := repoURL + var isPatched bool + var err error // Check if this is an update operation and the hostname is different from what we have in the zarfState // NOTE: We mutate on updates IF AND ONLY IF the hostname in the request is different from the hostname in the zarfState // NOTE: We are checking if the hostname is different before because we do not want to potentially mutate a URL that has already been mutated. if isUpdate { - isPatched, err = helpers.DoHostnamesMatch(zarfState.GitServer.Address, repoURL) + isPatched, err = helpers.DoHostnamesMatch(gs.Address, repoURL) if err != nil { return "", fmt.Errorf(lang.AgentErrHostnameMatch, err) } @@ -107,15 +122,15 @@ func getPatchedRepoURL(repoURL string) (string, error) { // Mutate the repoURL if necessary if isCreate || (isUpdate && !isPatched) { // Mutate the git URL so that the hostname matches the hostname in the Zarf state - transformedURL, err := transform.GitURL(zarfState.GitServer.Address, patchedURL, zarfState.GitServer.PushUsername) + transformedURL, err := transform.GitURL(gs.Address, patchedURL, gs.PushUsername) if err != nil { - message.Warnf("Unable to transform the repoURL, using the original url we have: %s", patchedURL) + return "", fmt.Errorf("unable to transform the git url: %w", err) } patchedURL = transformedURL.String() message.Debugf("original repoURL of (%s) got mutated to (%s)", repoURL, patchedURL) } - return patchedURL, err + return patchedURL, nil } // Patch updates of the Argo source spec. diff --git a/src/internal/agent/hooks/argocd-repository.go b/src/internal/agent/hooks/argocd-repository.go index a9b74cd72f..7a2dcc154a 100644 --- a/src/internal/agent/hooks/argocd-repository.go +++ b/src/internal/agent/hooks/argocd-repository.go @@ -5,6 +5,7 @@ package hooks import ( + "context" "encoding/base64" "encoding/json" "fmt" @@ -12,88 +13,91 @@ import ( "github.com/defenseunicorns/pkg/helpers" "github.com/defenseunicorns/zarf/src/config/lang" "github.com/defenseunicorns/zarf/src/internal/agent/operations" - "github.com/defenseunicorns/zarf/src/internal/agent/state" + "github.com/defenseunicorns/zarf/src/pkg/cluster" "github.com/defenseunicorns/zarf/src/pkg/message" "github.com/defenseunicorns/zarf/src/pkg/transform" "github.com/defenseunicorns/zarf/src/types" v1 "k8s.io/api/admission/v1" + corev1 "k8s.io/api/core/v1" ) -// ArgoRepository represents a subset of the Argo Repository object needed for Zarf Git URL mutations -type ArgoRepository struct { - Data struct { - URL string `json:"url"` - } +// RepoCreds holds the definition for repository credentials. +// This has been partially copied from upstream. +// +// https://github.com/argoproj/argo-cd/blob/v2.11.0/pkg/apis/application/v1alpha1/repository_types.go +// +// There were errors encountered when trying to import argocd as a Go package. +// +// For more information: https://argo-cd.readthedocs.io/en/stable/user-guide/import/ +type RepoCreds struct { + // URL is the URL that this credential matches to. + URL string `json:"url"` } -// NewRepositoryMutationHook creates a new instance of the ArgoCD Repository mutation hook. -func NewRepositoryMutationHook() operations.Hook { +// NewRepositorySecretMutationHook creates a new instance of the ArgoCD repository secret mutation hook. +func NewRepositorySecretMutationHook(ctx context.Context, cluster *cluster.Cluster) operations.Hook { message.Debug("hooks.NewRepositoryMutationHook()") return operations.Hook{ - Create: mutateRepository, - Update: mutateRepository, + Create: func(r *v1.AdmissionRequest) (*operations.Result, error) { + return mutateRepositorySecret(ctx, r, cluster) + }, + Update: func(r *v1.AdmissionRequest) (*operations.Result, error) { + return mutateRepositorySecret(ctx, r, cluster) + }, } } -// mutateRepository mutates the git repository URL to point to the repository URL defined in the ZarfState. -func mutateRepository(r *v1.AdmissionRequest) (result *operations.Result, err error) { - - var ( - zarfState *types.ZarfState - patches []operations.PatchOperation - isPatched bool - - isCreate = r.Operation == v1.Create - isUpdate = r.Operation == v1.Update - ) +// mutateRepositorySecret mutates the git URL in the ArgoCD repository secret to point to the repository URL defined in the ZarfState. +func mutateRepositorySecret(ctx context.Context, r *v1.AdmissionRequest, cluster *cluster.Cluster) (result *operations.Result, err error) { + isCreate := r.Operation == v1.Create + isUpdate := r.Operation == v1.Update + var isPatched bool - // Form the zarfState.GitServer.Address from the zarfState - if zarfState, err = state.GetZarfStateFromAgentPod(); err != nil { + state, err := cluster.LoadZarfState(ctx) + if err != nil { return nil, fmt.Errorf(lang.AgentErrGetState, err) } - message.Debugf("Using the url of (%s) to mutate the ArgoCD Repository Secret", zarfState.GitServer.Address) - - // parse to simple struct to read the git url - src := &ArgoRepository{} + message.Infof("Using the url of (%s) to mutate the ArgoCD Repository Secret", state.GitServer.Address) - if err = json.Unmarshal(r.Object.Raw, &src); err != nil { + secret := corev1.Secret{} + if err = json.Unmarshal(r.Object.Raw, &secret); err != nil { return nil, fmt.Errorf(lang.ErrUnmarshal, err) } - decodedURL, err := base64.StdEncoding.DecodeString(src.Data.URL) - if err != nil { - message.Fatalf("Error decoding URL from Repository Secret %s", src.Data.URL) + + url, exists := secret.Data["url"] + if !exists { + return nil, fmt.Errorf("url field not found in argocd repository secret data") } - src.Data.URL = string(decodedURL) - patchedURL := src.Data.URL + + var repoCreds RepoCreds + repoCreds.URL = string(url) // Check if this is an update operation and the hostname is different from what we have in the zarfState // NOTE: We mutate on updates IF AND ONLY IF the hostname in the request is different from the hostname in the zarfState // NOTE: We are checking if the hostname is different before because we do not want to potentially mutate a URL that has already been mutated. if isUpdate { - isPatched, err = helpers.DoHostnamesMatch(zarfState.GitServer.Address, src.Data.URL) + isPatched, err = helpers.DoHostnamesMatch(state.GitServer.Address, repoCreds.URL) if err != nil { return nil, fmt.Errorf(lang.AgentErrHostnameMatch, err) } } + patchedURL := repoCreds.URL // Mutate the repoURL if necessary if isCreate || (isUpdate && !isPatched) { // Mutate the git URL so that the hostname matches the hostname in the Zarf state - transformedURL, err := transform.GitURL(zarfState.GitServer.Address, patchedURL, zarfState.GitServer.PushUsername) + transformedURL, err := transform.GitURL(state.GitServer.Address, repoCreds.URL, state.GitServer.PushUsername) if err != nil { - message.Warnf("Unable to transform the url, using the original url we have: %s", patchedURL) + return nil, fmt.Errorf("unable the git url: %w", err) } patchedURL = transformedURL.String() - message.Debugf("original url of (%s) got mutated to (%s)", src.Data.URL, patchedURL) + message.Debugf("original url of (%s) got mutated to (%s)", repoCreds.URL, patchedURL) } - // Patch updates of the repo spec - patches = populateArgoRepositoryPatchOperations(patchedURL, zarfState.GitServer.PullPassword) - return &operations.Result{ Allowed: true, - PatchOps: patches, + PatchOps: populateArgoRepositoryPatchOperations(patchedURL, state.GitServer.PullPassword), }, nil } diff --git a/src/internal/agent/hooks/utils_test.go b/src/internal/agent/hooks/utils_test.go index 2a1c394935..138e83bad8 100644 --- a/src/internal/agent/hooks/utils_test.go +++ b/src/internal/agent/hooks/utils_test.go @@ -62,17 +62,16 @@ func sendAdmissionRequest(t *testing.T, admissionReq *v1.AdmissionRequest, handl return rr } -func verifyAdmission(t *testing.T, rr *httptest.ResponseRecorder, expectedCode int, expectedPatch []operations.PatchOperation, expectedErr error){ +func verifyAdmission(t *testing.T, rr *httptest.ResponseRecorder, expectedCode int, expectedPatch []operations.PatchOperation, expectedErr error) { t.Helper() require.Equal(t, expectedCode, rr.Code) if expectedErr != nil { - require.Contains(t, rr.Body.String(), expectedErr.Error() ) + require.Contains(t, rr.Body.String(), expectedErr.Error()) return } - var admissionReview v1.AdmissionReview err := json.NewDecoder(rr.Body).Decode(&admissionReview) diff --git a/src/internal/agent/http/server.go b/src/internal/agent/http/server.go index 2c56428fd7..49fa5a6e7e 100644 --- a/src/internal/agent/http/server.go +++ b/src/internal/agent/http/server.go @@ -31,8 +31,8 @@ func NewAdmissionServer(port string) *http.Server { // Instances hooks podsMutation := hooks.NewPodMutationHook(ctx, c) fluxGitRepositoryMutation := hooks.NewGitRepositoryMutationHook(ctx, c) - argocdApplicationMutation := hooks.NewApplicationMutationHook() - argocdRepositoryMutation := hooks.NewRepositoryMutationHook() + argocdApplicationMutation := hooks.NewApplicationMutationHook(ctx, c) + argocdRepositoryMutation := hooks.NewRepositorySecretMutationHook(ctx, c) // Routers ah := admission.NewHandler() From 804afcee27b5e3e080bec3b79a2ecb558444ef5c Mon Sep 17 00:00:00 2001 From: Austin Abro Date: Wed, 22 May 2024 17:11:32 +0000 Subject: [PATCH 36/45] argo changes --- src/internal/agent/hooks/argo_repo_test.go | 6 +++--- src/internal/agent/hooks/argocd-repository.go | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/internal/agent/hooks/argo_repo_test.go b/src/internal/agent/hooks/argo_repo_test.go index 5c1ffb36ec..b5409ea579 100644 --- a/src/internal/agent/hooks/argo_repo_test.go +++ b/src/internal/agent/hooks/argo_repo_test.go @@ -39,7 +39,8 @@ func TestArgoRepoWebhook(t *testing.T) { state := &types.ZarfState{GitServer: types.GitServerInfo{ Address: "https://git-server.com", PushUsername: "a-push-user", - PullPassword: "a-pull-user", + PullPassword: "a-pull-password", + PullUsername: "a-pull-user", }} c := createTestClientWithZarfState(ctx, t, state) handler := admission.NewHandler().Serve(NewRepositorySecretMutationHook(ctx, c)) @@ -66,8 +67,7 @@ func TestArgoRepoWebhook(t *testing.T) { ), operations.ReplacePatchOperation( "/data/username", - //TODO this should be different - b64.StdEncoding.EncodeToString([]byte("zarf-git-read-user")), + b64.StdEncoding.EncodeToString([]byte(state.GitServer.PullUsername)), ), operations.ReplacePatchOperation( "/data/password", diff --git a/src/internal/agent/hooks/argocd-repository.go b/src/internal/agent/hooks/argocd-repository.go index 7a2dcc154a..9e643414d7 100644 --- a/src/internal/agent/hooks/argocd-repository.go +++ b/src/internal/agent/hooks/argocd-repository.go @@ -97,16 +97,16 @@ func mutateRepositorySecret(ctx context.Context, r *v1.AdmissionRequest, cluster return &operations.Result{ Allowed: true, - PatchOps: populateArgoRepositoryPatchOperations(patchedURL, state.GitServer.PullPassword), + PatchOps: populateArgoRepositoryPatchOperations(patchedURL, state.GitServer), }, nil } // Patch updates of the Argo Repository Secret. -func populateArgoRepositoryPatchOperations(repoURL string, zarfGitPullPassword string) []operations.PatchOperation { +func populateArgoRepositoryPatchOperations(repoURL string, gitServer types.GitServerInfo) []operations.PatchOperation { var patches []operations.PatchOperation patches = append(patches, operations.ReplacePatchOperation("/data/url", base64.StdEncoding.EncodeToString([]byte(repoURL)))) - patches = append(patches, operations.ReplacePatchOperation("/data/username", base64.StdEncoding.EncodeToString([]byte(types.ZarfGitReadUser)))) - patches = append(patches, operations.ReplacePatchOperation("/data/password", base64.StdEncoding.EncodeToString([]byte(zarfGitPullPassword)))) + patches = append(patches, operations.ReplacePatchOperation("/data/username", base64.StdEncoding.EncodeToString([]byte(gitServer.PullUsername)))) + patches = append(patches, operations.ReplacePatchOperation("/data/password", base64.StdEncoding.EncodeToString([]byte(gitServer.PullPassword)))) return patches } From 34e515f33cf19e4845e6d53eb312814708822e9c Mon Sep 17 00:00:00 2001 From: Austin Abro Date: Wed, 22 May 2024 17:33:45 +0000 Subject: [PATCH 37/45] adding test --- packages/zarf-agent/manifests/deployment.yaml | 6 ---- src/internal/agent/hooks/argo_repo_test.go | 30 +++++++++++++++++++ src/internal/agent/http/proxy.go | 28 ++++++++++------- src/internal/agent/state/state.go | 26 ---------------- 4 files changed, 47 insertions(+), 43 deletions(-) delete mode 100644 src/internal/agent/state/state.go diff --git a/packages/zarf-agent/manifests/deployment.yaml b/packages/zarf-agent/manifests/deployment.yaml index a4113812f9..a8e481845f 100644 --- a/packages/zarf-agent/manifests/deployment.yaml +++ b/packages/zarf-agent/manifests/deployment.yaml @@ -43,9 +43,6 @@ spec: - name: tls-certs mountPath: /etc/certs readOnly: true - - name: zarf-state - mountPath: /etc/zarf-state - readOnly: true # Required for OpenShift to mount k9s vendored directories - name: config mountPath: /.config @@ -55,9 +52,6 @@ spec: - name: tls-certs secret: secretName: agent-hook-tls - - name: zarf-state - secret: - secretName: zarf-state # Required for OpenShift to mount k9s vendored directories - name: config emptyDir: {} diff --git a/src/internal/agent/hooks/argo_repo_test.go b/src/internal/agent/hooks/argo_repo_test.go index b5409ea579..b84a3e8d07 100644 --- a/src/internal/agent/hooks/argo_repo_test.go +++ b/src/internal/agent/hooks/argo_repo_test.go @@ -76,6 +76,36 @@ func TestArgoRepoWebhook(t *testing.T) { }, code: http.StatusOK, }, + { + name: "matching hostname on update should stay the same, but secret should be added", + admissionReq: createArgoRepoAdmissionRequest(t, v1.Update, &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{ + "argocd.argoproj.io/secret-type": "repository", + }, + Name: "argo-repo-secret", + Namespace: "argo", + }, + Data: map[string][]byte{ + "url": []byte("https://git-server.com/podinfo"), + }, + }), + patch: []operations.PatchOperation{ + operations.ReplacePatchOperation( + "/data/url", + b64.StdEncoding.EncodeToString([]byte("https://git-server.com/podinfo")), + ), + operations.ReplacePatchOperation( + "/data/username", + b64.StdEncoding.EncodeToString([]byte(state.GitServer.PullUsername)), + ), + operations.ReplacePatchOperation( + "/data/password", + b64.StdEncoding.EncodeToString([]byte(state.GitServer.PullPassword)), + ), + }, + code: http.StatusOK, + }, } for _, tt := range tests { diff --git a/src/internal/agent/http/proxy.go b/src/internal/agent/http/proxy.go index ea0a54f024..8afa937c55 100644 --- a/src/internal/agent/http/proxy.go +++ b/src/internal/agent/http/proxy.go @@ -5,6 +5,7 @@ package http import ( + "context" "crypto/tls" "fmt" "io" @@ -14,7 +15,7 @@ import ( "strings" "github.com/defenseunicorns/zarf/src/config/lang" - "github.com/defenseunicorns/zarf/src/internal/agent/state" + "github.com/defenseunicorns/zarf/src/pkg/cluster" "github.com/defenseunicorns/zarf/src/pkg/message" "github.com/defenseunicorns/zarf/src/pkg/transform" ) @@ -45,7 +46,12 @@ func proxyRequestTransform(r *http.Request) error { // We remove this so that go will encode and decode on our behalf (see https://pkg.go.dev/net/http#Transport DisableCompression) r.Header.Del("Accept-Encoding") - zarfState, err := state.GetZarfStateFromAgentPod() + c, err := cluster.NewCluster() + if err != nil { + message.Fatalf(err, err.Error()) + } + ctx := context.Background() + state, err := c.LoadZarfState(ctx) if err != nil { return err } @@ -55,31 +61,31 @@ func proxyRequestTransform(r *http.Request) error { // Setup authentication for each type of service based on User Agent switch { case isGitUserAgent(r.UserAgent()): - r.SetBasicAuth(zarfState.GitServer.PushUsername, zarfState.GitServer.PushPassword) + r.SetBasicAuth(state.GitServer.PushUsername, state.GitServer.PushPassword) case isNpmUserAgent(r.UserAgent()): - r.Header.Set("Authorization", "Bearer "+zarfState.ArtifactServer.PushToken) + r.Header.Set("Authorization", "Bearer "+state.ArtifactServer.PushToken) default: - r.SetBasicAuth(zarfState.ArtifactServer.PushUsername, zarfState.ArtifactServer.PushToken) + r.SetBasicAuth(state.ArtifactServer.PushUsername, state.ArtifactServer.PushToken) } // Transform the URL; if we see the NoTransform prefix, strip it; otherwise, transform the URL based on User Agent if strings.HasPrefix(r.URL.Path, transform.NoTransform) { switch { case isGitUserAgent(r.UserAgent()): - targetURL, err = transform.NoTransformTarget(zarfState.GitServer.Address, r.URL.Path) + targetURL, err = transform.NoTransformTarget(state.GitServer.Address, r.URL.Path) default: - targetURL, err = transform.NoTransformTarget(zarfState.ArtifactServer.Address, r.URL.Path) + targetURL, err = transform.NoTransformTarget(state.ArtifactServer.Address, r.URL.Path) } } else { switch { case isGitUserAgent(r.UserAgent()): - targetURL, err = transform.GitURL(zarfState.GitServer.Address, getTLSScheme(r.TLS)+r.Host+r.URL.String(), zarfState.GitServer.PushUsername) + targetURL, err = transform.GitURL(state.GitServer.Address, getTLSScheme(r.TLS)+r.Host+r.URL.String(), state.GitServer.PushUsername) case isPipUserAgent(r.UserAgent()): - targetURL, err = transform.PipTransformURL(zarfState.ArtifactServer.Address, getTLSScheme(r.TLS)+r.Host+r.URL.String()) + targetURL, err = transform.PipTransformURL(state.ArtifactServer.Address, getTLSScheme(r.TLS)+r.Host+r.URL.String()) case isNpmUserAgent(r.UserAgent()): - targetURL, err = transform.NpmTransformURL(zarfState.ArtifactServer.Address, getTLSScheme(r.TLS)+r.Host+r.URL.String()) + targetURL, err = transform.NpmTransformURL(state.ArtifactServer.Address, getTLSScheme(r.TLS)+r.Host+r.URL.String()) default: - targetURL, err = transform.GenTransformURL(zarfState.ArtifactServer.Address, getTLSScheme(r.TLS)+r.Host+r.URL.String()) + targetURL, err = transform.GenTransformURL(state.ArtifactServer.Address, getTLSScheme(r.TLS)+r.Host+r.URL.String()) } } diff --git a/src/internal/agent/state/state.go b/src/internal/agent/state/state.go deleted file mode 100644 index 7c99acb4d2..0000000000 --- a/src/internal/agent/state/state.go +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// SPDX-FileCopyrightText: 2021-Present The Zarf Authors - -// Package state provides helpers for interacting with the Zarf agent state. -package state - -import ( - "encoding/json" - "os" - - "github.com/defenseunicorns/zarf/src/types" -) - -const zarfStatePath = "/etc/zarf-state/state" - -// GetZarfStateFromAgentPod reads the state json file that was mounted into the agent pods. -func GetZarfStateFromAgentPod() (state *types.ZarfState, err error) { - // Read the state file - stateFile, err := os.ReadFile(zarfStatePath) - if err != nil { - return nil, err - } - - // Unmarshal the json file into a Go struct - return state, json.Unmarshal(stateFile, &state) -} From 12d8528c321cfff00332844c123ed27aff1931e7 Mon Sep 17 00:00:00 2001 From: Austin Abro Date: Wed, 22 May 2024 18:40:42 +0000 Subject: [PATCH 38/45] go mod --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 71368adc16..1275140145 100644 --- a/go.mod +++ b/go.mod @@ -20,6 +20,7 @@ require ( github.com/fairwindsops/pluto/v5 v5.18.4 github.com/fatih/color v1.16.0 github.com/fluxcd/helm-controller/api v0.37.4 + github.com/fluxcd/pkg/apis/meta v1.3.0 github.com/fluxcd/source-controller/api v1.2.4 github.com/go-git/go-git/v5 v5.11.0 github.com/go-logr/logr v1.4.1 @@ -227,7 +228,6 @@ require ( github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fluxcd/pkg/apis/acl v0.1.0 // indirect github.com/fluxcd/pkg/apis/kustomize v1.3.0 // indirect - github.com/fluxcd/pkg/apis/meta v1.3.0 github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/fvbommel/sortorder v1.1.0 // indirect github.com/gabriel-vasile/mimetype v1.4.3 // indirect From f35d515c76fcf4f4cbe92ba4a1f979c62702b9ba Mon Sep 17 00:00:00 2001 From: Austin Abro Date: Wed, 22 May 2024 19:24:55 +0000 Subject: [PATCH 39/45] comment --- src/config/lang/english.go | 1 - src/internal/agent/hooks/flux.go | 5 ++++- src/internal/agent/hooks/flux_test.go | 3 +-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/config/lang/english.go b/src/config/lang/english.go index 1400d5df6e..adae1f1a41 100644 --- a/src/config/lang/english.go +++ b/src/config/lang/english.go @@ -670,7 +670,6 @@ const ( AgentErrImageSwap = "Unable to swap the host for (%s)" AgentErrInvalidMethod = "invalid method only POST requests are allowed" AgentErrInvalidOp = "invalid operation: %s" - AgentErrTransformGitURL = "unable to transform the git url" AgentErrInvalidType = "only content type 'application/json' is supported" AgentErrMarshallJSONPatch = "unable to marshall the json patch" AgentErrMarshalResponse = "unable to marshal the response" diff --git a/src/internal/agent/hooks/flux.go b/src/internal/agent/hooks/flux.go index 95889a1088..617b91901c 100644 --- a/src/internal/agent/hooks/flux.go +++ b/src/internal/agent/hooks/flux.go @@ -21,6 +21,9 @@ import ( v1 "k8s.io/api/admission/v1" ) +// AgentErrTransformGitURL is thrown when the agent fails to make the git url a Zarf compatible url +const AgentErrTransformGitURL = "unable to transform the git url" + // NewGitRepositoryMutationHook creates a new instance of the git repo mutation hook. func NewGitRepositoryMutationHook(ctx context.Context, cluster *cluster.Cluster) operations.Hook { message.Debug("hooks.NewGitRepositoryMutationHook()") @@ -74,7 +77,7 @@ func mutateGitRepo(ctx context.Context, r *v1.AdmissionRequest, cluster *cluster // Mutate the git URL so that the hostname matches the hostname in the Zarf state transformedURL, err := transform.GitURL(state.GitServer.Address, patchedURL, state.GitServer.PushUsername) if err != nil { - return nil, fmt.Errorf("%s: %w", lang.AgentErrTransformGitURL, err) + return nil, fmt.Errorf("%s: %w", AgentErrTransformGitURL, err) } patchedURL = transformedURL.String() message.Debugf("original git URL of (%s) got mutated to (%s)", repo.Spec.URL, patchedURL) diff --git a/src/internal/agent/hooks/flux_test.go b/src/internal/agent/hooks/flux_test.go index 3538fdff37..cf56ac844a 100644 --- a/src/internal/agent/hooks/flux_test.go +++ b/src/internal/agent/hooks/flux_test.go @@ -10,7 +10,6 @@ import ( "testing" "github.com/defenseunicorns/zarf/src/config" - "github.com/defenseunicorns/zarf/src/config/lang" "github.com/defenseunicorns/zarf/src/internal/agent/http/admission" "github.com/defenseunicorns/zarf/src/internal/agent/operations" "github.com/defenseunicorns/zarf/src/types" @@ -80,7 +79,7 @@ func TestFluxMutationWebhook(t *testing.T) { }), patch: nil, code: http.StatusInternalServerError, - errContains: lang.AgentErrTransformGitURL, + errContains: AgentErrTransformGitURL, }, { name: "should patch to same url and update secret if hostname matches", From 084ded8d633fa226d675c453017e21cee425c4e4 Mon Sep 17 00:00:00 2001 From: Austin Abro Date: Wed, 22 May 2024 19:58:54 +0000 Subject: [PATCH 40/45] WIP --- .../agent/hooks/argo_application_test.go | 97 +++++++++++++++++++ .../agent/hooks/argocd-application.go | 2 +- 2 files changed, 98 insertions(+), 1 deletion(-) create mode 100644 src/internal/agent/hooks/argo_application_test.go diff --git a/src/internal/agent/hooks/argo_application_test.go b/src/internal/agent/hooks/argo_application_test.go new file mode 100644 index 0000000000..a14472b5ae --- /dev/null +++ b/src/internal/agent/hooks/argo_application_test.go @@ -0,0 +1,97 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2021-Present The Zarf Authors + +package hooks + +import ( + "context" + "encoding/json" + "net/http" + "testing" + + "github.com/defenseunicorns/zarf/src/internal/agent/http/admission" + "github.com/defenseunicorns/zarf/src/internal/agent/operations" + "github.com/defenseunicorns/zarf/src/types" + "github.com/stretchr/testify/require" + v1 "k8s.io/api/admission/v1" + "k8s.io/apimachinery/pkg/runtime" +) + +func createArgoAppAdmissionRequest(t *testing.T, op v1.Operation, argoApp *Application) *v1.AdmissionRequest { + t.Helper() + raw, err := json.Marshal(argoApp) + require.NoError(t, err) + return &v1.AdmissionRequest{ + Operation: op, + Object: runtime.RawExtension{ + Raw: raw, + }, + } +} + +func TestArgoAppWebhook(t *testing.T) { + t.Parallel() + + ctx := context.Background() + state := &types.ZarfState{GitServer: types.GitServerInfo{ + Address: "https://git-server.com", + PushUsername: "a-push-user", + PullPassword: "a-pull-password", + PullUsername: "a-pull-user", + }} + c := createTestClientWithZarfState(ctx, t, state) + handler := admission.NewHandler().Serve(NewApplicationMutationHook(ctx, c)) + + tests := []admissionTest{ + { + name: "should be mutated", + admissionReq: createArgoAppAdmissionRequest(t, v1.Create, &Application{ + Spec: ApplicationSpec{ + Source: &ApplicationSource{RepoURL: "https://diff-git-server.com/peanuts"}, + Sources: ApplicationSources{ + { + RepoURL: "https://diff-git-server.com/cashews", + }, + { + RepoURL: "https://diff-git-server.com/almonds", + }, + }, + }, + }), + patch: []operations.PatchOperation{ + operations.ReplacePatchOperation( + "/spec/source/repoURL", + "https://git-server.com/a-push-user/peanuts-3883081014", + ), + operations.ReplacePatchOperation( + "/spec/sources/0/repoURL", + "https://git-server.com/a-push-user/cashews-580170494", + ), + operations.ReplacePatchOperation( + "/spec/sources/1/repoURL", + "https://git-server.com/a-push-user/almonds-640159520", + ), + }, + code: http.StatusOK, + }, + { + name: "should be mutated", + admissionReq: createArgoAppAdmissionRequest(t, v1.Create, &Application{ + Spec: ApplicationSpec{ + Source: &ApplicationSource{RepoURL: "https://bad-url"}, + }, + }), + code: http.StatusInternalServerError, + errContains: "unable to transform", + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + rr := sendAdmissionRequest(t, tt.admissionReq, handler) + verifyAdmission(t, rr, tt) + }) + } +} diff --git a/src/internal/agent/hooks/argocd-application.go b/src/internal/agent/hooks/argocd-application.go index 466f382e85..8f9f239431 100644 --- a/src/internal/agent/hooks/argocd-application.go +++ b/src/internal/agent/hooks/argocd-application.go @@ -124,7 +124,7 @@ func getPatchedRepoURL(repoURL string, gs types.GitServerInfo, r *v1.AdmissionRe // Mutate the git URL so that the hostname matches the hostname in the Zarf state transformedURL, err := transform.GitURL(gs.Address, patchedURL, gs.PushUsername) if err != nil { - return "", fmt.Errorf("unable to transform the git url: %w", err) + return "", fmt.Errorf("%s: %w", lang.AgentErrTransformGitURL, err) } patchedURL = transformedURL.String() message.Debugf("original repoURL of (%s) got mutated to (%s)", repoURL, patchedURL) From bf23d5b1f7e75b4c30c94e80e92129c91d6e8863 Mon Sep 17 00:00:00 2001 From: Austin Abro Date: Wed, 22 May 2024 20:00:19 +0000 Subject: [PATCH 41/45] testing --- src/internal/agent/hooks/argo_application_test.go | 2 +- src/internal/agent/hooks/argocd-application.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/internal/agent/hooks/argo_application_test.go b/src/internal/agent/hooks/argo_application_test.go index a14472b5ae..77b827634b 100644 --- a/src/internal/agent/hooks/argo_application_test.go +++ b/src/internal/agent/hooks/argo_application_test.go @@ -82,7 +82,7 @@ func TestArgoAppWebhook(t *testing.T) { }, }), code: http.StatusInternalServerError, - errContains: "unable to transform", + errContains: AgentErrTransformGitURL, }, } diff --git a/src/internal/agent/hooks/argocd-application.go b/src/internal/agent/hooks/argocd-application.go index 8f9f239431..4077b2acaf 100644 --- a/src/internal/agent/hooks/argocd-application.go +++ b/src/internal/agent/hooks/argocd-application.go @@ -124,7 +124,7 @@ func getPatchedRepoURL(repoURL string, gs types.GitServerInfo, r *v1.AdmissionRe // Mutate the git URL so that the hostname matches the hostname in the Zarf state transformedURL, err := transform.GitURL(gs.Address, patchedURL, gs.PushUsername) if err != nil { - return "", fmt.Errorf("%s: %w", lang.AgentErrTransformGitURL, err) + return "", fmt.Errorf("%s: %w", AgentErrTransformGitURL, err) } patchedURL = transformedURL.String() message.Debugf("original repoURL of (%s) got mutated to (%s)", repoURL, patchedURL) From 532af608d1760fc5f2a67a9351a6bf5370fba433 Mon Sep 17 00:00:00 2001 From: Austin Abro Date: Wed, 22 May 2024 20:06:55 +0000 Subject: [PATCH 42/45] simplify --- src/internal/agent/hooks/argo_application_test.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/internal/agent/hooks/argo_application_test.go b/src/internal/agent/hooks/argo_application_test.go index 77b827634b..5831e28612 100644 --- a/src/internal/agent/hooks/argo_application_test.go +++ b/src/internal/agent/hooks/argo_application_test.go @@ -36,8 +36,6 @@ func TestArgoAppWebhook(t *testing.T) { state := &types.ZarfState{GitServer: types.GitServerInfo{ Address: "https://git-server.com", PushUsername: "a-push-user", - PullPassword: "a-pull-password", - PullUsername: "a-pull-user", }} c := createTestClientWithZarfState(ctx, t, state) handler := admission.NewHandler().Serve(NewApplicationMutationHook(ctx, c)) From ea90366153feddfcb38ad003e855cc44c6befc95 Mon Sep 17 00:00:00 2001 From: Austin Abro Date: Wed, 22 May 2024 20:07:40 +0000 Subject: [PATCH 43/45] simplify --- src/internal/agent/hooks/argo_application_test.go | 2 +- src/internal/agent/hooks/argocd-application.go | 7 ++----- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/src/internal/agent/hooks/argo_application_test.go b/src/internal/agent/hooks/argo_application_test.go index 5831e28612..3b7c5d139a 100644 --- a/src/internal/agent/hooks/argo_application_test.go +++ b/src/internal/agent/hooks/argo_application_test.go @@ -46,7 +46,7 @@ func TestArgoAppWebhook(t *testing.T) { admissionReq: createArgoAppAdmissionRequest(t, v1.Create, &Application{ Spec: ApplicationSpec{ Source: &ApplicationSource{RepoURL: "https://diff-git-server.com/peanuts"}, - Sources: ApplicationSources{ + Sources: []ApplicationSource{ { RepoURL: "https://diff-git-server.com/cashews", }, diff --git a/src/internal/agent/hooks/argocd-application.go b/src/internal/agent/hooks/argocd-application.go index 4077b2acaf..90a5b98744 100644 --- a/src/internal/agent/hooks/argocd-application.go +++ b/src/internal/agent/hooks/argocd-application.go @@ -34,8 +34,8 @@ type Application struct { // ApplicationSpec represents desired application state. Contains link to repository with application definition. type ApplicationSpec struct { // Source is a reference to the location of the application's manifests or chart. - Source *ApplicationSource `json:"source,omitempty"` - Sources ApplicationSources `json:"sources,omitempty"` + Source *ApplicationSource `json:"source,omitempty"` + Sources []ApplicationSource `json:"sources,omitempty"` } // ApplicationSource contains all required information about the source of an application. @@ -44,9 +44,6 @@ type ApplicationSource struct { RepoURL string `json:"repoURL"` } -// ApplicationSources contains list of required information about the sources of an application. -type ApplicationSources []ApplicationSource - // NewApplicationMutationHook creates a new instance of the ArgoCD Application mutation hook. func NewApplicationMutationHook(ctx context.Context, cluster *cluster.Cluster) operations.Hook { message.Debug("hooks.NewApplicationMutationHook()") From 69e21ed0599265d2820cfa8a988605bc06826e0d Mon Sep 17 00:00:00 2001 From: Austin Abro Date: Wed, 22 May 2024 20:10:44 +0000 Subject: [PATCH 44/45] proxy --- src/internal/agent/http/proxy.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/internal/agent/http/proxy.go b/src/internal/agent/http/proxy.go index 8afa937c55..860d147811 100644 --- a/src/internal/agent/http/proxy.go +++ b/src/internal/agent/http/proxy.go @@ -48,7 +48,7 @@ func proxyRequestTransform(r *http.Request) error { c, err := cluster.NewCluster() if err != nil { - message.Fatalf(err, err.Error()) + return err } ctx := context.Background() state, err := c.LoadZarfState(ctx) From e1fe865490ed09bffa7fcc5953e5a468a38543dd Mon Sep 17 00:00:00 2001 From: Lucas Rodriguez Date: Thu, 23 May 2024 10:19:55 -0500 Subject: [PATCH 45/45] fix test case name and test file names --- .../{argo_application_test.go => argocd-application_test.go} | 2 +- .../hooks/{argo_repo_test.go => argocd-repository_test.go} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename src/internal/agent/hooks/{argo_application_test.go => argocd-application_test.go} (97%) rename src/internal/agent/hooks/{argo_repo_test.go => argocd-repository_test.go} (100%) diff --git a/src/internal/agent/hooks/argo_application_test.go b/src/internal/agent/hooks/argocd-application_test.go similarity index 97% rename from src/internal/agent/hooks/argo_application_test.go rename to src/internal/agent/hooks/argocd-application_test.go index 3b7c5d139a..5d241dd571 100644 --- a/src/internal/agent/hooks/argo_application_test.go +++ b/src/internal/agent/hooks/argocd-application_test.go @@ -73,7 +73,7 @@ func TestArgoAppWebhook(t *testing.T) { code: http.StatusOK, }, { - name: "should be mutated", + name: "should return internal server error on bad git URL", admissionReq: createArgoAppAdmissionRequest(t, v1.Create, &Application{ Spec: ApplicationSpec{ Source: &ApplicationSource{RepoURL: "https://bad-url"}, diff --git a/src/internal/agent/hooks/argo_repo_test.go b/src/internal/agent/hooks/argocd-repository_test.go similarity index 100% rename from src/internal/agent/hooks/argo_repo_test.go rename to src/internal/agent/hooks/argocd-repository_test.go