diff --git a/.golangci.yml b/.golangci.yml index ec50a982..058f79a5 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -9,7 +9,6 @@ linters: - errcheck - exportloopref - goconst - - gocyclo - gofmt - goimports - gosimple diff --git a/api/v1alpha1/oob_types.go b/api/v1alpha1/oob_types.go index 068cf68c..76fc18c4 100644 --- a/api/v1alpha1/oob_types.go +++ b/api/v1alpha1/oob_types.go @@ -49,8 +49,7 @@ const ( type ConsoleProtocol struct { Name ConsoleProtocolName `json:"name"` - - Port int32 `json:"port"` + Port int32 `json:"port"` } type ConsoleProtocolName string @@ -79,7 +78,7 @@ type OOBStatus struct { // +optional FirmwareVersion string `json:"firmwareVersion,omitempty"` - // +kubebuilder:validation:Enum=Ready;Unready;Error + // +kubebuilder:validation:Enum=Ready;Unready;Ignored;Error // +optional State OOBState `json:"state,omitempty"` @@ -102,9 +101,18 @@ type OOBState string const ( OOBStateReady OOBState = "Ready" OOBStateUnready OOBState = "Unready" + OOBStateIgnored OOBState = "Ignored" OOBStateError OOBState = "Error" ) +const ( + OOBConditionTypeReady = "Ready" + OOBConditionReasonInProgress = "InProgress" + OOBConditionReasonNoEndpoint = "NoEndpoint" + OOBConditionReasonIgnored = "Ignored" + OOBConditionReasonError = "Error" +) + // +kubebuilder:object:root=true // +kubebuilder:subresource:status // +kubebuilder:resource:scope=Cluster diff --git a/client/applyconfiguration/internal/internal.go b/client/applyconfiguration/internal/internal.go index 3e392d87..d29c1dd8 100644 --- a/client/applyconfiguration/internal/internal.go +++ b/client/applyconfiguration/internal/internal.go @@ -26,46 +26,538 @@ func Parser() *typed.Parser { var parserOnce sync.Once var parser *typed.Parser var schemaYAML = typed.YAMLObject(`types: +- name: com.github.ironcore-dev.metal.api.v1alpha1.ConsoleProtocol + map: + fields: + - name: name + type: + scalar: string + default: "" + - name: port + type: + scalar: numeric + default: 0 - name: com.github.ironcore-dev.metal.api.v1alpha1.Machine - scalar: untyped - list: - elementType: - namedType: __untyped_atomic_ - elementRelationship: atomic map: - elementType: - namedType: __untyped_deduced_ - elementRelationship: separable + fields: + - name: apiVersion + type: + scalar: string + - name: kind + type: + scalar: string + - name: metadata + type: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta + default: {} + - name: spec + type: + namedType: com.github.ironcore-dev.metal.api.v1alpha1.MachineSpec + default: {} + - name: status + type: + namedType: com.github.ironcore-dev.metal.api.v1alpha1.MachineStatus + default: {} - name: com.github.ironcore-dev.metal.api.v1alpha1.MachineClaim - scalar: untyped - list: - elementType: - namedType: __untyped_atomic_ - elementRelationship: atomic map: - elementType: - namedType: __untyped_deduced_ - elementRelationship: separable + fields: + - name: apiVersion + type: + scalar: string + - name: kind + type: + scalar: string + - name: metadata + type: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta + default: {} + - name: spec + type: + namedType: com.github.ironcore-dev.metal.api.v1alpha1.MachineClaimSpec + default: {} + - name: status + type: + namedType: com.github.ironcore-dev.metal.api.v1alpha1.MachineClaimStatus + default: {} +- name: com.github.ironcore-dev.metal.api.v1alpha1.MachineClaimNetworkInterface + map: + fields: + - name: name + type: + scalar: string + default: "" + - name: prefix + type: + namedType: com.github.ironcore-dev.metal.api.v1alpha1.Prefix +- name: com.github.ironcore-dev.metal.api.v1alpha1.MachineClaimSpec + map: + fields: + - name: ignitionSecretRef + type: + namedType: io.k8s.api.core.v1.LocalObjectReference + - name: image + type: + scalar: string + default: "" + - name: machineRef + type: + namedType: io.k8s.api.core.v1.LocalObjectReference + - name: machineSelector + type: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.LabelSelector + - name: networkInterfaces + type: + list: + elementType: + namedType: com.github.ironcore-dev.metal.api.v1alpha1.MachineClaimNetworkInterface + elementRelationship: atomic + - name: power + type: + scalar: string + default: "" +- name: com.github.ironcore-dev.metal.api.v1alpha1.MachineClaimStatus + map: + fields: + - name: phase + type: + scalar: string +- name: com.github.ironcore-dev.metal.api.v1alpha1.MachineNetworkInterface + map: + fields: + - name: IPRef + type: + namedType: io.k8s.api.core.v1.LocalObjectReference + - name: macAddress + type: + scalar: string + default: "" + - name: name + type: + scalar: string + default: "" + - name: switchRef + type: + namedType: io.k8s.api.core.v1.LocalObjectReference +- name: com.github.ironcore-dev.metal.api.v1alpha1.MachineSpec + map: + fields: + - name: asn + type: + scalar: string + - name: inventoryRef + type: + namedType: io.k8s.api.core.v1.LocalObjectReference + - name: locatorLED + type: + scalar: string + - name: loopbackAddressRef + type: + namedType: io.k8s.api.core.v1.LocalObjectReference + - name: machineClaimRef + type: + namedType: io.k8s.api.core.v1.ObjectReference + - name: oobRef + type: + namedType: io.k8s.api.core.v1.LocalObjectReference + default: {} + - name: power + type: + scalar: string + - name: uuid + type: + scalar: string + default: "" +- name: com.github.ironcore-dev.metal.api.v1alpha1.MachineStatus + map: + fields: + - name: conditions + type: + list: + elementType: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.Condition + elementRelationship: associative + keys: + - type + - name: locatorLED + type: + scalar: string + - name: manufacturer + type: + scalar: string + - name: networkInterfaces + type: + list: + elementType: + namedType: com.github.ironcore-dev.metal.api.v1alpha1.MachineNetworkInterface + elementRelationship: atomic + - name: power + type: + scalar: string + - name: serialNumber + type: + scalar: string + - name: shutdownDeadline + type: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.Time + - name: sku + type: + scalar: string + - name: state + type: + scalar: string - name: com.github.ironcore-dev.metal.api.v1alpha1.OOB - scalar: untyped - list: - elementType: - namedType: __untyped_atomic_ - elementRelationship: atomic map: - elementType: - namedType: __untyped_deduced_ - elementRelationship: separable + fields: + - name: apiVersion + type: + scalar: string + - name: kind + type: + scalar: string + - name: metadata + type: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta + default: {} + - name: spec + type: + namedType: com.github.ironcore-dev.metal.api.v1alpha1.OOBSpec + default: {} + - name: status + type: + namedType: com.github.ironcore-dev.metal.api.v1alpha1.OOBStatus + default: {} - name: com.github.ironcore-dev.metal.api.v1alpha1.OOBSecret + map: + fields: + - name: apiVersion + type: + scalar: string + - name: kind + type: + scalar: string + - name: metadata + type: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta + default: {} + - name: spec + type: + namedType: com.github.ironcore-dev.metal.api.v1alpha1.OOBSecretSpec + default: {} + - name: status + type: + namedType: com.github.ironcore-dev.metal.api.v1alpha1.OOBSecretStatus + default: {} +- name: com.github.ironcore-dev.metal.api.v1alpha1.OOBSecretSpec + map: + fields: + - name: expirationTime + type: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.Time + - name: macAddress + type: + scalar: string + default: "" + - name: password + type: + scalar: string + default: "" + - name: username + type: + scalar: string + default: "" +- name: com.github.ironcore-dev.metal.api.v1alpha1.OOBSecretStatus + map: + fields: + - name: conditions + type: + list: + elementType: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.Condition + elementRelationship: associative + keys: + - type +- name: com.github.ironcore-dev.metal.api.v1alpha1.OOBSpec + map: + fields: + - name: consoleProtocol + type: + namedType: com.github.ironcore-dev.metal.api.v1alpha1.ConsoleProtocol + - name: endpointRef + type: + namedType: io.k8s.api.core.v1.LocalObjectReference + - name: flags + type: + map: + elementType: + scalar: string + - name: macAddress + type: + scalar: string + default: "" + - name: protocol + type: + namedType: com.github.ironcore-dev.metal.api.v1alpha1.Protocol + - name: secretRef + type: + namedType: io.k8s.api.core.v1.LocalObjectReference +- name: com.github.ironcore-dev.metal.api.v1alpha1.OOBStatus + map: + fields: + - name: conditions + type: + list: + elementType: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.Condition + elementRelationship: associative + keys: + - type + - name: firmwareVersion + type: + scalar: string + - name: manufacturer + type: + scalar: string + - name: serialNumber + type: + scalar: string + - name: sku + type: + scalar: string + - name: state + type: + scalar: string + - name: type + type: + scalar: string +- name: com.github.ironcore-dev.metal.api.v1alpha1.Prefix scalar: untyped - list: - elementType: - namedType: __untyped_atomic_ +- name: com.github.ironcore-dev.metal.api.v1alpha1.Protocol + map: + fields: + - name: name + type: + scalar: string + default: "" + - name: port + type: + scalar: numeric + default: 0 +- name: io.k8s.api.core.v1.LocalObjectReference + map: + fields: + - name: name + type: + scalar: string elementRelationship: atomic +- name: io.k8s.api.core.v1.ObjectReference + map: + fields: + - name: apiVersion + type: + scalar: string + - name: fieldPath + type: + scalar: string + - name: kind + type: + scalar: string + - name: name + type: + scalar: string + - name: namespace + type: + scalar: string + - name: resourceVersion + type: + scalar: string + - name: uid + type: + scalar: string + elementRelationship: atomic +- name: io.k8s.apimachinery.pkg.apis.meta.v1.Condition + map: + fields: + - name: lastTransitionTime + type: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.Time + - name: message + type: + scalar: string + default: "" + - name: observedGeneration + type: + scalar: numeric + - name: reason + type: + scalar: string + default: "" + - name: status + type: + scalar: string + default: "" + - name: type + type: + scalar: string + default: "" +- name: io.k8s.apimachinery.pkg.apis.meta.v1.FieldsV1 map: elementType: - namedType: __untyped_deduced_ - elementRelationship: separable + scalar: untyped + list: + elementType: + namedType: __untyped_atomic_ + elementRelationship: atomic + map: + elementType: + namedType: __untyped_deduced_ + elementRelationship: separable +- name: io.k8s.apimachinery.pkg.apis.meta.v1.LabelSelector + map: + fields: + - name: matchExpressions + type: + list: + elementType: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.LabelSelectorRequirement + elementRelationship: atomic + - name: matchLabels + type: + map: + elementType: + scalar: string + elementRelationship: atomic +- name: io.k8s.apimachinery.pkg.apis.meta.v1.LabelSelectorRequirement + map: + fields: + - name: key + type: + scalar: string + default: "" + - name: operator + type: + scalar: string + default: "" + - name: values + type: + list: + elementType: + scalar: string + elementRelationship: atomic +- name: io.k8s.apimachinery.pkg.apis.meta.v1.ManagedFieldsEntry + map: + fields: + - name: apiVersion + type: + scalar: string + - name: fieldsType + type: + scalar: string + - name: fieldsV1 + type: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.FieldsV1 + - name: manager + type: + scalar: string + - name: operation + type: + scalar: string + - name: subresource + type: + scalar: string + - name: time + type: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.Time +- name: io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta + map: + fields: + - name: annotations + type: + map: + elementType: + scalar: string + - name: creationTimestamp + type: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.Time + - name: deletionGracePeriodSeconds + type: + scalar: numeric + - name: deletionTimestamp + type: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.Time + - name: finalizers + type: + list: + elementType: + scalar: string + elementRelationship: associative + - name: generateName + type: + scalar: string + - name: generation + type: + scalar: numeric + - name: labels + type: + map: + elementType: + scalar: string + - name: managedFields + type: + list: + elementType: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.ManagedFieldsEntry + elementRelationship: atomic + - name: name + type: + scalar: string + - name: namespace + type: + scalar: string + - name: ownerReferences + type: + list: + elementType: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.OwnerReference + elementRelationship: associative + keys: + - uid + - name: resourceVersion + type: + scalar: string + - name: selfLink + type: + scalar: string + - name: uid + type: + scalar: string +- name: io.k8s.apimachinery.pkg.apis.meta.v1.OwnerReference + map: + fields: + - name: apiVersion + type: + scalar: string + default: "" + - name: blockOwnerDeletion + type: + scalar: boolean + - name: controller + type: + scalar: boolean + - name: kind + type: + scalar: string + default: "" + - name: name + type: + scalar: string + default: "" + - name: uid + type: + scalar: string + default: "" + elementRelationship: atomic +- name: io.k8s.apimachinery.pkg.apis.meta.v1.Time + scalar: untyped - name: __untyped_atomic_ scalar: untyped list: diff --git a/client/openapi/doc.go b/client/openapi/doc.go new file mode 100644 index 00000000..62ab2176 --- /dev/null +++ b/client/openapi/doc.go @@ -0,0 +1,6 @@ +// SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and IronCore contributors +// SPDX-License-Identifier: Apache-2.0 + +// +kubebuilder:object:generate=false + +package openapi diff --git a/cmd/main.go b/cmd/main.go index b579c78f..7d782971 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -14,6 +14,7 @@ import ( "syscall" "github.com/go-logr/logr" + ipamv1alpha1 "github.com/ironcore-dev/ipam/api/ipam/v1alpha1" "github.com/spf13/pflag" "github.com/spf13/viper" "k8s.io/apimachinery/pkg/runtime" @@ -147,6 +148,12 @@ func main() { exitCode = 1 return } + err = ipamv1alpha1.AddToScheme(scheme) + if err != nil { + log.Error(ctx, fmt.Errorf("cannot create type scheme: %w", err)) + exitCode = 1 + return + } //+kubebuilder:scaffold:scheme var kcfg *rest.Config diff --git a/config/crd/bases/metal.ironcore.dev_oobs.yaml b/config/crd/bases/metal.ironcore.dev_oobs.yaml index 46e1dca4..0243f940 100644 --- a/config/crd/bases/metal.ironcore.dev_oobs.yaml +++ b/config/crd/bases/metal.ironcore.dev_oobs.yaml @@ -209,6 +209,7 @@ spec: enum: - Ready - Unready + - Ignored - Error type: string type: diff --git a/go.mod b/go.mod index 0a73ab63..9af55e9a 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,7 @@ require ( github.com/golangci/golangci-lint v1.57.2 github.com/google/addlicense v1.1.1 github.com/google/uuid v1.6.0 + github.com/ironcore-dev/ipam v0.2.1 github.com/ironcore-dev/vgopath v0.1.4 github.com/onsi/ginkgo/v2 v2.17.1 github.com/onsi/gomega v1.32.0 @@ -215,6 +216,7 @@ require ( go.uber.org/automaxprocs v1.5.3 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect + go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 // indirect golang.org/x/exp/typeparams v0.0.0-20240314144324-c7f7c6466f7f // indirect golang.org/x/mod v0.16.0 // indirect diff --git a/go.sum b/go.sum index 33198f54..fae391ee 100644 --- a/go.sum +++ b/go.sum @@ -337,6 +337,8 @@ github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/ironcore-dev/ipam v0.2.1 h1:IKV3ojpD4xEQjd/GXxeUfEEy9jMUDPnO45zMluD0K3M= +github.com/ironcore-dev/ipam v0.2.1/go.mod h1:okbNL8imniqLS0XiZPZBS0Wk9bb+AKjc8KYiKQD1j0A= github.com/ironcore-dev/vgopath v0.1.4 h1:hBMuv7+wnZp5JHkVfdg4mtP8hsIGvuv42+l+F2wmQxk= github.com/ironcore-dev/vgopath v0.1.4/go.mod h1:PTGnX8xW/QDytFR7oU4kcXr1RPDLCgAJ0ZUa5Rp8vyI= github.com/jgautheron/goconst v1.7.1 h1:VpdAG7Ca7yvvJk5n8dMwQhfEZJh95kl/Hl9S1OI5Jkk= @@ -654,6 +656,8 @@ go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M= +go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= diff --git a/internal/controller/machineclaim_controller.go b/internal/controller/machineclaim_controller.go index 26cd02f3..fbb9d602 100644 --- a/internal/controller/machineclaim_controller.go +++ b/internal/controller/machineclaim_controller.go @@ -19,7 +19,7 @@ import ( metalv1alpha1 "github.com/ironcore-dev/metal/api/v1alpha1" metalv1alpha1apply "github.com/ironcore-dev/metal/client/applyconfiguration/api/v1alpha1" "github.com/ironcore-dev/metal/internal/log" - "github.com/ironcore-dev/metal/internal/patch" + "github.com/ironcore-dev/metal/internal/ssa" "github.com/ironcore-dev/metal/internal/util" ) @@ -91,7 +91,7 @@ func (r *MachineClaimReconciler) finalize(ctx context.Context, claim *metalv1alp log.Debug(ctx, "Updating Machine") machineApply := metalv1alpha1apply.Machine(machine.Name, machine.Namespace).WithFinalizers().WithSpec(metalv1alpha1apply.MachineSpec()) - err = r.Patch(ctx, &machine, patch.Apply(machineApply), client.FieldOwner(MachineClaimFieldOwner), client.ForceOwnership) + err = r.Patch(ctx, &machine, ssa.Apply(machineApply), client.FieldOwner(MachineClaimFieldOwner), client.ForceOwnership) if err != nil { return fmt.Errorf("cannot patch Machine: %w", err) } @@ -99,7 +99,7 @@ func (r *MachineClaimReconciler) finalize(ctx context.Context, claim *metalv1alp log.Debug(ctx, "Removing finalizer") apply := metalv1alpha1apply.MachineClaim(claim.Name, claim.Namespace).WithFinalizers() - err := r.Patch(ctx, claim, patch.Apply(apply), client.FieldOwner(MachineClaimFieldOwner), client.ForceOwnership) + err := r.Patch(ctx, claim, ssa.Apply(apply), client.FieldOwner(MachineClaimFieldOwner), client.ForceOwnership) if err != nil { return fmt.Errorf("cannot remove finalizer: %w", err) } @@ -157,7 +157,7 @@ func (r *MachineClaimReconciler) reconcile(ctx context.Context, claim *metalv1al !util.NilOrEqual(machine.Spec.MachineClaimRef, machineApply.Spec.MachineClaimRef) || machine.Spec.Power != *machineApply.Spec.Power { log.Debug(ctx, "Updating Machine") - err := r.Patch(ctx, &machine, patch.Apply(machineApply), client.FieldOwner(MachineClaimFieldOwner), client.ForceOwnership) + err := r.Patch(ctx, &machine, ssa.Apply(machineApply), client.FieldOwner(MachineClaimFieldOwner), client.ForceOwnership) if err != nil { return ctrl.Result{}, fmt.Errorf("cannot patch Machine: %w", err) } @@ -173,7 +173,7 @@ func (r *MachineClaimReconciler) reconcile(ctx context.Context, claim *metalv1al if !controllerutil.ContainsFinalizer(claim, MachineClaimFinalizer) || !util.NilOrEqual(claim.Spec.MachineRef, apply.Spec.MachineRef) { log.Debug(ctx, "Updating") - err := r.Patch(ctx, claim, patch.Apply(apply), client.FieldOwner(MachineClaimFieldOwner), client.ForceOwnership) + err := r.Patch(ctx, claim, ssa.Apply(apply), client.FieldOwner(MachineClaimFieldOwner), client.ForceOwnership) if err != nil { return ctrl.Result{}, fmt.Errorf("cannot patch MachineClaim: %w", err) } @@ -182,7 +182,7 @@ func (r *MachineClaimReconciler) reconcile(ctx context.Context, claim *metalv1al apply = metalv1alpha1apply.MachineClaim(claim.Name, claim.Namespace).WithStatus(applyStatus) if claim.Status.Phase != *apply.Status.Phase { log.Debug(ctx, "Updating status") - err := r.Status().Patch(ctx, claim, patch.Apply(apply), client.FieldOwner(MachineClaimFieldOwner), client.ForceOwnership) + err := r.Status().Patch(ctx, claim, ssa.Apply(apply), client.FieldOwner(MachineClaimFieldOwner), client.ForceOwnership) if err != nil { return ctrl.Result{}, fmt.Errorf("cannot patch MachineClaim status: %w", err) } diff --git a/internal/controller/suite_test.go b/internal/controller/suite_test.go index 88b898e4..09eb80a3 100644 --- a/internal/controller/suite_test.go +++ b/internal/controller/suite_test.go @@ -12,6 +12,7 @@ import ( "time" "github.com/go-logr/logr" + ipamv1alpha1 "github.com/ironcore-dev/ipam/api/ipam/v1alpha1" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" v1 "k8s.io/api/core/v1" @@ -59,10 +60,16 @@ var _ = BeforeSuite(func() { Expect(metalv1alpha1.AddToScheme(scheme)).To(Succeed()) //+kubebuilder:scaffold:scheme + Expect(kscheme.AddToScheme(scheme)).To(Succeed()) + Expect(metalv1alpha1.AddToScheme(scheme)).To(Succeed()) + Expect(ipamv1alpha1.AddToScheme(scheme)).To(Succeed()) + //+kubebuilder:scaffold:scheme + testEnv := &envtest.Environment{ ErrorIfCRDPathMissing: true, CRDDirectoryPaths: []string{ filepath.Join("..", "..", "config", "crd", "bases"), + filepath.Join("..", "..", "test", "ipam.metal.ironcore.dev_ips.yaml"), }, } var cfg *rest.Config @@ -76,13 +83,15 @@ var _ = BeforeSuite(func() { Expect(k8sClient).NotTo(BeNil()) SetClient(k8sClient) - ns := v1.Namespace{ + ns := &v1.Namespace{ ObjectMeta: metav1.ObjectMeta{ GenerateName: "system-", }, } - Expect(k8sClient.Create(ctx, &ns)).To(Succeed()) - DeferCleanup(k8sClient.Delete, &ns) + Expect(k8sClient.Create(ctx, ns)).To(Succeed()) + DeferCleanup(func(ctx SpecContext) { + Expect(k8sClient.Delete(ctx, ns)).To(Succeed()) + }) var mgr manager.Manager mgr, err = ctrl.NewManager(cfg, ctrl.Options{ diff --git a/internal/patch/patch.go b/internal/patch/patch.go deleted file mode 100644 index 403d8b4c..00000000 --- a/internal/patch/patch.go +++ /dev/null @@ -1,28 +0,0 @@ -// SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and IronCore contributors -// SPDX-License-Identifier: Apache-2.0 - -package patch - -import ( - "k8s.io/apimachinery/pkg/types" - "k8s.io/apimachinery/pkg/util/json" - "sigs.k8s.io/controller-runtime/pkg/client" -) - -func Apply(applyConfig interface{}) client.Patch { - return applyPatch{ - applyConfig: applyConfig, - } -} - -type applyPatch struct { - applyConfig interface{} -} - -func (p applyPatch) Type() types.PatchType { - return types.ApplyPatchType -} - -func (p applyPatch) Data(_ client.Object) ([]byte, error) { - return json.Marshal(p.applyConfig) -} diff --git a/internal/ssa/ssa.go b/internal/ssa/ssa.go new file mode 100644 index 00000000..09d634e0 --- /dev/null +++ b/internal/ssa/ssa.go @@ -0,0 +1,75 @@ +// SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and IronCore contributors +// SPDX-License-Identifier: Apache-2.0 + +package ssa + +import ( + "slices" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/json" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +func Apply(applyConfig interface{}) client.Patch { + return applyPatch{ + applyConfig: applyConfig, + } +} + +type applyPatch struct { + applyConfig interface{} +} + +func (p applyPatch) Type() types.PatchType { + return types.ApplyPatchType +} + +func (p applyPatch) Data(_ client.Object) ([]byte, error) { + return json.Marshal(p.applyConfig) +} + +func Add(fins []string, fin string) []string { + for _, f := range fins { + if f == fin { + return fins + } + } + return append(fins, fin) +} + +func GetCondition(conds []metav1.Condition, typ string) (metav1.Condition, bool) { + for _, c := range conds { + if c.Type == typ { + return c, true + } + } + return metav1.Condition{}, false +} + +func SetCondition(conds []metav1.Condition, cond metav1.Condition) ([]metav1.Condition, bool) { + if cond.LastTransitionTime.IsZero() { + cond.LastTransitionTime = metav1.Now() + } + + for i, c := range conds { + if c.Type == cond.Type { + if cond.Status == c.Status && cond.Reason == c.Reason && cond.Message == c.Message { + return conds, false + } + return slices.Concat(conds[:i], []metav1.Condition{cond}, conds[i+1:]), true + } + } + + return append(conds, cond), true +} + +func SetErrorCondition(conds []metav1.Condition, typ string, err error) ([]metav1.Condition, bool) { + return SetCondition(conds, metav1.Condition{ + Type: typ, + Status: metav1.ConditionFalse, + Reason: "Error", + Message: err.Error(), + }) +} diff --git a/internal/tools/generate.sh b/internal/tools/generate.sh index a1f800c4..21975679 100755 --- a/internal/tools/generate.sh +++ b/internal/tools/generate.sh @@ -23,7 +23,7 @@ go run k8s.io/code-generator/cmd/openapi-gen \ -O zz_generated.openapi \ --report-filename "/dev/null" -go run github.com/ironcore-dev/metal/internal/tools/models-schema --openapi-package "github.com/ironcore-dev/metal/client/openapi" --openapi-title "metal" > "$MODELSSCHEMA" +go run github.com/ironcore-dev/metal/internal/tools/models-schema > "$MODELSSCHEMA" go run k8s.io/code-generator/cmd/applyconfiguration-gen \ --output-base "$GOPATH/src" \ --go-header-file hack/boilerplate.go.txt \ diff --git a/internal/tools/models-schema/main.go b/internal/tools/models-schema/main.go index caf0ca71..745feaea 100644 --- a/internal/tools/models-schema/main.go +++ b/internal/tools/models-schema/main.go @@ -4,86 +4,67 @@ package main import ( - _ "embed" - "errors" - "flag" + "encoding/json" "fmt" - "io/fs" "os" - "os/exec" - "text/template" + "strings" - "github.com/go-logr/logr" - "sigs.k8s.io/controller-runtime/pkg/log/zap" -) - -var ( - //go:embed main.go.tmpl - mainGoTemplateData string + "k8s.io/kube-openapi/pkg/common" + "k8s.io/kube-openapi/pkg/validation/spec" - mainGoTemplate = template.Must(template.New("main.go").Parse(mainGoTemplateData)) + "github.com/ironcore-dev/metal/client/openapi" ) -type mainGoTemplateArgs struct { - OpenAPIPackage string - OpenAPITitle string -} - func main() { - var ( - zapOpts = zap.Options{Development: true} - log logr.Logger - openapiPackage string - openapiTitle string - ) - - zapOpts.BindFlags(flag.CommandLine) - flag.StringVar(&openapiPackage, "openapi-package", "", "Package containing the openapi definitions.") - flag.StringVar(&openapiTitle, "openapi-title", "", "Title for the generated openapi json definition.") - flag.Parse() - log = zap.New(zap.UseFlagOptions(&zapOpts)) - - if openapiPackage == "" { - log.Error(fmt.Errorf("must specify openapi-package"), "Invalid flags") - os.Exit(1) - } - if openapiTitle == "" { - log.Error(fmt.Errorf("must specify openapi-title"), "Invalid flags") - os.Exit(1) + refFunc := func(name string) spec.Ref { + return spec.MustCreateRef(fmt.Sprintf("#/definitions/%s", friendlyName(name))) } - - err := run(log, openapiPackage, openapiTitle) - if err != nil { - log.Error(err, "Error running models-schema") + defs := openapi.GetOpenAPIDefinitions(refFunc) + schemaDefs := make(map[string]spec.Schema, len(defs)) + for k, v := range defs { + schema, ok := v.Schema.Extensions[common.ExtensionV2Schema] + if ok { + v2Schema, isOpenAPISchema := schema.(spec.Schema) + if isOpenAPISchema { + schemaDefs[friendlyName(k)] = v2Schema + continue + } + } + schemaDefs[friendlyName(k)] = v.Schema } -} -func run(log logr.Logger, openapiPackage, openapiTitle string) error { - tmpFile, err := os.CreateTemp("", "models-schema-*.go") + data, err := json.Marshal(&spec.Swagger{ + SwaggerProps: spec.SwaggerProps{ + Definitions: schemaDefs, + Info: &spec.Info{ + InfoProps: spec.InfoProps{ + Title: "metal", + Version: "unversioned", + }, + }, + Swagger: "2.0", + }, + }) if err != nil { - return fmt.Errorf("error creating temporary file: %w", err) + _, _ = fmt.Fprintln(os.Stderr, err) + os.Exit(1) } - defer func() { - err = os.Remove(tmpFile.Name()) - if err != nil && !errors.Is(err, fs.ErrNotExist) { - log.Error(err, "Error cleaning up temporary file") - } - }() - err = mainGoTemplate.Execute(tmpFile, mainGoTemplateArgs{ - OpenAPIPackage: openapiPackage, - OpenAPITitle: openapiTitle, - }) + _, err = os.Stdout.Write(data) if err != nil { - return fmt.Errorf("error executing template: %w", err) + _, _ = fmt.Fprintln(os.Stderr, err) + os.Exit(1) } +} - cmd := exec.Command("go", "run", tmpFile.Name()) - cmd.Stderr = os.Stderr - cmd.Stdout = os.Stdout - err = cmd.Run() - if err != nil { - return fmt.Errorf("error running command: %w", err) +func friendlyName(name string) string { + nameParts := strings.Split(name, "/") + if len(nameParts) > 0 && strings.Contains(nameParts[0], ".") { + parts := strings.Split(nameParts[0], ".") + for i, j := 0, len(parts)-1; i < j; i, j = i+1, j-1 { + parts[i], parts[j] = parts[j], parts[i] + } + nameParts[0] = strings.Join(parts, ".") } - return nil + return strings.Join(nameParts, ".") } diff --git a/internal/tools/models-schema/main.go.tmpl b/internal/tools/models-schema/main.go.tmpl deleted file mode 100644 index 5718ae11..00000000 --- a/internal/tools/models-schema/main.go.tmpl +++ /dev/null @@ -1,79 +0,0 @@ -// SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and IronCore contributors -// SPDX-License-Identifier: Apache-2.0 - -package main - -import ( - "encoding/json" - "fmt" - "os" - "strings" - - "k8s.io/apiextensions-apiserver/pkg/generated/openapi" - "k8s.io/kube-openapi/pkg/common" - "k8s.io/kube-openapi/pkg/validation/spec" -) - -// Outputs openAPI schema JSON containing the schema definitions in zz_generated.openapi.go. -func main() { - err := output() - if err != nil { - os.Stderr.WriteString(fmt.Sprintf("Failed: %v", err)) - os.Exit(1) - } -} - -func output() error { - refFunc := func(name string) spec.Ref { - return spec.MustCreateRef(fmt.Sprintf("#/definitions/%s", friendlyName(name))) - } - defs := openapi.GetOpenAPIDefinitions(refFunc) - schemaDefs := make(map[string]spec.Schema, len(defs)) - for k, v := range defs { - // Replace top-level schema with v2 if a v2 schema is embedded - // so that the output of this program is always in OpenAPI v2. - // This is done by looking up an extension that marks the embedded v2 - // schema, and, if the v2 schema is found, make it the resulting schema for - // the type. - if schema, ok := v.Schema.Extensions[common.ExtensionV2Schema]; ok { - v2Schema, isOpenAPISchema := schema.(spec.Schema) - if isOpenAPISchema { - schemaDefs[friendlyName(k)] = v2Schema - continue - } - } - - schemaDefs[friendlyName(k)] = v.Schema - } - data, err := json.Marshal(&spec.Swagger{ - SwaggerProps: spec.SwaggerProps{ - Definitions: schemaDefs, - Info: &spec.Info{ - InfoProps: spec.InfoProps{ - Title: "{{ .OpenAPITitle }}", - Version: "unversioned", - }, - }, - Swagger: "2.0", - }, - }) - if err != nil { - return fmt.Errorf("error serializing api definitions: %w", err) - } - os.Stdout.Write(data) - return nil -} - -// From k8s.io/apiserver/pkg/endpoints/openapi/openapi.go -func friendlyName(name string) string { - nameParts := strings.Split(name, "/") - // Reverse first part. e.g., io.k8s... instead of k8s.io... - if len(nameParts) > 0 && strings.Contains(nameParts[0], ".") { - parts := strings.Split(nameParts[0], ".") - for i, j := 0, len(parts)-1; i < j; i, j = i+1, j-1 { - parts[i], parts[j] = parts[j], parts[i] - } - nameParts[0] = strings.Join(parts, ".") - } - return strings.Join(nameParts, ".") -} diff --git a/test/ipam.metal.ironcore.dev_ips.yaml b/test/ipam.metal.ironcore.dev_ips.yaml new file mode 100644 index 00000000..8ffa0235 --- /dev/null +++ b/test/ipam.metal.ironcore.dev_ips.yaml @@ -0,0 +1,131 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.14.0 + name: ips.ipam.metal.ironcore.dev +spec: + group: ipam.metal.ironcore.dev + names: + kind: IP + listKind: IPList + plural: ips + singular: ip + scope: Namespaced + versions: + - additionalPrinterColumns: + - description: IP Address + jsonPath: .status.reserved + name: IP + type: string + - description: Subnet + jsonPath: .spec.subnet.name + name: Subnet + type: string + - description: Consumer Group + jsonPath: .spec.consumer.apiVersion + name: Consumer Group + type: string + - description: Consumer Kind + jsonPath: .spec.consumer.kind + name: Consumer Kind + type: string + - description: Consumer Name + jsonPath: .spec.consumer.name + name: Consumer Name + type: string + - description: Processing state + jsonPath: .status.state + name: State + type: string + - description: Message + jsonPath: .status.message + name: Message + type: string + name: v1alpha1 + schema: + openAPIV3Schema: + description: IP is the Schema for the ips API + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: IPSpec defines the desired state of IP + properties: + consumer: + description: Consumer refers to resource IP has been booked for + properties: + apiVersion: + description: APIVersion is resource's API group + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-./a-z0-9]*[a-z0-9])?$ + type: string + kind: + description: Kind is CRD Kind for lookup + maxLength: 63 + minLength: 1 + pattern: ^[A-Z]([-A-Za-z0-9]*[A-Za-z0-9])?$ + type: string + name: + description: Name is CRD Name for lookup + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - kind + - name + type: object + ip: + description: IP allows to set desired IP address explicitly + type: string + subnet: + description: SubnetName is referring to parent subnet that holds requested + IP + properties: + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid? + type: string + type: object + x-kubernetes-map-type: atomic + required: + - subnet + type: object + status: + description: IPStatus defines the observed state of IP + properties: + message: + description: Message contains error details if the one has occurred + type: string + reserved: + description: Reserved is a reserved IP + type: string + state: + description: State is a network creation request processing state + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {}