Skip to content

Commit

Permalink
Merge pull request kubernetes#1877 from justinsb/cloudformation
Browse files Browse the repository at this point in the history
Initial support for cloudformation output
  • Loading branch information
krisnova authored Feb 19, 2017
2 parents aaba0e1 + fec89f0 commit 540947c
Show file tree
Hide file tree
Showing 38 changed files with 2,216 additions and 46 deletions.
2 changes: 2 additions & 0 deletions cloudmock/aws/mockec2/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ type MockEC2 struct {
volumeNumber int
Volumes []*ec2.Volume

KeyPairs []*ec2.KeyPairInfo

Tags []*ec2.TagDescription

vpcNumber int
Expand Down
102 changes: 102 additions & 0 deletions cloudmock/aws/mockec2/keypairs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
/*
Copyright 2016 The Kubernetes Authors.
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 mockec2

import (
"fmt"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/request"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/golang/glog"
)

func (m *MockEC2) DescribeKeyPairsRequest(*ec2.DescribeKeyPairsInput) (*request.Request, *ec2.DescribeKeyPairsOutput) {
panic("MockEC2 DescribeKeyPairsRequest not implemented")
}
func (m *MockEC2) ImportKeyPairRequest(*ec2.ImportKeyPairInput) (*request.Request, *ec2.ImportKeyPairOutput) {
panic("MockEC2 ImportKeyPairRequest not implemented")
}
func (m *MockEC2) ImportKeyPair(request *ec2.ImportKeyPairInput) (*ec2.ImportKeyPairOutput, error) {
glog.Infof("ImportKeyPair: %v", request)

fp := "12345" // TODO: calculate fingerprint

kp := &ec2.KeyPairInfo{
KeyFingerprint: aws.String(fp),
KeyName: request.KeyName,
}
m.KeyPairs = append(m.KeyPairs, kp)
response := &ec2.ImportKeyPairOutput{
KeyFingerprint: kp.KeyFingerprint,
KeyName: kp.KeyName,
}
return response, nil
}
func (m *MockEC2) CreateKeyPairRequest(*ec2.CreateKeyPairInput) (*request.Request, *ec2.CreateKeyPairOutput) {
panic("MockEC2 CreateKeyPairRequest not implemented")
}
func (m *MockEC2) CreateKeyPair(*ec2.CreateKeyPairInput) (*ec2.CreateKeyPairOutput, error) {
panic("MockEC2 CreateKeyPair not implemented")
}

func (m *MockEC2) DescribeKeyPairs(request *ec2.DescribeKeyPairsInput) (*ec2.DescribeKeyPairsOutput, error) {
glog.Infof("DescribeKeyPairs: %v", request)

var keypairs []*ec2.KeyPairInfo

for _, keypair := range m.KeyPairs {
allFiltersMatch := true

if len(request.KeyNames) != 0 {
match := false
for _, keyname := range request.KeyNames {
if aws.StringValue(keyname) == aws.StringValue(keypair.KeyName) {
match = true
}
}
if !match {
allFiltersMatch = false
}
}
for _, filter := range request.Filters {
match := false
switch *filter.Name {

default:
return nil, fmt.Errorf("unknown filter name: %q", *filter.Name)
}

if !match {
allFiltersMatch = false
break
}
}

if !allFiltersMatch {
continue
}

copy := *keypair
keypairs = append(keypairs, &copy)
}

response := &ec2.DescribeKeyPairsOutput{
KeyPairs: keypairs,
}

return response, nil
}
24 changes: 0 additions & 24 deletions cloudmock/aws/mockec2/unimplemented.go
Original file line number Diff line number Diff line change
Expand Up @@ -234,14 +234,6 @@ func (m *MockEC2) CreateInternetGateway(*ec2.CreateInternetGatewayInput) (*ec2.C
panic("Not implemented")
return nil, nil
}
func (m *MockEC2) CreateKeyPairRequest(*ec2.CreateKeyPairInput) (*request.Request, *ec2.CreateKeyPairOutput) {
panic("Not implemented")
return nil, nil
}
func (m *MockEC2) CreateKeyPair(*ec2.CreateKeyPairInput) (*ec2.CreateKeyPairOutput, error) {
panic("Not implemented")
return nil, nil
}
func (m *MockEC2) CreateNatGatewayRequest(*ec2.CreateNatGatewayInput) (*request.Request, *ec2.CreateNatGatewayOutput) {
panic("Not implemented")
return nil, nil
Expand Down Expand Up @@ -706,14 +698,6 @@ func (m *MockEC2) DescribeInternetGateways(*ec2.DescribeInternetGatewaysInput) (
panic("Not implemented")
return nil, nil
}
func (m *MockEC2) DescribeKeyPairsRequest(*ec2.DescribeKeyPairsInput) (*request.Request, *ec2.DescribeKeyPairsOutput) {
panic("Not implemented")
return nil, nil
}
func (m *MockEC2) DescribeKeyPairs(*ec2.DescribeKeyPairsInput) (*ec2.DescribeKeyPairsOutput, error) {
panic("Not implemented")
return nil, nil
}
func (m *MockEC2) DescribeMovingAddressesRequest(*ec2.DescribeMovingAddressesInput) (*request.Request, *ec2.DescribeMovingAddressesOutput) {
panic("Not implemented")
return nil, nil
Expand Down Expand Up @@ -1118,14 +1102,6 @@ func (m *MockEC2) ImportInstance(*ec2.ImportInstanceInput) (*ec2.ImportInstanceO
panic("Not implemented")
return nil, nil
}
func (m *MockEC2) ImportKeyPairRequest(*ec2.ImportKeyPairInput) (*request.Request, *ec2.ImportKeyPairOutput) {
panic("Not implemented")
return nil, nil
}
func (m *MockEC2) ImportKeyPair(*ec2.ImportKeyPairInput) (*ec2.ImportKeyPairOutput, error) {
panic("Not implemented")
return nil, nil
}
func (m *MockEC2) ImportSnapshotRequest(*ec2.ImportSnapshotInput) (*request.Request, *ec2.ImportSnapshotOutput) {
panic("Not implemented")
return nil, nil
Expand Down
4 changes: 3 additions & 1 deletion cmd/kops/create_cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ func NewCmdCreateCluster(f *util.Factory, out io.Writer) *cobra.Command {
}

cmd.Flags().BoolVar(&options.Yes, "yes", options.Yes, "Specify --yes to immediately create the cluster")
cmd.Flags().StringVar(&options.Target, "target", options.Target, "Target - direct, terraform")
cmd.Flags().StringVar(&options.Target, "target", options.Target, "Target - direct, terraform, cloudformation")
cmd.Flags().StringVar(&options.Models, "model", options.Models, "Models to apply (separate multiple models with commas)")

cmd.Flags().StringVar(&options.Cloud, "cloud", options.Cloud, "Cloud provider to use - gce, aws")
Expand Down Expand Up @@ -201,6 +201,8 @@ func RunCreateCluster(f *util.Factory, out io.Writer, c *CreateClusterOptions) e
if c.OutDir == "" {
if c.Target == cloudup.TargetTerraform {
c.OutDir = "out/terraform"
} else if c.Target == cloudup.TargetCloudformation {
c.OutDir = "out/cloudformation"
} else {
c.OutDir = "out"
}
Expand Down
100 changes: 100 additions & 0 deletions cmd/kops/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,13 @@ func TestMinimal(t *testing.T) {
runTest(t, "minimal.example.com", "../../tests/integration/minimal", "v1alpha2", false)
}

// TestMinimalCloudformation runs the test on a minimum configuration, similar to kops create cluster minimal.example.com --zones us-west-1a
func TestMinimalCloudformation(t *testing.T) {
//runTestCloudformation(t, "minimal.example.com", "../../tests/integration/minimal", "v1alpha0", false)
//runTestCloudformation(t, "minimal.example.com", "../../tests/integration/minimal", "v1alpha1", false)
runTestCloudformation(t, "minimal.example.com", "../../tests/integration/minimal", "v1alpha2", false)
}

// TestMinimal_141 runs the test on a configuration from 1.4.1 release
func TestMinimal_141(t *testing.T) {
runTest(t, "minimal-141.example.com", "../../tests/integration/minimal-141", "v1alpha0", false)
Expand Down Expand Up @@ -209,6 +216,97 @@ func runTest(t *testing.T, clusterName string, srcDir string, version string, pr
}
}

func runTestCloudformation(t *testing.T, clusterName string, srcDir string, version string, private bool) {
var stdout bytes.Buffer

inputYAML := "in-" + version + ".yaml"
expectedCfPath := "cloudformation.json"

factoryOptions := &util.FactoryOptions{}
factoryOptions.RegistryPath = "memfs://tests"

h := NewIntegrationTestHarness(t)
defer h.Close()

h.SetupMockAWS()

factory := util.NewFactory(factoryOptions)

{
options := &CreateOptions{}
options.Filenames = []string{path.Join(srcDir, inputYAML)}

err := RunCreate(factory, &stdout, options)
if err != nil {
t.Fatalf("error running %q create: %v", inputYAML, err)
}
}

{
options := &CreateSecretPublickeyOptions{}
options.ClusterName = clusterName
options.Name = "admin"
options.PublicKeyPath = path.Join(srcDir, "id_rsa.pub")

err := RunCreateSecretPublicKey(factory, &stdout, options)
if err != nil {
t.Fatalf("error running %q create: %v", inputYAML, err)
}
}

{
options := &UpdateClusterOptions{}
options.InitDefaults()
options.Target = "cloudformation"
options.OutDir = path.Join(h.TempDir, "out")
options.MaxTaskDuration = 30 * time.Second

// We don't test it here, and it adds a dependency on kubectl
options.CreateKubecfg = false

err := RunUpdateCluster(factory, clusterName, &stdout, options)
if err != nil {
t.Fatalf("error running update cluster %q: %v", clusterName, err)
}
}

// Compare main files
{
files, err := ioutil.ReadDir(path.Join(h.TempDir, "out"))
if err != nil {
t.Fatalf("failed to read dir: %v", err)
}

var fileNames []string
for _, f := range files {
fileNames = append(fileNames, f.Name())
}
sort.Strings(fileNames)

actualFilenames := strings.Join(fileNames, ",")
expectedFilenames := "kubernetes.json"
if actualFilenames != expectedFilenames {
t.Fatalf("unexpected files. actual=%q, expected=%q", actualFilenames, expectedFilenames)
}

actualCF, err := ioutil.ReadFile(path.Join(h.TempDir, "out", "kubernetes.json"))
if err != nil {
t.Fatalf("unexpected error reading actual cloudformation output: %v", err)
}
expectedCF, err := ioutil.ReadFile(path.Join(srcDir, expectedCfPath))
if err != nil {
t.Fatalf("unexpected error reading expected cloudformation output: %v", err)
}

if !bytes.Equal(actualCF, expectedCF) {
diffString := diff.FormatDiff(string(expectedCF), string(actualCF))
t.Logf("diff:\n%s\n", diffString)

t.Fatalf("cloudformation output differed from expected")
}
}
}

type IntegrationTestHarness struct {
TempDir string
T *testing.T
Expand Down Expand Up @@ -256,6 +354,8 @@ func (h *IntegrationTestHarness) SetupMockAWS() {
ImageId: aws.String("ami-12345678"),
Name: aws.String("k8s-1.4-debian-jessie-amd64-hvm-ebs-2016-10-21"),
OwnerId: aws.String(awsup.WellKnownAccountKopeio),

RootDeviceName: aws.String("/dev/xvda"),
})
}

Expand Down
14 changes: 14 additions & 0 deletions cmd/kops/update_cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"fmt"
"io"
"io/ioutil"
"path/filepath"
"strings"
"time"

Expand Down Expand Up @@ -107,6 +108,8 @@ func RunUpdateCluster(f *util.Factory, clusterName string, out io.Writer, c *Upd
if c.OutDir == "" {
if c.Target == cloudup.TargetTerraform {
c.OutDir = "out/terraform"
} else if c.Target == cloudup.TargetCloudformation {
c.OutDir = "out/cloudformation"
} else {
c.OutDir = "out"
}
Expand Down Expand Up @@ -233,6 +236,17 @@ func RunUpdateCluster(f *util.Factory, clusterName string, out io.Writer, c *Upd
fmt.Fprintf(sb, " terraform apply\n")
fmt.Fprintf(sb, "\n")
}
} else if c.Target == cloudup.TargetCloudformation {
fmt.Fprintf(sb, "\n")
fmt.Fprintf(sb, "Cloudformation output has been placed into %s\n", c.OutDir)

if firstRun {
cfName := "kubernetes-" + strings.Replace(clusterName, ".", "-", -1)
cfPath := filepath.Join(c.OutDir, "kubernetes.json")
fmt.Fprintf(sb, "Run this command to apply the configuration:\n")
fmt.Fprintf(sb, " aws cloudformation create-stack --capabilities CAPABILITY_NAMED_IAM --stack-name %s --template-body file://%s\n", cfName, cfPath)
fmt.Fprintf(sb, "\n")
}
} else if firstRun {
fmt.Fprintf(sb, "\n")
fmt.Fprintf(sb, "Cluster is starting. It should be ready in a few minutes.\n")
Expand Down
1 change: 1 addition & 0 deletions hack/.packages
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ k8s.io/kops/upup/pkg/fi
k8s.io/kops/upup/pkg/fi/cloudup
k8s.io/kops/upup/pkg/fi/cloudup/awstasks
k8s.io/kops/upup/pkg/fi/cloudup/awsup
k8s.io/kops/upup/pkg/fi/cloudup/cloudformation
k8s.io/kops/upup/pkg/fi/cloudup/gce
k8s.io/kops/upup/pkg/fi/cloudup/gcetasks
k8s.io/kops/upup/pkg/fi/cloudup/terraform
Expand Down
Loading

0 comments on commit 540947c

Please sign in to comment.