Skip to content

Commit ef6012c

Browse files
alegacyclaude
andcommitted
Add BareMetalSwitch CRD and controller for switch config generation
Introduces the BareMetalSwitch (BMS) CRD for defining Top-of-Rack switches managed by the Ironic Networking Service. The controller watches BMS resources and credential secrets, then generates two output secrets: 1. Switch configs secret (IRONIC_SWITCH_CONFIGS_SECRET): a single INI-format file with per-switch sections including address, credentials, driver/device type, and optional fields. 2. Switch credentials secret (IRONIC_SWITCH_CREDENTIALS_SECRET): SSH private key files (keyed by <mac-address>.key) for switches using publickey authentication. Password-authenticated switches store credentials inline in the INI config instead. The controller is only registered when IRONIC_NETWORKING_ENABLED=true and requires IRONIC_SWITCH_CONFIGS_SECRET, IRONIC_SWITCH_CREDENTIALS_SECRET, and IRONIC_SWITCH_CREDENTIALS_PATH to be set at startup. Credential secret changes (e.g. password rotation, key replacement) trigger config regeneration via a secret watch with a mapper that filters to only secrets referenced by BMS resources. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Signed-off-by: Allain Legacy <alegacy@redhat.com>
1 parent 3e370ce commit ef6012c

22 files changed

Lines changed: 2833 additions & 1 deletion
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
/*
2+
Copyright 2025 The Metal3 Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package v1alpha1
18+
19+
import (
20+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
21+
)
22+
23+
// SwitchCredentialType defines the type of credential used for switch authentication.
24+
type SwitchCredentialType string
25+
26+
const (
27+
// SwitchCredentialTypePassword indicates password-based authentication.
28+
SwitchCredentialTypePassword SwitchCredentialType = "password"
29+
// SwitchCredentialTypePublicKey indicates SSH public key-based authentication.
30+
SwitchCredentialTypePublicKey SwitchCredentialType = "publickey"
31+
)
32+
33+
// SwitchCredentials defines the credentials used to access the switch.
34+
type SwitchCredentials struct {
35+
// Type is the type of switch credentials.
36+
// This is currently limited to "password", but will be expanded to others.
37+
// +kubebuilder:validation:Enum=password;publickey
38+
// +kubebuilder:default=password
39+
Type SwitchCredentialType `json:"type"`
40+
41+
// The name of the secret containing the switch credentials.
42+
// For password authentication, requires keys "username" and "password"
43+
// For SSH key authentication, requires keys "username" and "ssh-privatekey".
44+
// In both cases, an optional "enable-secret" key can be provided if needed
45+
// to enable privileged mode.
46+
// +kubebuilder:validation:MinLength=1
47+
SecretName string `json:"secretName"`
48+
}
49+
50+
// BareMetalSwitchSpec defines the desired state of BareMetalSwitch.
51+
type BareMetalSwitchSpec struct {
52+
// Address is the network address of the switch (IP address or hostname).
53+
// +kubebuilder:validation:Required
54+
Address string `json:"address"`
55+
56+
// MACAddress is the MAC address of the switch management interface.
57+
// Used to correlate LLDP information from nodes to identify which switch they're connected to.
58+
// +kubebuilder:validation:Pattern=`^[0-9a-fA-F]{2}(:[0-9a-fA-F]{2}){5}$`
59+
// +kubebuilder:validation:Required
60+
MACAddress string `json:"macAddress"`
61+
62+
// Driver specifies the driver to use. Currently only "generic-switch" is supported.
63+
// +kubebuilder:validation:Enum=generic-switch
64+
// +kubebuilder:default=generic-switch
65+
// +optional
66+
Driver string `json:"driver,omitempty"`
67+
68+
// DeviceType specifies the device type for the generic-switch driver.
69+
// Must be one of the device types supported by networking-generic-switch.
70+
// Examples: netmiko_cisco_ios, netmiko_dell_force10, netmiko_dell_os10,
71+
// netmiko_juniper_junos, netmiko_arista_eos
72+
// See https://github.com/openstack/networking-generic-switch/blob/master/setup.cfg
73+
// +kubebuilder:validation:Required
74+
DeviceType string `json:"deviceType"`
75+
76+
// Credentials references the secret containing switch authentication credentials.
77+
// +kubebuilder:validation:Required
78+
Credentials SwitchCredentials `json:"credentials"`
79+
80+
// Port specifies the management port to connect to (e.g., SSH port 22, HTTPS port 443).
81+
// If not specified, the driver will use its default port based on the device type.
82+
// +kubebuilder:validation:Minimum=1
83+
// +kubebuilder:validation:Maximum=65535
84+
// +optional
85+
Port *int32 `json:"port,omitempty"`
86+
87+
// DisableCertificateVerification disables TLS certificate verification when using
88+
// HTTPS to connect to the switch. This is required for self-signed certificates,
89+
// but is insecure as it allows man-in-the-middle attacks.
90+
// +optional
91+
DisableCertificateVerification *bool `json:"disableCertificateVerification,omitempty"`
92+
}
93+
94+
// SwitchConditionType defines the condition types for BareMetalSwitch.
95+
type SwitchConditionType string
96+
97+
const (
98+
// SwitchConditionReconciled indicates whether the switch has been
99+
// successfully reconciled into the switch config secret.
100+
SwitchConditionReconciled SwitchConditionType = "Reconciled"
101+
)
102+
103+
// BareMetalSwitchStatus defines the observed state of BareMetalSwitch.
104+
type BareMetalSwitchStatus struct {
105+
// Conditions describes the state of the BareMetalSwitch resource.
106+
// +patchMergeKey=type
107+
// +patchStrategy=merge
108+
// +listType=map
109+
// +listMapKey=type
110+
// +optional
111+
Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type" protobuf:"bytes,1,rep,name=conditions"`
112+
}
113+
114+
// +kubebuilder:object:root=true
115+
// +kubebuilder:resource:scope=Namespaced,shortName=bms
116+
// +kubebuilder:subresource:status
117+
// +kubebuilder:printcolumn:name="Driver",type="string",JSONPath=".spec.driver",description="Switch driver type"
118+
// +kubebuilder:printcolumn:name="Device Type",type="string",JSONPath=".spec.deviceType",description="Switch device type"
119+
// +kubebuilder:printcolumn:name="Address",type="string",JSONPath=".spec.address",description="Switch address"
120+
// +kubebuilder:printcolumn:name="Reconciled",type="string",JSONPath=".status.conditions[?(@.type==\"Reconciled\")].status",description="Reconciled status"
121+
122+
// BareMetalSwitch represents a Top-of-Rack switch managed by Ironic Networking.
123+
type BareMetalSwitch struct {
124+
metav1.TypeMeta `json:",inline"`
125+
metav1.ObjectMeta `json:"metadata,omitempty"`
126+
127+
Spec BareMetalSwitchSpec `json:"spec,omitempty"`
128+
Status BareMetalSwitchStatus `json:"status,omitempty"`
129+
}
130+
131+
// GetConditions returns the set of conditions for this object.
132+
func (s *BareMetalSwitch) GetConditions() []metav1.Condition {
133+
return s.Status.Conditions
134+
}
135+
136+
// SetConditions sets conditions for this object.
137+
func (s *BareMetalSwitch) SetConditions(conditions []metav1.Condition) {
138+
s.Status.Conditions = conditions
139+
}
140+
141+
// +kubebuilder:object:root=true
142+
143+
// BareMetalSwitchList contains a list of BareMetalSwitch.
144+
type BareMetalSwitchList struct {
145+
metav1.TypeMeta `json:",inline"`
146+
metav1.ListMeta `json:"metadata,omitempty"`
147+
Items []BareMetalSwitch `json:"items"`
148+
}
149+
150+
func init() {
151+
SchemeBuilder.Register(&BareMetalSwitch{}, &BareMetalSwitchList{})
152+
}

apis/metal3.io/v1alpha1/zz_generated.deepcopy.go

Lines changed: 122 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)