From 49e7b70395c043225618aaf0d7f53b8edc3a336b Mon Sep 17 00:00:00 2001 From: Georgi Chulkov Date: Tue, 19 Mar 2024 18:14:56 +0100 Subject: [PATCH] Add CRD definitions --- .github/workflows/publish-docker.yml | 2 +- .reuse/dep5 | 2 + Dockerfile | 4 +- Makefile | 2 +- PROJECT | 38 +- README.md | 22 +- api/v1alpha1/common.go | 72 +++ api/v1alpha1/groupversion_info.go | 36 ++ api/v1alpha1/machine_types.go | 155 +++++ api/v1alpha1/machineclaim_types.go | 88 +++ api/v1alpha1/oob_types.go | 149 +++++ api/v1alpha1/oobsecret_types.go | 71 +++ api/v1alpha1/zz_generated.deepcopy.go | 539 ++++++++++++++++++ cmd/main.go | 65 ++- .../metal.ironcore.dev_machineclaims.yaml | 152 +++++ .../bases/metal.ironcore.dev_machines.yaml | 314 ++++++++++ config/crd/bases/metal.ironcore.dev_oobs.yaml | 220 +++++++ .../bases/metal.ironcore.dev_oobsecrets.yaml | 141 +++++ config/crd/kustomization.yaml | 22 + config/crd/kustomizeconfig.yaml | 18 + config/default/kustomization.yaml | 40 +- config/default/manager_auth_proxy_patch.yaml | 2 - config/manager/manager.yaml | 53 +- config/prometheus/monitor.yaml | 1 - config/rbac/kustomization.yaml | 8 - config/rbac/leader_election_role.yaml | 1 - config/rbac/machine_editor_role.yaml | 30 + config/rbac/machine_viewer_role.yaml | 26 + config/rbac/machineclaim_editor_role.yaml | 30 + config/rbac/machineclaim_viewer_role.yaml | 26 + config/rbac/oob_editor_role.yaml | 30 + config/rbac/oob_viewer_role.yaml | 26 + config/rbac/oobsecret_editor_role.yaml | 30 + config/rbac/oobsecret_viewer_role.yaml | 26 + config/rbac/role.yaml | 115 +++- internal/controller/machine_controller.go | 55 ++ .../controller/machine_controller_test.go | 79 +++ .../controller/machineclaim_controller.go | 53 ++ .../machineclaim_controller_test.go | 79 +++ internal/controller/oob_controller.go | 53 ++ internal/controller/oob_controller_test.go | 82 +++ internal/controller/oobsecret_controller.go | 53 ++ .../controller/oobsecret_controller_test.go | 82 +++ internal/controller/suite_test.go | 79 +++ test/e2e/e2e_suite_test.go | 2 +- test/e2e/e2e_test.go | 7 +- test/utils/utils.go | 12 +- 47 files changed, 3036 insertions(+), 156 deletions(-) create mode 100644 api/v1alpha1/common.go create mode 100644 api/v1alpha1/groupversion_info.go create mode 100644 api/v1alpha1/machine_types.go create mode 100644 api/v1alpha1/machineclaim_types.go create mode 100644 api/v1alpha1/oob_types.go create mode 100644 api/v1alpha1/oobsecret_types.go create mode 100644 api/v1alpha1/zz_generated.deepcopy.go create mode 100644 config/crd/bases/metal.ironcore.dev_machineclaims.yaml create mode 100644 config/crd/bases/metal.ironcore.dev_machines.yaml create mode 100644 config/crd/bases/metal.ironcore.dev_oobs.yaml create mode 100644 config/crd/bases/metal.ironcore.dev_oobsecrets.yaml create mode 100644 config/crd/kustomization.yaml create mode 100644 config/crd/kustomizeconfig.yaml create mode 100644 config/rbac/machine_editor_role.yaml create mode 100644 config/rbac/machine_viewer_role.yaml create mode 100644 config/rbac/machineclaim_editor_role.yaml create mode 100644 config/rbac/machineclaim_viewer_role.yaml create mode 100644 config/rbac/oob_editor_role.yaml create mode 100644 config/rbac/oob_viewer_role.yaml create mode 100644 config/rbac/oobsecret_editor_role.yaml create mode 100644 config/rbac/oobsecret_viewer_role.yaml create mode 100644 internal/controller/machine_controller.go create mode 100644 internal/controller/machine_controller_test.go create mode 100644 internal/controller/machineclaim_controller.go create mode 100644 internal/controller/machineclaim_controller_test.go create mode 100644 internal/controller/oob_controller.go create mode 100644 internal/controller/oob_controller_test.go create mode 100644 internal/controller/oobsecret_controller.go create mode 100644 internal/controller/oobsecret_controller_test.go create mode 100644 internal/controller/suite_test.go diff --git a/.github/workflows/publish-docker.yml b/.github/workflows/publish-docker.yml index 78981009..31f13427 100644 --- a/.github/workflows/publish-docker.yml +++ b/.github/workflows/publish-docker.yml @@ -1,4 +1,4 @@ -name: Build and publish docker image +name: Build and publish Docker image env: platforms: linux/amd64,linux/arm64 diff --git a/.reuse/dep5 b/.reuse/dep5 index 30ec176f..88ba77b2 100644 --- a/.reuse/dep5 +++ b/.reuse/dep5 @@ -26,9 +26,11 @@ Disclaimer: The code in this project may include calls to APIs ("API Calls") of Files: .github/* + api/* cmd/* config/* hack/* + internal/* test/* .dockerignore .gitignore diff --git a/Dockerfile b/Dockerfile index 45d967ba..e2ff2521 100644 --- a/Dockerfile +++ b/Dockerfile @@ -7,9 +7,9 @@ COPY go.mod go.mod COPY go.sum go.sum RUN go mod download -#COPY api/ api/ +COPY api/ api/ COPY cmd/ cmd/ -#COPY internal/ internal/ +COPY internal/ internal/ RUN CGO_ENABLED=0 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH} go build -a -o metal cmd/main.go FROM debian:bookworm-20240311-slim diff --git a/Makefile b/Makefile index b6a31ea9..6209ed86 100644 --- a/Makefile +++ b/Makefile @@ -52,7 +52,7 @@ vet: ## Run go vet against code. .PHONY: test test: manifests generate fmt vet envtest ## Run tests. - KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) --bin-dir $(LOCALBIN) -p path)" go test $$(go list ./... | grep -v /e2e) -coverprofile cover.out + KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) --bin-dir $(LOCALBIN) -p path)" go test $$(go list ./... | grep -v /e2e) .PHONY: test-e2e diff --git a/PROJECT b/PROJECT index 8615e35b..6a5778ad 100644 --- a/PROJECT +++ b/PROJECT @@ -1,10 +1,40 @@ -# Code generated by tool. DO NOT EDIT. -# This file is used to track the info used to scaffold your project -# and allow the plugins properly work. -# More info: https://book.kubebuilder.io/reference/project-config.html domain: ironcore.dev layout: - go.kubebuilder.io/v4 projectName: metal repo: github.com/ironcore-dev/metal +resources: +- api: + crdVersion: v1 + controller: true + domain: ironcore.dev + group: metal + kind: Machine + path: github.com/ironcore-dev/metal/api/v1alpha1 + version: v1alpha1 +- api: + crdVersion: v1 + namespaced: true + controller: true + domain: ironcore.dev + group: metal + kind: MachineClaim + path: github.com/ironcore-dev/metal/api/v1alpha1 + version: v1alpha1 +- api: + crdVersion: v1 + controller: true + domain: ironcore.dev + group: metal + kind: OOB + path: github.com/ironcore-dev/metal/api/v1alpha1 + version: v1alpha1 +- api: + crdVersion: v1 + controller: true + domain: ironcore.dev + group: metal + kind: OOBSecret + path: github.com/ironcore-dev/metal/api/v1alpha1 + version: v1alpha1 version: "3" diff --git a/README.md b/README.md index 33cbd6ba..8275270e 100644 --- a/README.md +++ b/README.md @@ -1,29 +1,9 @@ -# ironcore-dev Repository Template - -Default templates for ironcore-dev open source repositories, including LICENSE, .reuse/dep5, Code of Conduct, etc... All repositories on github.com/SAP will be created based on this template. - -## To-Do - -In case you are the maintainer of a new SAP open source project, these are the steps to do with the template files: - -- Check if the default license (Apache 2.0) also applies to your project. A license change should only be required in exceptional cases. If this is the case, please change the [license file](LICENSE). -- Enter the correct metadata for the REUSE tool. See our [wiki page](https://wiki.one.int.sap/wiki/display/ospodocs/Using+the+Reuse+Tool+of+FSFE+for+Copyright+and+License+Information) for details how to do it. You can find an initial .reuse/dep5 file to build on. Please replace the parts inside the single angle quotation marks < > by the specific information for your repository and be sure to run the REUSE tool to validate that the metadata is correct. -- Adjust the contribution guidelines (e.g. add coding style guidelines, pull request checklists, different license if needed etc.) -- Add information about your project to this README (name, description, requirements etc). Especially take care for the placeholders - those ones need to be replaced with your project name. See the sections below the horizontal line and [our guidelines on our wiki page](https://wiki.one.int.sap/wiki/pages/viewpage.action?pageId=3564976048#GuidelinesforGitHubHealthfiles(Readme,Contributing,CodeofConduct)-Readme.md) what is required and recommended. -- Remove all content in this README above and including the horizontal line ;) - -*** - -# Our new open source project +# Metal ## About this project -*Insert a short description of your project here...* - ## Requirements and Setup -*Insert a short description what is required to get your project running...* - ## Support, Feedback, Contributing This project is open to feature requests/suggestions, bug reports etc. via [GitHub issues](https://github.com/ironcore-dev//issues). Contribution and feedback are encouraged and always welcome. For more information about how to contribute, the project structure, as well as additional contribution information, see our [Contribution Guidelines](CONTRIBUTING.md). diff --git a/api/v1alpha1/common.go b/api/v1alpha1/common.go new file mode 100644 index 00000000..aadb9a16 --- /dev/null +++ b/api/v1alpha1/common.go @@ -0,0 +1,72 @@ +package v1alpha1 + +import ( + "encoding/json" + "net/netip" +) + +type Prefix struct { + netip.Prefix `json:"-"` +} + +func (p *Prefix) UnmarshalJSON(b []byte) error { + if len(b) == 4 && string(b) == "null" { + p.Prefix = netip.Prefix{} + return nil + } + + var str string + err := json.Unmarshal(b, &str) + if err != nil { + return err + } + + var pr netip.Prefix + pr, err = netip.ParsePrefix(str) + if err != nil { + return err + } + + p.Prefix = pr + return nil +} + +func (p *Prefix) MarshalJSON() ([]byte, error) { + if p.IsZero() { + return []byte("null"), nil + } + + return json.Marshal(p.String()) +} + +func (p *Prefix) ToUnstructured() interface{} { + if p.IsZero() { + return nil + } + + return p.String() +} + +func (p *Prefix) DeepCopyInto(out *Prefix) { + *out = *p +} + +func (p *Prefix) DeepCopy() *Prefix { + return &Prefix{p.Prefix} +} + +func (p *Prefix) IsValid() bool { + return p != nil && p.Prefix.IsValid() +} + +func (p *Prefix) IsZero() bool { + return p == nil || !p.Prefix.IsValid() +} + +func (p *Prefix) OpenAPISchemaType() []string { + return []string{"string"} +} + +func (p *Prefix) OpenAPISchemaFormat() string { + return "prefix" +} diff --git a/api/v1alpha1/groupversion_info.go b/api/v1alpha1/groupversion_info.go new file mode 100644 index 00000000..53a59b5b --- /dev/null +++ b/api/v1alpha1/groupversion_info.go @@ -0,0 +1,36 @@ +/* +Copyright 2024. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Package v1alpha1 contains API Schema definitions for the metal v1alpha1 API group +// +kubebuilder:object:generate=true +// +groupName=metal.ironcore.dev +package v1alpha1 + +import ( + "k8s.io/apimachinery/pkg/runtime/schema" + "sigs.k8s.io/controller-runtime/pkg/scheme" +) + +var ( + // GroupVersion is group version used to register these objects + GroupVersion = schema.GroupVersion{Group: "metal.ironcore.dev", Version: "v1alpha1"} + + // SchemeBuilder is used to add go types to the GroupVersionKind scheme + SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion} + + // AddToScheme adds the types in this group-version to the given scheme. + AddToScheme = SchemeBuilder.AddToScheme +) diff --git a/api/v1alpha1/machine_types.go b/api/v1alpha1/machine_types.go new file mode 100644 index 00000000..04d23e9c --- /dev/null +++ b/api/v1alpha1/machine_types.go @@ -0,0 +1,155 @@ +/* +Copyright 2024. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1alpha1 + +import ( + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +const ( + MachineOperationKeyName string = "machine.metal.ironcore.dev/operation" + MachineOperationReboot string = "Reboot" + MachineOperationReset string = "Reset" + MachineOperationForceOff string = "ForceOff" +) + +// MachineSpec defines the desired state of Machine +type MachineSpec struct { + UUID string `json:"uuid"` //todo valiation + + OOBRef v1.LocalObjectReference `json:"oobRef"` + + InventoryRef v1.LocalObjectReference `json:"inventoryRef"` + + //+optional + MachineClaimRef *v1.ObjectReference `json:"machineClaimRef,omitempty"` + + //+optional + LoopbackAddressRef *v1.LocalObjectReference `json:"loopbackAddressRef,omitempty"` + + //+optional + ASN string `json:"asn,omitempty"` + + //+optional + Power Power `json:"power,omitempty"` // todo revisit whether optional + + //+optional + LocatorLED LocatorLED `json:"locatorLED,omitempty"` +} + +type Power string + +const ( + PowerOn Power = "On" + PowerOff Power = "Off" +) + +type LocatorLED string + +const ( + LocatorLEDOn Power = "On" + LocatorLEDOff Power = "Off" + LocatorLEDBlinking Power = "Blinking" +) + +// MachineStatus defines the observed state of Machine +type MachineStatus struct { + //+optional + Manufacturer string `json:"manufacturer,omitempty"` + + //+optional + SKU string `json:"sku,omitempty"` + + //+optional + SerialNumber string `json:"serialNumber,omitempty"` + + //+optional + Power Power `json:"power,omitempty"` + + //+optional + LocatorLED LocatorLED `json:"locatorLED,omitempty"` + + //+optional + ShutdownDeadline *metav1.Time `json:"shutdownDeadline,omitempty"` + + //+optional + NetworkInterfaces []MachineNetworkInterface `json:"networkInterfaces"` + + //+optional + State MachineState `json:"state,omitempty"` + + //+patchStrategy=merge + //+patchMergeKey=type + //+optional + Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type" protobuf:"bytes,1,rep,name=conditions"` +} + +type MachineNetworkInterface struct { + Name string `json:"name"` + + //+kubebuilder:validation:Pattern=`^[0-9a-f]{12}$` + MacAddress string `json:"macAddress"` + + //+optional + IPRef *v1.LocalObjectReference `json:"IPRef,omitempty"` + + //+optional + SwitchRef *v1.LocalObjectReference `json:"switchRef,omitempty"` +} + +type MachineState string + +const ( + MachineStateReady MachineState = "Ready" + MachineStateError MachineState = "Error" +) + +//+kubebuilder:object:root=true +//+kubebuilder:subresource:status +//+kubebuilder:resource:scope=Cluster +//+kubebuilder:printcolumn:name="UUID",type=string,JSONPath=`.status.uuid` +//+kubebuilder:printcolumn:name="Manufacturer",type=string,JSONPath=`.status.manufacturer` +//+kubebuilder:printcolumn:name="SKU",type=string,JSONPath=`.status.sku`,priority=100 +//+kubebuilder:printcolumn:name="SerialNumber",type=string,JSONPath=`.status.serialNumber`,priority=100 +//+kubebuilder:printcolumn:name="Power",type=string,JSONPath=`.status.power` +//+kubebuilder:printcolumn:name="LocatorLED",type=string,JSONPath=`.status.locatorLED`,priority=100 +//+kubebuilder:printcolumn:name="State",type=string,JSONPath=`.status.state` +//+kubebuilder:printcolumn:name="Age",type=date,JSONPath=`.metadata.creationTimeStamp` +// +genclient + +// Machine is the Schema for the machines API +type Machine struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec MachineSpec `json:"spec,omitempty"` + Status MachineStatus `json:"status,omitempty"` +} + +//+kubebuilder:object:root=true + +// MachineList contains a list of Machine +type MachineList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []Machine `json:"items"` +} + +func init() { + SchemeBuilder.Register(&Machine{}, &MachineList{}) +} diff --git a/api/v1alpha1/machineclaim_types.go b/api/v1alpha1/machineclaim_types.go new file mode 100644 index 00000000..32da2d85 --- /dev/null +++ b/api/v1alpha1/machineclaim_types.go @@ -0,0 +1,88 @@ +/* +Copyright 2024. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1alpha1 + +import ( + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// MachineClaimSpec defines the desired state of MachineClaim +type MachineClaimSpec struct { + //+optional + MachineRef *v1.LocalObjectReference `json:"machineRef,omitempty"` //todo validation + + //+optional + MachineSelector *metav1.LabelSelector `json:"machineSelector,omitempty"` + + Image string `json:"image"` + + Power Power `json:"power"` + + //+optional + IgnitionSecretRef *v1.LocalObjectReference `json:"ignitionSecretRef,omitempty"` + + //+optional + NetworkInterfaces []MachineClaimNetworkInterface `json:"networkInterfaces,omitempty"` // todo is it optional? +} + +type MachineClaimNetworkInterface struct { + Name string `json:"name"` + + Prefix Prefix `json:"prefix"` +} + +// MachineClaimStatus defines the observed state of MachineClaim +type MachineClaimStatus struct { + //+optional + Phase MachineClaimPhase `json:"phase,omitempty"` +} + +type MachineClaimPhase string + +const ( + MachineClaimPhaseBound MachineClaimPhase = "Bound" + MachineClaimPhaseUnBound MachineClaimPhase = "Unbound" +) + +//+kubebuilder:object:root=true +//+kubebuilder:subresource:status +//+kubebuilder:printcolumn:name="Phase",type=string,JSONPath=`.status.phase` +//+kubebuilder:printcolumn:name="Age",type=date,JSONPath=`.metadata.creationTimeStamp` +// +genclient + +// MachineClaim is the Schema for the machineclaims API +type MachineClaim struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec MachineClaimSpec `json:"spec,omitempty"` + Status MachineClaimStatus `json:"status,omitempty"` +} + +//+kubebuilder:object:root=true + +// MachineClaimList contains a list of MachineClaim +type MachineClaimList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []MachineClaim `json:"items"` +} + +func init() { + SchemeBuilder.Register(&MachineClaim{}, &MachineClaimList{}) +} diff --git a/api/v1alpha1/oob_types.go b/api/v1alpha1/oob_types.go new file mode 100644 index 00000000..da690a41 --- /dev/null +++ b/api/v1alpha1/oob_types.go @@ -0,0 +1,149 @@ +/* +Copyright 2024. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1alpha1 + +import ( + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +const ( + OOBOperationKeyName string = "oob.metal.ironcore.dev/operation" + OOBOperationReset string = "Reset" //TODO: check proper names here + OOBOperationForceReset string = "ForceReset" +) + +// OOBSpec defines the desired state of OOB +type OOBSpec struct { + //+kubebuilder:validation:Pattern=`^[0-9a-f]{12}$` + MACAddress string `json:"macAddress"` + + EndpointRef v1.LocalObjectReference `json:"endpointRef"` + + SecretRef v1.LocalObjectReference `json:"secretRef"` + + Protocol Protocol `json:"protocol"` + + //+optional + Flags map[string]string `json:"flags,omitempty"` + + //+optional + ConsoleProtocol *ConsoleProtocol `json:"consoleProtocol,omitempty"` +} + +type Protocol struct { + Name ProtocolName `json:"name"` + Port int32 `json:"port"` +} + +type ProtocolName string + +const ( + ProtocolNameRedfish ProtocolName = "Redfish" + ProtocolNameIPMI ProtocolName = "IPMI" + ProtocolNameSSH ProtocolName = "SSH" +) + +type ConsoleProtocol struct { + Name ConsoleProtocolName `json:"name"` + + Port int32 `json:"port"` +} + +type ConsoleProtocolName string + +const ( + ConsoleProtocolNameIPMI ConsoleProtocolName = "IPMI" + ConsoleProtocolNameSSH ConsoleProtocolName = "SSH" + ConsoleProtocolNameSSHLenovo ConsoleProtocolName = "SSHLenovo" +) + +// OOBStatus defines the observed state of OOB +type OOBStatus struct { + //+optional + Type OOBType `json:"type,omitempty"` + + //+optional + Manufacturer string `json:"manufacturer,omitempty"` + + //+optional + SKU string `json:"sku,omitempty"` + + //+optional + SerialNumber string `json:"serialNumber,omitempty"` + + //+optional + FirmwareVersion string `json:"firmwareVersion,omitempty"` + + //+optional + State OOBState `json:"state,omitempty"` + + //+patchStrategy=merge + //+patchMergeKey=type + //+optional + Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type" protobuf:"bytes,1,rep,name=conditions"` +} + +type OOBType string + +const ( + OOBTypeMachine OOBType = "Machine" + OOBTypeRouter OOBType = "Router" + OOBTypeSwitch OOBType = "Switch" +) + +type OOBState string + +const ( + OOBStateReady OOBState = "Ready" + OOBStateError OOBState = "Error" +) + +//+kubebuilder:object:root=true +//+kubebuilder:subresource:status +//+kubebuilder:resource:scope=Cluster +//+kubebuilder:printcolumn:name="MACAddress",type=string,JSONPath=`.spec.macAddress` +//+kubebuilder:printcolumn:name="Type",type=string,JSONPath=`.status.type` +//+kubebuilder:printcolumn:name="Manufacturer",type=string,JSONPath=`.status.manufacturer` +//+kubebuilder:printcolumn:name="SKU",type=string,JSONPath=`.status.sku`,priority=100 +//+kubebuilder:printcolumn:name="SerialNumber",type=string,JSONPath=`.status.serialNumber`,priority=100 +//+kubebuilder:printcolumn:name="FirmwareVersion",type=string,JSONPath=`.status.firmwareVersion`,priority=100 +//+kubebuilder:printcolumn:name="State",type=string,JSONPath=`.status.state` +//+kubebuilder:printcolumn:name="Age",type=date,JSONPath=`.metadata.creationTimeStamp` +// +genclient + +// OOB is the Schema for the oobs API +type OOB struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec OOBSpec `json:"spec,omitempty"` + Status OOBStatus `json:"status,omitempty"` +} + +//+kubebuilder:object:root=true + +// OOBList contains a list of OOB +type OOBList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []OOB `json:"items"` +} + +func init() { + SchemeBuilder.Register(&OOB{}, &OOBList{}) +} diff --git a/api/v1alpha1/oobsecret_types.go b/api/v1alpha1/oobsecret_types.go new file mode 100644 index 00000000..b20541a4 --- /dev/null +++ b/api/v1alpha1/oobsecret_types.go @@ -0,0 +1,71 @@ +/* +Copyright 2024. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// OOBSecretSpec defines the desired state of OOBSecret +type OOBSecretSpec struct { + //+kubebuilder:validation:Pattern=`^[0-9a-f]{12}$` + MACAddress string `json:"macAddress"` + + Username string `json:"username"` + + Password string `json:"password"` + + //+optional + ExpirationDate *metav1.Time `json:"expirationDate,omitempty"` +} + +// OOBSecretStatus defines the observed state of OOBSecret +type OOBSecretStatus struct { + //+patchStrategy=merge + //+patchMergeKey=type + //+optional + Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type" protobuf:"bytes,1,rep,name=conditions"` +} + +//+kubebuilder:object:root=true +//+kubebuilder:subresource:status +//+kubebuilder:resource:scope=Cluster +//+kubebuilder:printcolumn:name="MACAddress",type=string,JSONPath=`.spec.macAddress` +//+kubebuilder:printcolumn:name="Age",type=date,JSONPath=`.metadata.creationTimeStamp` +// +genclient + +// OOBSecret is the Schema for the oobsecrets API +type OOBSecret struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec OOBSecretSpec `json:"spec,omitempty"` + Status OOBSecretStatus `json:"status,omitempty"` +} + +//+kubebuilder:object:root=true + +// OOBSecretList contains a list of OOBSecret +type OOBSecretList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []OOBSecret `json:"items"` +} + +func init() { + SchemeBuilder.Register(&OOBSecret{}, &OOBSecretList{}) +} diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go new file mode 100644 index 00000000..3c4cdd3f --- /dev/null +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -0,0 +1,539 @@ +//go:build !ignore_autogenerated + +/* +Copyright 2024. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by controller-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" +) + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ConsoleProtocol) DeepCopyInto(out *ConsoleProtocol) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConsoleProtocol. +func (in *ConsoleProtocol) DeepCopy() *ConsoleProtocol { + if in == nil { + return nil + } + out := new(ConsoleProtocol) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Machine) DeepCopyInto(out *Machine) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Machine. +func (in *Machine) DeepCopy() *Machine { + if in == nil { + return nil + } + out := new(Machine) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Machine) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *MachineClaim) DeepCopyInto(out *MachineClaim) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + out.Status = in.Status +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MachineClaim. +func (in *MachineClaim) DeepCopy() *MachineClaim { + if in == nil { + return nil + } + out := new(MachineClaim) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *MachineClaim) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *MachineClaimList) DeepCopyInto(out *MachineClaimList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]MachineClaim, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MachineClaimList. +func (in *MachineClaimList) DeepCopy() *MachineClaimList { + if in == nil { + return nil + } + out := new(MachineClaimList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *MachineClaimList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *MachineClaimNetworkInterface) DeepCopyInto(out *MachineClaimNetworkInterface) { + *out = *in + in.Prefix.DeepCopyInto(&out.Prefix) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MachineClaimNetworkInterface. +func (in *MachineClaimNetworkInterface) DeepCopy() *MachineClaimNetworkInterface { + if in == nil { + return nil + } + out := new(MachineClaimNetworkInterface) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *MachineClaimSpec) DeepCopyInto(out *MachineClaimSpec) { + *out = *in + if in.MachineRef != nil { + in, out := &in.MachineRef, &out.MachineRef + *out = new(v1.LocalObjectReference) + **out = **in + } + if in.MachineSelector != nil { + in, out := &in.MachineSelector, &out.MachineSelector + *out = new(metav1.LabelSelector) + (*in).DeepCopyInto(*out) + } + if in.IgnitionSecretRef != nil { + in, out := &in.IgnitionSecretRef, &out.IgnitionSecretRef + *out = new(v1.LocalObjectReference) + **out = **in + } + if in.NetworkInterfaces != nil { + in, out := &in.NetworkInterfaces, &out.NetworkInterfaces + *out = make([]MachineClaimNetworkInterface, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MachineClaimSpec. +func (in *MachineClaimSpec) DeepCopy() *MachineClaimSpec { + if in == nil { + return nil + } + out := new(MachineClaimSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *MachineClaimStatus) DeepCopyInto(out *MachineClaimStatus) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MachineClaimStatus. +func (in *MachineClaimStatus) DeepCopy() *MachineClaimStatus { + if in == nil { + return nil + } + out := new(MachineClaimStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *MachineList) DeepCopyInto(out *MachineList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Machine, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MachineList. +func (in *MachineList) DeepCopy() *MachineList { + if in == nil { + return nil + } + out := new(MachineList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *MachineList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *MachineNetworkInterface) DeepCopyInto(out *MachineNetworkInterface) { + *out = *in + if in.IPRef != nil { + in, out := &in.IPRef, &out.IPRef + *out = new(v1.LocalObjectReference) + **out = **in + } + if in.SwitchRef != nil { + in, out := &in.SwitchRef, &out.SwitchRef + *out = new(v1.LocalObjectReference) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MachineNetworkInterface. +func (in *MachineNetworkInterface) DeepCopy() *MachineNetworkInterface { + if in == nil { + return nil + } + out := new(MachineNetworkInterface) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *MachineSpec) DeepCopyInto(out *MachineSpec) { + *out = *in + out.OOBRef = in.OOBRef + out.InventoryRef = in.InventoryRef + if in.MachineClaimRef != nil { + in, out := &in.MachineClaimRef, &out.MachineClaimRef + *out = new(v1.ObjectReference) + **out = **in + } + if in.LoopbackAddressRef != nil { + in, out := &in.LoopbackAddressRef, &out.LoopbackAddressRef + *out = new(v1.LocalObjectReference) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MachineSpec. +func (in *MachineSpec) DeepCopy() *MachineSpec { + if in == nil { + return nil + } + out := new(MachineSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *MachineStatus) DeepCopyInto(out *MachineStatus) { + *out = *in + if in.ShutdownDeadline != nil { + in, out := &in.ShutdownDeadline, &out.ShutdownDeadline + *out = (*in).DeepCopy() + } + if in.NetworkInterfaces != nil { + in, out := &in.NetworkInterfaces, &out.NetworkInterfaces + *out = make([]MachineNetworkInterface, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]metav1.Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MachineStatus. +func (in *MachineStatus) DeepCopy() *MachineStatus { + if in == nil { + return nil + } + out := new(MachineStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *OOB) DeepCopyInto(out *OOB) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OOB. +func (in *OOB) DeepCopy() *OOB { + if in == nil { + return nil + } + out := new(OOB) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *OOB) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *OOBList) DeepCopyInto(out *OOBList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]OOB, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OOBList. +func (in *OOBList) DeepCopy() *OOBList { + if in == nil { + return nil + } + out := new(OOBList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *OOBList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *OOBSecret) DeepCopyInto(out *OOBSecret) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OOBSecret. +func (in *OOBSecret) DeepCopy() *OOBSecret { + if in == nil { + return nil + } + out := new(OOBSecret) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *OOBSecret) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *OOBSecretList) DeepCopyInto(out *OOBSecretList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]OOBSecret, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OOBSecretList. +func (in *OOBSecretList) DeepCopy() *OOBSecretList { + if in == nil { + return nil + } + out := new(OOBSecretList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *OOBSecretList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *OOBSecretSpec) DeepCopyInto(out *OOBSecretSpec) { + *out = *in + if in.ExpirationDate != nil { + in, out := &in.ExpirationDate, &out.ExpirationDate + *out = (*in).DeepCopy() + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OOBSecretSpec. +func (in *OOBSecretSpec) DeepCopy() *OOBSecretSpec { + if in == nil { + return nil + } + out := new(OOBSecretSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *OOBSecretStatus) DeepCopyInto(out *OOBSecretStatus) { + *out = *in + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]metav1.Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OOBSecretStatus. +func (in *OOBSecretStatus) DeepCopy() *OOBSecretStatus { + if in == nil { + return nil + } + out := new(OOBSecretStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *OOBSpec) DeepCopyInto(out *OOBSpec) { + *out = *in + out.EndpointRef = in.EndpointRef + out.SecretRef = in.SecretRef + out.Protocol = in.Protocol + if in.Flags != nil { + in, out := &in.Flags, &out.Flags + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + if in.ConsoleProtocol != nil { + in, out := &in.ConsoleProtocol, &out.ConsoleProtocol + *out = new(ConsoleProtocol) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OOBSpec. +func (in *OOBSpec) DeepCopy() *OOBSpec { + if in == nil { + return nil + } + out := new(OOBSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *OOBStatus) DeepCopyInto(out *OOBStatus) { + *out = *in + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]metav1.Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OOBStatus. +func (in *OOBStatus) DeepCopy() *OOBStatus { + if in == nil { + return nil + } + out := new(OOBStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Protocol) DeepCopyInto(out *Protocol) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Protocol. +func (in *Protocol) DeepCopy() *Protocol { + if in == nil { + return nil + } + out := new(Protocol) + in.DeepCopyInto(out) + return out +} diff --git a/cmd/main.go b/cmd/main.go index 2e1c36e2..fdb93ec4 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -21,18 +21,18 @@ import ( "flag" "os" - // Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.) - // to ensure that exec-entrypoint and run can make use of them. - _ "k8s.io/client-go/plugin/pkg/client/auth" - "k8s.io/apimachinery/pkg/runtime" utilruntime "k8s.io/apimachinery/pkg/util/runtime" clientgoscheme "k8s.io/client-go/kubernetes/scheme" + _ "k8s.io/client-go/plugin/pkg/client/auth" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/healthz" "sigs.k8s.io/controller-runtime/pkg/log/zap" metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server" "sigs.k8s.io/controller-runtime/pkg/webhook" + + metalv1alpha1 "github.com/ironcore-dev/metal/api/v1alpha1" + "github.com/ironcore-dev/metal/internal/controller" //+kubebuilder:scaffold:imports ) @@ -43,7 +43,7 @@ var ( func init() { utilruntime.Must(clientgoscheme.AddToScheme(scheme)) - + utilruntime.Must(metalv1alpha1.AddToScheme(scheme)) //+kubebuilder:scaffold:scheme } @@ -70,18 +70,12 @@ func main() { ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts))) - // if the enable-http2 flag is false (the default), http/2 should be disabled - // due to its vulnerabilities. More specifically, disabling http/2 will - // prevent from being vulnerable to the HTTP/2 Stream Cancelation and - // Rapid Reset CVEs. For more information see: - // - https://github.com/advisories/GHSA-qppj-fm5r-hxr3 - // - https://github.com/advisories/GHSA-4374-p667-p6c8 disableHTTP2 := func(c *tls.Config) { setupLog.Info("disabling http/2") c.NextProtos = []string{"http/1.1"} } - tlsOpts := []func(*tls.Config){} + var tlsOpts []func(*tls.Config) if !enableHTTP2 { tlsOpts = append(tlsOpts, disableHTTP2) } @@ -100,37 +94,54 @@ func main() { WebhookServer: webhookServer, HealthProbeBindAddress: probeAddr, LeaderElection: enableLeaderElection, - LeaderElectionID: "46e299f1.ironcore.dev", - // LeaderElectionReleaseOnCancel defines if the leader should step down voluntarily - // when the Manager ends. This requires the binary to immediately end when the - // Manager is stopped, otherwise, this setting is unsafe. Setting this significantly - // speeds up voluntary leader transitions as the new leader don't have to wait - // LeaseDuration time first. - // - // In the default scaffold provided, the program ends immediately after - // the manager stops, so would be fine to enable this option. However, - // if you are doing or is intended to do any operation such as perform cleanups - // after the manager stops then its usage might be unsafe. - // LeaderElectionReleaseOnCancel: true, + LeaderElectionID: "metal.ironcore.dev", }) if err != nil { setupLog.Error(err, "unable to start manager") os.Exit(1) } + if err = (&controller.MachineReconciler{ + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", "Machine") + os.Exit(1) + } + if err = (&controller.MachineClaimReconciler{ + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", "MachineClaim") + os.Exit(1) + } + if err = (&controller.OOBReconciler{ + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", "OOB") + os.Exit(1) + } + if err = (&controller.OOBSecretReconciler{ + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", "OOBSecret") + os.Exit(1) + } //+kubebuilder:scaffold:builder - if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil { + if err = mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil { setupLog.Error(err, "unable to set up health check") os.Exit(1) } - if err := mgr.AddReadyzCheck("readyz", healthz.Ping); err != nil { + if err = mgr.AddReadyzCheck("readyz", healthz.Ping); err != nil { setupLog.Error(err, "unable to set up ready check") os.Exit(1) } setupLog.Info("starting manager") - if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil { + if err = mgr.Start(ctrl.SetupSignalHandler()); err != nil { setupLog.Error(err, "problem running manager") os.Exit(1) } diff --git a/config/crd/bases/metal.ironcore.dev_machineclaims.yaml b/config/crd/bases/metal.ironcore.dev_machineclaims.yaml new file mode 100644 index 00000000..e3e06357 --- /dev/null +++ b/config/crd/bases/metal.ironcore.dev_machineclaims.yaml @@ -0,0 +1,152 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.14.0 + name: machineclaims.metal.ironcore.dev +spec: + group: metal.ironcore.dev + names: + kind: MachineClaim + listKind: MachineClaimList + plural: machineclaims + singular: machineclaim + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.phase + name: Phase + type: string + - jsonPath: .metadata.creationTimeStamp + name: Age + type: date + name: v1alpha1 + schema: + openAPIV3Schema: + description: MachineClaim is the Schema for the machineclaims 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: MachineClaimSpec defines the desired state of MachineClaim + properties: + ignitionSecretRef: + description: |- + LocalObjectReference contains enough information to let you locate the + referenced object inside the same namespace. + 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 + image: + type: string + machineRef: + description: |- + LocalObjectReference contains enough information to let you locate the + referenced object inside the same namespace. + 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 + machineSelector: + description: |- + A label selector is a label query over a set of resources. The result of matchLabels and + matchExpressions are ANDed. An empty label selector matches all objects. A null + label selector matches no objects. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. + The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + networkInterfaces: + items: + properties: + name: + type: string + prefix: + type: object + required: + - name + - prefix + type: object + type: array + power: + type: string + required: + - image + - power + type: object + status: + description: MachineClaimStatus defines the observed state of MachineClaim + properties: + phase: + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/crd/bases/metal.ironcore.dev_machines.yaml b/config/crd/bases/metal.ironcore.dev_machines.yaml new file mode 100644 index 00000000..57e4c2b9 --- /dev/null +++ b/config/crd/bases/metal.ironcore.dev_machines.yaml @@ -0,0 +1,314 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.14.0 + name: machines.metal.ironcore.dev +spec: + group: metal.ironcore.dev + names: + kind: Machine + listKind: MachineList + plural: machines + singular: machine + scope: Cluster + versions: + - additionalPrinterColumns: + - jsonPath: .status.uuid + name: UUID + type: string + - jsonPath: .status.manufacturer + name: Manufacturer + type: string + - jsonPath: .status.sku + name: SKU + priority: 100 + type: string + - jsonPath: .status.serialNumber + name: SerialNumber + priority: 100 + type: string + - jsonPath: .status.power + name: Power + type: string + - jsonPath: .status.locatorLED + name: LocatorLED + priority: 100 + type: string + - jsonPath: .status.state + name: State + type: string + - jsonPath: .metadata.creationTimeStamp + name: Age + type: date + name: v1alpha1 + schema: + openAPIV3Schema: + description: Machine is the Schema for the machines 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: MachineSpec defines the desired state of Machine + properties: + asn: + type: string + inventoryRef: + description: |- + LocalObjectReference contains enough information to let you locate the + referenced object inside the same namespace. + 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 + locatorLED: + type: string + loopbackAddressRef: + description: |- + LocalObjectReference contains enough information to let you locate the + referenced object inside the same namespace. + 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 + machineClaimRef: + description: |- + ObjectReference contains enough information to let you inspect or modify the referred object. + --- + New uses of this type are discouraged because of difficulty describing its usage when embedded in APIs. + 1. Ignored fields. It includes many fields which are not generally honored. For instance, ResourceVersion and FieldPath are both very rarely valid in actual usage. + 2. Invalid usage help. It is impossible to add specific help for individual usage. In most embedded usages, there are particular + restrictions like, "must refer only to types A and B" or "UID not honored" or "name must be restricted". + Those cannot be well described when embedded. + 3. Inconsistent validation. Because the usages are different, the validation rules are different by usage, which makes it hard for users to predict what will happen. + 4. The fields are both imprecise and overly precise. Kind is not a precise mapping to a URL. This can produce ambiguity + during interpretation and require a REST mapping. In most cases, the dependency is on the group,resource tuple + and the version of the actual struct is irrelevant. + 5. We cannot easily change it. Because this type is embedded in many locations, updates to this type + will affect numerous schemas. Don't make new APIs embed an underspecified API type they do not control. + + + Instead of using this type, create a locally provided and used type that is well-focused on your reference. + For example, ServiceReferences for admission registration: https://github.com/kubernetes/api/blob/release-1.17/admissionregistration/v1/types.go#L533 . + properties: + apiVersion: + description: API version of the referent. + type: string + fieldPath: + description: |- + If referring to a piece of an object instead of an entire object, this string + should contain a valid JSON/Go field access statement, such as desiredState.manifest.containers[2]. + For example, if the object reference is to a container within a pod, this would take on a value like: + "spec.containers{name}" (where "name" refers to the name of the container that triggered + the event) or if no container name is specified "spec.containers[2]" (container with + index 2 in this pod). This syntax is chosen only to have some well-defined way of + referencing a part of an object. + TODO: this design is not final and this field is subject to change in the future. + type: string + kind: + description: |- + Kind of the referent. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + namespace: + description: |- + Namespace of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/ + type: string + resourceVersion: + description: |- + Specific resourceVersion to which this reference is made, if any. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency + type: string + uid: + description: |- + UID of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids + type: string + type: object + x-kubernetes-map-type: atomic + oobRef: + description: |- + LocalObjectReference contains enough information to let you locate the + referenced object inside the same namespace. + 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 + power: + type: string + uuid: + type: string + required: + - inventoryRef + - oobRef + - uuid + type: object + status: + description: MachineStatus defines the observed state of Machine + properties: + conditions: + items: + description: "Condition contains details for one aspect of the current + state of this API Resource.\n---\nThis struct is intended for + direct use as an array at the field path .status.conditions. For + example,\n\n\n\ttype FooStatus struct{\n\t // Represents the + observations of a foo's current state.\n\t // Known .status.conditions.type + are: \"Available\", \"Progressing\", and \"Degraded\"\n\t // + +patchMergeKey=type\n\t // +patchStrategy=merge\n\t // +listType=map\n\t + \ // +listMapKey=type\n\t Conditions []metav1.Condition `json:\"conditions,omitempty\" + patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t + \ // other fields\n\t}" + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: |- + type of condition in CamelCase or in foo.example.com/CamelCase. + --- + Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be + useful (see .node.status.conditions), the ability to deconflict is important. + The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + locatorLED: + type: string + manufacturer: + type: string + networkInterfaces: + items: + properties: + IPRef: + description: |- + LocalObjectReference contains enough information to let you locate the + referenced object inside the same namespace. + 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 + macAddress: + pattern: ^[0-9a-f]{12}$ + type: string + name: + type: string + switchRef: + description: |- + LocalObjectReference contains enough information to let you locate the + referenced object inside the same namespace. + 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: + - macAddress + - name + type: object + type: array + power: + type: string + serialNumber: + type: string + shutdownDeadline: + format: date-time + type: string + sku: + type: string + state: + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/crd/bases/metal.ironcore.dev_oobs.yaml b/config/crd/bases/metal.ironcore.dev_oobs.yaml new file mode 100644 index 00000000..f66aa777 --- /dev/null +++ b/config/crd/bases/metal.ironcore.dev_oobs.yaml @@ -0,0 +1,220 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.14.0 + name: oobs.metal.ironcore.dev +spec: + group: metal.ironcore.dev + names: + kind: OOB + listKind: OOBList + plural: oobs + singular: oob + scope: Cluster + versions: + - additionalPrinterColumns: + - jsonPath: .spec.macAddress + name: MACAddress + type: string + - jsonPath: .status.type + name: Type + type: string + - jsonPath: .status.manufacturer + name: Manufacturer + type: string + - jsonPath: .status.sku + name: SKU + priority: 100 + type: string + - jsonPath: .status.serialNumber + name: SerialNumber + priority: 100 + type: string + - jsonPath: .status.firmwareVersion + name: FirmwareVersion + priority: 100 + type: string + - jsonPath: .status.state + name: State + type: string + - jsonPath: .metadata.creationTimeStamp + name: Age + type: date + name: v1alpha1 + schema: + openAPIV3Schema: + description: OOB is the Schema for the oobs 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: OOBSpec defines the desired state of OOB + properties: + consoleProtocol: + properties: + name: + type: string + port: + format: int32 + type: integer + required: + - name + - port + type: object + endpointRef: + description: |- + LocalObjectReference contains enough information to let you locate the + referenced object inside the same namespace. + 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 + flags: + additionalProperties: + type: string + type: object + macAddress: + pattern: ^[0-9a-f]{12}$ + type: string + protocol: + properties: + name: + type: string + port: + format: int32 + type: integer + required: + - name + - port + type: object + secretRef: + description: |- + LocalObjectReference contains enough information to let you locate the + referenced object inside the same namespace. + 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: + - endpointRef + - macAddress + - protocol + - secretRef + type: object + status: + description: OOBStatus defines the observed state of OOB + properties: + conditions: + items: + description: "Condition contains details for one aspect of the current + state of this API Resource.\n---\nThis struct is intended for + direct use as an array at the field path .status.conditions. For + example,\n\n\n\ttype FooStatus struct{\n\t // Represents the + observations of a foo's current state.\n\t // Known .status.conditions.type + are: \"Available\", \"Progressing\", and \"Degraded\"\n\t // + +patchMergeKey=type\n\t // +patchStrategy=merge\n\t // +listType=map\n\t + \ // +listMapKey=type\n\t Conditions []metav1.Condition `json:\"conditions,omitempty\" + patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t + \ // other fields\n\t}" + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: |- + type of condition in CamelCase or in foo.example.com/CamelCase. + --- + Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be + useful (see .node.status.conditions), the ability to deconflict is important. + The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + firmwareVersion: + type: string + manufacturer: + type: string + serialNumber: + type: string + sku: + type: string + state: + type: string + type: + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/crd/bases/metal.ironcore.dev_oobsecrets.yaml b/config/crd/bases/metal.ironcore.dev_oobsecrets.yaml new file mode 100644 index 00000000..b463a899 --- /dev/null +++ b/config/crd/bases/metal.ironcore.dev_oobsecrets.yaml @@ -0,0 +1,141 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.14.0 + name: oobsecrets.metal.ironcore.dev +spec: + group: metal.ironcore.dev + names: + kind: OOBSecret + listKind: OOBSecretList + plural: oobsecrets + singular: oobsecret + scope: Cluster + versions: + - additionalPrinterColumns: + - jsonPath: .spec.macAddress + name: MACAddress + type: string + - jsonPath: .metadata.creationTimeStamp + name: Age + type: date + name: v1alpha1 + schema: + openAPIV3Schema: + description: OOBSecret is the Schema for the oobsecrets 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: OOBSecretSpec defines the desired state of OOBSecret + properties: + expirationDate: + format: date-time + type: string + macAddress: + pattern: ^[0-9a-f]{12}$ + type: string + password: + type: string + username: + type: string + required: + - macAddress + - password + - username + type: object + status: + description: OOBSecretStatus defines the observed state of OOBSecret + properties: + conditions: + items: + description: "Condition contains details for one aspect of the current + state of this API Resource.\n---\nThis struct is intended for + direct use as an array at the field path .status.conditions. For + example,\n\n\n\ttype FooStatus struct{\n\t // Represents the + observations of a foo's current state.\n\t // Known .status.conditions.type + are: \"Available\", \"Progressing\", and \"Degraded\"\n\t // + +patchMergeKey=type\n\t // +patchStrategy=merge\n\t // +listType=map\n\t + \ // +listMapKey=type\n\t Conditions []metav1.Condition `json:\"conditions,omitempty\" + patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t + \ // other fields\n\t}" + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: |- + type of condition in CamelCase or in foo.example.com/CamelCase. + --- + Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be + useful (see .node.status.conditions), the ability to deconflict is important. + The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/crd/kustomization.yaml b/config/crd/kustomization.yaml new file mode 100644 index 00000000..20dd16a1 --- /dev/null +++ b/config/crd/kustomization.yaml @@ -0,0 +1,22 @@ +resources: +- bases/metal.ironcore.dev_machines.yaml +- bases/metal.ironcore.dev_machineclaims.yaml +- bases/metal.ironcore.dev_oobs.yaml +- bases/metal.ironcore.dev_oobsecrets.yaml +#+kubebuilder:scaffold:crdkustomizeresource + +patches: +#- path: patches/webhook_in_machines.yaml +#- path: patches/webhook_in_machineclaims.yaml +#- path: patches/webhook_in_oobs.yaml +#- path: patches/webhook_in_oobsecrets.yaml +#+kubebuilder:scaffold:crdkustomizewebhookpatch + +#- path: patches/cainjection_in_machines.yaml +#- path: patches/cainjection_in_machineclaims.yaml +#- path: patches/cainjection_in_oobs.yaml +#- path: patches/cainjection_in_oobsecrets.yaml +#+kubebuilder:scaffold:crdkustomizecainjectionpatch + +#configurations: +#- kustomizeconfig.yaml diff --git a/config/crd/kustomizeconfig.yaml b/config/crd/kustomizeconfig.yaml new file mode 100644 index 00000000..e0dd2980 --- /dev/null +++ b/config/crd/kustomizeconfig.yaml @@ -0,0 +1,18 @@ +nameReference: +- kind: Service + version: v1 + fieldSpecs: + - kind: CustomResourceDefinition + version: v1 + group: apiextensions.k8s.io + path: spec/conversion/webhook/clientConfig/service/name + +namespace: +- kind: CustomResourceDefinition + version: v1 + group: apiextensions.k8s.io + path: spec/conversion/webhook/clientConfig/service/namespace + create: false + +varReference: +- path: metadata/annotations diff --git a/config/default/kustomization.yaml b/config/default/kustomization.yaml index 57d554d3..4870c1d0 100644 --- a/config/default/kustomization.yaml +++ b/config/default/kustomization.yaml @@ -1,21 +1,9 @@ -# Adds namespace to all resources. namespace: metal-system -# Value of this field is prepended to the -# names of all resources, e.g. a deployment named -# "wordpress" becomes "alices-wordpress". -# Note that it should also match with the prefix (text before '-') of the namespace -# field above. namePrefix: metal- -# Labels to add to all resources and selectors. -#labels: -#- includeSelectors: true -# pairs: -# someName: someValue - resources: -#- ../crd +- ../crd - ../rbac - ../manager # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in @@ -27,29 +15,17 @@ resources: #- ../prometheus patches: -# Protect the /metrics endpoint by putting it behind auth. -# If you want your controller-manager to expose the /metrics -# endpoint w/o any authn/z, please comment the following line. - path: manager_auth_proxy_patch.yaml - -# [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in -# crd/kustomization.yaml #- path: manager_webhook_patch.yaml - -# [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'. -# Uncomment 'CERTMANAGER' sections in crd/kustomization.yaml to enable the CA injection in the admission webhooks. -# 'CERTMANAGER' needs to be enabled to use ca injection #- path: webhookcainjection_patch.yaml -# [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER' prefix. -# Uncomment the following replacements to add the cert-manager CA injection annotations #replacements: -# - source: # Add cert-manager annotation to ValidatingWebhookConfiguration, MutatingWebhookConfiguration and CRDs +# - source: # kind: Certificate # group: cert-manager.io # version: v1 -# name: serving-cert # this name should match the one in certificate.yaml -# fieldPath: .metadata.namespace # namespace of the certificate CR +# name: serving-cert +# fieldPath: .metadata.namespace # targets: # - select: # kind: ValidatingWebhookConfiguration @@ -79,7 +55,7 @@ patches: # kind: Certificate # group: cert-manager.io # version: v1 -# name: serving-cert # this name should match the one in certificate.yaml +# name: serving-cert # fieldPath: .metadata.name # targets: # - select: @@ -106,11 +82,11 @@ patches: # delimiter: '/' # index: 1 # create: true -# - source: # Add cert-manager annotation to the webhook Service +# - source: # kind: Service # version: v1 # name: webhook-service -# fieldPath: .metadata.name # namespace of the service +# fieldPath: .metadata.name # targets: # - select: # kind: Certificate @@ -127,7 +103,7 @@ patches: # kind: Service # version: v1 # name: webhook-service -# fieldPath: .metadata.namespace # namespace of the service +# fieldPath: .metadata.namespace # targets: # - select: # kind: Certificate diff --git a/config/default/manager_auth_proxy_patch.yaml b/config/default/manager_auth_proxy_patch.yaml index 70c3437f..a4d6ef09 100644 --- a/config/default/manager_auth_proxy_patch.yaml +++ b/config/default/manager_auth_proxy_patch.yaml @@ -1,5 +1,3 @@ -# This patch inject a sidecar container which is a HTTP proxy for the -# controller manager, it performs RBAC authorization against the Kubernetes API using SubjectAccessReviews. apiVersion: apps/v1 kind: Deployment metadata: diff --git a/config/manager/manager.yaml b/config/manager/manager.yaml index 124dd5e1..b190617d 100644 --- a/config/manager/manager.yaml +++ b/config/manager/manager.yaml @@ -36,35 +36,24 @@ spec: labels: control-plane: controller-manager spec: - # TODO(user): Uncomment the following code to configure the nodeAffinity expression - # according to the platforms which are supported by your solution. - # It is considered best practice to support multiple architectures. You can - # build your manager image using the makefile target docker-buildx. - # affinity: - # nodeAffinity: - # requiredDuringSchedulingIgnoredDuringExecution: - # nodeSelectorTerms: - # - matchExpressions: - # - key: kubernetes.io/arch - # operator: In - # values: - # - amd64 - # - arm64 - # - ppc64le - # - s390x - # - key: kubernetes.io/os - # operator: In - # values: - # - linux + affinity: + nodeAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: kubernetes.io/arch + operator: In + values: + - amd64 + - arm64 + - key: kubernetes.io/os + operator: In + values: + - linux securityContext: runAsNonRoot: true - # TODO(user): For common cases that do not require escalating privileges - # it is recommended to ensure that all your Pods/Containers are restrictive. - # More info: https://kubernetes.io/docs/concepts/security/pod-security-standards/#restricted - # Please uncomment the following code if your project does NOT have to work on old Kubernetes - # versions < 1.19 or on vendors versions which do NOT support this field by default (i.e. Openshift < 4.11 ). - # seccompProfile: - # type: RuntimeDefault + seccompProfile: + type: RuntimeDefault containers: - command: - /metal @@ -89,14 +78,12 @@ spec: port: 8081 initialDelaySeconds: 5 periodSeconds: 10 - # TODO(user): Configure the resources accordingly based on the project requirements. - # More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ resources: limits: - cpu: 500m - memory: 128Mi + cpu: 1 + memory: 256Mi requests: - cpu: 10m - memory: 64Mi + cpu: 100m + memory: 128Mi serviceAccountName: controller-manager terminationGracePeriodSeconds: 10 diff --git a/config/prometheus/monitor.yaml b/config/prometheus/monitor.yaml index 697a3f02..d9a1f71a 100644 --- a/config/prometheus/monitor.yaml +++ b/config/prometheus/monitor.yaml @@ -1,4 +1,3 @@ -# Prometheus Monitor Service (Metrics) apiVersion: monitoring.coreos.com/v1 kind: ServiceMonitor metadata: diff --git a/config/rbac/kustomization.yaml b/config/rbac/kustomization.yaml index 731832a6..ca04e399 100644 --- a/config/rbac/kustomization.yaml +++ b/config/rbac/kustomization.yaml @@ -1,17 +1,9 @@ resources: -# All RBAC will be applied under this service account in -# the deployment namespace. You may comment out this resource -# if your manager will use a service account that exists at -# runtime. Be sure to update RoleBinding and ClusterRoleBinding -# subjects if changing service account names. - service_account.yaml - role.yaml - role_binding.yaml - leader_election_role.yaml - leader_election_role_binding.yaml -# Comment the following 4 lines if you want to disable -# the auth proxy (https://github.com/brancz/kube-rbac-proxy) -# which protects your /metrics endpoint. - auth_proxy_service.yaml - auth_proxy_role.yaml - auth_proxy_role_binding.yaml diff --git a/config/rbac/leader_election_role.yaml b/config/rbac/leader_election_role.yaml index c5cf4aa4..8c520b9b 100644 --- a/config/rbac/leader_election_role.yaml +++ b/config/rbac/leader_election_role.yaml @@ -1,4 +1,3 @@ -# permissions to do leader election. apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: diff --git a/config/rbac/machine_editor_role.yaml b/config/rbac/machine_editor_role.yaml new file mode 100644 index 00000000..9084391a --- /dev/null +++ b/config/rbac/machine_editor_role.yaml @@ -0,0 +1,30 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/name: clusterrole + app.kubernetes.io/instance: machine-editor-role + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: metal + app.kubernetes.io/part-of: metal + app.kubernetes.io/managed-by: kustomize + name: machine-editor-role +rules: +- apiGroups: + - metal.ironcore.dev + resources: + - machines + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - metal.ironcore.dev + resources: + - machines/status + verbs: + - get diff --git a/config/rbac/machine_viewer_role.yaml b/config/rbac/machine_viewer_role.yaml new file mode 100644 index 00000000..9f9cb33e --- /dev/null +++ b/config/rbac/machine_viewer_role.yaml @@ -0,0 +1,26 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/name: clusterrole + app.kubernetes.io/instance: machine-viewer-role + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: metal + app.kubernetes.io/part-of: metal + app.kubernetes.io/managed-by: kustomize + name: machine-viewer-role +rules: +- apiGroups: + - metal.ironcore.dev + resources: + - machines + verbs: + - get + - list + - watch +- apiGroups: + - metal.ironcore.dev + resources: + - machines/status + verbs: + - get diff --git a/config/rbac/machineclaim_editor_role.yaml b/config/rbac/machineclaim_editor_role.yaml new file mode 100644 index 00000000..781a6484 --- /dev/null +++ b/config/rbac/machineclaim_editor_role.yaml @@ -0,0 +1,30 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/name: clusterrole + app.kubernetes.io/instance: machineclaim-editor-role + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: metal + app.kubernetes.io/part-of: metal + app.kubernetes.io/managed-by: kustomize + name: machineclaim-editor-role +rules: +- apiGroups: + - metal.ironcore.dev + resources: + - machineclaims + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - metal.ironcore.dev + resources: + - machineclaims/status + verbs: + - get diff --git a/config/rbac/machineclaim_viewer_role.yaml b/config/rbac/machineclaim_viewer_role.yaml new file mode 100644 index 00000000..78a2e965 --- /dev/null +++ b/config/rbac/machineclaim_viewer_role.yaml @@ -0,0 +1,26 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/name: clusterrole + app.kubernetes.io/instance: machineclaim-viewer-role + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: metal + app.kubernetes.io/part-of: metal + app.kubernetes.io/managed-by: kustomize + name: machineclaim-viewer-role +rules: +- apiGroups: + - metal.ironcore.dev + resources: + - machineclaims + verbs: + - get + - list + - watch +- apiGroups: + - metal.ironcore.dev + resources: + - machineclaims/status + verbs: + - get diff --git a/config/rbac/oob_editor_role.yaml b/config/rbac/oob_editor_role.yaml new file mode 100644 index 00000000..41bd4c9b --- /dev/null +++ b/config/rbac/oob_editor_role.yaml @@ -0,0 +1,30 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/name: clusterrole + app.kubernetes.io/instance: oob-editor-role + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: metal + app.kubernetes.io/part-of: metal + app.kubernetes.io/managed-by: kustomize + name: oob-editor-role +rules: +- apiGroups: + - metal.ironcore.dev + resources: + - oobs + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - metal.ironcore.dev + resources: + - oobs/status + verbs: + - get diff --git a/config/rbac/oob_viewer_role.yaml b/config/rbac/oob_viewer_role.yaml new file mode 100644 index 00000000..5eb1202e --- /dev/null +++ b/config/rbac/oob_viewer_role.yaml @@ -0,0 +1,26 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/name: clusterrole + app.kubernetes.io/instance: oob-viewer-role + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: metal + app.kubernetes.io/part-of: metal + app.kubernetes.io/managed-by: kustomize + name: oob-viewer-role +rules: +- apiGroups: + - metal.ironcore.dev + resources: + - oobs + verbs: + - get + - list + - watch +- apiGroups: + - metal.ironcore.dev + resources: + - oobs/status + verbs: + - get diff --git a/config/rbac/oobsecret_editor_role.yaml b/config/rbac/oobsecret_editor_role.yaml new file mode 100644 index 00000000..da684e39 --- /dev/null +++ b/config/rbac/oobsecret_editor_role.yaml @@ -0,0 +1,30 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/name: clusterrole + app.kubernetes.io/instance: oobsecret-editor-role + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: metal + app.kubernetes.io/part-of: metal + app.kubernetes.io/managed-by: kustomize + name: oobsecret-editor-role +rules: +- apiGroups: + - metal.ironcore.dev + resources: + - oobsecrets + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - metal.ironcore.dev + resources: + - oobsecrets/status + verbs: + - get diff --git a/config/rbac/oobsecret_viewer_role.yaml b/config/rbac/oobsecret_viewer_role.yaml new file mode 100644 index 00000000..21bb51d1 --- /dev/null +++ b/config/rbac/oobsecret_viewer_role.yaml @@ -0,0 +1,26 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/name: clusterrole + app.kubernetes.io/instance: oobsecret-viewer-role + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: metal + app.kubernetes.io/part-of: metal + app.kubernetes.io/managed-by: kustomize + name: oobsecret-viewer-role +rules: +- apiGroups: + - metal.ironcore.dev + resources: + - oobsecrets + verbs: + - get + - list + - watch +- apiGroups: + - metal.ironcore.dev + resources: + - oobsecrets/status + verbs: + - get diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index ca669c4b..5b6a495c 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -1,15 +1,110 @@ +--- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: - labels: - app.kubernetes.io/name: clusterrole - app.kubernetes.io/instance: manager-role - app.kubernetes.io/component: rbac - app.kubernetes.io/created-by: metal - app.kubernetes.io/part-of: metal - app.kubernetes.io/managed-by: kustomize name: manager-role rules: -- apiGroups: [""] - resources: ["pods"] - verbs: ["get", "list", "watch"] +- apiGroups: + - metal.ironcore.dev + resources: + - machineclaims + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - metal.ironcore.dev + resources: + - machineclaims/finalizers + verbs: + - update +- apiGroups: + - metal.ironcore.dev + resources: + - machineclaims/status + verbs: + - get + - patch + - update +- apiGroups: + - metal.ironcore.dev + resources: + - machines + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - metal.ironcore.dev + resources: + - machines/finalizers + verbs: + - update +- apiGroups: + - metal.ironcore.dev + resources: + - machines/status + verbs: + - get + - patch + - update +- apiGroups: + - metal.ironcore.dev + resources: + - oobs + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - metal.ironcore.dev + resources: + - oobs/finalizers + verbs: + - update +- apiGroups: + - metal.ironcore.dev + resources: + - oobs/status + verbs: + - get + - patch + - update +- apiGroups: + - metal.ironcore.dev + resources: + - oobsecrets + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - metal.ironcore.dev + resources: + - oobsecrets/finalizers + verbs: + - update +- apiGroups: + - metal.ironcore.dev + resources: + - oobsecrets/status + verbs: + - get + - patch + - update diff --git a/internal/controller/machine_controller.go b/internal/controller/machine_controller.go new file mode 100644 index 00000000..ec8b498d --- /dev/null +++ b/internal/controller/machine_controller.go @@ -0,0 +1,55 @@ +/* +Copyright 2024. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package controller + +import ( + "context" + + "k8s.io/apimachinery/pkg/runtime" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/log" + + metalv1alpha1 "github.com/ironcore-dev/metal/api/v1alpha1" +) + +// MachineReconciler reconciles a Machine object +type MachineReconciler struct { + client.Client + Scheme *runtime.Scheme +} + +//+kubebuilder:rbac:groups=metal.ironcore.dev,resources=machines,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=metal.ironcore.dev,resources=machines/status,verbs=get;update;patch +//+kubebuilder:rbac:groups=metal.ironcore.dev,resources=machines/finalizers,verbs=update + +// Reconcile is part of the main kubernetes reconciliation loop which aims to +// move the current state of the cluster closer to the desired state. +func (r *MachineReconciler) Reconcile(ctx context.Context, _ ctrl.Request) (ctrl.Result, error) { + _ = log.FromContext(ctx) + + // TODO(user): your logic here + + return ctrl.Result{}, nil +} + +// SetupWithManager sets up the controller with the Manager. +func (r *MachineReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&metalv1alpha1.Machine{}). + Complete(r) +} diff --git a/internal/controller/machine_controller_test.go b/internal/controller/machine_controller_test.go new file mode 100644 index 00000000..f20ccbe2 --- /dev/null +++ b/internal/controller/machine_controller_test.go @@ -0,0 +1,79 @@ +/* +Copyright 2024. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package controller + +import ( + "context" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + metalv1alpha1 "github.com/ironcore-dev/metal/api/v1alpha1" +) + +var _ = Describe("Machine Controller", func() { + Context("When reconciling a resource", func() { + const resourceName = "test-resource" + + ctx := context.Background() + + typeNamespacedName := types.NamespacedName{ + Name: resourceName, + Namespace: "default", + } + machine := &metalv1alpha1.Machine{} + + BeforeEach(func() { + By("creating the custom resource for the Kind Machine") + err := k8sClient.Get(ctx, typeNamespacedName, machine) + if err != nil && errors.IsNotFound(err) { + resource := &metalv1alpha1.Machine{ + ObjectMeta: metav1.ObjectMeta{ + Name: resourceName, + Namespace: "default", + }, + } + Expect(k8sClient.Create(ctx, resource)).To(Succeed()) + } + }) + + AfterEach(func() { + resource := &metalv1alpha1.Machine{} + err := k8sClient.Get(ctx, typeNamespacedName, resource) + Expect(err).NotTo(HaveOccurred()) + + By("Cleanup the specific resource instance Machine") + Expect(k8sClient.Delete(ctx, resource)).To(Succeed()) + }) + It("should successfully reconcile the resource", func() { + By("Reconciling the created resource") + controllerReconciler := &MachineReconciler{ + Client: k8sClient, + Scheme: k8sClient.Scheme(), + } + + _, err := controllerReconciler.Reconcile(ctx, reconcile.Request{ + NamespacedName: typeNamespacedName, + }) + Expect(err).NotTo(HaveOccurred()) + }) + }) +}) diff --git a/internal/controller/machineclaim_controller.go b/internal/controller/machineclaim_controller.go new file mode 100644 index 00000000..697ae43e --- /dev/null +++ b/internal/controller/machineclaim_controller.go @@ -0,0 +1,53 @@ +/* +Copyright 2024. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package controller + +import ( + "context" + + "k8s.io/apimachinery/pkg/runtime" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/log" + + metalv1alpha1 "github.com/ironcore-dev/metal/api/v1alpha1" +) + +// MachineClaimReconciler reconciles a MachineClaim object +type MachineClaimReconciler struct { + client.Client + Scheme *runtime.Scheme +} + +//+kubebuilder:rbac:groups=metal.ironcore.dev,resources=machineclaims,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=metal.ironcore.dev,resources=machineclaims/status,verbs=get;update;patch +//+kubebuilder:rbac:groups=metal.ironcore.dev,resources=machineclaims/finalizers,verbs=update + +// Reconcile is part of the main kubernetes reconciliation loop which aims to +// move the current state of the cluster closer to the desired state. +func (r *MachineClaimReconciler) Reconcile(ctx context.Context, _ ctrl.Request) (ctrl.Result, error) { + _ = log.FromContext(ctx) + + return ctrl.Result{}, nil +} + +// SetupWithManager sets up the controller with the Manager. +func (r *MachineClaimReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&metalv1alpha1.MachineClaim{}). + Complete(r) +} diff --git a/internal/controller/machineclaim_controller_test.go b/internal/controller/machineclaim_controller_test.go new file mode 100644 index 00000000..faf143a0 --- /dev/null +++ b/internal/controller/machineclaim_controller_test.go @@ -0,0 +1,79 @@ +/* +Copyright 2024. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package controller + +import ( + "context" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + metalv1alpha1 "github.com/ironcore-dev/metal/api/v1alpha1" +) + +var _ = Describe("MachineClaim Controller", func() { + Context("When reconciling a resource", func() { + const resourceName = "test-resource" + + ctx := context.Background() + + typeNamespacedName := types.NamespacedName{ + Name: resourceName, + Namespace: "default", + } + machineclaim := &metalv1alpha1.MachineClaim{} + + BeforeEach(func() { + By("creating the custom resource for the Kind MachineClaim") + err := k8sClient.Get(ctx, typeNamespacedName, machineclaim) + if err != nil && errors.IsNotFound(err) { + resource := &metalv1alpha1.MachineClaim{ + ObjectMeta: metav1.ObjectMeta{ + Name: resourceName, + Namespace: "default", + }, + } + Expect(k8sClient.Create(ctx, resource)).To(Succeed()) + } + }) + + AfterEach(func() { + resource := &metalv1alpha1.MachineClaim{} + err := k8sClient.Get(ctx, typeNamespacedName, resource) + Expect(err).NotTo(HaveOccurred()) + + By("Cleanup the specific resource instance MachineClaim") + Expect(k8sClient.Delete(ctx, resource)).To(Succeed()) + }) + It("should successfully reconcile the resource", func() { + By("Reconciling the created resource") + controllerReconciler := &MachineClaimReconciler{ + Client: k8sClient, + Scheme: k8sClient.Scheme(), + } + + _, err := controllerReconciler.Reconcile(ctx, reconcile.Request{ + NamespacedName: typeNamespacedName, + }) + Expect(err).NotTo(HaveOccurred()) + }) + }) +}) diff --git a/internal/controller/oob_controller.go b/internal/controller/oob_controller.go new file mode 100644 index 00000000..4feaeee6 --- /dev/null +++ b/internal/controller/oob_controller.go @@ -0,0 +1,53 @@ +/* +Copyright 2024. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package controller + +import ( + "context" + + "k8s.io/apimachinery/pkg/runtime" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/log" + + metalv1alpha1 "github.com/ironcore-dev/metal/api/v1alpha1" +) + +// OOBReconciler reconciles a OOB object +type OOBReconciler struct { + client.Client + Scheme *runtime.Scheme +} + +//+kubebuilder:rbac:groups=metal.ironcore.dev,resources=oobs,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=metal.ironcore.dev,resources=oobs/status,verbs=get;update;patch +//+kubebuilder:rbac:groups=metal.ironcore.dev,resources=oobs/finalizers,verbs=update + +// Reconcile is part of the main kubernetes reconciliation loop which aims to +// move the current state of the cluster closer to the desired state. +func (r *OOBReconciler) Reconcile(ctx context.Context, _ ctrl.Request) (ctrl.Result, error) { + _ = log.FromContext(ctx) + + return ctrl.Result{}, nil +} + +// SetupWithManager sets up the controller with the Manager. +func (r *OOBReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&metalv1alpha1.OOB{}). + Complete(r) +} diff --git a/internal/controller/oob_controller_test.go b/internal/controller/oob_controller_test.go new file mode 100644 index 00000000..11e1eb88 --- /dev/null +++ b/internal/controller/oob_controller_test.go @@ -0,0 +1,82 @@ +/* +Copyright 2024. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package controller + +import ( + "context" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + metalv1alpha1 "github.com/ironcore-dev/metal/api/v1alpha1" +) + +var _ = Describe("OOB Controller", func() { + Context("When reconciling a resource", func() { + const resourceName = "test-resource" + + ctx := context.Background() + + typeNamespacedName := types.NamespacedName{ + Name: resourceName, + Namespace: "default", + } + oob := &metalv1alpha1.OOB{} + + BeforeEach(func() { + By("creating the custom resource for the Kind OOB") + err := k8sClient.Get(ctx, typeNamespacedName, oob) + if err != nil && errors.IsNotFound(err) { + resource := &metalv1alpha1.OOB{ + ObjectMeta: metav1.ObjectMeta{ + Name: resourceName, + Namespace: "default", + }, + Spec: metalv1alpha1.OOBSpec{ + MACAddress: "aabbcc001122", + }, + } + Expect(k8sClient.Create(ctx, resource)).To(Succeed()) + } + }) + + AfterEach(func() { + resource := &metalv1alpha1.OOB{} + err := k8sClient.Get(ctx, typeNamespacedName, resource) + Expect(err).NotTo(HaveOccurred()) + + By("Cleanup the specific resource instance OOB") + Expect(k8sClient.Delete(ctx, resource)).To(Succeed()) + }) + It("should successfully reconcile the resource", func() { + By("Reconciling the created resource") + controllerReconciler := &OOBReconciler{ + Client: k8sClient, + Scheme: k8sClient.Scheme(), + } + + _, err := controllerReconciler.Reconcile(ctx, reconcile.Request{ + NamespacedName: typeNamespacedName, + }) + Expect(err).NotTo(HaveOccurred()) + }) + }) +}) diff --git a/internal/controller/oobsecret_controller.go b/internal/controller/oobsecret_controller.go new file mode 100644 index 00000000..0e84a67f --- /dev/null +++ b/internal/controller/oobsecret_controller.go @@ -0,0 +1,53 @@ +/* +Copyright 2024. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package controller + +import ( + "context" + + "k8s.io/apimachinery/pkg/runtime" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/log" + + metalv1alpha1 "github.com/ironcore-dev/metal/api/v1alpha1" +) + +// OOBSecretReconciler reconciles a OOBSecret object +type OOBSecretReconciler struct { + client.Client + Scheme *runtime.Scheme +} + +//+kubebuilder:rbac:groups=metal.ironcore.dev,resources=oobsecrets,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=metal.ironcore.dev,resources=oobsecrets/status,verbs=get;update;patch +//+kubebuilder:rbac:groups=metal.ironcore.dev,resources=oobsecrets/finalizers,verbs=update + +// Reconcile is part of the main kubernetes reconciliation loop which aims to +// move the current state of the cluster closer to the desired state. +func (r *OOBSecretReconciler) Reconcile(ctx context.Context, _ ctrl.Request) (ctrl.Result, error) { + _ = log.FromContext(ctx) + + return ctrl.Result{}, nil +} + +// SetupWithManager sets up the controller with the Manager. +func (r *OOBSecretReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&metalv1alpha1.OOBSecret{}). + Complete(r) +} diff --git a/internal/controller/oobsecret_controller_test.go b/internal/controller/oobsecret_controller_test.go new file mode 100644 index 00000000..1be2a091 --- /dev/null +++ b/internal/controller/oobsecret_controller_test.go @@ -0,0 +1,82 @@ +/* +Copyright 2024. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package controller + +import ( + "context" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + metalv1alpha1 "github.com/ironcore-dev/metal/api/v1alpha1" +) + +var _ = Describe("OOBSecret Controller", func() { + Context("When reconciling a resource", func() { + const resourceName = "test-resource" + + ctx := context.Background() + + typeNamespacedName := types.NamespacedName{ + Name: resourceName, + Namespace: "default", + } + oobsecret := &metalv1alpha1.OOBSecret{} + + BeforeEach(func() { + By("creating the custom resource for the Kind OOBSecret") + err := k8sClient.Get(ctx, typeNamespacedName, oobsecret) + if err != nil && errors.IsNotFound(err) { + resource := &metalv1alpha1.OOBSecret{ + ObjectMeta: metav1.ObjectMeta{ + Name: resourceName, + Namespace: "default", + }, + Spec: metalv1alpha1.OOBSecretSpec{ + MACAddress: "aabbcc001122", + }, + } + Expect(k8sClient.Create(ctx, resource)).To(Succeed()) + } + }) + + AfterEach(func() { + resource := &metalv1alpha1.OOBSecret{} + err := k8sClient.Get(ctx, typeNamespacedName, resource) + Expect(err).NotTo(HaveOccurred()) + + By("Cleanup the specific resource instance OOBSecret") + Expect(k8sClient.Delete(ctx, resource)).To(Succeed()) + }) + It("should successfully reconcile the resource", func() { + By("Reconciling the created resource") + controllerReconciler := &OOBSecretReconciler{ + Client: k8sClient, + Scheme: k8sClient.Scheme(), + } + + _, err := controllerReconciler.Reconcile(ctx, reconcile.Request{ + NamespacedName: typeNamespacedName, + }) + Expect(err).NotTo(HaveOccurred()) + }) + }) +}) diff --git a/internal/controller/suite_test.go b/internal/controller/suite_test.go new file mode 100644 index 00000000..1f2e3340 --- /dev/null +++ b/internal/controller/suite_test.go @@ -0,0 +1,79 @@ +/* +Copyright 2024. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package controller + +import ( + "fmt" + "path/filepath" + "runtime" + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "k8s.io/client-go/kubernetes/scheme" + "k8s.io/client-go/rest" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/envtest" + logf "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/log/zap" + + metalv1alpha1 "github.com/ironcore-dev/metal/api/v1alpha1" + //+kubebuilder:scaffold:imports +) + +var cfg *rest.Config +var k8sClient client.Client +var testEnv *envtest.Environment + +func TestControllers(t *testing.T) { + RegisterFailHandler(Fail) + + RunSpecs(t, "Controller Suite") +} + +var _ = BeforeSuite(func() { + logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true))) + + By("bootstrapping test environment") + testEnv = &envtest.Environment{ + CRDDirectoryPaths: []string{filepath.Join("..", "..", "config", "crd", "bases")}, + ErrorIfCRDPathMissing: true, + + BinaryAssetsDirectory: filepath.Join("..", "..", "bin", "k8s", + fmt.Sprintf("1.29.0-%s-%s", runtime.GOOS, runtime.GOARCH)), + } + + var err error + cfg, err = testEnv.Start() + Expect(err).NotTo(HaveOccurred()) + Expect(cfg).NotTo(BeNil()) + + err = metalv1alpha1.AddToScheme(scheme.Scheme) + Expect(err).NotTo(HaveOccurred()) + //+kubebuilder:scaffold:scheme + + k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}) + Expect(err).NotTo(HaveOccurred()) + Expect(k8sClient).NotTo(BeNil()) + +}) + +var _ = AfterSuite(func() { + By("tearing down the test environment") + err := testEnv.Stop() + Expect(err).NotTo(HaveOccurred()) +}) diff --git a/test/e2e/e2e_suite_test.go b/test/e2e/e2e_suite_test.go index 5ad1a0a7..9f67c8d5 100644 --- a/test/e2e/e2e_suite_test.go +++ b/test/e2e/e2e_suite_test.go @@ -27,6 +27,6 @@ import ( // Run e2e tests using the Ginkgo runner. func TestE2E(t *testing.T) { RegisterFailHandler(Fail) - fmt.Fprintf(GinkgoWriter, "Starting metal suite\n") + _, _ = fmt.Fprintf(GinkgoWriter, "Starting metal suite\n") RunSpecs(t, "e2e suite") } diff --git a/test/e2e/e2e_test.go b/test/e2e/e2e_test.go index 2cc6929f..1ff1141e 100644 --- a/test/e2e/e2e_test.go +++ b/test/e2e/e2e_test.go @@ -59,7 +59,6 @@ var _ = Describe("controller", Ordered, func() { var controllerPodName string var err error - // projectimage stores the name of the image used in the example var projectimage = "example.com/metal:v0.0.1" By("building the manager(Operator) image") @@ -83,8 +82,6 @@ var _ = Describe("controller", Ordered, func() { By("validating that the controller-manager pod is running as expected") verifyControllerUp := func() error { - // Get pod name - cmd = exec.Command("kubectl", "get", "pods", "-l", "control-plane=controller-manager", "-o", "go-template={{ range .items }}"+ @@ -103,12 +100,12 @@ var _ = Describe("controller", Ordered, func() { controllerPodName = podNames[0] ExpectWithOffset(2, controllerPodName).Should(ContainSubstring("controller-manager")) - // Validate pod status cmd = exec.Command("kubectl", "get", "pods", controllerPodName, "-o", "jsonpath={.status.phase}", "-n", namespace, ) - status, err := utils.Run(cmd) + var status []byte + status, err = utils.Run(cmd) ExpectWithOffset(2, err).NotTo(HaveOccurred()) if string(status) != "Running" { return fmt.Errorf("controller pod in %s status", status) diff --git a/test/utils/utils.go b/test/utils/utils.go index 7363aa56..2463e61f 100644 --- a/test/utils/utils.go +++ b/test/utils/utils.go @@ -22,7 +22,7 @@ import ( "os/exec" "strings" - . "github.com/onsi/ginkgo/v2" //nolint:golint,revive + . "github.com/onsi/ginkgo/v2" ) const ( @@ -35,7 +35,7 @@ const ( ) func warnError(err error) { - fmt.Fprintf(GinkgoWriter, "warning: %v\n", err) + _, _ = fmt.Fprintf(GinkgoWriter, "warning: %v\n", err) } // InstallPrometheusOperator installs the prometheus Operator to be used to export the enabled metrics. @@ -52,12 +52,12 @@ func Run(cmd *exec.Cmd) ([]byte, error) { cmd.Dir = dir if err := os.Chdir(cmd.Dir); err != nil { - fmt.Fprintf(GinkgoWriter, "chdir dir: %s\n", err) + _, _ = fmt.Fprintf(GinkgoWriter, "chdir dir: %s\n", err) } cmd.Env = append(os.Environ(), "GO111MODULE=on") command := strings.Join(cmd.Args, " ") - fmt.Fprintf(GinkgoWriter, "running: %s\n", command) + _, _ = fmt.Fprintf(GinkgoWriter, "running: %s\n", command) output, err := cmd.CombinedOutput() if err != nil { return output, fmt.Errorf("%s failed with error: (%v) %s", command, err, string(output)) @@ -91,8 +91,6 @@ func InstallCertManager() error { if _, err := Run(cmd); err != nil { return err } - // Wait for cert-manager-webhook to be ready, which can take time if cert-manager - // was re-installed after uninstalling on a cluster. cmd = exec.Command("kubectl", "wait", "deployment.apps/cert-manager-webhook", "--for", "condition=Available", "--namespace", "cert-manager", @@ -103,7 +101,7 @@ func InstallCertManager() error { return err } -// LoadImageToKindCluster loads a local docker image to the kind cluster +// LoadImageToKindClusterWithName loads a local docker image to the kind cluster func LoadImageToKindClusterWithName(name string) error { cluster := "kind" if v, ok := os.LookupEnv("KIND_CLUSTER"); ok {