From 371098411039797e9b931aa7c70bcb39b9225273 Mon Sep 17 00:00:00 2001 From: Naadir Jeewa Date: Tue, 9 Oct 2018 22:35:37 +0100 Subject: [PATCH] clusterctl: Get API Server endpoint from AWS (#207) * clusterctl: Get API Server endpoint from AWS * docs: Reminder about the environment --- cloud/aws/actuators/cluster/actuator.go | 86 ++++++++++++++++++-- cloud/aws/actuators/cluster/actuator_test.go | 60 ++++++++++++++ cloud/aws/deployer/deployer.go | 67 +++------------ cloud/aws/deployer/deployer_test.go | 61 -------------- cloud/aws/services/elb/loadbalancer.go | 16 +++- cloud/aws/services/interfaces.go | 1 + cloud/aws/services/mocks/elb.go | 13 +++ docs/getting-started.md | 3 + 8 files changed, 182 insertions(+), 125 deletions(-) delete mode 100644 cloud/aws/deployer/deployer_test.go diff --git a/cloud/aws/actuators/cluster/actuator.go b/cloud/aws/actuators/cluster/actuator.go index e083c36a9f..043722b343 100644 --- a/cloud/aws/actuators/cluster/actuator.go +++ b/cloud/aws/actuators/cluster/actuator.go @@ -14,11 +14,7 @@ package cluster import ( - providerconfigv1 "sigs.k8s.io/cluster-api-provider-aws/cloud/aws/providerconfig/v1alpha1" - service "sigs.k8s.io/cluster-api-provider-aws/cloud/aws/services" - "sigs.k8s.io/cluster-api-provider-aws/cloud/aws/services/certificates" - ec2svc "sigs.k8s.io/cluster-api-provider-aws/cloud/aws/services/ec2" - elbsvc "sigs.k8s.io/cluster-api-provider-aws/cloud/aws/services/elb" + "fmt" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/session" @@ -26,6 +22,12 @@ import ( "github.com/aws/aws-sdk-go/service/elb" "github.com/golang/glog" "github.com/pkg/errors" + "k8s.io/client-go/tools/clientcmd" + providerconfigv1 "sigs.k8s.io/cluster-api-provider-aws/cloud/aws/providerconfig/v1alpha1" + service "sigs.k8s.io/cluster-api-provider-aws/cloud/aws/services" + "sigs.k8s.io/cluster-api-provider-aws/cloud/aws/services/certificates" + ec2svc "sigs.k8s.io/cluster-api-provider-aws/cloud/aws/services/ec2" + elbsvc "sigs.k8s.io/cluster-api-provider-aws/cloud/aws/services/elb" clusterv1 "sigs.k8s.io/cluster-api/pkg/apis/cluster/v1alpha1" client "sigs.k8s.io/cluster-api/pkg/client/clientset_generated/clientset/typed/cluster/v1alpha1" controllerError "sigs.k8s.io/cluster-api/pkg/controller/error" @@ -182,6 +184,80 @@ func (a *Actuator) Delete(cluster *clusterv1.Cluster) error { return nil } +// GetIP returns the IP of a machine, but this is going away. +func (a *Actuator) GetIP(cluster *clusterv1.Cluster, machine *clusterv1.Machine) (string, error) { + + // Load provider status. + status, err := a.loadProviderStatus(cluster) + if err != nil { + return "", errors.Errorf("failed to load cluster provider status: %v", err) + } + + if status.Network.APIServerELB.DNSName != "" { + return status.Network.APIServerELB.DNSName, nil + } + + // Load provider config. + config, err := a.loadProviderConfig(cluster) + if err != nil { + return "", errors.Errorf("failed to load cluster provider config: %v", err) + } + + sess := a.servicesGetter.Session(config) + elb := a.servicesGetter.ELB(sess) + + dnsName, err := elb.GetAPIServerDNSName(cluster.Name) + + if err != nil { + return "", errors.Errorf("failed to get DNS name for load balancer") + } + + return dnsName, nil +} + +// GetKubeConfig returns the kubeconfig after the bootstrap process is complete. +func (a *Actuator) GetKubeConfig(cluster *clusterv1.Cluster, machine *clusterv1.Machine) (string, error) { + + // Load provider status. + status, err := a.loadProviderStatus(cluster) + if err != nil { + return "", errors.Errorf("failed to load cluster provider status: %v", err) + } + + cert, err := certificates.DecodeCertPEM(status.CACertificate) + if err != nil { + return "", errors.Wrap(err, "failed to decode CA Cert") + } + if cert == nil { + return "", errors.New("certificate not found in status") + } + key, err := certificates.DecodePrivateKeyPEM(status.CAPrivateKey) + if err != nil { + return "", errors.Wrap(err, "failed to decode private key") + } + if key == nil { + return "", errors.New("key not found in status") + } + + dnsName, err := a.GetIP(cluster, machine) + + if err != nil { + return "", errors.Wrap(err, "failed to get DNS address") + } + + server := fmt.Sprintf("https://%s:6443", dnsName) + + cfg, err := certificates.NewKubeconfig(server, cert, key) + if err != nil { + return "", errors.Wrap(err, "failed to generate a kubeconfig") + } + yaml, err := clientcmd.Write(*cfg) + if err != nil { + return "", errors.Wrap(err, "failed to serialize config to yaml") + } + return string(yaml), nil +} + func (a *Actuator) loadProviderConfig(cluster *clusterv1.Cluster) (*providerconfigv1.AWSClusterProviderConfig, error) { providerConfig := &providerconfigv1.AWSClusterProviderConfig{} err := a.codec.DecodeFromProviderConfig(cluster.Spec.ProviderConfig, providerConfig) diff --git a/cloud/aws/actuators/cluster/actuator_test.go b/cloud/aws/actuators/cluster/actuator_test.go index 8286772499..e185b8e0b1 100644 --- a/cloud/aws/actuators/cluster/actuator_test.go +++ b/cloud/aws/actuators/cluster/actuator_test.go @@ -54,6 +54,66 @@ func (d *testServicesGetter) ELB(session *session.Session) service.ELBInterface return d.elb } +func TestGetIP(t *testing.T) { + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + c, err := providerconfigv1.NewCodec() + if err != nil { + t.Fatalf("failed to create codec: %v", err) + } + + clusters := &clusterGetter{ + ci: mock_clusteriface.NewMockClusterInterface(mockCtrl), + } + + services := &testServicesGetter{ + ec2: mocks.NewMockEC2Interface(mockCtrl), + elb: mocks.NewMockELBInterface(mockCtrl), + } + + ap := cluster.ActuatorParams{ + Codec: c, + ClustersGetter: clusters, + ServicesGetter: services, + } + + actuator, err := cluster.NewActuator(ap) + if err != nil { + t.Fatalf("could not create an actuator: %v", err) + } + + cluster := &clusterv1.Cluster{ + ObjectMeta: metav1.ObjectMeta{Name: "test", ClusterName: "test"}, + Spec: clusterv1.ClusterSpec{ + ProviderConfig: clusterv1.ProviderConfig{ + Value: &runtime.RawExtension{ + Raw: []byte(`{"kind":"AWSClusterProviderConfig","apiVersion":"awsproviderconfig/v1alpha1","region":"us-east-1"}`), + }, + }, + }, + } + + machine := &clusterv1.Machine{ + ObjectMeta: metav1.ObjectMeta{Name: "test", ClusterName: "test"}, + Spec: clusterv1.MachineSpec{ + ProviderConfig: clusterv1.ProviderConfig{ + Value: &runtime.RawExtension{ + Raw: []byte(`{"kind":"AWSClusterProviderConfig","apiVersion":"awsproviderconfig/v1alpha1","region":"us-east-1"}`), + }, + }, + }, + } + + services.elb.EXPECT(). + GetAPIServerDNSName("test"). + Return("", nil) + + if _, err := actuator.GetIP(cluster, machine); err != nil { + t.Fatalf("failed to get API server address: %v", err) + } +} + func TestReconcile(t *testing.T) { mockCtrl := gomock.NewController(t) defer mockCtrl.Finish() diff --git a/cloud/aws/deployer/deployer.go b/cloud/aws/deployer/deployer.go index 333ae9c8ea..213bb62b7c 100644 --- a/cloud/aws/deployer/deployer.go +++ b/cloud/aws/deployer/deployer.go @@ -14,81 +14,34 @@ See the License for the specific language governing permissions and limitations under the License. */ -package deployer +package cluster import ( - "fmt" - "github.com/pkg/errors" - "k8s.io/client-go/tools/clientcmd" - clustercommon "sigs.k8s.io/cluster-api/pkg/apis/cluster/common" - clusterv1 "sigs.k8s.io/cluster-api/pkg/apis/cluster/v1alpha1" + "github.com/golang/glog" + clusteractuator "sigs.k8s.io/cluster-api-provider-aws/cloud/aws/actuators/cluster" "sigs.k8s.io/cluster-api-provider-aws/cloud/aws/providerconfig/v1alpha1" - "sigs.k8s.io/cluster-api-provider-aws/cloud/aws/services/certificates" + clustercommon "sigs.k8s.io/cluster-api/pkg/apis/cluster/common" ) // ProviderName is the name of the cloud provider const ProviderName = "aws" func init() { - clustercommon.RegisterClusterProvisioner(ProviderName, &AWSDeployer{}) -} - -// AWSDeployer implements the cluster-api Deployer interface. -type AWSDeployer struct{} - -// GetIP returns the IP of a machine, but this is going away. -func (*AWSDeployer) GetIP(cluster *clusterv1.Cluster, machine *clusterv1.Machine) (string, error) { codec, err := v1alpha1.NewCodec() if err != nil { - return "", errors.Wrap(err, "Failed to create codec in deployer") + glog.Fatalf("Could not create codec: %v", err) } - status := &v1alpha1.AWSClusterProviderStatus{} - if err := codec.DecodeProviderStatus(cluster.Status.ProviderStatus, status); err != nil { - return "", errors.Wrap(err, "failed to decode cluster provider status in deployer") - } - if status.Network.APIServerELB.DNSName == "" { - return "", errors.New("ELB has no DNS name") - } - return status.Network.APIServerELB.DNSName, nil -} - -// GetKubeConfig returns the kubeconfig after the bootstrap process is complete. -func (*AWSDeployer) GetKubeConfig(cluster *clusterv1.Cluster, machine *clusterv1.Machine) (string, error) { - codec, err := v1alpha1.NewCodec() - if err != nil { - return "", errors.Wrap(err, "Failed to create codec in deployer") + params := clusteractuator.ActuatorParams{ + Codec: codec, } - status := &v1alpha1.AWSClusterProviderStatus{} - if err := codec.DecodeProviderStatus(cluster.Status.ProviderStatus, status); err != nil { - return "", errors.Wrap(err, "failed to decode machine provider status in deployer") - } - cert, err := certificates.DecodeCertPEM(status.CACertificate) - if err != nil { - return "", errors.Wrap(err, "failed to decode CA Cert") - } - if cert == nil { - return "", errors.New("certificate not found in status") - } - key, err := certificates.DecodePrivateKeyPEM(status.CAPrivateKey) + actuator, err := clusteractuator.NewActuator(params) if err != nil { - return "", errors.Wrap(err, "failed to decode private key") - } - if key == nil { - return "", errors.New("key not found in status") + glog.Fatalf("Could not create aws cluster actuator: %v", err) } - server := fmt.Sprintf("https://%s:6443", status.Network.APIServerELB.DNSName) + clustercommon.RegisterClusterProvisioner(ProviderName, actuator) - cfg, err := certificates.NewKubeconfig(server, cert, key) - if err != nil { - return "", errors.Wrap(err, "failed to generate a kubeconfig") - } - yaml, err := clientcmd.Write(*cfg) - if err != nil { - return "", errors.Wrap(err, "failed to serialize config to yaml") - } - return string(yaml), nil } diff --git a/cloud/aws/deployer/deployer_test.go b/cloud/aws/deployer/deployer_test.go deleted file mode 100644 index 79d0ae94fb..0000000000 --- a/cloud/aws/deployer/deployer_test.go +++ /dev/null @@ -1,61 +0,0 @@ -package deployer_test - -import ( - "testing" - - "k8s.io/apimachinery/pkg/runtime" - clusterv1 "sigs.k8s.io/cluster-api/pkg/apis/cluster/v1alpha1" - - "sigs.k8s.io/cluster-api-provider-aws/cloud/aws/deployer" - "sigs.k8s.io/cluster-api-provider-aws/cloud/aws/providerconfig/v1alpha1" -) - -func statusToRawExtension(status *v1alpha1.AWSClusterProviderStatus, t *testing.T) *runtime.RawExtension { - codec, err := v1alpha1.NewCodec() - if err != nil { - t.Fatalf("failed to create a codec: %v", err) - } - out, err := codec.EncodeProviderStatus(status) - if err != nil { - t.Fatalf("failed to convert status to extension: %v", err) - } - return out -} - -func TestGetIP(t *testing.T) { - testcases := []struct { - name string - clusterProviderStatus v1alpha1.AWSClusterProviderStatus - }{ - { - name: "simple test", - clusterProviderStatus: v1alpha1.AWSClusterProviderStatus{ - Network: v1alpha1.Network{ - APIServerELB: v1alpha1.ClassicELB{ - DNSName: "example.com", - }, - }, - }, - }, - } - - for _, tc := range testcases { - t.Run(tc.name, func(t *testing.T) { - deployer := deployer.AWSDeployer{} - cluster := &clusterv1.Cluster{ - Status: clusterv1.ClusterStatus{ - ProviderStatus: statusToRawExtension(&tc.clusterProviderStatus, t), - }, - } - machine := &clusterv1.Machine{} - - ip, err := deployer.GetIP(cluster, machine) - if err != nil { - t.Fatalf("did not expect an error, but got: %v", err) - } - if ip != tc.clusterProviderStatus.Network.APIServerELB.DNSName { - t.Fatalf("expected %v got %v", tc.clusterProviderStatus.Network.APIServerELB.DNSName, ip) - } - }) - } -} diff --git a/cloud/aws/services/elb/loadbalancer.go b/cloud/aws/services/elb/loadbalancer.go index 2ba6aaa498..0dd55c8edd 100644 --- a/cloud/aws/services/elb/loadbalancer.go +++ b/cloud/aws/services/elb/loadbalancer.go @@ -15,11 +15,12 @@ package elb import ( "fmt" - "sigs.k8s.io/cluster-api-provider-aws/cloud/aws/services/awserrors" - "sigs.k8s.io/cluster-api-provider-aws/cloud/aws/services/wait" "strings" "time" + "sigs.k8s.io/cluster-api-provider-aws/cloud/aws/services/awserrors" + "sigs.k8s.io/cluster-api-provider-aws/cloud/aws/services/wait" + "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/elb" "github.com/golang/glog" @@ -55,6 +56,17 @@ func (s *Service) ReconcileLoadbalancers(clusterName string, network *v1alpha1.N return nil } +// GetAPIServerDNSName returns the DNS name endpoint for the API server +func (s *Service) GetAPIServerDNSName(clusterName string) (string, error) { + apiELB, err := s.describeClassicELB(GenerateELBName(clusterName, TagValueAPIServerRole)) + + if err != nil { + return "", err + } + + return apiELB.DNSName, nil +} + // DeleteLoadbalancers deletes the load balancers for the given cluster. func (s *Service) DeleteLoadbalancers(clusterName string, network *v1alpha1.Network) error { glog.V(2).Info("Delete load balancers") diff --git a/cloud/aws/services/interfaces.go b/cloud/aws/services/interfaces.go index 47bd5594d6..0bc387e4de 100644 --- a/cloud/aws/services/interfaces.go +++ b/cloud/aws/services/interfaces.go @@ -72,4 +72,5 @@ type ELBInterface interface { ReconcileLoadbalancers(clusterName string, network *providerv1.Network) error DeleteLoadbalancers(clusterName string, network *providerv1.Network) error RegisterInstanceWithAPIServerELB(clusterName string, instanceID string) error + GetAPIServerDNSName(clusterName string) (string, error) } diff --git a/cloud/aws/services/mocks/elb.go b/cloud/aws/services/mocks/elb.go index 4c749fc918..912055e094 100644 --- a/cloud/aws/services/mocks/elb.go +++ b/cloud/aws/services/mocks/elb.go @@ -58,6 +58,19 @@ func (mr *MockELBInterfaceMockRecorder) DeleteLoadbalancers(arg0, arg1 interface return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteLoadbalancers", reflect.TypeOf((*MockELBInterface)(nil).DeleteLoadbalancers), arg0, arg1) } +// GetAPIServerDNSName mocks base method +func (m *MockELBInterface) GetAPIServerDNSName(arg0 string) (string, error) { + ret := m.ctrl.Call(m, "GetAPIServerDNSName", arg0) + ret0, _ := ret[0].(string) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetAPIServerDNSName indicates an expected call of GetAPIServerDNSName +func (mr *MockELBInterfaceMockRecorder) GetAPIServerDNSName(arg0 interface{}) *gomock.Call { + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAPIServerDNSName", reflect.TypeOf((*MockELBInterface)(nil).GetAPIServerDNSName), arg0) +} + // ReconcileLoadbalancers mocks base method func (m *MockELBInterface) ReconcileLoadbalancers(arg0 string, arg1 *v1alpha1.Network) error { ret := m.ctrl.Call(m, "ReconcileLoadbalancers", arg0, arg1) diff --git a/docs/getting-started.md b/docs/getting-started.md index 4fe3d90e02..fc950baca3 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -217,6 +217,9 @@ sh -c "cd ./clusterctl/examples/aws && ./generate-yaml.sh" ### Starting Cluster API +If you haven't already, set up your [environment]((#setting-up-the-environment)) +in the [terminal session] you're working in. + You can now start the Cluster API controllers and deploy a new cluster in AWS: *Bash:*