Skip to content

Commit f457d9b

Browse files
committed
fixup! data/cluster-api: add cluster-api components
Signed-off-by: Vince Prignano <[email protected]>
1 parent 715bc11 commit f457d9b

File tree

11 files changed

+303
-13
lines changed

11 files changed

+303
-13
lines changed

cmd/openshift-install/create.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,7 @@ func clusterCreatePostRun(ctx context.Context) (int, error) {
198198
"Warning: this should only be used for debugging purposes, and poses a risk to cluster stability.")
199199
} else {
200200
logrus.Info("Destroying the bootstrap resources...")
201-
err = destroybootstrap.Destroy(command.RootOpts.Dir)
201+
err = destroybootstrap.Destroy(ctx, command.RootOpts.Dir)
202202
if err != nil {
203203
return 0, err
204204
}

cmd/openshift-install/destroy.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package main
22

33
import (
4+
"context"
45
"os"
56
"path/filepath"
67

@@ -125,7 +126,7 @@ func newDestroyBootstrapCmd() *cobra.Command {
125126
defer cleanup()
126127

127128
timer.StartTimer(timer.TotalTimeElapsed)
128-
err := bootstrap.Destroy(command.RootOpts.Dir)
129+
err := bootstrap.Destroy(context.TODO(), command.RootOpts.Dir)
129130
if err != nil {
130131
logrus.Fatal(err)
131132
}

pkg/asset/cluster/cluster.go

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,29 @@ import (
55
"fmt"
66
"path/filepath"
77
"strings"
8+
"time"
89

910
"github.com/pkg/errors"
1011
"github.com/sirupsen/logrus"
12+
"gopkg.in/yaml.v2"
13+
apierrors "k8s.io/apimachinery/pkg/api/errors"
14+
"k8s.io/apimachinery/pkg/util/wait"
15+
clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
16+
utilkubeconfig "sigs.k8s.io/cluster-api/util/kubeconfig"
17+
"sigs.k8s.io/controller-runtime/pkg/client"
1118

1219
"github.com/openshift/installer/pkg/asset"
1320
"github.com/openshift/installer/pkg/asset/cluster/aws"
1421
"github.com/openshift/installer/pkg/asset/cluster/azure"
1522
"github.com/openshift/installer/pkg/asset/cluster/openstack"
1623
"github.com/openshift/installer/pkg/asset/installconfig"
24+
awsconfig "github.com/openshift/installer/pkg/asset/installconfig/aws"
25+
"github.com/openshift/installer/pkg/asset/kubeconfig"
26+
"github.com/openshift/installer/pkg/asset/manifests/capiutils"
27+
capimanifests "github.com/openshift/installer/pkg/asset/manifests/clusterapi"
1728
"github.com/openshift/installer/pkg/asset/password"
1829
"github.com/openshift/installer/pkg/asset/quota"
30+
"github.com/openshift/installer/pkg/clusterapi"
1931
infra "github.com/openshift/installer/pkg/infrastructure/platform"
2032
typesaws "github.com/openshift/installer/pkg/types/aws"
2133
typesazure "github.com/openshift/installer/pkg/types/azure"
@@ -56,6 +68,8 @@ func (c *Cluster) Dependencies() []asset.Asset {
5668
&quota.PlatformQuotaCheck{},
5769
&TerraformVariables{},
5870
&password.KubeadminPassword{},
71+
&capimanifests.Cluster{},
72+
&kubeconfig.AdminClient{},
5973
}
6074
}
6175

@@ -82,6 +96,16 @@ func (c *Cluster) Generate(parents asset.Parents) (err error) {
8296
return errors.New("cluster cannot be created with bootstrapInPlace set")
8397
}
8498

99+
// Check if we're using Cluster API.
100+
if capiutils.IsEnabled(installConfig) {
101+
return c.provisionWithClusterAPI(context.TODO(), parents, installConfig, clusterID)
102+
}
103+
104+
// Otherwise, use the normal path.
105+
return c.provision(installConfig, clusterID, terraformVariables)
106+
}
107+
108+
func (c *Cluster) provision(installConfig *installconfig.InstallConfig, clusterID *installconfig.ClusterID, terraformVariables *TerraformVariables) error {
85109
platform := installConfig.Config.Platform.Name()
86110

87111
if azure := installConfig.Config.Platform.Azure; azure != nil && azure.CloudName == typesazure.StackCloud {
@@ -124,6 +148,137 @@ func (c *Cluster) Generate(parents asset.Parents) (err error) {
124148
return nil
125149
}
126150

151+
func (c *Cluster) provisionWithClusterAPI(ctx context.Context, parents asset.Parents, installConfig *installconfig.InstallConfig, clusterID *installconfig.ClusterID) error {
152+
capiManifests := &capimanifests.Cluster{}
153+
clusterKubeconfigAsset := &kubeconfig.AdminClient{}
154+
parents.Get(
155+
capiManifests,
156+
clusterKubeconfigAsset,
157+
)
158+
159+
// Only need the objects--not the files.
160+
manifests := []client.Object{}
161+
for _, m := range capiManifests.RuntimeFiles() {
162+
manifests = append(manifests, m.Object)
163+
}
164+
165+
// Run the CAPI system.
166+
capiSystem := clusterapi.System()
167+
if err := capiSystem.Run(ctx, installConfig); err != nil {
168+
return fmt.Errorf("failed to run cluster api system: %w", err)
169+
}
170+
171+
// Grab the client.
172+
cl := capiSystem.Client()
173+
174+
// Create all the manifests and store them.
175+
for _, m := range manifests {
176+
m.SetNamespace(capiutils.Namespace)
177+
if err := cl.Create(context.Background(), m); err != nil {
178+
return fmt.Errorf("failed to create manifest: %w", err)
179+
}
180+
logrus.Infof("Created manifest %+T, namespace=%s name=%s", m, m.GetNamespace(), m.GetName())
181+
}
182+
183+
// Pass cluster kubeconfig and store it in; this is usually the role of a bootstrap provider.
184+
{
185+
key := client.ObjectKey{
186+
Name: clusterID.InfraID,
187+
Namespace: capiutils.Namespace,
188+
}
189+
cluster := &clusterv1.Cluster{}
190+
if err := cl.Get(context.Background(), key, cluster); err != nil {
191+
return err
192+
}
193+
// Create the secret.
194+
clusterKubeconfig := clusterKubeconfigAsset.Files()[0].Data
195+
secret := utilkubeconfig.GenerateSecret(cluster, clusterKubeconfig)
196+
if err := cl.Create(context.Background(), secret); err != nil {
197+
return err
198+
}
199+
}
200+
201+
// Wait for the load balancer to be ready by checking the control plane endpoint
202+
// on the cluster object.
203+
var cluster *clusterv1.Cluster
204+
{
205+
if err := wait.ExponentialBackoff(wait.Backoff{
206+
Duration: time.Second * 10,
207+
Factor: float64(1.5),
208+
Steps: 32,
209+
}, func() (bool, error) {
210+
c := &clusterv1.Cluster{}
211+
if err := cl.Get(context.Background(), client.ObjectKey{
212+
Name: clusterID.InfraID,
213+
Namespace: capiutils.Namespace,
214+
}, c); err != nil {
215+
if apierrors.IsNotFound(err) {
216+
return false, nil
217+
}
218+
return false, err
219+
}
220+
cluster = c
221+
return cluster.Spec.ControlPlaneEndpoint.IsValid(), nil
222+
}); err != nil {
223+
return err
224+
}
225+
if cluster == nil {
226+
return errors.New("error occurred during load balancer ready check")
227+
}
228+
if cluster.Spec.ControlPlaneEndpoint.Host == "" {
229+
return errors.New("control plane endpoint is not set")
230+
}
231+
}
232+
233+
// Run the post-provisioning steps for the platform we're on.
234+
// TODO(vincepri): The following should probably be in a separate package with a clear
235+
// interface and multiple hooks at different stages of the cluster lifecycle.
236+
switch installConfig.Config.Platform.Name() {
237+
case typesaws.Name:
238+
ssn, err := installConfig.AWS.Session(context.TODO())
239+
if err != nil {
240+
return fmt.Errorf("failed to create session: %w", err)
241+
}
242+
client := awsconfig.NewClient(ssn)
243+
r53cfg := awsconfig.GetR53ClientCfg(ssn, "")
244+
err = client.CreateOrUpdateRecord(installConfig.Config, cluster.Spec.ControlPlaneEndpoint.Host, r53cfg)
245+
if err != nil {
246+
return fmt.Errorf("failed to create route53 records: %w", err)
247+
}
248+
logrus.Infof("Created Route53 records to control plane load balancer.")
249+
default:
250+
}
251+
252+
// For each manifest we created, retrieve it and store it in the asset.
253+
for _, m := range manifests {
254+
key := client.ObjectKey{
255+
Name: m.GetName(),
256+
Namespace: m.GetNamespace(),
257+
}
258+
if err := cl.Get(context.Background(), key, m); err != nil {
259+
return fmt.Errorf("failed to get manifest: %w", err)
260+
}
261+
262+
gvk, err := cl.GroupVersionKindFor(m)
263+
if err != nil {
264+
return fmt.Errorf("failed to get GVK for manifest: %w", err)
265+
}
266+
fileName := fmt.Sprintf("%s-%s-%s.yaml", gvk.Kind, m.GetNamespace(), m.GetName())
267+
objData, err := yaml.Marshal(m)
268+
if err != nil {
269+
errMsg := fmt.Sprintf("failed to create infrastructure manifest %s from InstallConfig", fileName)
270+
return errors.Wrapf(err, errMsg)
271+
}
272+
c.FileList = append(c.FileList, &asset.File{
273+
Filename: fileName,
274+
Data: objData,
275+
})
276+
}
277+
278+
logrus.Infof("Cluster API resources have been created. Waiting for cluster to become ready...")
279+
return nil
280+
}
281+
127282
// Files returns the FileList generated by the asset.
128283
func (c *Cluster) Files() []*asset.File {
129284
return c.FileList

pkg/asset/installconfig/aws/route53.go

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66

77
"github.com/aws/aws-sdk-go/aws"
88
"github.com/aws/aws-sdk-go/aws/credentials/stscreds"
9+
"github.com/aws/aws-sdk-go/aws/endpoints"
910
awss "github.com/aws/aws-sdk-go/aws/session"
1011
"github.com/aws/aws-sdk-go/service/route53"
1112
"github.com/pkg/errors"
@@ -146,3 +147,73 @@ func GetR53ClientCfg(sess *awss.Session, roleARN string) *aws.Config {
146147
creds := stscreds.NewCredentials(sess, roleARN)
147148
return &aws.Config{Credentials: creds}
148149
}
150+
151+
// CreateOrUpdateRecord Creates or Updates the Route53 Record for the cluster endpoint.
152+
func (c *Client) CreateOrUpdateRecord(ic *types.InstallConfig, target string, cfg *aws.Config) error {
153+
zone, err := c.GetBaseDomain(ic.BaseDomain)
154+
if err != nil {
155+
return err
156+
}
157+
158+
params := &route53.ChangeResourceRecordSetsInput{
159+
ChangeBatch: &route53.ChangeBatch{
160+
Comment: aws.String(fmt.Sprintf("Creating record for api and api-int in domain %s", ic.ClusterDomain())),
161+
},
162+
HostedZoneId: zone.Id,
163+
}
164+
for _, prefix := range []string{"api", "api-int"} {
165+
params.ChangeBatch.Changes = append(params.ChangeBatch.Changes, &route53.Change{
166+
Action: aws.String("UPSERT"),
167+
ResourceRecordSet: &route53.ResourceRecordSet{
168+
Name: aws.String(fmt.Sprintf("%s.%s.", prefix, ic.ClusterDomain())),
169+
Type: aws.String("A"),
170+
AliasTarget: &route53.AliasTarget{
171+
DNSName: aws.String(target),
172+
HostedZoneId: aws.String(hostedZoneIDPerRegionNLBMap[ic.AWS.Region]),
173+
EvaluateTargetHealth: aws.Bool(true),
174+
},
175+
},
176+
})
177+
}
178+
svc := route53.New(c.ssn, cfg)
179+
if _, err := svc.ChangeResourceRecordSets(params); err != nil {
180+
return fmt.Errorf("failed to create records for api/api-int: %w", err)
181+
}
182+
return nil
183+
}
184+
185+
// See https://docs.aws.amazon.com/general/latest/gr/elb.html#elb_region
186+
187+
var hostedZoneIDPerRegionNLBMap = map[string]string{
188+
endpoints.AfSouth1RegionID: "Z203XCE67M25HM",
189+
endpoints.ApEast1RegionID: "Z12Y7K3UBGUAD1",
190+
endpoints.ApNortheast1RegionID: "Z31USIVHYNEOWT",
191+
endpoints.ApNortheast2RegionID: "ZIBE1TIR4HY56",
192+
endpoints.ApNortheast3RegionID: "Z1GWIQ4HH19I5X",
193+
endpoints.ApSouth1RegionID: "ZVDDRBQ08TROA",
194+
endpoints.ApSouth2RegionID: "Z0711778386UTO08407HT",
195+
endpoints.ApSoutheast1RegionID: "ZKVM4W9LS7TM",
196+
endpoints.ApSoutheast2RegionID: "ZCT6FZBF4DROD",
197+
endpoints.ApSoutheast3RegionID: "Z01971771FYVNCOVWJU1G",
198+
endpoints.ApSoutheast4RegionID: "Z01156963G8MIIL7X90IV",
199+
endpoints.CaCentral1RegionID: "Z2EPGBW3API2WT",
200+
endpoints.CnNorth1RegionID: "Z3QFB96KMJ7ED6",
201+
endpoints.CnNorthwest1RegionID: "ZQEIKTCZ8352D",
202+
endpoints.EuCentral1RegionID: "Z3F0SRJ5LGBH90",
203+
endpoints.EuCentral2RegionID: "Z02239872DOALSIDCX66S",
204+
endpoints.EuNorth1RegionID: "Z1UDT6IFJ4EJM",
205+
endpoints.EuSouth1RegionID: "Z23146JA1KNAFP",
206+
endpoints.EuSouth2RegionID: "Z1011216NVTVYADP1SSV",
207+
endpoints.EuWest1RegionID: "Z2IFOLAFXWLO4F",
208+
endpoints.EuWest2RegionID: "ZD4D7Y8KGAS4G",
209+
endpoints.EuWest3RegionID: "Z1CMS0P5QUZ6D5",
210+
endpoints.MeCentral1RegionID: "Z00282643NTTLPANJJG2P",
211+
endpoints.MeSouth1RegionID: "Z3QSRYVP46NYYV",
212+
endpoints.SaEast1RegionID: "ZTK26PT1VY4CU",
213+
endpoints.UsEast1RegionID: "Z26RNL4JYFTOTI",
214+
endpoints.UsEast2RegionID: "ZLMOA37VPKANP",
215+
endpoints.UsGovEast1RegionID: "Z1ZSMQQ6Q24QQ8",
216+
endpoints.UsGovWest1RegionID: "ZMG1MZ2THAWF1",
217+
endpoints.UsWest1RegionID: "Z24FKFUX50B4VW",
218+
endpoints.UsWest2RegionID: "Z18D5FSROUN65G",
219+
}

pkg/asset/machines/clusterapi.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ func GenerateClusterAPI(ctx context.Context, installConfig *installconfig.Instal
108108
// delete the machine when the stage is complete.
109109
bootstrapAWSMachine := &capa.AWSMachine{
110110
ObjectMeta: metav1.ObjectMeta{
111-
Name: fmt.Sprintf("%s-%s-bootstrap", clusterID.InfraID, pool.Name),
111+
Name: capiutils.GenerateBoostrapMachineName(clusterID.InfraID),
112112
Labels: map[string]string{
113113
"cluster.x-k8s.io/control-plane": "",
114114
"install.openshift.io/bootstrap": "",

pkg/asset/manifests/capiutils/helpers.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,9 @@ func CIDRFromInstallConfig(installConfig *installconfig.InstallConfig) *ipnet.IP
2323
func IsEnabled(installConfig *installconfig.InstallConfig) bool {
2424
return installConfig.Config.EnabledFeatureGates().Enabled(v1.FeatureGateClusterAPIInstall)
2525
}
26+
27+
// GenerateBoostrapMachineName generates the Cluster API Machine used for bootstrapping
28+
// from the cluster ID and machine type.
29+
func GenerateBoostrapMachineName(infraID string) string {
30+
return infraID + "-bootstrap"
31+
}

pkg/asset/manifests/clusterapi/cluster.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ func (c *Cluster) Generate(dependencies asset.Parents) error {
8686
Name: capiutils.Namespace,
8787
},
8888
}
89-
c.FileList = append(c.FileList, &asset.RuntimeFile{Object: namespace, File: asset.File{Filename: "00_capi-namespace.yaml"}})
89+
c.FileList = append(c.FileList, &asset.RuntimeFile{Object: namespace, File: asset.File{Filename: "000_capi-namespace.yaml"}})
9090

9191
cluster := &clusterv1.Cluster{
9292
ObjectMeta: metav1.ObjectMeta{

pkg/clusterapi/localcontrolplane.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,10 +56,10 @@ func (c *localControlPlane) Run(ctx context.Context) error {
5656
// Create a temporary directory to unpack the cluster-api binaries.
5757
c.BinDir = filepath.Join(command.RootOpts.Dir, "bin", "cluster-api")
5858
if err := UnpackClusterAPIBinary(c.BinDir); err != nil {
59-
return err
59+
return fmt.Errorf("failed to unpack cluster-api binary: %w", err)
6060
}
6161
if err := UnpackEnvtestBinaries(c.BinDir); err != nil {
62-
return err
62+
return fmt.Errorf("failed to unpack envtest binaries: %w", err)
6363
}
6464

6565
log.SetLogger(klog.NewKlogr())

pkg/clusterapi/providers.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ func (p Provider) Extract(dir string) error {
100100
}
101101

102102
// Ensure the directory exists.
103-
logrus.Debugf("creating %s directory", dir)
103+
logrus.Debugf("Creating %s directory", dir)
104104
if err := os.MkdirAll(dir, 0o777); err != nil {
105105
return errors.Wrapf(err, "could not make directory for the %s provider", p.Name)
106106
}
@@ -115,7 +115,7 @@ func (p Provider) Extract(dir string) error {
115115
if err != nil {
116116
return errors.Wrapf(err, "failed to sanitize archive file %q", name)
117117
}
118-
logrus.Debugf("extracting %s file", path)
118+
logrus.Debugf("Extracting %s file", path)
119119
if err := unpackFile(f, path); err != nil {
120120
return errors.Wrapf(err, "failed to extract %q", path)
121121
}

0 commit comments

Comments
 (0)