Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 12 additions & 4 deletions cmd/crdinstaller/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"flag"
"fmt"

admv1 "k8s.io/api/admissionregistration/v1"
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/klog/v2"
Expand All @@ -21,9 +22,7 @@ import (
"go.goms.io/fleet/cmd/crdinstaller/utils"
)

var (
mode = flag.String("mode", "", "Mode to run in: 'hub' or 'member' (required)")
)
var mode = flag.String("mode", "", "Mode to run in: 'hub' or 'member' (required)")

func main() {
klog.InitFlags(nil)
Expand Down Expand Up @@ -55,10 +54,12 @@ func main() {
if err := apiextensionsv1.AddToScheme(scheme); err != nil {
klog.Fatalf("Failed to add apiextensions scheme: %v", err)
}
if err := admv1.AddToScheme(scheme); err != nil {
klog.Fatalf("Failed to add admissionregistration scheme: %v", err)
}
client, err := client.New(config, client.Options{
Scheme: scheme,
})

if err != nil {
klog.Fatalf("Failed to create Kubernetes client: %v", err)
}
Expand All @@ -70,6 +71,13 @@ func main() {
}

klog.Infof("Successfully installed %s CRDs", *mode)

if err := utils.InstallManagedResourceVAP(ctx, client, *mode); err != nil {
klog.Warningf("Failed to install managed resource ValidatingAdmissionPolicy: %v", err)
return
}

klog.Infof("Successfully installed %s managed resource ValidatingAdmissionPolicy", *mode)
}

// installCRDs installs the CRDs from the specified directory based on the mode.
Expand Down
39 changes: 34 additions & 5 deletions cmd/crdinstaller/utils/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,15 @@ import (
"strings"

apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/serializer"
"k8s.io/klog/v2"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"

"go.goms.io/fleet/pkg/webhook/managedresource"
)

const (
Expand Down Expand Up @@ -51,7 +54,7 @@ func InstallCRD(ctx context.Context, client client.Client, crd *apiextensionsv1.
},
}

createOrUpdateRes, err := controllerutil.CreateOrUpdate(ctx, client, &existingCRD, func() error {
mutFn := func() error {
// Copy spec from our decoded CRD to the object we're creating/updating.
existingCRD.Spec = crd.Spec

Expand All @@ -65,14 +68,24 @@ func InstallCRD(ctx context.Context, client client.Client, crd *apiextensionsv1.
// needed for clean up of CRD by kube-addon-manager.
existingCRD.Labels[AzureManagedLabelKey] = FleetLabelValue
return nil
})
}
err := install(ctx, client, &existingCRD, mutFn)
if err == nil {
klog.Infof("Successfully created/updated CRD %s", crd.Name)
}
return err
}

func install(ctx context.Context, client client.Client, obj client.Object, mut func() error) error {
if mut == nil {
mut = func() error { return nil }
}

result, err := controllerutil.CreateOrUpdate(ctx, client, obj, mut)
if err != nil {
klog.ErrorS(err, "Failed to create or update CRD", "name", crd.Name, "operation", createOrUpdateRes)
klog.ErrorS(err, "Failed to create or update", "kind", obj.GetObjectKind().GroupVersionKind().Kind, "name", obj.GetName(), "operation", result)
return err
}

klog.Infof("Successfully created/updated CRD %s", crd.Name)
return nil
}

Expand Down Expand Up @@ -161,3 +174,19 @@ func GetCRDFromPath(crdPath string, scheme *runtime.Scheme) (*apiextensionsv1.Cu

return crd, nil
}

func InstallManagedResourceVAP(ctx context.Context, c client.Client, mode string) error {
vap := managedresource.GetValidatingAdmissionPolicy(mode == "hub")
vapBinding := managedresource.GetValidatingAdmissionPolicyBinding()
for _, ob := range []client.Object{vap, vapBinding} {
if err := install(ctx, c, ob, nil); err != nil {
if meta.IsNoMatchError(err) {
klog.Infof("Cluster does not support %s resource, skipping installation", ob.GetObjectKind().GroupVersionKind().Kind)
return nil
}
return err
}
}
klog.Infof("Successfully installed managed resource ValidatingAdmissionPolicy")
return nil
}
232 changes: 227 additions & 5 deletions cmd/crdinstaller/utils/util_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,24 @@ Licensed under the MIT license.
package utils

import (
"context"
"os"
"testing"

"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
admv1 "k8s.io/api/admissionregistration/v1"
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client/fake"
)

var (
lessFunc = func(s1, s2 string) bool {
return s1 < s2
}
)
var lessFunc = func(s1, s2 string) bool {
return s1 < s2
}

// Test using the actual config/crd/bases directory.
func TestCollectCRDFileNamesWithActualPath(t *testing.T) {
Expand Down Expand Up @@ -114,3 +118,221 @@ func runTest(t *testing.T, crdPath string) {
})
}
}

func TestInstallCRD(t *testing.T) {
scheme := runtime.NewScheme()
if err := apiextensionsv1.AddToScheme(scheme); err != nil {
t.Fatalf("Failed to add apiextensions scheme: %v", err)
}

testCRD := &apiextensionsv1.CustomResourceDefinition{
ObjectMeta: metav1.ObjectMeta{
Name: "test.example.com",
},
Spec: apiextensionsv1.CustomResourceDefinitionSpec{
Group: "example.com",
Versions: []apiextensionsv1.CustomResourceDefinitionVersion{
{
Name: "v1",
Served: true,
Storage: true,
Schema: &apiextensionsv1.CustomResourceValidation{
OpenAPIV3Schema: &apiextensionsv1.JSONSchemaProps{
Type: "object",
},
},
},
},
Scope: apiextensionsv1.NamespaceScoped,
Names: apiextensionsv1.CustomResourceDefinitionNames{
Plural: "tests",
Singular: "test",
Kind: "Test",
},
},
}

tests := []struct {
name string
crd *apiextensionsv1.CustomResourceDefinition
wantError bool
}{
{
name: "successful CRD installation",
crd: testCRD,
wantError: false,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
fakeClient := fake.NewClientBuilder().WithScheme(scheme).Build()
err := InstallCRD(context.Background(), fakeClient, tt.crd)

if tt.wantError {
if err == nil {
t.Errorf("InstallCRD() expected error but got none")
}
return
}

if err != nil {
t.Errorf("InstallCRD() unexpected error: %v", err)
return
}

var installedCRD apiextensionsv1.CustomResourceDefinition
err = fakeClient.Get(context.Background(), types.NamespacedName{Name: tt.crd.Name}, &installedCRD)
if err != nil {
t.Errorf("Failed to get installed CRD: %v", err)
return
}

if installedCRD.Labels[CRDInstallerLabelKey] != "true" {
t.Errorf("Expected CRD label %s to be 'true', got %q", CRDInstallerLabelKey, installedCRD.Labels[CRDInstallerLabelKey])
}

if installedCRD.Labels[AzureManagedLabelKey] != FleetLabelValue {
t.Errorf("Expected CRD label %s to be %q, got %q", AzureManagedLabelKey, FleetLabelValue, installedCRD.Labels[AzureManagedLabelKey])
}

if diff := cmp.Diff(tt.crd.Spec, installedCRD.Spec); diff != "" {
t.Errorf("CRD spec mismatch (-want +got):\n%s", diff)
}
})
}
}

func TestInstall(t *testing.T) {
scheme := runtime.NewScheme()
if err := apiextensionsv1.AddToScheme(scheme); err != nil {
t.Fatalf("Failed to add apiextensions scheme: %v", err)
}

testCRD := &apiextensionsv1.CustomResourceDefinition{
ObjectMeta: metav1.ObjectMeta{
Name: "test.example.com",
},
Spec: apiextensionsv1.CustomResourceDefinitionSpec{
Group: "example.com",
Versions: []apiextensionsv1.CustomResourceDefinitionVersion{
{
Name: "v1",
Served: true,
Storage: true,
Schema: &apiextensionsv1.CustomResourceValidation{
OpenAPIV3Schema: &apiextensionsv1.JSONSchemaProps{
Type: "object",
},
},
},
},
Scope: apiextensionsv1.NamespaceScoped,
Names: apiextensionsv1.CustomResourceDefinitionNames{
Plural: "tests",
Singular: "test",
Kind: "Test",
},
},
}

tests := []struct {
name string
obj client.Object
mutFunc func() error
wantError bool
}{
{
name: "successful install with mutation",
obj: testCRD,
mutFunc: func() error {
if testCRD.Labels == nil {
testCRD.Labels = make(map[string]string)
}
testCRD.Labels["test"] = "value"
return nil
},
wantError: false,
},
{
name: "successful install without mutation",
obj: &apiextensionsv1.CustomResourceDefinition{
ObjectMeta: metav1.ObjectMeta{
Name: "test2.example.com",
},
Spec: testCRD.Spec,
},
mutFunc: func() error {
return nil
},
wantError: false,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
fakeClient := fake.NewClientBuilder().WithScheme(scheme).Build()
err := install(context.Background(), fakeClient, tt.obj, tt.mutFunc)

if tt.wantError {
if err == nil {
t.Errorf("install() expected error but got none")
}
return
}

if err != nil {
t.Errorf("install() unexpected error: %v", err)
return
}

var installed apiextensionsv1.CustomResourceDefinition
err = fakeClient.Get(context.Background(), types.NamespacedName{Name: tt.obj.GetName()}, &installed)
if err != nil {
t.Errorf("Failed to get installed object: %v", err)
return
}

if tt.mutFunc != nil && tt.obj.GetName() == "test.example.com" {
if installed.Labels["test"] != "value" {
t.Errorf("Expected label 'test' to be 'value', got %q", installed.Labels["test"])
}
}
})
}
}

func TestInstallManagedResourceVAP(t *testing.T) {
tests := []struct {
name string
mode string
}{
{
name: "hub mode",
mode: "hub",
},
{
name: "member mode",
mode: "member",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
scheme := runtime.NewScheme()
if err := admv1.AddToScheme(scheme); err != nil {
t.Fatalf("Failed to add admissionregistration scheme: %v", err)
}

fakeClient := fake.NewClientBuilder().WithScheme(scheme).Build()
err := InstallManagedResourceVAP(context.Background(), fakeClient, tt.mode)

// The function should complete without errors
// The actual installation behavior depends on the RESTMapper implementation
// which is difficult to test reliably with the fake client
if err != nil {
t.Errorf("InstallManagedResourceVAP() unexpected error: %v", err)
}
})
}
}
1 change: 1 addition & 0 deletions docker/crd-installer.Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ RUN go mod download

# Copy the go source
COPY cmd/crdinstaller/ cmd/crdinstaller/
COPY pkg pkg

ARG TARGETARCH

Expand Down
Loading
Loading