Merge pull request #1877 from justinsb/cloudformation

Initial support for cloudformation output
This commit is contained in:
Kris Nova 2017-02-19 10:19:14 -07:00 committed by GitHub
commit 540947cbc4
38 changed files with 2219 additions and 49 deletions

View File

@ -38,6 +38,8 @@ type MockEC2 struct {
volumeNumber int
Volumes []*ec2.Volume
KeyPairs []*ec2.KeyPairInfo
Tags []*ec2.TagDescription
vpcNumber int

View File

@ -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
}

View File

@ -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
@ -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
@ -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

View File

@ -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")
@ -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"
}

View File

@ -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)
@ -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
@ -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"),
})
}

View File

@ -21,6 +21,7 @@ import (
"fmt"
"io"
"io/ioutil"
"path/filepath"
"strings"
"time"
@ -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"
}
@ -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")

View File

@ -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

File diff suppressed because one or more lines are too long

View File

@ -34,6 +34,7 @@ import (
"k8s.io/kops/upup/pkg/fi"
"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"
@ -514,6 +515,7 @@ func (c *ApplyClusterCmd) Run() error {
var target fi.Target
dryRun := false
shouldPrecreateDNS := true
switch c.TargetName {
case TargetDirect:
@ -531,9 +533,24 @@ func (c *ApplyClusterCmd) Run() error {
outDir := c.OutDir
target = terraform.NewTerraformTarget(cloud, region, project, outDir)
// Can cause conflicts with terraform management
shouldPrecreateDNS = false
case TargetCloudformation:
checkExisting = false
outDir := c.OutDir
target = cloudformation.NewCloudformationTarget(cloud, region, project, outDir)
// Can cause conflicts with cloudformation management
shouldPrecreateDNS = false
case TargetDryRun:
target = fi.NewDryRunTarget(os.Stdout)
dryRun = true
// Avoid making changes on a dry-run
shouldPrecreateDNS = false
default:
return fmt.Errorf("unsupported target type %q", c.TargetName)
}
@ -564,7 +581,7 @@ func (c *ApplyClusterCmd) Run() error {
return fmt.Errorf("error running tasks: %v", err)
}
if !dryRun {
if shouldPrecreateDNS {
if err := precreateDNS(cluster, cloud); err != nil {
return err
}

View File

@ -28,6 +28,7 @@ import (
"github.com/golang/glog"
"k8s.io/kops/upup/pkg/fi"
"k8s.io/kops/upup/pkg/fi/cloudup/awsup"
"k8s.io/kops/upup/pkg/fi/cloudup/cloudformation"
"k8s.io/kops/upup/pkg/fi/cloudup/terraform"
)
@ -295,3 +296,54 @@ func (_ *AutoscalingGroup) RenderTerraform(t *terraform.TerraformTarget, a, e, c
func (e *AutoscalingGroup) TerraformLink() *terraform.Literal {
return terraform.LiteralProperty("aws_autoscaling_group", *e.Name, "id")
}
type cloudformationASGTag struct {
Key *string `json:"Key"`
Value *string `json:"Value"`
PropagateAtLaunch *bool `json:"PropagateAtLaunch"`
}
type cloudformationAutoscalingGroup struct {
//Name *string `json:"name,omitempty"`
LaunchConfigurationName *cloudformation.Literal `json:"LaunchConfigurationName,omitempty"`
MaxSize *int64 `json:"MaxSize,omitempty"`
MinSize *int64 `json:"MinSize,omitempty"`
VPCZoneIdentifier []*cloudformation.Literal `json:"VPCZoneIdentifier,omitempty"`
Tags []*cloudformationASGTag `json:"Tags,omitempty"`
LoadBalancerNames []*cloudformation.Literal `json:"LoadBalancerNames,omitempty"`
}
func (_ *AutoscalingGroup) RenderCloudformation(t *cloudformation.CloudformationTarget, a, e, changes *AutoscalingGroup) error {
tf := &cloudformationAutoscalingGroup{
//Name: e.Name,
MinSize: e.MinSize,
MaxSize: e.MaxSize,
LaunchConfigurationName: e.LaunchConfiguration.CloudformationLink(),
}
for _, s := range e.Subnets {
tf.VPCZoneIdentifier = append(tf.VPCZoneIdentifier, s.CloudformationLink())
}
tags := e.buildTags(t.Cloud)
// Make sure we output in a stable order
var tagKeys []string
for k := range tags {
tagKeys = append(tagKeys, k)
}
sort.Strings(tagKeys)
for _, k := range tagKeys {
v := tags[k]
tf.Tags = append(tf.Tags, &cloudformationASGTag{
Key: fi.String(k),
Value: fi.String(v),
PropagateAtLaunch: fi.Bool(true),
})
}
return t.RenderResource("AWS::AutoScaling::AutoScalingGroup", *e.Name, tf)
}
func (e *AutoscalingGroup) CloudformationLink() *cloudformation.Literal {
return cloudformation.Ref("AWS::AutoScaling::AutoScalingGroup", *e.Name)
}

View File

@ -0,0 +1,48 @@
/*
Copyright 2017 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 awstasks
import (
"github.com/aws/aws-sdk-go/aws"
"sort"
)
type cloudformationTag struct {
Key *string `json:"Key"`
Value *string `json:"Value"`
}
type cfTagByKey []cloudformationTag
func (a cfTagByKey) Len() int { return len(a) }
func (a cfTagByKey) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a cfTagByKey) Less(i, j int) bool {
return aws.StringValue(a[i].Key) < aws.StringValue(a[j].Key)
}
func buildCloudformationTags(tags map[string]string) []cloudformationTag {
var cfTags []cloudformationTag
for k, v := range tags {
cfTag := cloudformationTag{
Key: aws.String(k),
Value: aws.String(v),
}
cfTags = append(cfTags, cfTag)
}
sort.Sort(cfTagByKey(cfTags))
return cfTags
}

View File

@ -24,6 +24,7 @@ import (
"github.com/golang/glog"
"k8s.io/kops/upup/pkg/fi"
"k8s.io/kops/upup/pkg/fi/cloudup/awsup"
"k8s.io/kops/upup/pkg/fi/cloudup/cloudformation"
"k8s.io/kops/upup/pkg/fi/cloudup/terraform"
"strings"
)
@ -178,3 +179,28 @@ func (_ *DHCPOptions) RenderTerraform(t *terraform.TerraformTarget, a, e, change
func (e *DHCPOptions) TerraformLink() *terraform.Literal {
return terraform.LiteralProperty("aws_vpc_dhcp_options", *e.Name, "id")
}
type cloudformationDHCPOptions struct {
DomainName *string `json:"DomainName,omitempty"`
DomainNameServers []string `json:"DomainNameServers,omitempty"`
Tags []cloudformationTag `json:"Tags,omitempty"`
}
func (_ *DHCPOptions) RenderCloudformation(t *cloudformation.CloudformationTarget, a, e, changes *DHCPOptions) error {
cloud := t.Cloud.(awsup.AWSCloud)
cf := &cloudformationDHCPOptions{
DomainName: e.DomainName,
Tags: buildCloudformationTags(cloud.BuildTags(e.Name)),
}
if e.DomainNameServers != nil {
cf.DomainNameServers = strings.Split(*e.DomainNameServers, ",")
}
return t.RenderResource("AWS::EC2::DHCPOptions", *e.Name, cf)
}
func (e *DHCPOptions) CloudformationLink() *cloudformation.Literal {
return cloudformation.Ref("AWS::EC2::DHCPOptions", *e.Name)
}

View File

@ -26,6 +26,7 @@ import (
"github.com/golang/glog"
"k8s.io/kops/upup/pkg/fi"
"k8s.io/kops/upup/pkg/fi/cloudup/awsup"
"k8s.io/kops/upup/pkg/fi/cloudup/cloudformation"
"k8s.io/kops/upup/pkg/fi/cloudup/terraform"
)
@ -210,3 +211,41 @@ func (_ *DNSName) RenderTerraform(t *terraform.TerraformTarget, a, e, changes *D
func (e *DNSName) TerraformLink() *terraform.Literal {
return terraform.LiteralSelfLink("aws_route53_record", *e.Name)
}
type cloudformationRoute53Record struct {
Name *string `json:"Name"`
Type *string `json:"Type"`
TTL *string `json:"TTL,omitempty"`
ResourceRecords []string `json:"ResourceRecords,omitempty"`
AliasTarget *cloudformationAlias `json:"AliasTarget,omitempty"`
ZoneID *cloudformation.Literal `json:"HostedZoneId"`
}
type cloudformationAlias struct {
DNSName *cloudformation.Literal `json:"DNSName,omitempty"`
ZoneID *cloudformation.Literal `json:"HostedZoneId,omitempty"`
EvaluateTargetHealth *bool `json:"EvaluateTargetHealth,omitempty"`
}
func (_ *DNSName) RenderCloudformation(t *cloudformation.CloudformationTarget, a, e, changes *DNSName) error {
cf := &cloudformationRoute53Record{
Name: e.Name,
ZoneID: e.Zone.CloudformationLink(),
Type: e.ResourceType,
}
if e.TargetLoadBalancer != nil {
cf.AliasTarget = &cloudformationAlias{
DNSName: e.TargetLoadBalancer.CloudformationAttrDNSName(),
EvaluateTargetHealth: aws.Bool(false),
ZoneID: e.TargetLoadBalancer.CloudformationAttrCanonicalHostedZoneNameID(),
}
}
return t.RenderResource("AWS::Route53::RecordSet", *e.Name, cf)
}
func (e *DNSName) CloudformationLink() *cloudformation.Literal {
return cloudformation.Ref("AWS::Route53::RecordSet", *e.Name)
}

View File

@ -24,6 +24,7 @@ import (
"github.com/golang/glog"
"k8s.io/kops/upup/pkg/fi"
"k8s.io/kops/upup/pkg/fi/cloudup/awsup"
"k8s.io/kops/upup/pkg/fi/cloudup/cloudformation"
"k8s.io/kops/upup/pkg/fi/cloudup/terraform"
"math/rand"
"reflect"
@ -280,3 +281,55 @@ func (e *DNSZone) TerraformLink() *terraform.Literal {
return terraform.LiteralSelfLink("aws_route53_zone", *e.Name)
}
type cloudformationRoute53Zone struct {
Name *string `json:"Name"`
VPCs []*cloudformation.Literal `json:"VPCs,omitempty"`
Tags []cloudformationTag `json:"HostedZoneTags,omitempty"`
}
func (_ *DNSZone) RenderCloudformation(t *cloudformation.CloudformationTarget, a, e, changes *DNSZone) error {
cloud := t.Cloud.(awsup.AWSCloud)
dnsName := fi.StringValue(e.DNSName)
// As a special case, we check for an existing zone
// It is really painful to have TF create a new one...
// (you have to reconfigure the DNS NS records)
glog.Infof("Check for existing route53 zone to re-use with name %q", dnsName)
z, err := e.findExisting(cloud)
if err != nil {
return err
}
if z != nil {
glog.Infof("Existing zone %q found; will configure cloudformation to reuse", aws.StringValue(z.HostedZone.Name))
e.ZoneID = z.HostedZone.Id
// Don't render a task
return nil
}
if !fi.BoolValue(e.Private) {
return fmt.Errorf("Creation of public Route53 hosted zones is not supported for cloudformation")
}
// We will create private zones (and delete them)
tf := &cloudformationRoute53Zone{
Name: e.Name,
VPCs: []*cloudformation.Literal{e.PrivateVPC.CloudformationLink()},
Tags: buildCloudformationTags(cloud.BuildTags(e.Name)),
}
return t.RenderResource("AWS::Route53::HostedZone", *e.Name, tf)
}
func (e *DNSZone) CloudformationLink() *cloudformation.Literal {
if e.ZoneID != nil {
glog.V(4).Infof("reusing existing route53 zone with id %q", *e.ZoneID)
return cloudformation.LiteralString(*e.ZoneID)
}
return cloudformation.Ref("AWS::Route53::HostedZone", *e.Name)
}

View File

@ -23,6 +23,7 @@ import (
"github.com/golang/glog"
"k8s.io/kops/upup/pkg/fi"
"k8s.io/kops/upup/pkg/fi/cloudup/awsup"
"k8s.io/kops/upup/pkg/fi/cloudup/cloudformation"
"k8s.io/kops/upup/pkg/fi/cloudup/terraform"
)
@ -171,3 +172,29 @@ func (_ *EBSVolume) RenderTerraform(t *terraform.TerraformTarget, a, e, changes
func (e *EBSVolume) TerraformLink() *terraform.Literal {
return terraform.LiteralSelfLink("aws_ebs_volume", *e.Name)
}
type cloudformationVolume struct {
AvailabilityZone *string `json:"AvailabilityZone,omitempty"`
Size *int64 `json:"Size,omitempty"`
Type *string `json:"VolumeType,omitempty"`
KmsKeyId *string `json:"KmsKeyId,omitempty"`
Encrypted *bool `json:"Encrypted,omitempty"`
Tags []cloudformationTag `json:"Tags,omitempty"`
}
func (_ *EBSVolume) RenderCloudformation(t *cloudformation.CloudformationTarget, a, e, changes *EBSVolume) error {
cf := &cloudformationVolume{
AvailabilityZone: e.AvailabilityZone,
Size: e.SizeGB,
Type: e.VolumeType,
KmsKeyId: e.KmsKeyId,
Encrypted: e.Encrypted,
Tags: buildCloudformationTags(e.Tags),
}
return t.RenderResource("AWS::EC2::Volume", *e.Name, cf)
}
func (e *EBSVolume) CloudformationLink() *cloudformation.Literal {
return cloudformation.Ref("AWS::EC2::Volume", *e.Name)
}

View File

@ -26,6 +26,7 @@ import (
"github.com/golang/glog"
"k8s.io/kops/upup/pkg/fi"
"k8s.io/kops/upup/pkg/fi/cloudup/awsup"
"k8s.io/kops/upup/pkg/fi/cloudup/cloudformation"
"k8s.io/kops/upup/pkg/fi/cloudup/terraform"
)
@ -261,3 +262,24 @@ func (_ *ElasticIP) RenderTerraform(t *terraform.TerraformTarget, a, e, changes
func (e *ElasticIP) TerraformLink() *terraform.Literal {
return terraform.LiteralProperty("aws_eip", *e.Name, "id")
}
type cloudformationElasticIP struct {
Domain *string `json:"Domain"`
}
func (_ *ElasticIP) RenderCloudformation(t *cloudformation.CloudformationTarget, a, e, changes *ElasticIP) error {
tf := &cloudformationElasticIP{
Domain: aws.String("vpc"),
}
return t.RenderResource("AWS::EC2::EIP", *e.Name, tf)
}
// Removed because you normally want CloudformationAllocationID
//func (e *ElasticIP) CloudformationLink() *cloudformation.Literal {
// return cloudformation.Ref("AWS::EC2::EIP", *e.Name)
//}
func (e *ElasticIP) CloudformationAllocationID() *cloudformation.Literal {
return cloudformation.GetAtt("AWS::EC2::EIP", *e.Name, "AllocationId")
}

View File

@ -26,6 +26,7 @@ import (
"github.com/golang/glog"
"k8s.io/kops/upup/pkg/fi"
"k8s.io/kops/upup/pkg/fi/cloudup/awsup"
"k8s.io/kops/upup/pkg/fi/cloudup/cloudformation"
"k8s.io/kops/upup/pkg/fi/cloudup/terraform"
)
@ -150,3 +151,12 @@ func (_ *IAMInstanceProfile) RenderTerraform(t *terraform.TerraformTarget, a, e,
func (e *IAMInstanceProfile) TerraformLink() *terraform.Literal {
return terraform.LiteralProperty("aws_iam_instance_profile", *e.Name, "id")
}
func (_ *IAMInstanceProfile) RenderCloudformation(t *cloudformation.CloudformationTarget, a, e, changes *IAMInstanceProfile) error {
// Done on IAMInstanceProfileRole
return nil
}
func (e *IAMInstanceProfile) CloudformationLink() *cloudformation.Literal {
return cloudformation.Ref("AWS::IAM::InstanceProfile", *e.Name)
}

View File

@ -25,6 +25,7 @@ import (
"github.com/golang/glog"
"k8s.io/kops/upup/pkg/fi"
"k8s.io/kops/upup/pkg/fi/cloudup/awsup"
"k8s.io/kops/upup/pkg/fi/cloudup/cloudformation"
"k8s.io/kops/upup/pkg/fi/cloudup/terraform"
)
@ -120,3 +121,17 @@ func (_ *IAMInstanceProfileRole) RenderTerraform(t *terraform.TerraformTarget, a
return t.RenderResource("aws_iam_instance_profile", *e.InstanceProfile.Name, tf)
}
type cloudformationIAMInstanceProfile struct {
//Path *string `json:"name"`
Roles []*cloudformation.Literal `json:"Roles"`
}
func (_ *IAMInstanceProfileRole) RenderCloudformation(t *cloudformation.CloudformationTarget, a, e, changes *IAMInstanceProfileRole) error {
cf := &cloudformationIAMInstanceProfile{
//Path: e.InstanceProfile.Name,
Roles: []*cloudformation.Literal{e.Role.CloudformationLink()},
}
return t.RenderResource("AWS::IAM::InstanceProfile", *e.InstanceProfile.Name, cf)
}

View File

@ -26,6 +26,7 @@ import (
"github.com/golang/glog"
"k8s.io/kops/upup/pkg/fi"
"k8s.io/kops/upup/pkg/fi/cloudup/awsup"
"k8s.io/kops/upup/pkg/fi/cloudup/cloudformation"
"k8s.io/kops/upup/pkg/fi/cloudup/terraform"
"k8s.io/kubernetes/pkg/util/diff"
"net/url"
@ -199,3 +200,32 @@ func (_ *IAMRole) RenderTerraform(t *terraform.TerraformTarget, a, e, changes *I
func (e *IAMRole) TerraformLink() *terraform.Literal {
return terraform.LiteralProperty("aws_iam_role", *e.Name, "name")
}
type cloudformationIAMRole struct {
RoleName *string `json:"RoleName"`
AssumeRolePolicyDocument map[string]interface{}
}
func (_ *IAMRole) RenderCloudformation(t *cloudformation.CloudformationTarget, a, e, changes *IAMRole) error {
jsonString, err := e.RolePolicyDocument.AsBytes()
if err != nil {
return err
}
data := make(map[string]interface{})
err = json.Unmarshal(jsonString, &data)
if err != nil {
return fmt.Errorf("error parsing RolePolicyDocument: %v", err)
}
cf := &cloudformationIAMRole{
RoleName: e.Name,
AssumeRolePolicyDocument: data,
}
return t.RenderResource("AWS::IAM::Role", *e.Name, cf)
}
func (e *IAMRole) CloudformationLink() *cloudformation.Literal {
return cloudformation.Ref("AWS::IAM::Role", *e.Name)
}

View File

@ -19,12 +19,14 @@ package awstasks
import (
"fmt"
"encoding/json"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/service/iam"
"github.com/golang/glog"
"k8s.io/kops/upup/pkg/fi"
"k8s.io/kops/upup/pkg/fi/cloudup/awsup"
"k8s.io/kops/upup/pkg/fi/cloudup/cloudformation"
"k8s.io/kops/upup/pkg/fi/cloudup/terraform"
"k8s.io/kubernetes/pkg/util/diff"
"net/url"
@ -201,3 +203,48 @@ func (_ *IAMRolePolicy) RenderTerraform(t *terraform.TerraformTarget, a, e, chan
func (e *IAMRolePolicy) TerraformLink() *terraform.Literal {
return terraform.LiteralSelfLink("aws_iam_role_policy", *e.Name)
}
type cloudformationIAMRolePolicy struct {
PolicyName *string `json:"PolicyName"`
Roles []*cloudformation.Literal `json:"Roles"`
PolicyDocument map[string]interface{} `json:"PolicyDocument"`
}
func (_ *IAMRolePolicy) RenderCloudformation(t *cloudformation.CloudformationTarget, a, e, changes *IAMRolePolicy) error {
{
policyString, err := e.PolicyDocument.AsString()
if err != nil {
return fmt.Errorf("error rendering PolicyDocument: %v", err)
}
if policyString == "" {
// A deletion; we simply don't render; cloudformation will observe the removal
return nil
}
}
tf := &cloudformationIAMRolePolicy{
PolicyName: e.Name,
Roles: []*cloudformation.Literal{e.Role.CloudformationLink()},
}
{
jsonString, err := e.PolicyDocument.AsBytes()
if err != nil {
return err
}
data := make(map[string]interface{})
err = json.Unmarshal(jsonString, &data)
if err != nil {
return fmt.Errorf("error parsing PolicyDocument: %v", err)
}
tf.PolicyDocument = data
}
return t.RenderResource("AWS::IAM::Policy", *e.Name, tf)
}
func (e *IAMRolePolicy) CloudformationLink() *cloudformation.Literal {
return cloudformation.Ref("AWS::IAM::Policy", *e.Name)
}

View File

@ -23,6 +23,7 @@ import (
"github.com/golang/glog"
"k8s.io/kops/upup/pkg/fi"
"k8s.io/kops/upup/pkg/fi/cloudup/awsup"
"k8s.io/kops/upup/pkg/fi/cloudup/cloudformation"
"k8s.io/kops/upup/pkg/fi/cloudup/terraform"
)
@ -219,3 +220,81 @@ func (e *InternetGateway) TerraformLink() *terraform.Literal {
return terraform.LiteralProperty("aws_internet_gateway", *e.Name, "id")
}
type cloudformationInternetGateway struct {
Tags []cloudformationTag `json:"Tags,omitempty"`
}
type cloudformationVpcGatewayAttachment struct {
VpcId *cloudformation.Literal `json:"VpcId,omitempty"`
InternetGatewayId *cloudformation.Literal `json:"InternetGatewayId,omitempty"`
}
func (_ *InternetGateway) RenderCloudformation(t *cloudformation.CloudformationTarget, a, e, changes *InternetGateway) error {
shared := fi.BoolValue(e.Shared)
if shared {
// Not cloudformation owned / managed
// But ... attempt to discover the ID so CloudformationLink works
if e.ID == nil {
request := &ec2.DescribeInternetGatewaysInput{}
vpcID := fi.StringValue(e.VPC.ID)
if vpcID == "" {
return fmt.Errorf("VPC ID is required when InternetGateway is shared")
}
request.Filters = []*ec2.Filter{awsup.NewEC2Filter("attachment.vpc-id", vpcID)}
igw, err := findInternetGateway(t.Cloud.(awsup.AWSCloud), request)
if err != nil {
return err
}
if igw == nil {
glog.Warningf("Cannot find internet gateway for VPC %q", vpcID)
} else {
e.ID = igw.InternetGatewayId
}
}
return nil
}
cloud := t.Cloud.(awsup.AWSCloud)
{
cf := &cloudformationInternetGateway{
Tags: buildCloudformationTags(cloud.BuildTags(e.Name)),
}
err := t.RenderResource("AWS::EC2::InternetGateway", *e.Name, cf)
if err != nil {
return err
}
}
{
cf := &cloudformationVpcGatewayAttachment{
VpcId: e.VPC.CloudformationLink(),
InternetGatewayId: e.CloudformationLink(),
}
err := t.RenderResource("AWS::EC2::VPCGatewayAttachment", *e.Name, cf)
if err != nil {
return err
}
}
return nil
}
func (e *InternetGateway) CloudformationLink() *cloudformation.Literal {
shared := fi.BoolValue(e.Shared)
if shared {
if e.ID == nil {
glog.Fatalf("ID must be set, if InternetGateway is shared: %s", e)
}
glog.V(4).Infof("reusing existing InternetGateway with id %q", *e.ID)
return cloudformation.LiteralString(*e.ID)
}
return cloudformation.Ref("AWS::EC2::InternetGateway", *e.Name)
}

View File

@ -27,6 +27,7 @@ import (
"github.com/golang/glog"
"k8s.io/kops/upup/pkg/fi"
"k8s.io/kops/upup/pkg/fi/cloudup/awsup"
"k8s.io/kops/upup/pkg/fi/cloudup/cloudformation"
"k8s.io/kops/upup/pkg/fi/cloudup/terraform"
"time"
)
@ -428,3 +429,127 @@ func (_ *LaunchConfiguration) RenderTerraform(t *terraform.TerraformTarget, a, e
func (e *LaunchConfiguration) TerraformLink() *terraform.Literal {
return terraform.LiteralProperty("aws_launch_configuration", *e.Name, "id")
}
type cloudformationLaunchConfiguration struct {
AssociatePublicIpAddress *bool `json:"AssociatePublicIpAddress,omitempty"`
BlockDeviceMappings []*cloudformationBlockDevice `json:"BlockDeviceMappings,omitempty"`
IAMInstanceProfile *cloudformation.Literal `json:"IamInstanceProfile,omitempty"`
ImageID *string `json:"ImageId,omitempty"`
InstanceType *string `json:"InstanceType,omitempty"`
KeyName *string `json:"KeyName,omitempty"`
SecurityGroups []*cloudformation.Literal `json:"SecurityGroups,omitempty"`
SpotPrice *string `json:"SpotPrice,omitempty"`
UserData *string `json:"UserData,omitempty"`
//NamePrefix *string `json:"name_prefix,omitempty"`
//Lifecycle *cloudformation.Lifecycle `json:"lifecycle,omitempty"`
}
type cloudformationBlockDevice struct {
// For ephemeral devices
DeviceName *string `json:"DeviceName,omitempty"`
VirtualName *string `json:"VirtualName,omitempty"`
// For root
Ebs *cloudformationBlockDeviceEBS `json:"Ebs,omitempty"`
}
type cloudformationBlockDeviceEBS struct {
VolumeType *string `json:"VolumeType,omitempty"`
VolumeSize *int64 `json:"VolumeSize,omitempty"`
DeleteOnTermination *bool `json:"DeleteOnTermination,omitempty"`
}
func (_ *LaunchConfiguration) RenderCloudformation(t *cloudformation.CloudformationTarget, a, e, changes *LaunchConfiguration) error {
cloud := t.Cloud.(awsup.AWSCloud)
if e.ImageID == nil {
return fi.RequiredField("ImageID")
}
image, err := cloud.ResolveImage(*e.ImageID)
if err != nil {
return err
}
cf := &cloudformationLaunchConfiguration{
//NamePrefix: fi.String(*e.Name + "-"),
ImageID: image.ImageId,
InstanceType: e.InstanceType,
}
if e.SpotPrice != "" {
cf.SpotPrice = aws.String(e.SpotPrice)
}
if e.SSHKey != nil {
if e.SSHKey.Name == nil {
return fmt.Errorf("SSHKey Name not set")
}
cf.KeyName = e.SSHKey.Name
}
for _, sg := range e.SecurityGroups {
cf.SecurityGroups = append(cf.SecurityGroups, sg.CloudformationLink())
}
cf.AssociatePublicIpAddress = e.AssociatePublicIP
{
rootDevices, err := e.buildRootDevice(cloud)
if err != nil {
return err
}
ephemeralDevices, err := buildEphemeralDevices(e.InstanceType)
if err != nil {
return err
}
if len(rootDevices) != 0 {
if len(rootDevices) != 1 {
return fmt.Errorf("unexpectedly found multiple root devices")
}
for deviceName, bdm := range rootDevices {
d := &cloudformationBlockDevice{
DeviceName: fi.String(deviceName),
Ebs: &cloudformationBlockDeviceEBS{
VolumeType: bdm.EbsVolumeType,
VolumeSize: bdm.EbsVolumeSize,
DeleteOnTermination: fi.Bool(true),
},
}
cf.BlockDeviceMappings = append(cf.BlockDeviceMappings, d)
}
}
if len(ephemeralDevices) != 0 {
for deviceName, bdm := range ephemeralDevices {
cf.BlockDeviceMappings = append(cf.BlockDeviceMappings, &cloudformationBlockDevice{
VirtualName: bdm.VirtualName,
DeviceName: fi.String(deviceName),
})
}
}
}
if e.UserData != nil {
d, err := e.UserData.AsBytes()
if err != nil {
return fmt.Errorf("error rendering AutoScalingLaunchConfiguration UserData: %v", err)
}
cf.UserData = aws.String(base64.StdEncoding.EncodeToString(d))
}
if e.IAMInstanceProfile != nil {
cf.IAMInstanceProfile = e.IAMInstanceProfile.CloudformationLink()
}
// So that we can update configurations
//tf.Lifecycle = &cloudformation.Lifecycle{CreateBeforeDestroy: fi.Bool(true)}
return t.RenderResource("AWS::AutoScaling::LaunchConfiguration", *e.Name, cf)
}
func (e *LaunchConfiguration) CloudformationLink() *cloudformation.Literal {
return cloudformation.Ref("AWS::AutoScaling::LaunchConfiguration", *e.Name)
}

View File

@ -30,6 +30,7 @@ import (
"github.com/golang/glog"
"k8s.io/kops/upup/pkg/fi"
"k8s.io/kops/upup/pkg/fi/cloudup/awsup"
"k8s.io/kops/upup/pkg/fi/cloudup/cloudformation"
"k8s.io/kops/upup/pkg/fi/cloudup/terraform"
"sort"
)
@ -586,3 +587,137 @@ func (e *LoadBalancer) TerraformLink(params ...string) *terraform.Literal {
}
return terraform.LiteralProperty("aws_elb", *e.Name, prop)
}
type cloudformationLoadBalancer struct {
Name *string `json:"LoadBalancerName,omitempty"`
Listener []*cloudformationLoadBalancerListener `json:"Listeners,omitempty"`
SecurityGroups []*cloudformation.Literal `json:"SecurityGroups,omitempty"`
Subnets []*cloudformation.Literal `json:"Subnets,omitempty"`
Scheme *string `json:"Scheme,omitempty"`
HealthCheck *cloudformationLoadBalancerHealthCheck `json:"HealthCheck,omitempty"`
AccessLog *cloudformationLoadBalancerAccessLog `json:"AccessLoggingPolicy,omitempty"`
ConnectionDrainingPolicy *cloudformationConnectionDrainingPolicy `json:"ConnectionDrainingPolicy,omitempty"`
ConnectionSettings *cloudformationConnectionSettings `json:"ConnectionSettings,omitempty"`
CrossZoneLoadBalancing *bool `json:"CrossZone,omitempty"`
Tags []cloudformationTag `json:"Tags,omitempty"`
}
type cloudformationLoadBalancerListener struct {
InstancePort int `json:"InstancePort"`
InstanceProtocol string `json:"InstanceProtocol"`
LoadBalancerPort int64 `json:"LoadBalancerPort"`
LoadBalancerProtocol string `json:"Protocol"`
}
type cloudformationLoadBalancerHealthCheck struct {
Target *string `json:"Target"`
HealthyThreshold *int64 `json:"HealthyThreshold"`
UnhealthyThreshold *int64 `json:"UnhealthyThreshold"`
Interval *int64 `json:"Interval"`
Timeout *int64 `json:"Timeout"`
}
type cloudformationConnectionDrainingPolicy struct {
Enabled *bool `json:"Enabled,omitempty"`
Timeout *int64 `json:"Timeout,omitempty"`
}
type cloudformationConnectionSettings struct {
IdleTimeout *int64 `json:"IdleTimeout,omitempty"`
}
func (_ *LoadBalancer) RenderCloudformation(t *cloudformation.CloudformationTarget, a, e, changes *LoadBalancer) error {
// TODO: From http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-elb.html:
// If this resource has a public IP address and is also in a VPC that is defined in the same template,
// you must use the DependsOn attribute to declare a dependency on the VPC-gateway attachment.
cloud := t.Cloud.(awsup.AWSCloud)
elbName := e.ID
if elbName == nil {
elbName = e.Name
}
tf := &cloudformationLoadBalancer{
Name: elbName,
Scheme: e.Scheme,
}
for _, subnet := range e.Subnets {
tf.Subnets = append(tf.Subnets, subnet.CloudformationLink())
}
for _, sg := range e.SecurityGroups {
tf.SecurityGroups = append(tf.SecurityGroups, sg.CloudformationLink())
}
for loadBalancerPort, listener := range e.Listeners {
loadBalancerPortInt, err := strconv.ParseInt(loadBalancerPort, 10, 64)
if err != nil {
return fmt.Errorf("error parsing load balancer listener port: %q", loadBalancerPort)
}
tf.Listener = append(tf.Listener, &cloudformationLoadBalancerListener{
InstanceProtocol: "TCP",
InstancePort: listener.InstancePort,
LoadBalancerPort: loadBalancerPortInt,
LoadBalancerProtocol: "TCP",
})
}
if e.HealthCheck != nil {
tf.HealthCheck = &cloudformationLoadBalancerHealthCheck{
Target: e.HealthCheck.Target,
HealthyThreshold: e.HealthCheck.HealthyThreshold,
UnhealthyThreshold: e.HealthCheck.UnhealthyThreshold,
Interval: e.HealthCheck.Interval,
Timeout: e.HealthCheck.Timeout,
}
}
if e.AccessLog != nil {
tf.AccessLog = &cloudformationLoadBalancerAccessLog{
EmitInterval: e.AccessLog.EmitInterval,
Enabled: e.AccessLog.Enabled,
S3BucketName: e.AccessLog.S3BucketName,
S3BucketPrefix: e.AccessLog.S3BucketPrefix,
}
}
if e.ConnectionDraining != nil {
tf.ConnectionDrainingPolicy = &cloudformationConnectionDrainingPolicy{
Enabled: e.ConnectionDraining.Enabled,
Timeout: e.ConnectionDraining.Timeout,
}
}
if e.ConnectionSettings != nil {
tf.ConnectionSettings = &cloudformationConnectionSettings{
IdleTimeout: e.ConnectionSettings.IdleTimeout,
}
}
if e.CrossZoneLoadBalancing != nil {
tf.CrossZoneLoadBalancing = e.CrossZoneLoadBalancing.Enabled
}
tf.Tags = buildCloudformationTags(cloud.BuildTags(e.Name))
return t.RenderResource("AWS::ElasticLoadBalancing::LoadBalancer", *e.Name, tf)
}
func (e *LoadBalancer) CloudformationLink() *cloudformation.Literal {
return cloudformation.Ref("AWS::ElasticLoadBalancing::LoadBalancer", *e.Name)
}
func (e *LoadBalancer) CloudformationAttrCanonicalHostedZoneNameID() *cloudformation.Literal {
return cloudformation.GetAtt("AWS::ElasticLoadBalancing::LoadBalancer", *e.Name, "CanonicalHostedZoneNameID")
}
func (e *LoadBalancer) CloudformationAttrDNSName() *cloudformation.Literal {
return cloudformation.GetAtt("AWS::ElasticLoadBalancing::LoadBalancer", *e.Name, "DNSName")
}

View File

@ -25,6 +25,7 @@ import (
"github.com/golang/glog"
"k8s.io/kops/upup/pkg/fi"
"k8s.io/kops/upup/pkg/fi/cloudup/awsup"
"k8s.io/kops/upup/pkg/fi/cloudup/cloudformation"
"k8s.io/kops/upup/pkg/fi/cloudup/terraform"
)
@ -160,3 +161,23 @@ func (e *LoadBalancerAttachment) TerraformLink() *terraform.Literal {
}
return nil
}
func (_ *LoadBalancerAttachment) RenderCloudformation(t *cloudformation.CloudformationTarget, a, e, changes *LoadBalancerAttachment) error {
if e.AutoscalingGroup != nil {
cfObj, ok := t.Find(e.AutoscalingGroup.CloudformationLink())
if !ok {
// topo-sort fail?
return fmt.Errorf("AutoScalingGroup not yet rendered")
}
cf, ok := cfObj.(*cloudformationAutoscalingGroup)
if !ok {
return fmt.Errorf("unexpected type for CF record: %T", cfObj)
}
cf.LoadBalancerNames = append(cf.LoadBalancerNames, e.LoadBalancer.CloudformationLink())
}
if e.Instance != nil {
return fmt.Errorf("expected Instance to be nil")
}
return nil
}

View File

@ -38,10 +38,17 @@ func (_ *LoadBalancerAccessLog) GetDependencies(tasks map[string]fi.Task) []fi.T
}
type terraformLoadBalancerAccessLog struct {
EmitInterval *int64 `json:"internal,omitempty"`
EmitInterval *int64 `json:"interval,omitempty"`
Enabled *bool `json:"enabled,omitempty"`
S3BucketName *string `json:"bucket,omitempty"`
S3BucketPrefix *string `json:"bucekt_prefix,omitempty"`
S3BucketPrefix *string `json:"bucket_prefix,omitempty"`
}
type cloudformationLoadBalancerAccessLog struct {
EmitInterval *int64 `json:"EmitInterval,omitempty"`
Enabled *bool `json:"Enabled,omitempty"`
S3BucketName *string `json:"S3BucketName,omitempty"`
S3BucketPrefix *string `json:"S3BucketPrefix,omitempty"`
}
//type LoadBalancerAdditionalAttribute struct {

View File

@ -24,6 +24,7 @@ import (
"github.com/golang/glog"
"k8s.io/kops/upup/pkg/fi"
"k8s.io/kops/upup/pkg/fi/cloudup/awsup"
"k8s.io/kops/upup/pkg/fi/cloudup/cloudformation"
"k8s.io/kops/upup/pkg/fi/cloudup/terraform"
)
@ -340,3 +341,21 @@ func (_ *NatGateway) RenderTerraform(t *terraform.TerraformTarget, a, e, changes
func (e *NatGateway) TerraformLink() *terraform.Literal {
return terraform.LiteralProperty("aws_nat_gateway", *e.Name, "id")
}
type cloudformationNATGateway struct {
AllocationID *cloudformation.Literal `json:"AllocationId,omitempty"`
SubnetID *cloudformation.Literal `json:"SubnetId,omitempty"`
}
func (_ *NatGateway) RenderCloudformation(t *cloudformation.CloudformationTarget, a, e, changes *NatGateway) error {
tf := &cloudformationNATGateway{
AllocationID: e.ElasticIP.CloudformationAllocationID(),
SubnetID: e.Subnet.CloudformationLink(),
}
return t.RenderResource("AWS::EC2::NatGateway", *e.Name, tf)
}
func (e *NatGateway) CloudformationLink() *cloudformation.Literal {
return cloudformation.Ref("AWS::EC2::NatGateway", *e.Name)
}

View File

@ -24,6 +24,7 @@ import (
"github.com/golang/glog"
"k8s.io/kops/upup/pkg/fi"
"k8s.io/kops/upup/pkg/fi/cloudup/awsup"
"k8s.io/kops/upup/pkg/fi/cloudup/cloudformation"
"k8s.io/kops/upup/pkg/fi/cloudup/terraform"
)
@ -221,7 +222,6 @@ type terraformRoute struct {
InternetGatewayID *terraform.Literal `json:"gateway_id,omitempty"`
NATGatewayID *terraform.Literal `json:"nat_gateway_id,omitempty"`
InstanceID *terraform.Literal `json:"instance_id,omitempty"`
// TODO Kris - Add terraform support for NAT Gateway routes
}
func (_ *Route) RenderTerraform(t *terraform.TerraformTarget, a, e, changes *Route) error {
@ -244,3 +244,33 @@ func (_ *Route) RenderTerraform(t *terraform.TerraformTarget, a, e, changes *Rou
return t.RenderResource("aws_route", *e.Name, tf)
}
type cloudformationRoute struct {
RouteTableID *cloudformation.Literal `json:"RouteTableId"`
CIDR *string `json:"DestinationCidrBlock,omitempty"`
InternetGatewayID *cloudformation.Literal `json:"GatewayId,omitempty"`
NATGatewayID *cloudformation.Literal `json:"NatGatewayId,omitempty"`
InstanceID *cloudformation.Literal `json:"InstanceId,omitempty"`
}
func (_ *Route) RenderCloudformation(t *cloudformation.CloudformationTarget, a, e, changes *Route) error {
tf := &cloudformationRoute{
CIDR: e.CIDR,
RouteTableID: e.RouteTable.CloudformationLink(),
}
if e.InternetGateway == nil && e.NatGateway == nil {
return fmt.Errorf("missing target for route")
} else if e.InternetGateway != nil {
tf.InternetGatewayID = e.InternetGateway.CloudformationLink()
} else if e.NatGateway != nil {
tf.NATGatewayID = e.NatGateway.CloudformationLink()
}
if e.Instance != nil {
return fmt.Errorf("instance cloudformation routes not yet implemented")
//tf.InstanceID = e.Instance.CloudformationLink()
}
return t.RenderResource("AWS::EC2::Route", *e.Name, tf)
}

View File

@ -23,6 +23,7 @@ import (
"github.com/golang/glog"
"k8s.io/kops/upup/pkg/fi"
"k8s.io/kops/upup/pkg/fi/cloudup/awsup"
"k8s.io/kops/upup/pkg/fi/cloudup/cloudformation"
"k8s.io/kops/upup/pkg/fi/cloudup/terraform"
)
@ -147,3 +148,23 @@ func (_ *RouteTable) RenderTerraform(t *terraform.TerraformTarget, a, e, changes
func (e *RouteTable) TerraformLink() *terraform.Literal {
return terraform.LiteralProperty("aws_route_table", *e.Name, "id")
}
type cloudformationRouteTable struct {
VPCID *cloudformation.Literal `json:"VpcId,omitempty"`
Tags []cloudformationTag `json:"Tags,omitempty"`
}
func (_ *RouteTable) RenderCloudformation(t *cloudformation.CloudformationTarget, a, e, changes *RouteTable) error {
cloud := t.Cloud.(awsup.AWSCloud)
cf := &cloudformationRouteTable{
VPCID: e.VPC.CloudformationLink(),
Tags: buildCloudformationTags(cloud.BuildTags(e.Name)),
}
return t.RenderResource("AWS::EC2::RouteTable", *e.Name, cf)
}
func (e *RouteTable) CloudformationLink() *cloudformation.Literal {
return cloudformation.Ref("AWS::EC2::RouteTable", *e.Name)
}

View File

@ -24,6 +24,7 @@ import (
"github.com/golang/glog"
"k8s.io/kops/upup/pkg/fi"
"k8s.io/kops/upup/pkg/fi/cloudup/awsup"
"k8s.io/kops/upup/pkg/fi/cloudup/cloudformation"
"k8s.io/kops/upup/pkg/fi/cloudup/terraform"
)
@ -196,3 +197,21 @@ func (_ *RouteTableAssociation) RenderTerraform(t *terraform.TerraformTarget, a,
func (e *RouteTableAssociation) TerraformLink() *terraform.Literal {
return terraform.LiteralSelfLink("aws_route_table_association", *e.Name)
}
type cloudformationRouteTableAssociation struct {
SubnetID *cloudformation.Literal `json:"SubnetId,omitempty"`
RouteTableID *cloudformation.Literal `json:"RouteTableId,omitempty"`
}
func (_ *RouteTableAssociation) RenderCloudformation(t *cloudformation.CloudformationTarget, a, e, changes *RouteTableAssociation) error {
cf := &cloudformationRouteTableAssociation{
SubnetID: e.Subnet.CloudformationLink(),
RouteTableID: e.RouteTable.CloudformationLink(),
}
return t.RenderResource("AWS::EC2::SubnetRouteTableAssociation", *e.Name, cf)
}
func (e *RouteTableAssociation) CloudformationLink() *cloudformation.Literal {
return cloudformation.Ref("AWS::EC2::SubnetRouteTableAssociation", *e.Name)
}

View File

@ -24,6 +24,7 @@ import (
"github.com/golang/glog"
"k8s.io/kops/upup/pkg/fi"
"k8s.io/kops/upup/pkg/fi/cloudup/awsup"
"k8s.io/kops/upup/pkg/fi/cloudup/cloudformation"
"k8s.io/kops/upup/pkg/fi/cloudup/terraform"
"strconv"
"strings"
@ -182,6 +183,30 @@ func (e *SecurityGroup) TerraformLink() *terraform.Literal {
return terraform.LiteralProperty("aws_security_group", *e.Name, "id")
}
type cloudformationSecurityGroup struct {
//Name *string `json:"name"`
VpcId *cloudformation.Literal `json:"VpcId"`
Description *string `json:"GroupDescription"`
Tags []cloudformationTag `json:"Tags,omitempty"`
}
func (_ *SecurityGroup) RenderCloudformation(t *cloudformation.CloudformationTarget, a, e, changes *SecurityGroup) error {
cloud := t.Cloud.(awsup.AWSCloud)
tf := &cloudformationSecurityGroup{
//Name: e.Name,
VpcId: e.VPC.CloudformationLink(),
Description: e.Description,
Tags: buildCloudformationTags(cloud.BuildTags(e.Name)),
}
return t.RenderResource("AWS::EC2::SecurityGroup", *e.Name, tf)
}
func (e *SecurityGroup) CloudformationLink() *cloudformation.Literal {
return cloudformation.Ref("AWS::EC2::SecurityGroup", *e.Name)
}
type deleteSecurityGroupRule struct {
groupID *string
permission *ec2.IpPermission

View File

@ -24,6 +24,7 @@ import (
"github.com/golang/glog"
"k8s.io/kops/upup/pkg/fi"
"k8s.io/kops/upup/pkg/fi/cloudup/awsup"
"k8s.io/kops/upup/pkg/fi/cloudup/cloudformation"
"k8s.io/kops/upup/pkg/fi/cloudup/terraform"
"k8s.io/kubernetes/pkg/util/validation/field"
"strings"
@ -331,3 +332,53 @@ func (_ *SecurityGroupRule) RenderTerraform(t *terraform.TerraformTarget, a, e,
}
return t.RenderResource("aws_security_group_rule", *e.Name, tf)
}
type cloudformationSecurityGroupIngress struct {
SecurityGroup *cloudformation.Literal `json:"GroupId,omitempty"`
SourceGroup *cloudformation.Literal `json:"SourceSecurityGroupId,omitempty"`
FromPort *int64 `json:"FromPort,omitempty"`
ToPort *int64 `json:"ToPort,omitempty"`
Protocol *string `json:"IpProtocol,omitempty"`
CidrIp *string `json:"CidrIp,omitempty"`
}
func (_ *SecurityGroupRule) RenderCloudformation(t *cloudformation.CloudformationTarget, a, e, changes *SecurityGroupRule) error {
cfType := "AWS::EC2::SecurityGroupIngress"
if fi.BoolValue(e.Egress) {
cfType = "AWS::EC2::SecurityGroupEgress"
}
tf := &cloudformationSecurityGroupIngress{
SecurityGroup: e.SecurityGroup.CloudformationLink(),
FromPort: e.FromPort,
ToPort: e.ToPort,
Protocol: e.Protocol,
}
if e.Protocol == nil {
tf.Protocol = fi.String("-1")
tf.FromPort = fi.Int64(0)
tf.ToPort = fi.Int64(0)
}
if tf.FromPort == nil {
// FromPort is required by tf
tf.FromPort = fi.Int64(0)
}
if tf.ToPort == nil {
// ToPort is required by tf
tf.ToPort = fi.Int64(65535)
}
if e.SourceGroup != nil {
tf.SourceGroup = e.SourceGroup.CloudformationLink()
}
if e.CIDR != nil {
tf.CidrIp = e.CIDR
}
return t.RenderResource(cfType, *e.Name, tf)
}

View File

@ -33,6 +33,7 @@ import (
"golang.org/x/crypto/ssh"
"k8s.io/kops/upup/pkg/fi"
"k8s.io/kops/upup/pkg/fi/cloudup/awsup"
"k8s.io/kops/upup/pkg/fi/cloudup/cloudformation"
"k8s.io/kops/upup/pkg/fi/cloudup/terraform"
"k8s.io/kops/upup/pkg/fi/utils"
)
@ -55,6 +56,10 @@ func (e *SSHKey) CompareWithID() *string {
func (e *SSHKey) Find(c *fi.Context) (*SSHKey, error) {
cloud := c.Cloud.(awsup.AWSCloud)
return e.find(cloud)
}
func (e *SSHKey) find(cloud awsup.AWSCloud) (*SSHKey, error) {
request := &ec2.DescribeKeyPairsInput{
KeyNames: []*string{e.Name},
}
@ -212,28 +217,34 @@ func (s *SSHKey) CheckChanges(a, e, changes *SSHKey) error {
return nil
}
func (e *SSHKey) createKeypair(cloud awsup.AWSCloud) error {
glog.V(2).Infof("Creating SSHKey with Name:%q", *e.Name)
request := &ec2.ImportKeyPairInput{
KeyName: e.Name,
}
if e.PublicKey != nil {
d, err := e.PublicKey.AsBytes()
if err != nil {
return fmt.Errorf("error rendering SSHKey PublicKey: %v", err)
}
request.PublicKeyMaterial = d
}
response, err := cloud.EC2().ImportKeyPair(request)
if err != nil {
return fmt.Errorf("error creating SSHKey: %v", err)
}
e.KeyFingerprint = response.KeyFingerprint
return nil
}
func (_ *SSHKey) RenderAWS(t *awsup.AWSAPITarget, a, e, changes *SSHKey) error {
if a == nil {
glog.V(2).Infof("Creating SSHKey with Name:%q", *e.Name)
request := &ec2.ImportKeyPairInput{
KeyName: e.Name,
}
if e.PublicKey != nil {
d, err := e.PublicKey.AsBytes()
if err != nil {
return fmt.Errorf("error rendering SSHKey PublicKey: %v", err)
}
request.PublicKeyMaterial = d
}
response, err := t.Cloud.EC2().ImportKeyPair(request)
if err != nil {
return fmt.Errorf("error creating SSHKey: %v", err)
}
e.KeyFingerprint = response.KeyFingerprint
return e.createKeypair(t.Cloud)
}
// No tags on SSH public key
@ -264,3 +275,23 @@ func (e *SSHKey) TerraformLink() *terraform.Literal {
tfName := strings.Replace(*e.Name, ":", "", -1)
return terraform.LiteralProperty("aws_key_pair", tfName, "id")
}
func (_ *SSHKey) RenderCloudformation(t *cloudformation.CloudformationTarget, a, e, changes *SSHKey) error {
cloud := t.Cloud.(awsup.AWSCloud)
glog.Warningf("Cloudformation does not manage SSH keys; pre-creating SSH key")
a, err := e.find(cloud)
if err != nil {
return err
}
if a == nil {
err := e.createKeypair(cloud)
if err != nil {
return err
}
}
return nil
}

View File

@ -23,6 +23,7 @@ import (
"github.com/golang/glog"
"k8s.io/kops/upup/pkg/fi"
"k8s.io/kops/upup/pkg/fi/cloudup/awsup"
"k8s.io/kops/upup/pkg/fi/cloudup/cloudformation"
"k8s.io/kops/upup/pkg/fi/cloudup/terraform"
"k8s.io/kops/upup/pkg/fi/utils"
)
@ -222,3 +223,43 @@ func (e *Subnet) TerraformLink() *terraform.Literal {
return terraform.LiteralProperty("aws_subnet", *e.Name, "id")
}
type cloudformationSubnet struct {
VPCID *cloudformation.Literal `json:"VpcId,omitempty"`
CIDR *string `json:"CidrBlock,omitempty"`
AvailabilityZone *string `json:"AvailabilityZone,omitempty"`
Tags []cloudformationTag `json:"Tags,omitempty"`
}
func (_ *Subnet) RenderCloudformation(t *cloudformation.CloudformationTarget, a, e, changes *Subnet) error {
cloud := t.Cloud.(awsup.AWSCloud)
shared := fi.BoolValue(e.Shared)
if shared {
// Not cloudformation owned / managed
return nil
}
cf := &cloudformationSubnet{
VPCID: e.VPC.CloudformationLink(),
CIDR: e.CIDR,
AvailabilityZone: e.AvailabilityZone,
Tags: buildCloudformationTags(cloud.BuildTags(e.Name)),
}
return t.RenderResource("AWS::EC2::Subnet", *e.Name, cf)
}
func (e *Subnet) CloudformationLink() *cloudformation.Literal {
shared := fi.BoolValue(e.Shared)
if shared {
if e.ID == nil {
glog.Fatalf("ID must be set, if subnet is shared: %s", e)
}
glog.V(4).Infof("reusing existing subnet with id %q", *e.ID)
return cloudformation.LiteralString(*e.ID)
}
return cloudformation.Ref("AWS::EC2::Subnet", *e.Name)
}

View File

@ -25,6 +25,7 @@ import (
"k8s.io/kops/pkg/featureflag"
"k8s.io/kops/upup/pkg/fi"
"k8s.io/kops/upup/pkg/fi/cloudup/awsup"
"k8s.io/kops/upup/pkg/fi/cloudup/cloudformation"
"k8s.io/kops/upup/pkg/fi/cloudup/terraform"
)
@ -229,3 +230,43 @@ func (e *VPC) TerraformLink() *terraform.Literal {
return terraform.LiteralProperty("aws_vpc", *e.Name, "id")
}
type cloudformationVPC struct {
CidrBlock *string `json:"CidrBlock,omitempty"`
EnableDnsHostnames *bool `json:"EnableDnsHostnames,omitempty"`
EnableDnsSupport *bool `json:"EnableDnsSupport,omitempty"`
Tags []cloudformationTag `json:"Tags,omitempty"`
}
func (_ *VPC) RenderCloudformation(t *cloudformation.CloudformationTarget, a, e, changes *VPC) error {
cloud := t.Cloud.(awsup.AWSCloud)
shared := fi.BoolValue(e.Shared)
if shared {
// Not cloudformation owned / managed
return nil
}
tf := &cloudformationVPC{
CidrBlock: e.CIDR,
EnableDnsHostnames: e.EnableDNSHostnames,
EnableDnsSupport: e.EnableDNSSupport,
Tags: buildCloudformationTags(cloud.BuildTags(e.Name)),
}
return t.RenderResource("AWS::EC2::VPC", *e.Name, tf)
}
func (e *VPC) CloudformationLink() *cloudformation.Literal {
shared := fi.BoolValue(e.Shared)
if shared {
if e.ID == nil {
glog.Fatalf("ID must be set, if VPC is shared: %s", e)
}
glog.V(4).Infof("reusing existing VPC with id %q", *e.ID)
return cloudformation.LiteralString(*e.ID)
}
return cloudformation.Ref("AWS::EC2::VPC", *e.Name)
}

View File

@ -23,6 +23,7 @@ import (
"github.com/golang/glog"
"k8s.io/kops/upup/pkg/fi"
"k8s.io/kops/upup/pkg/fi/cloudup/awsup"
"k8s.io/kops/upup/pkg/fi/cloudup/cloudformation"
"k8s.io/kops/upup/pkg/fi/cloudup/terraform"
)
@ -111,3 +112,17 @@ func (_ *VPCDHCPOptionsAssociation) RenderTerraform(t *terraform.TerraformTarget
return t.RenderResource("aws_vpc_dhcp_options_association", *e.Name, tf)
}
type cloudformationVPCDHCPOptionsAssociation struct {
VpcId *cloudformation.Literal `json:"VpcId"`
DhcpOptionsId *cloudformation.Literal `json:"DhcpOptionsId"`
}
func (_ *VPCDHCPOptionsAssociation) RenderCloudformation(t *cloudformation.CloudformationTarget, a, e, changes *VPCDHCPOptionsAssociation) error {
tf := &cloudformationVPCDHCPOptionsAssociation{
VpcId: e.VPC.CloudformationLink(),
DhcpOptionsId: e.DHCPOptions.CloudformationLink(),
}
return t.RenderResource("AWS::EC2::VPCDHCPOptionsAssociation", *e.Name, tf)
}

View File

@ -0,0 +1,93 @@
/*
Copyright 2017 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 cloudformation
import (
"encoding/json"
)
type Literal struct {
json interface{}
}
var _ json.Marshaler = &Literal{}
func (l *Literal) MarshalJSON() ([]byte, error) {
return json.Marshal(&l.json)
}
func (l *Literal) extractRef() string {
m, ok := l.json.(map[string]interface{})
if !ok {
return ""
}
ref := m["Ref"]
if ref == nil {
return ""
}
s, ok := ref.(string)
if !ok {
return ""
}
return s
}
func literalRef(s string) *Literal {
j := make(map[string]interface{})
j["Ref"] = s
return &Literal{json: j}
}
func Ref(resourceType, resourceName string) *Literal {
return literalRef(sanitizeCloudformationResourceName(resourceType + "::" + resourceName))
}
func GetAtt(resourceType, resourceName string, attribute string) *Literal {
path := []string{
sanitizeCloudformationResourceName(resourceType + "::" + resourceName),
attribute,
}
j := make(map[string]interface{})
j["Fn::GetAtt"] = path
return &Literal{json: j}
}
func LiteralString(v string) *Literal {
j := &v
return &Literal{json: j}
}
//
//func LiteralSelfLink(resourceType, resourceName string) *Literal {
// return LiteralProperty(resourceType, resourceName, "self_link")
//}
//
//
//func DefaultProperty(resourceType, resourceName string) *Literal {
// return LiteralProperty(resourceType, resourceName, "")
//}
//
//func LiteralProperty(resourceType, resourceName, prop string) *Literal {
// tfName := sanitizeCloudformationResourceName( resourceType + "::" + resourceName)
//
// expr := "${" + resourceType + "." + tfName + "." + prop + "}"
// return LiteralExpression(expr)
//}
//
//func LiteralFromStringValue(s string) *Literal {
// return &Literal{value: s}
//}

View File

@ -0,0 +1,173 @@
/*
Copyright 2017 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 cloudformation
import (
"encoding/json"
"fmt"
"github.com/golang/glog"
"io/ioutil"
"k8s.io/kops/upup/pkg/fi"
"os"
"path"
"strings"
"sync"
)
type CloudformationTarget struct {
Cloud fi.Cloud
Region string
Project string
outDir string
// mutex protects the following items (resources & files)
mutex sync.Mutex
resources map[string]*cloudformationResource
}
func NewCloudformationTarget(cloud fi.Cloud, region, project string, outDir string) *CloudformationTarget {
return &CloudformationTarget{
Cloud: cloud,
Region: region,
Project: project,
outDir: outDir,
resources: make(map[string]*cloudformationResource),
}
}
var _ fi.Target = &CloudformationTarget{}
type cloudformationResource struct {
Type string
Properties interface{}
}
// A cloudformation resource name must be alphanumeric
func sanitizeCloudformationResourceName(name string) string {
name = strings.Replace(name, ".", "", -1)
name = strings.Replace(name, "-", "", -1)
name = strings.Replace(name, ":", "", -1)
name = strings.Replace(name, "/", "", -1)
return name
}
func (t *CloudformationTarget) ProcessDeletions() bool {
// Terraform tracks & performs deletions itself
return false
}
func (t *CloudformationTarget) RenderResource(resourceType string, resourceName string, e interface{}) error {
res := &cloudformationResource{
Type: resourceType,
Properties: e,
}
name := resourceType + "::" + resourceName
name = sanitizeCloudformationResourceName(name)
t.mutex.Lock()
defer t.mutex.Unlock()
if t.resources[name] != nil {
return fmt.Errorf("resource %q already exists in cloudformation", name)
}
t.resources[name] = res
return nil
}
func (t *CloudformationTarget) Find(ref *Literal) (interface{}, bool) {
key := ref.extractRef()
if key == "" {
glog.Warningf("Unable to extract ref from %v", ref)
return nil, false
}
t.mutex.Lock()
defer t.mutex.Unlock()
r := t.resources[key]
if r == nil {
return nil, false
}
return r.Properties, true
}
func (t *CloudformationTarget) Finish(taskMap map[string]fi.Task) error {
//resourcesByType := make(map[string]map[string]interface{})
//
//for _, res := range t.resources {
// resources := resourcesByType[res.ResourceType]
// if resources == nil {
// resources = make(map[string]interface{})
// resourcesByType[res.ResourceType] = resources
// }
//
// tfName := tfSanitize(res.ResourceName)
//
// if resources[tfName] != nil {
// return fmt.Errorf("duplicate resource found: %s.%s", res.ResourceType, tfName)
// }
//
// resources[tfName] = res.Item
//}
//providersByName := make(map[string]map[string]interface{})
//if t.Cloud.ProviderID() == fi.CloudProviderGCE {
// providerGoogle := make(map[string]interface{})
// providerGoogle["project"] = t.Project
// providerGoogle["region"] = t.Region
// providersByName["google"] = providerGoogle
//} else if t.Cloud.ProviderID() == fi.CloudProviderAWS {
// providerAWS := make(map[string]interface{})
// providerAWS["region"] = t.Region
// providersByName["aws"] = providerAWS
//}
data := make(map[string]interface{})
data["Resources"] = t.resources
//if len(providersByName) != 0 {
// data["provider"] = providersByName
//}
jsonBytes, err := json.MarshalIndent(data, "", " ")
if err != nil {
return fmt.Errorf("error marshalling cloudformation data to json: %v", err)
}
files := make(map[string][]byte)
files["kubernetes.json"] = jsonBytes
for relativePath, contents := range files {
p := path.Join(t.outDir, relativePath)
err = os.MkdirAll(path.Dir(p), os.FileMode(0755))
if err != nil {
return fmt.Errorf("error creating output directory %q: %v", path.Dir(p), err)
}
err = ioutil.WriteFile(p, contents, os.FileMode(0644))
if err != nil {
return fmt.Errorf("error writing cloudformation data to output file %q: %v", p, err)
}
}
glog.Infof("Cloudformation output is in %s", t.outDir)
return nil
}

View File

@ -19,3 +19,4 @@ package cloudup
const TargetDirect = "direct"
const TargetDryRun = "dryrun"
const TargetTerraform = "terraform"
const TargetCloudformation = "cloudformation"