Skip to content

Commit 1dd313c

Browse files
committed
ignition: add vpc IPv6 CIDR to the cluster-config-v1 ConfigMap
The etcd operator requires IPv6 machine networks in dualstack networking. Since we may not know the VPC IPV6 at install time, we need to add it in later on once the infrastructure is ready.
1 parent 1802f78 commit 1dd313c

6 files changed

Lines changed: 171 additions & 7 deletions

File tree

pkg/infrastructure/aws/clusterapi/aws.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,10 @@ func (*Provider) PreProvision(ctx context.Context, in clusterapi.PreProvisionInp
9292
// infrastructure CR. The infrastructure CR is updated and added to the ignition files. CAPA creates a
9393
// bucket for ignition, and this ignition data will be placed in the bucket.
9494
func (p Provider) Ignition(ctx context.Context, in clusterapi.IgnitionInput) ([]*corev1.Secret, error) {
95-
ignOutput, err := editIgnition(ctx, in)
95+
ignOutput, err := clusterapi.ApplyIgnitionEdits(ctx, in,
96+
editIgnitionForCustomDNS,
97+
editIgnitionForDualStack,
98+
)
9699
if err != nil {
97100
return nil, fmt.Errorf("failed to edit bootstrap master or worker ignition: %w", err)
98101
}

pkg/infrastructure/aws/clusterapi/ignition.go

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,14 @@ import (
1414
awsconfig "github.com/openshift/installer/pkg/asset/installconfig/aws"
1515
"github.com/openshift/installer/pkg/asset/manifests/capiutils"
1616
"github.com/openshift/installer/pkg/infrastructure/clusterapi"
17+
"github.com/openshift/installer/pkg/ipnet"
18+
"github.com/openshift/installer/pkg/types"
1719
awstypes "github.com/openshift/installer/pkg/types/aws"
1820
"github.com/openshift/installer/pkg/types/dns"
21+
"github.com/openshift/installer/pkg/types/network"
1922
)
2023

21-
func editIgnition(ctx context.Context, in clusterapi.IgnitionInput) (*clusterapi.IgnitionOutput, error) {
24+
func editIgnitionForCustomDNS(ctx context.Context, in clusterapi.IgnitionInput) (*clusterapi.IgnitionOutput, error) {
2225
if in.InstallConfig.Config.AWS.UserProvisionedDNS != dns.UserProvisionedDNSEnabled {
2326
return &clusterapi.IgnitionOutput{
2427
UpdatedBootstrapIgn: in.BootstrapIgnData,
@@ -83,5 +86,48 @@ func editIgnition(ctx context.Context, in clusterapi.IgnitionInput) (*clusterapi
8386
publicIPAddresses = privateIPAddresses
8487
}
8588
logrus.Debugf("AWS: Editing Ignition files to start in-cluster DNS when UserProvisionedDNS is enabled")
86-
return clusterapi.EditIgnition(in, awstypes.Name, publicIPAddresses, privateIPAddresses)
89+
return clusterapi.EditIgnitionForCustomDNS(in, awstypes.Name, publicIPAddresses, privateIPAddresses)
90+
}
91+
92+
func editIgnitionForDualStack(ctx context.Context, in clusterapi.IgnitionInput) (*clusterapi.IgnitionOutput, error) {
93+
ic := in.InstallConfig.Config
94+
machineCIDRs := capiutils.MachineCIDRsFromInstallConfig(in.InstallConfig)
95+
96+
// If the machine network entries contain IPv6 CIDRs, the users must have added in manually for BYO subnets.
97+
// In this case, those CIDRs are already passed to the AWSCluster node port ingress rule spec
98+
if !ic.AWS.IPFamily.DualStackEnabled() || len(capiutils.GetIPv6CIDRs(machineCIDRs)) > 0 {
99+
return &clusterapi.IgnitionOutput{
100+
UpdatedBootstrapIgn: in.BootstrapIgnData,
101+
UpdatedMasterIgn: in.MasterIgnData,
102+
UpdatedWorkerIgn: in.WorkerIgnData}, nil
103+
}
104+
105+
awsCluster := &capa.AWSCluster{}
106+
key := k8sClient.ObjectKey{
107+
Name: in.InfraID,
108+
Namespace: capiutils.Namespace,
109+
}
110+
if err := in.Client.Get(ctx, key, awsCluster); err != nil {
111+
return nil, fmt.Errorf("failed to get AWSCluster: %w", err)
112+
}
113+
114+
vpcSpec := awsCluster.Spec.NetworkSpec.VPC
115+
if vpcSpec.IPv6 == nil || vpcSpec.IPv6.CidrBlock == "" {
116+
return nil, fmt.Errorf("dualstack networking is enabled, but VPC does not have IPV6 CIDR")
117+
}
118+
119+
machineNetworks := ic.MachineNetwork
120+
ipv6Entry := []types.MachineNetworkEntry{
121+
{
122+
CIDR: *ipnet.MustParseCIDR(vpcSpec.IPv6.CidrBlock),
123+
},
124+
}
125+
126+
if ic.AWS.IPFamily == network.DualStackIPv6Primary {
127+
machineNetworks = append(ipv6Entry, machineNetworks...)
128+
} else {
129+
machineNetworks = append(machineNetworks, ipv6Entry...)
130+
}
131+
132+
return clusterapi.EditIgnitionForDualStack(in, awstypes.Name, machineNetworks)
87133
}

pkg/infrastructure/azure/ignition.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,5 +50,5 @@ func editIgnition(ctx context.Context, in clusterapi.IgnitionInput, publicIP str
5050
apiLBIP = publicIP
5151
}
5252
logrus.Debugf("Azure: Editing Ignition files with API LB IP: %s and API Int LB IP: %s", apiLBIP, apiIntLBIP)
53-
return clusterapi.EditIgnition(in, azure.Name, []string{apiLBIP}, []string{apiIntLBIP})
53+
return clusterapi.EditIgnitionForCustomDNS(in, azure.Name, []string{apiLBIP}, []string{apiIntLBIP})
5454
}

pkg/infrastructure/clusterapi/ignition.go

Lines changed: 81 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"github.com/openshift/installer/pkg/asset/lbconfig"
2121
"github.com/openshift/installer/pkg/asset/machines"
2222
"github.com/openshift/installer/pkg/asset/tls"
23+
"github.com/openshift/installer/pkg/types"
2324
awstypes "github.com/openshift/installer/pkg/types/aws"
2425
azuretypes "github.com/openshift/installer/pkg/types/azure"
2526
gcptypes "github.com/openshift/installer/pkg/types/gcp"
@@ -32,20 +33,24 @@ const (
3233
mcsCertFile = "/opt/openshift/tls/machine-config-server.crt"
3334
masterUserDataFile = "/opt/openshift/openshift/99_openshift-cluster-api_master-user-data-secret.yaml"
3435
workerUserDataFile = "/opt/openshift/openshift/99_openshift-cluster-api_worker-user-data-secret.yaml"
36+
clusterConfigDataFile = "/opt/openshift/manifests/cluster-config.yaml"
3537

3638
// header is the string that precedes the encoded data in the ignition data.
3739
// The data must be replaced before decoding the string, and the string must be
3840
// prepended to the encoded data.
3941
header = "data:text/plain;charset=utf-8;base64,"
4042

43+
// The key in the cluster-config-v1 ConfigMap to extract the install-config.
44+
clusterConfigCMKey = "install-config"
45+
4146
masterRole = "master"
4247
workerRole = "worker"
4348
)
4449

45-
// EditIgnition attempts to edit the contents of the bootstrap ignition when the user has selected
50+
// EditIgnitionForCustomDNS attempts to edit the contents of the bootstrap ignition when the user has selected
4651
// a custom DNS configuration. Find the public and private load balancer addresses and fill in the
4752
// infrastructure file within the ignition struct.
48-
func EditIgnition(in IgnitionInput, platform string, publicIPAddresses, privateIPAddresses []string) (*IgnitionOutput, error) {
53+
func EditIgnitionForCustomDNS(in IgnitionInput, platform string, publicIPAddresses, privateIPAddresses []string) (*IgnitionOutput, error) {
4954
ignData := &igntypes.Config{}
5055
err := json.Unmarshal(in.BootstrapIgnData, ignData)
5156
if err != nil {
@@ -292,3 +297,77 @@ func updateUserDataSecret(in IgnitionInput, role string, config *igntypes.Config
292297
}
293298
return nil
294299
}
300+
301+
// EditIgnitionForDualStack attempts to edit the contents of the bootstrap ignition when the cluster is in dualstack.
302+
func EditIgnitionForDualStack(in IgnitionInput, platform string, machineNetworks []types.MachineNetworkEntry) (*IgnitionOutput, error) {
303+
ignData := &igntypes.Config{}
304+
err := json.Unmarshal(in.BootstrapIgnData, ignData)
305+
if err != nil {
306+
return nil, fmt.Errorf("failed to unmarshal bootstrap ignition: %w", err)
307+
}
308+
309+
err = updateMachineNetworks(in, ignData, machineNetworks)
310+
if err != nil {
311+
return nil, fmt.Errorf("failed to update machine networks in ignition config: %w", err)
312+
}
313+
logrus.Debugf("Successfully updated the install-config machine networks")
314+
315+
editedIgnBytes, err := json.Marshal(ignData)
316+
if err != nil {
317+
return nil, fmt.Errorf("failed to convert ignition data to json: %w", err)
318+
}
319+
logrus.Debugf("Successfully updated bootstrap ignition with updated manifests for dualstack networking")
320+
321+
return &IgnitionOutput{
322+
UpdatedBootstrapIgn: editedIgnBytes,
323+
UpdatedMasterIgn: in.MasterIgnData,
324+
UpdatedWorkerIgn: in.WorkerIgnData,
325+
}, nil
326+
}
327+
328+
func updateMachineNetworks(in IgnitionInput, config *igntypes.Config, machineNetworks []types.MachineNetworkEntry) error {
329+
for i, fileData := range config.Storage.Files {
330+
if fileData.Path != clusterConfigDataFile {
331+
continue
332+
}
333+
334+
contents := strings.Split(*config.Storage.Files[i].Contents.Source, ",")
335+
rawDecodedText, err := base64.StdEncoding.DecodeString(contents[1])
336+
if err != nil {
337+
return fmt.Errorf("failed to decode contents of ignition file: %w", err)
338+
}
339+
340+
configCM := &corev1.ConfigMap{}
341+
if err := yaml.Unmarshal(rawDecodedText, configCM); err != nil {
342+
return fmt.Errorf("failed to unmarshal cluster-config ConfigMap: %w", err)
343+
}
344+
345+
installConfig := &types.InstallConfig{}
346+
if err := yaml.Unmarshal([]byte(configCM.Data[clusterConfigCMKey]), installConfig); err != nil {
347+
return fmt.Errorf("failed to unmarshal install-config content: %w", err)
348+
}
349+
350+
// Update the machine network field
351+
installConfig.MachineNetwork = machineNetworks
352+
353+
// Convert the installconfig back to string and save it to the configmap
354+
icContents, err := yaml.Marshal(installConfig)
355+
if err != nil {
356+
return fmt.Errorf("failed to marshal install-config: %w", err)
357+
}
358+
configCM.Data[clusterConfigCMKey] = string(icContents)
359+
360+
// convert the infrastructure back to an encoded string
361+
configCMContents, err := yaml.Marshal(configCM)
362+
if err != nil {
363+
return fmt.Errorf("failed to marshal cluster-config ConfigMap: %w", err)
364+
}
365+
366+
encoded := fmt.Sprintf("%s%s", header, base64.StdEncoding.EncodeToString(configCMContents))
367+
// replace the contents with the edited information
368+
config.Storage.Files[i].Contents.Source = &encoded
369+
370+
break
371+
}
372+
return nil
373+
}

pkg/infrastructure/clusterapi/types.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,42 @@ type IgnitionInput struct {
7272
RootCA *tls.RootCA
7373
}
7474

75+
// WithOutput returns a new IgnitionInput with ignition data from the output.
76+
// This allows chaining multiple ignition edits.
77+
func (in IgnitionInput) WithOutput(output *IgnitionOutput) IgnitionInput {
78+
if output == nil {
79+
return in
80+
}
81+
in.BootstrapIgnData = output.UpdatedBootstrapIgn
82+
in.MasterIgnData = output.UpdatedMasterIgn
83+
in.WorkerIgnData = output.UpdatedWorkerIgn
84+
return in
85+
}
86+
87+
// IgnitionEditFunc is a function that edits ignition data.
88+
type IgnitionEditFunc func(context.Context, IgnitionInput) (*IgnitionOutput, error)
89+
90+
// ApplyIgnitionEdits applies multiple ignition edit functions in sequence, passing the ignition output
91+
// of each as input to the next. Returns the final output or the first error encountered.
92+
func ApplyIgnitionEdits(ctx context.Context, in IgnitionInput, edits ...IgnitionEditFunc) (*IgnitionOutput, error) {
93+
output := &IgnitionOutput{
94+
UpdatedBootstrapIgn: in.BootstrapIgnData,
95+
UpdatedMasterIgn: in.MasterIgnData,
96+
UpdatedWorkerIgn: in.WorkerIgnData,
97+
}
98+
99+
for _, edit := range edits {
100+
result, err := edit(ctx, in)
101+
if err != nil {
102+
return nil, err
103+
}
104+
output = result
105+
in = in.WithOutput(result)
106+
}
107+
108+
return output, nil
109+
}
110+
75111
// IgnitionOutput collects updated Ignition Data for Bootstrap, Master and Worker nodes.
76112
type IgnitionOutput struct {
77113
UpdatedBootstrapIgn []byte

pkg/infrastructure/gcp/clusterapi/ignition.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,5 +76,5 @@ func editIgnition(ctx context.Context, in clusterapi.IgnitionInput) (*clusterapi
7676
}
7777

7878
logrus.Debugf("GCP: Editing Ignition files to start in-cluster DNS when UserProvisionedDNS is enabled")
79-
return clusterapi.EditIgnition(in, gcp.Name, []string{computeAddress}, []string{computeIntAddress})
79+
return clusterapi.EditIgnitionForCustomDNS(in, gcp.Name, []string{computeAddress}, []string{computeIntAddress})
8080
}

0 commit comments

Comments
 (0)