Skip to content

Commit

Permalink
works on my machine
Browse files Browse the repository at this point in the history
  • Loading branch information
Lucas Rodriguez committed May 20, 2024
1 parent 5bf7e01 commit 6dbbb7d
Show file tree
Hide file tree
Showing 5 changed files with 219 additions and 22 deletions.
18 changes: 9 additions & 9 deletions src/internal/agent/hooks/pods.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
},
}
}

Expand All @@ -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
Expand All @@ -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
Expand Down
143 changes: 143 additions & 0 deletions src/internal/agent/hooks/pods_test.go
Original file line number Diff line number Diff line change
@@ -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))
}
})
}
}
45 changes: 45 additions & 0 deletions src/internal/agent/hooks/test_utils.go
Original file line number Diff line number Diff line change
@@ -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
}
Original file line number Diff line number Diff line change
@@ -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"
Expand All @@ -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)
Expand Down Expand Up @@ -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
}
Expand All @@ -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)
Expand Down
11 changes: 9 additions & 2 deletions src/internal/agent/http/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand All @@ -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))
Expand Down

0 comments on commit 6dbbb7d

Please sign in to comment.