From e8ef987fa2d18266656a2dae636af579f581ebbe Mon Sep 17 00:00:00 2001 From: Vlad Ghinea Date: Wed, 13 Jan 2016 20:37:09 -0600 Subject: [PATCH 01/49] Add a VPC Cloudformation template --- cfn/vpc.json | 185 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 185 insertions(+) create mode 100644 cfn/vpc.json diff --git a/cfn/vpc.json b/cfn/vpc.json new file mode 100644 index 0000000..baff19b --- /dev/null +++ b/cfn/vpc.json @@ -0,0 +1,185 @@ +{ + "Description": "VPC CloudFormation Stack", + + "Mappings": { + "CIDRs": { + "us-east-1": { + "VPC": "10.1.0.0/16", + "Subnet1": "10.1.1.0/24", + "Subnet2": "10.1.2.0/24", + "Subnet3": "10.1.3.0/24", + "Subnet4": "10.1.4.0/24" + } + } + }, + + "Resources": { + "VPC": { + "Type": "AWS::EC2::VPC", + "Properties": { + "EnableDnsSupport": "true", + "EnableDnsHostnames": "true", + "CidrBlock": {"Fn::FindInMap": ["CIDRs", "us-east-1", "VPC"]} + } + }, + + "Subnet1": { + "Type": "AWS::EC2::Subnet", + "DependsOn": "AttachGateway", + "Properties": { + "AvailabilityZone": "us-east-1b", + "VpcId": {"Ref": "VPC" }, + "CidrBlock": {"Fn::FindInMap": ["CIDRs", "us-east-1", "Subnet1"]} + } + }, + "Subnet2": { + "Type": "AWS::EC2::Subnet", + "DependsOn": "AttachGateway", + "Properties": { + "AvailabilityZone": "us-east-1c", + "VpcId": {"Ref": "VPC" }, + "CidrBlock": {"Fn::FindInMap": ["CIDRs", "us-east-1", "Subnet2"]} + } + }, + "Subnet3": { + "Type": "AWS::EC2::Subnet", + "DependsOn": "AttachGateway", + "Properties": { + "AvailabilityZone": "us-east-1d", + "VpcId": {"Ref": "VPC" }, + "CidrBlock": {"Fn::FindInMap": ["CIDRs", "us-east-1", "Subnet3"]} + } + }, + "Subnet4": { + "Type": "AWS::EC2::Subnet", + "DependsOn": "AttachGateway", + "Properties": { + "AvailabilityZone": "us-east-1e", + "VpcId": {"Ref": "VPC" }, + "CidrBlock": {"Fn::FindInMap": ["CIDRs", "us-east-1", "Subnet4"]} + } + }, + + "InternetGateway": { + "Type": "AWS::EC2::InternetGateway" + }, + "AttachGateway": { + "Type": "AWS::EC2::VPCGatewayAttachment", + "Properties": { + "VpcId": {"Ref": "VPC"}, + "InternetGatewayId": {"Ref": "InternetGateway"} + } + }, + + "RouteTable": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": {"Ref": "VPC"} + } + }, + "Route": { + "Type": "AWS::EC2::Route", + "DependsOn": "AttachGateway", + "Properties": { + "RouteTableId": {"Ref": "RouteTable"}, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": {"Ref": "InternetGateway"} + } + }, + "Subnet1RouteTableAssociation": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "SubnetId": {"Ref": "Subnet1"}, + "RouteTableId": {"Ref": "RouteTable"} + } + }, + "Subnet2RouteTableAssociation": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "SubnetId": {"Ref": "Subnet2"}, + "RouteTableId": {"Ref": "RouteTable"} + } + }, + "Subnet3RouteTableAssociation": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "SubnetId": {"Ref": "Subnet3"}, + "RouteTableId": {"Ref": "RouteTable"} + } + }, + "Subnet4RouteTableAssociation": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "SubnetId": {"Ref": "Subnet4"}, + "RouteTableId": {"Ref": "RouteTable"} + } + }, + + "NetworkAcl": { + "Type": "AWS::EC2::NetworkAcl", + "Properties": { + "VpcId": {"Ref": "VPC"} + } + }, + "Subnet1NetworkAclAssociation": { + "Type": "AWS::EC2::SubnetNetworkAclAssociation", + "Properties": { + "SubnetId": {"Ref": "Subnet1"}, + "NetworkAclId": {"Ref": "NetworkAcl"} + } + }, + "Subnet2NetworkAclAssociation": { + "Type": "AWS::EC2::SubnetNetworkAclAssociation", + "Properties": { + "SubnetId": {"Ref": "Subnet2"}, + "NetworkAclId": {"Ref": "NetworkAcl"} + } + }, + "Subnet3NetworkAclAssociation": { + "Type": "AWS::EC2::SubnetNetworkAclAssociation", + "Properties": { + "SubnetId": {"Ref": "Subnet3"}, + "NetworkAclId": {"Ref": "NetworkAcl"} + } + }, + "Subnet4NetworkAclAssociation": { + "Type": "AWS::EC2::SubnetNetworkAclAssociation", + "Properties": { + "SubnetId": {"Ref": "Subnet4"}, + "NetworkAclId": {"Ref": "NetworkAcl"} + } + }, + + "InBoundAllTrafficNetworkAclEntry": { + "Type": "AWS::EC2::NetworkAclEntry", + "Properties": { + "NetworkAclId": {"Ref": "NetworkAcl"}, + "RuleNumber": "100", + "Protocol": "-1", + "RuleAction": "allow", + "Egress": "false", + "CidrBlock": "0.0.0.0/0" + } + }, + "OutBoundAllTrafficNetworkAclEntry": { + "Type": "AWS::EC2::NetworkAclEntry", + "Properties": { + "NetworkAclId": {"Ref": "NetworkAcl"}, + "RuleNumber": "100", + "Protocol": "-1", + "RuleAction": "allow", + "Egress": "true", + "CidrBlock": "0.0.0.0/0" + } + } + }, + + "Outputs": { + "VPCId": { "Description": "VPC ID", "Value": {"Ref": "VPC"} }, + "Subnet1": { "Description": "Subnet1 ID", "Value": {"Ref": "Subnet1"} }, + "Subnet2": { "Description": "Subnet2 ID", "Value": {"Ref": "Subnet2"} }, + "Subnet3": { "Description": "Subnet3 ID", "Value": {"Ref": "Subnet3"} }, + "Subnet4": { "Description": "Subnet4 ID", "Value": {"Ref": "Subnet4"} } + } + +} From 516be5cdc50b821d5cc188188bb755a9d95207c5 Mon Sep 17 00:00:00 2001 From: Vlad Ghinea Date: Thu, 14 Jan 2016 07:39:38 -0600 Subject: [PATCH 02/49] Add security groups cloudformation template --- cfn/sec_grp.json | 76 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 cfn/sec_grp.json diff --git a/cfn/sec_grp.json b/cfn/sec_grp.json new file mode 100644 index 0000000..708cade --- /dev/null +++ b/cfn/sec_grp.json @@ -0,0 +1,76 @@ +{ + "Description": "Security Groups CloudFormation Stack", + + "Parameters": { + "VPCId": { + "Type": "AWS::EC2::VPC::Id", + "Description": "The VPC ID" + } + }, + + "Resources": { + "ELBSecurityGroup": { + "Type": "AWS::EC2::SecurityGroup", + "Properties": { + "GroupDescription": "Elastic Load Balancer Firewall", + "VpcId": {"Ref": "VPCId"} + } + }, + "ELBSecurityGroupAllowHTTP": { + "Type": "AWS::EC2::SecurityGroupIngress", + "Properties": { + "GroupId": {"Fn::GetAtt": ["ELBSecurityGroup", "GroupId"]}, "IpProtocol": "tcp", "FromPort": "80", "ToPort": "80", "CidrIp": "0.0.0.0/0" + } + }, + + "InstanceSecurityGroup": { + "Type": "AWS::EC2::SecurityGroup", + "Properties": { + "GroupDescription": "Instance Firewall", + "VpcId": {"Ref": "VPCId"} + } + }, + "InstanceSecurityGroupAllowSelf": { + "Type": "AWS::EC2::SecurityGroupIngress", + "Properties": { + "GroupId": {"Fn::GetAtt": ["InstanceSecurityGroup", "GroupId"]}, "IpProtocol": "tcp", "FromPort": "0", "ToPort": "65535", "SourceSecurityGroupId": {"Fn::GetAtt": ["InstanceSecurityGroup", "GroupId"]} + } + }, + "InstanceSecurityGroupAllowELB": { + "Type": "AWS::EC2::SecurityGroupIngress", + "Properties": { + "GroupId": {"Fn::GetAtt": ["InstanceSecurityGroup", "GroupId"]}, "IpProtocol": "tcp", "FromPort": "0", "ToPort": "65535", "SourceSecurityGroupId": {"Fn::GetAtt": ["ELBSecurityGroup", "GroupId"]} + } + }, + + "DBSecurityGroup": { + "DependsOn": ["InstanceSecurityGroup"], + "Type": "AWS::EC2::SecurityGroup", + "Properties": { + "GroupDescription": "RDS Firewall", + "VpcId": {"Ref": "VPCId"} + } + }, + "DBSecurityGroupAllowInstance": { + "Type": "AWS::EC2::SecurityGroupIngress", + "Properties": { + "GroupId": {"Fn::GetAtt": ["DBSecurityGroup", "GroupId"]}, "IpProtocol": "tcp", "FromPort": "5432", "ToPort": "5432", "SourceSecurityGroupId": {"Fn::GetAtt": ["InstanceSecurityGroup", "GroupId"]} + } + } + }, + + "Outputs": { + "ELBSecurityGroup": { + "Description": "The Elastic Load Balancer security group", + "Value": {"Ref" : "ELBSecurityGroup"} + }, + "InstanceSecurityGroup": { + "Description": "The instance security group", + "Value": {"Ref" : "InstanceSecurityGroup"} + }, + "DBSecurityGroup": { + "Description": "The database security group", + "Value": {"Ref" : "DBSecurityGroup"} + } + } +} From c3f0bd15f241b557316b1474d0fd5daf6d700f03 Mon Sep 17 00:00:00 2001 From: Vlad Ghinea Date: Thu, 14 Jan 2016 21:06:50 -0600 Subject: [PATCH 03/49] Update README --- README.md | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 1c139e3..05b87e7 100644 --- a/README.md +++ b/README.md @@ -22,16 +22,19 @@ nodes assigned to that environment. ### bin/ Contains various executable scripts. -### data/ -Contains the hiera data files. It's intended to serve as a base only, for -public data, and it should be overwritten or amended with data from private -sources. +### cfn/ +Contains AWS CloudFormation templates. ### dist/ Contains organization-specific roles and profiles. This directory is specified as a modulepath in environment.conf [Designing Puppet – Roles and Profiles.](http://www.craigdunn.org/2012/05/239/) +### hieradata/ +Contains the hiera data files. It's intended to serve as a base only, for +public data, with sane defaults. It should be overwritten or amended with data +from private sources. + ### manifests/ Contains Puppet's manifests: - `bootstrap.pp`: the bootstrapping manifest From 148eced6c1057229673f33c3de825758df1c79dd Mon Sep 17 00:00:00 2001 From: Vlad Ghinea Date: Fri, 15 Jan 2016 07:03:45 -0600 Subject: [PATCH 04/49] Fix alignment --- dist/profile/spec/spec_helper.rb | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/dist/profile/spec/spec_helper.rb b/dist/profile/spec/spec_helper.rb index 64018ed..566fd52 100644 --- a/dist/profile/spec/spec_helper.rb +++ b/dist/profile/spec/spec_helper.rb @@ -15,8 +15,10 @@ SimpleCov.start do add_filter '/spec' add_filter '/vendor' - formatter SimpleCov::Formatter::MultiFormatter.new([ - SimpleCov::Formatter::HTMLFormatter, - SimpleCov::Formatter::Console - ]) + formatter SimpleCov::Formatter::MultiFormatter.new( + [ + SimpleCov::Formatter::HTMLFormatter, + SimpleCov::Formatter::Console + ] + ) end From a0094099ae82a5e5989e7decb0a988a842e625fa Mon Sep 17 00:00:00 2001 From: Vlad Ghinea Date: Fri, 15 Jan 2016 07:04:35 -0600 Subject: [PATCH 05/49] Update modules --- Puppetfile | 21 ++++----------------- dist/profile/.fixtures.yml | 19 ++++++++++++------- dist/profile/metadata.json | 8 ++++---- 3 files changed, 20 insertions(+), 28 deletions(-) diff --git a/Puppetfile b/Puppetfile index eedf3b3..2f4f880 100644 --- a/Puppetfile +++ b/Puppetfile @@ -8,26 +8,13 @@ mod 'puppetlabs/mysql', '3.6.2' mod 'puppetlabs/ntp', '4.1.2' mod 'puppetlabs/stdlib', '4.10.0' mod 'puppetlabs/vcsrepo', '1.3.2' -mod 'concat', - :git => 'https://github.com/puppetlabs/puppetlabs-concat.git', - :tag => '2.0.1' +mod 'puppetlabs/concat', '1.2.5' # Puppet approved modules +mod 'garethr/docker', '5.0.0' +mod 'hunner/hiera', '1.4.1' mod 'maestrodev/wget', '1.7.1' -# This can revert to forge syntax when v5.x is released -# Currently there is now support for the new docker repositories -mod 'docker', - :git => 'https://github.com/garethr/garethr-docker.git', - :ref => 'master' -# This can revert to forge syntax when v1.3.3 is released -# PR #86 is needed -mod 'hiera', - :git => 'https://github.com/hunner/puppet-hiera.git', - :ref => 'master' -# This can revert to forge syntax when version > 3.1.1 is released -mod 'r10k', - :git => 'https://github.com/acidprime/r10k.git', - :ref => 'master' +mod 'zack/r10k', '3.2.0' # Others mod 'saz/limits', '2.3.0' diff --git a/dist/profile/.fixtures.yml b/dist/profile/.fixtures.yml index 858a730..47e936b 100644 --- a/dist/profile/.fixtures.yml +++ b/dist/profile/.fixtures.yml @@ -5,9 +5,18 @@ fixtures: apt: repo: 'puppetlabs/apt' ref: '2.2.1' + concat: + repo: 'puppetlabs/concat' + ref: '1.2.5' git: repo: 'puppetlabs/git' ref: '0.4.0' + docker: + repo: 'garethr/docker' + ref: '5.0.0' + hiera: + repo: 'hunner/hiera' + ref: '1.4.1' inifile: repo: 'puppetlabs/inifile' ref: '1.4.3' @@ -38,10 +47,6 @@ fixtures: sudo: repo: 'saz/sudo' ref: '3.1.0' - repositories: - docker: 'https://github.com/garethr/garethr-docker.git' - hiera: 'https://github.com/hunner/puppet-hiera.git' - r10k: 'https://github.com/acidprime/r10k.git' - concat: - repo: 'https://github.com/puppetlabs/puppetlabs-concat.git' - tag: '2.0.1' + r10k: + repo: 'zack/r10k' + ref: '3.2.0' diff --git a/dist/profile/metadata.json b/dist/profile/metadata.json index a172bc0..6e667c5 100644 --- a/dist/profile/metadata.json +++ b/dist/profile/metadata.json @@ -35,11 +35,11 @@ { "name": "puppetlabs/ntp", "version_requirement": "4.1.2" }, { "name": "puppetlabs/stdlib", "version_requirement": "4.10.0" }, { "name": "puppetlabs/vcsrepo", "version_requirement": "1.3.2" }, - { "name": "puppetlabs/concat" }, + { "name": "puppetlabs/concat", "version_requirement": "1.2.5" }, { "name": "maestrodev/wget", "version_requirement": "1.7.1" }, - { "name": "garethr/docker" }, - { "name": "hunner/hiera" }, - { "name": "zack/r10k" }, + { "name": "garethr/docker", "version_requirement": "5.0.0" }, + { "name": "hunner/hiera", "version_requirement": "1.4.1" }, + { "name": "zack/r10k", "version_requirement": "3.2.0" }, { "name": "saz/limits", "version_requirement": "2.3.0" }, { "name": "saz/locales", "version_requirement": "2.2.2" }, { "name": "saz/ssh", "version_requirement": "2.8.1" }, From a9b4f10c907421cee7dd51fe6f4d6d9bb633ec48 Mon Sep 17 00:00:00 2001 From: Vlad Ghinea Date: Sat, 16 Jan 2016 08:25:49 -0600 Subject: [PATCH 06/49] Improve bootstrap --- bin/bootstrap.sh | 44 ++++++++++++++++++++----------------- dist/role/manifests/none.pp | 2 +- 2 files changed, 25 insertions(+), 21 deletions(-) diff --git a/bin/bootstrap.sh b/bin/bootstrap.sh index 5c18966..844bbbb 100755 --- a/bin/bootstrap.sh +++ b/bin/bootstrap.sh @@ -24,7 +24,6 @@ PP_SECRET=${PP_SECRET:-none} PP_COLLECTION=${PP_COLLECTION:-pc1} PP_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd -P)" PATH="/opt/puppetlabs/bin:/opt/puppetlabs/puppet/bin:${PATH}" -CONFDIR="$(puppet master --configprint confdir)" # Immediately exit on errors set -euo pipefail @@ -98,19 +97,23 @@ configure_puppet(){ echo 'Install/update puppet modules' r10k puppetfile install \ --puppetfile "${PP_DIR}/Puppetfile" \ - --moduledir "${PP_DIR}/modules" \ + --moduledir '/tmp/modules' \ --verbose elif [[ "$PP_MASTER" != 'puppet' ]]; then echo "Set puppet master address - '$PP_MASTER'" puppet config set \ server "$PP_MASTER" --section master fi + + # VARs + PP_CONFDIR="$(puppet master --configprint confdir)" } # Generate certificate request attributes file generate_csr_attributes_file(){ + [[ "$PP_MASTER" == 'none' ]] && return echo 'Generating a CSR Attributes file' - local file="${CONFDIR}/csr_attributes.yaml" + local file="${PP_CONFDIR}/csr_attributes.yaml" local file_path; file_path=$(dirname "$file") # Ensure directory is present @@ -150,24 +153,25 @@ EPP }" > "$file" } +# Apply puppet +apply_puppet(){ + [[ "$PP_MASTER" == 'none' ]] || return 1 + echo 'Applying puppet' + FACTER_ROLE="${PP_ROLE}" puppet apply \ + --modulepath "${PP_DIR}/dist:/tmp/modules" \ + "${PP_DIR}/manifests/site.pp" +} + # Run puppet run_puppet(){ - if [[ "$PP_MASTER" == 'none' ]]; then - echo 'Applying puppet' - FACTER_ROLE="${PP_ROLE}" puppet apply \ - --modulepath "${PP_DIR}/dist:${PP_DIR}/modules" \ - "${PP_DIR}/manifests/site.pp" - elif [[ "$PP_MASTER" != 'puppet' ]]; then - echo 'Running puppet' - puppet agent \ - --server "$PP_MASTER" \ - --waitforcert 5 \ - --no-daemonize \ - --onetime \ - --verbose - else - echo 'WARNING: No puppet master specified' - fi + [[ -n "$PP_MASTER" ]] || return 1 + echo 'Running puppet' + puppet agent \ + --server "$PP_MASTER" \ + --waitforcert 5 \ + --no-daemonize \ + --onetime \ + --verbose } # Logic @@ -176,7 +180,7 @@ main(){ install_release_pkg configure_puppet generate_csr_attributes_file - run_puppet + apply_puppet || run_puppet || echo 'WARNING: No puppet master specified' } # Run diff --git a/dist/role/manifests/none.pp b/dist/role/manifests/none.pp index 8d1056d..d809a32 100644 --- a/dist/role/manifests/none.pp +++ b/dist/role/manifests/none.pp @@ -1,5 +1,5 @@ # No Role Class class role::none { - warning('Nothing to do here. Yet.') + warning('Nothing to do here. For now.') } From 105115c51f663e0cb3f3936d698b32e922452c63 Mon Sep 17 00:00:00 2001 From: Vlad Ghinea Date: Sun, 17 Jan 2016 08:37:37 -0600 Subject: [PATCH 07/49] Add puppet color support in the bootstrap script --- bin/bootstrap.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/bin/bootstrap.sh b/bin/bootstrap.sh index 844bbbb..3f462e9 100755 --- a/bin/bootstrap.sh +++ b/bin/bootstrap.sh @@ -23,6 +23,7 @@ PP_ROLE=${PP_ROLE:-none} PP_SECRET=${PP_SECRET:-none} PP_COLLECTION=${PP_COLLECTION:-pc1} PP_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd -P)" +PP_COLOR=${PP_COLOR:-true} PATH="/opt/puppetlabs/bin:/opt/puppetlabs/puppet/bin:${PATH}" # Immediately exit on errors @@ -158,6 +159,7 @@ apply_puppet(){ [[ "$PP_MASTER" == 'none' ]] || return 1 echo 'Applying puppet' FACTER_ROLE="${PP_ROLE}" puppet apply \ + --color="$PP_COLOR" \ --modulepath "${PP_DIR}/dist:/tmp/modules" \ "${PP_DIR}/manifests/site.pp" } @@ -171,6 +173,7 @@ run_puppet(){ --waitforcert 5 \ --no-daemonize \ --onetime \ + --color="$PP_COLOR" \ --verbose } From afa071a04ef555f0739a2ac60578c0470fce1de1 Mon Sep 17 00:00:00 2001 From: Vlad Ghinea Date: Sun, 17 Jan 2016 09:04:39 -0600 Subject: [PATCH 08/49] Add AWS IAM cloudformation template --- cfn/iam.json | 177 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 177 insertions(+) create mode 100644 cfn/iam.json diff --git a/cfn/iam.json b/cfn/iam.json new file mode 100644 index 0000000..08b3e19 --- /dev/null +++ b/cfn/iam.json @@ -0,0 +1,177 @@ +{ + "Description": "IAM CloudFormation SubStack", + + "Parameters": { + "AssetsBucket": { + "Type": "String", + "Description": "The S3 bucket containing the assets" + } + }, + + "Resources": { + "InstanceProfile": { + "Type": "AWS::IAM::InstanceProfile", + "Properties": { + "Path": "/", + "Roles": [{"Ref": "InstanceRole"}] + } + }, + "InstanceRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Principal": { "Service": [ "ec2.amazonaws.com" ] }, + "Effect": "Allow", + "Action": "sts:AssumeRole" + } + ] + }, + "Path": "/", + "ManagedPolicyArns": ["arn:aws:iam::aws:policy/service-role/AmazonEC2ContainerServiceforEC2Role"], + "Policies":[ + { + "PolicyName": "InstancePolicies", + "PolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "AllowScalingOperations", + "Action": [ + "ec2:Describe*", + "autoscaling:Describe*", + "autoscaling:EnterStandby", + "autoscaling:ExitStandby", + "elasticloadbalancing:ConfigureHealthCheck", + "elasticloadbalancing:DescribeLoadBalancers" + ], + "Resource": "*", + "Effect": "Allow" + }, + { + "Sid": "AllowLogging", + "Effect": "Allow", + "Action": [ + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:PutLogEvents", + "logs:DescribeLogStreams" + ], + "Resource": [ + "arn:aws:logs:*:*:*" + ] + }, + { + "Sid": "AllowListingOfFolder", + "Action": ["s3:ListBucket"], + "Effect": "Allow", + "Resource": [{"Fn::Join": ["", ["arn:aws:s3:::", {"Ref": "AssetsBucket"}]]}], + "Condition": { + "StringLike": {"s3:prefix": [ + "deploy/*", + "private/*", + "backup/*" + ]} + } + }, + { + "Sid": "AllowFullAccessToFolder", + "Effect": "Allow", + "Action": ["s3:*"], + "Resource": [ + {"Fn::Join": ["", ["arn:aws:s3:::", {"Ref": "AssetsBucket"}, "/backup/*"]]} + ] + }, + { + "Sid": "AllowReadOnlyAccessToFolder", + "Effect": "Allow", + "Action": ["s3:Get*", "s3:List*"], + "Resource": [ + {"Fn::Join": ["", ["arn:aws:s3:::", {"Ref": "AssetsBucket"}, "/deploy/*"]]}, + {"Fn::Join": ["", ["arn:aws:s3:::", {"Ref": "AssetsBucket"}, "/private/*"]]} + ] + } + ] + } + } + ] + } + }, + + "CodeDeployRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Principal": { + "Service": [ + "codedeploy.us-east-1.amazonaws.com", + "codedeploy.us-west-2.amazonaws.com" + ] + }, + "Effect": "Allow", + "Action": "sts:AssumeRole" + } + ] + }, + "ManagedPolicyArns": ["arn:aws:iam::aws:policy/service-role/AWSCodeDeployRole"], + "Path": "/" + } + }, + + "CI": { + "Type": "AWS::IAM::User", + "Properties": { + "Policies": [ + { + "PolicyName": "CIAccess", + "PolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "AllowAccessToCodeDeploy", + "Effect": "Allow", + "Action": ["codedeploy:*"], + "Resource": "arn:aws:codedeploy:*" + }, + { + "Sid": "AllowUploadingDeployments", + "Effect": "Allow", + "Action": ["s3:PutObject"], + "Resource": [{"Fn::Join": ["", ["arn:aws:s3:::", {"Ref": "AssetsBucket"}, "/deploy/*"]]}] + } + ] + } + } + ] + } + }, + "CIAccessKey": { + "Type": "AWS::IAM::AccessKey", + "Properties": { + "UserName": {"Ref": "CI"} + } + } + }, + + "Outputs": { + "InstanceProfile": { + "Description": "The Instance Profile", + "Value": {"Ref": "InstanceProfile" } + }, + "CodeDeployServiceRoleARN": { + "Description": "The CodeDeploy service role ARN", + "Value": {"Fn::GetAtt": ["CodeDeployRole", "Arn"]} + }, + "CIAccessKey": { + "Description": "The CI Access Key", + "Value": {"Ref": "CIAccessKey" } + }, + "CISecretKey": { + "Description": "The CI Secret Key", + "Value": {"Fn::GetAtt": [ "CIAccessKey", "SecretAccessKey" ]} + } + } +} From c665520cf7b5c4e699cc3bd26f277cd1fd23fb1a Mon Sep 17 00:00:00 2001 From: Vlad Ghinea Date: Sun, 17 Jan 2016 09:23:34 -0600 Subject: [PATCH 09/49] Validate Cloudformation templates when committing --- bin/pre-commit-hook | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/bin/pre-commit-hook b/bin/pre-commit-hook index 5803f88..eef5f8e 100755 --- a/bin/pre-commit-hook +++ b/bin/pre-commit-hook @@ -17,6 +17,10 @@ [ -s "${HOME}/.rvm/scripts/rvm" ] && source "${HOME}/.rvm/scripts/rvm" [ -d "${HOME}/.rvm" ] && export PATH="$PATH:$HOME/.rvm/bin" +# Add /usr/local/bin to PATH +PATH=/usr/local/bin:${PATH} + +# Check YAML function checkyaml() { ruby -e "require 'yaml'; YAML.load_file('$1')" } @@ -56,6 +60,14 @@ if ! [ -x "$path_to_ruby" ]; then exit 1 fi +path_to_aws=$(command -v aws) +if ! [ -x "$path_to_aws" ]; then + echo "The AWS CLI binary wasn't found." + echo "Sorry, I won't allow you to commit without aws cli installed." + echo "Please install aws cli and try again." + exit 1 +fi + echo "### Checking puppet syntax, for science! ###" # for file in `git diff --name-only --cached | grep -E '\.(pp|erb)'` for file in $(git diff --name-only --cached | grep -E '\.(pp)'); do @@ -134,6 +146,22 @@ for file in $(git diff --name-only --cached | grep -E '\.(yaml)'); do done echo "" +echo "### Checking if CloudFormation syntax is valid ###" +for file in $(git diff --name-only --cached | grep -E 'cfn\/.*\.(json)'); do + if [[ -f $file ]]; then + aws --region 'us-west-2' --output text \ + cloudformation validate-template \ + --template-body "file://${file}" + if [[ $? -ne 0 ]]; then + echo "ERROR: Cloudformation syntax validation failed at: $file" + syntax_is_bad=1 + else + echo "OK: $file looks valid" + fi + fi +done +echo "" + if [[ $syntax_is_bad -eq 1 ]]; then echo echo "################################################################" From fcf83c9bb0a4c9933f72e4ad5a6a850fe8ba7065 Mon Sep 17 00:00:00 2001 From: Vlad Ghinea Date: Sun, 17 Jan 2016 10:11:16 -0600 Subject: [PATCH 10/49] Minor change --- bin/bootstrap.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bin/bootstrap.sh b/bin/bootstrap.sh index 3f462e9..c473cbe 100755 --- a/bin/bootstrap.sh +++ b/bin/bootstrap.sh @@ -17,6 +17,9 @@ # # * Trusted facts info: https://docs.puppetlabs.com/puppet/latest/reference/lang_facts_and_builtin_vars.html#trusted-facts +# Immediately exit on errors +set -euo pipefail + # DEFAULTS PP_MASTER=${PP_MASTER:-puppet} PP_ROLE=${PP_ROLE:-none} @@ -26,9 +29,6 @@ PP_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd -P)" PP_COLOR=${PP_COLOR:-true} PATH="/opt/puppetlabs/bin:/opt/puppetlabs/puppet/bin:${PATH}" -# Immediately exit on errors -set -euo pipefail - # Check if root is_root(){ if [[ $EUID != 0 ]]; then From 40169b330d102011ce50a4a17ac1c70799b9a379 Mon Sep 17 00:00:00 2001 From: Vlad Ghinea Date: Sun, 17 Jan 2016 11:28:01 -0600 Subject: [PATCH 11/49] Add common functions and variables --- bin/common.sh | 32 ++++++++++++++++++++++++++++++++ environment.sh | 5 +++++ 2 files changed, 37 insertions(+) create mode 100755 bin/common.sh create mode 100644 environment.sh diff --git a/bin/common.sh b/bin/common.sh new file mode 100755 index 0000000..f4b3c8f --- /dev/null +++ b/bin/common.sh @@ -0,0 +1,32 @@ +#!/usr/bin/env bash +# Common functions + +# Immediately exit on errors +set -euo pipefail + +# Load environment +# shellcheck disable=1090 +. "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)/../environment.sh" + +# FUNCTIONS +## Check if root +is_root() { [[ $EUID == 0 ]] ;} + +## Check if is Linux +is_linux(){ [[ $(uname) != Linux ]] ;} + +## Check if command exists +is_cmd() { command -v "$@" >/dev/null 2>&1 ;} + +## Get codename +get_dist() { is_cmd lsb_release && lsb_release -cs ;} + +## APT install package +apt_install(){ echo "Installing $*"; apt-get -qy install "$@" < /dev/null ;} + +## Update APT +apt_update() { echo 'Updating APT' && apt-get -qy update < /dev/null ;} + +## Upgrade box +apt_upgrade(){ echo 'Upgrading box' && sudo apt-get -qy upgrade < /dev/null ;} + diff --git a/environment.sh b/environment.sh new file mode 100644 index 0000000..ff13a7e --- /dev/null +++ b/environment.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +# VARs +PATH="/opt/puppetlabs/bin:/opt/puppetlabs/puppet/bin:${PATH}" + From 8aaf7f3ade7496fc013bdc474cc37862e60403ab Mon Sep 17 00:00:00 2001 From: Vlad Ghinea Date: Sun, 17 Jan 2016 11:28:15 -0600 Subject: [PATCH 12/49] Add AWS functions --- bin/aws.sh | 501 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 501 insertions(+) create mode 100755 bin/aws.sh diff --git a/bin/aws.sh b/bin/aws.sh new file mode 100755 index 0000000..8ef66b0 --- /dev/null +++ b/bin/aws.sh @@ -0,0 +1,501 @@ +#!/usr/bin/env bash +# Common AWS functions + +# Load environment +# shellcheck disable=1090 +. "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)/common.sh" + +# NAME: aws_get_metadata +# DESCRIPTION: AWS MetaData Service +aws_get_metadata(){ + wget --timeout=2 --tries=0 -qO- "http://169.254.169.254/latest/meta-data/${*}" +} + +# NAME: aws_get_instance_id +# DESCRIPTION: Returns the EC2 instance ID for the local instance +aws_get_instance_id() { + if [ -z "${INSTANCE_ID}" ]; then + export INSTANCE_ID; INSTANCE_ID=$(aws_get_metadata instance-id || true) + fi + echo "$INSTANCE_ID" +} + +# NAME: aws_get_instance_az +# DESCRIPTION: Returns the the AWS availability zone +aws_get_instance_az() { + local az; az=$(aws_get_metadata placement/availability-zone) + if [[ "$az" =~ ^[a-z]{2}-[a-z]+-[0-9][a-z]$ ]]; then + echo "$az" + else + echo "Invalid availability zone name: '${az}'"; return 1 + fi +} + +# NAME: aws_get_instance_region +# DESCRIPTION: Returns the the AWS region (defaults to us-east-1) +aws_get_instance_region() { + if [ -z "${AWS_REGION}" ]; then + zone=$(aws_get_instance_az) + export AWS_REGION="${zone%?}" + echo "${AWS_REGION:-us-east-1}" + else + echo "$AWS_REGION" + fi +} + +# AWS CLI Command +aws_cmd(){ + /usr/local/bin/aws --region "$(aws_get_instance_region)" "${@}" +} + +# NAME: list_all_tags +# DESCRIPTION: Returns all EC2 tags associated with the current instance +list_all_tags(){ + aws_cmd --output text ec2 describe-tags \ + --filters "Name=resource-id,Values=$(aws_get_instance_id)" \ + --query "Tags[*].[join(\`=\`,[Key,Value])]" 2>/dev/null | \ + awk '{print tolower($0)}' | \ + sed 's/.*/ec2_tag_&/' +} + +# NAME: aws_get_tag +# DESCRIPTION: Gets the value of an ec2 tag. +# USAGE: aws_get_tag {Key Name} {Resource ID} +# PARAMETERS: +# 1) The key name (defaults to 'Name') +# 2) The resource id (defaults to the current instance id) +aws_get_tag(){ + local name=${1:-'Name'} + local instance_id; instance_id=$(aws_get_instance_id) + local resource=${2:-$instance_id} + + aws_cmd --output text ec2 describe-tags \ + --filters "Name=resource-id,Values=${resource}" "Name=key,Values=$name" \ + --query "Tags[*].[Value]" 2>/dev/null +} + +# NAME: aws_get_instance_state_asg +# DESCRIPTION: Gets the state of the given as known by the AutoScaling +# group it's a part of. +# USAGE: aws_get_instance_state_asg {EC2 instance ID} +# PARAMETERS: +# 1) The instance id +aws_get_instance_state_asg() { + local instance_id=$1 + local state; state=$(aws_cmd autoscaling describe-auto-scaling-instances \ + --instance-ids "$instance_id" \ + --query "AutoScalingInstances[?InstanceId == \`$instance_id\`].LifecycleState | [0]" \ + --output text) + if [ $? != 0 ]; then + return 1 + else + echo "$state" + return 0 + fi +} + +# NAME: aws_get_autoscaling_group_name +# DESCRIPTION: Returns the name of the AutoScaling group this instance is a +# part of. +# USAGE: aws_get_autoscaling_group_name {EC2 instance ID} +# PARAMETERS: +# 1) The instance id +aws_get_autoscaling_group_name() { + local instance_id=$1 + local autoscaling_name; autoscaling_name=$(aws_cmd autoscaling \ + describe-auto-scaling-instances \ + --instance-ids "$instance_id" \ + --output text \ + --query AutoScalingInstances[0].AutoScalingGroupName) + + if [ $? != 0 ]; then + return 1 + else + echo "$autoscaling_name" + fi + + return 0 +} + +# NAME: aws_autoscaling_enter_standby +# DESCRIPTION: Move the instance into the Standby state in AutoScaling group +aws_autoscaling_enter_standby(){ + local instance_id; instance_id=$(aws_get_instance_id) + local asg_name; asg_name=$(aws_get_autoscaling_group_name "$instance_id") + + echo "Checking if this instance has already been moved in the Standby state" + local instance_state + instance_state=$(aws_get_instance_state_asg "$instance_id") + if [ $? != 0 ]; then + echo "Unable to get this instance's lifecycle state." + return 1 + fi + + if [ "$instance_state" == "Standby" ]; then + echo "Instance is already in Standby; nothing to do." + return 0 + elif [ "$instance_state" == "Pending" ]; then + echo "Instance is Pending; nothing to do." + return 0 + elif [ "$instance_state" == "Pending:Wait" ]; then + echo "Instance is Pending:Wait; nothing to do." + return 0 + fi + + echo "Putting instance $instance_id into Standby" + aws_cmd autoscaling enter-standby \ + --instance-ids "$instance_id" \ + --auto-scaling-group-name "$asg_name" \ + --should-decrement-desired-capacity + if [ $? != 0 ]; then + echo "Failed to put instance $instance_id into Standby for ASG $asg_name." + return 1 + fi + + printf "Waiting for instance to reach state Standby..." + while [ "$(aws_get_instance_state_asg "$instance_id")" != "Standby" ]; do + printf '.' && sleep 5 + done + echo ' Done.' + + return 0 +} + +# NAME: aws_autoscaling_exit_standby +# DESCRIPTION: Attempts to move instance out of Standby and into InService. +aws_autoscaling_exit_standby(){ + local instance_id; instance_id=$(aws_get_instance_id) + local asg_name; asg_name=$(aws_get_autoscaling_group_name "$instance_id") + + echo "Checking if this instance has already been moved out of Standby state" + local instance_state + instance_state=$(aws_get_instance_state_asg "$instance_id") + if [ $? != 0 ]; then + echo "Unable to get this instance's lifecycle state." + return 1 + fi + + if [ "$instance_state" == "InService" ]; then + echo "Instance is already in InService; nothing to do." + return 0 + elif [ "$instance_state" == "Pending" ]; then + echo "Instance is Pending; nothing to do." + return 0 + elif [ "$instance_state" == "Pending:Wait" ]; then + echo "Instance is Pending:Wait; nothing to do." + return 0 + fi + + echo "Moving instance $instance_id out of Standby" + aws_cmd autoscaling exit-standby \ + --instance-ids "$instance_id" \ + --auto-scaling-group-name "$asg_name" + if [ $? != 0 ]; then + echo "Failed to put instance $instance_id back into InService for ASG $asg_name." + return 1 + fi + + printf "Waiting for instance to reach state InService..." + while [ "$(aws_get_instance_state_asg "$instance_id")" != "InService" ]; do + printf '.' && sleep 5 + done + echo ' Done.' + + return 0 +} + +# NAME: aws_deploy_list_running_deployments +# DESCRIPTION: Returns the a list of running deployments for the given +# CodeDeploy application and group. +# USAGE: aws_deploy_list_running_deployments {App} {Group} +# PARAMETERS: +# 1) The application name +# 2) The deployment group name +aws_deploy_list_running_deployments() { + local app=$1 + local group=$2 + + aws_cmd deploy list-deployments \ + --output text \ + --query 'deployments' \ + --application-name "$1" \ + --deployment-group-name "$2" \ + --include-only-statuses Queued InProgress +} + +# NAME: aws_deploy_group_exists +# DESCRIPTION: Returns true if the given deployment group exists. +# USAGE: aws_deploy_group_exists {App} {Group} +# PARAMETERS: +# 1) The application name +# 2) The deployment group name +aws_deploy_group_exists() { + local app=$1 + local group=$2 + + aws_cmd deploy get-deployment-group \ + --query 'deploymentGroupInfo.deploymentGroupId' --output text \ + --application-name "$1" \ + --deployment-group-name "$2" >/dev/null +} + +# NAME: aws_deploy_wait +# DESCRIPTION: Waits until there are no other deployments in progress for the +# given CodeDeploy application and group. +# USAGE: aws_deploy_wait {App} {Group} +# PARAMETERS: +# 1) The application name +# 2) The deployment group name +aws_deploy_wait() { + local app=$1 + local group=$2 + + echo 'Waiting for other deployments to finish ...' + until [[ -z "$(aws_deploy_list_running_deployments "$app" "$group")" ]]; do + sleep 5 + done + echo ' Done.' +} + +# NAME: aws_deploy_create_deployment +# DESCRIPTION: Creates a CodeDeploy revision. +# USAGE: aws_deploy_create_deployment {App} {Group} {Bucket} {Key} {Bundle} {Config} +# PARAMETERS: +# 1) The application name +# 2) The deployment group name +# 3) The S3 bucket name +# 4) The S3 key name +# 5) The bundle type ("tar"|"tgz"|"zip") +# 6) The deployment config name +aws_deploy_create_deployment(){ + local app=$1 + local group=$2 + local bucket=$3 + local key=$4 + local bundle=$5 + local config=$6 + + if aws_deploy_group_exists "$@"; then + aws_deploy_wait "$@" + + echo "Creating deployment for application '${app}', group '${group}'" + aws_cmd deploy create-deployment \ + --application-name "$app" \ + --s3-location bucket="${bucket}",key="${key}",bundleType="${bundle}" \ + --deployment-group-name "$group" \ + --deployment-config-name "$config" + else + echo "The '${group}' group does not exist in the '${app}' application" + fi +} + +# NAME: aws_get_private_env +# DESCRIPTION: Retrieves a private file from AWS S3, saves it to a .env file +# in the current directory, and applies strict read-only permissions. The file +# is sourced afterwards. This function fails silently, and returns 0. +# USAGE: aws_get_private_env {S3 Path} +# PARAMETERS: +# 1) The S3 path to the file (required) +aws_get_private_env(){ + local src=$1 + local file; file="$(pwd)/.env" + if [ -z "$src" ]; then + echo "Usage: ${FUNCNAME[0]} {S3 Path}" && return + fi + if [ -s "$file" ]; then + echo "File '${file}' already exists. Replacing" + sudo rm "$file" + fi + aws_cmd s3 cp "$src" "$file" || echo "Failed to get ${src}" + if [ -s "$file" ]; then + echo "Setting permissions for ${file}" + sudo chmod 400 "$file" + echo "Loading ${file}" + # shellcheck disable=1090 + . "$file" || echo "Failed to load ${file}" + else + echo "'${file}' does not exist" && return + fi +} + +# NAME: aws_get_ubuntu_official_ami_id +# DESCRIPTION: Retrieves the latest AMI ID for the official Ubuntu image. +# The region should be exported separately as part of the awscli instalation +# (Defaults to us-east-1). +# USAGE: aws_get_ubuntu_official_ami_id {Distribution} {Type} {Arch} \ +# {Virtualization} +# PARAMETERS: +# 1) Distribution code name (defaults to 'trusty') +# 2) Root device type (defaults to 'ebs-ssd') +# 3) Architecture (defaults to 'amd64') +# 4) Virtualization (defaults to 'hvm') +aws_get_ubuntu_official_ami_id() { + local dist=${1:-trusty} + local dtyp=${2:-ebs-ssd} + local arch=${3:-amd64} + local virt=${4:-hvm} + local region=${AWS_REGION:-us-east-1} + + curl -Ls "http://cloud-images.ubuntu.com/query/${dist}/server/released.current.txt" | \ + awk -v region="$region" \ + -v dist="$dist" \ + -v dtyp="$dtyp" \ + -v arch="$arch" \ + -v virt="$virt" \ + '$5 == dtyp && $6 == arch && $7 == region && $9 == virt { print $8 }' +} + +# NAME: aws_cfn_wait_for_stack +# DESCRIPTION: Waits for the CloudFormation stack. +# USAGE: aws_cfn_wait_for_stack {App} {Group} +# PARAMETERS: +# 1) Stack name +aws_cfn_wait_for_stack(){ + local stack="$1" + local status='UNKNOWN_IN_PROGRESS' + + if [[ -z "$stack" ]]; then echo "Usage: ${FUNCNAME[0]} stack"; return 1; fi + + echo "Waiting for $stack to complete ..." >&2 + until [[ $status =~ _(COMPLETE|FAILED)$ ]]; do + sleep 5 + status="$(aws_cmd cloudformation describe-stacks --stack-name "$1" --output text --query 'Stacks[0].StackStatus')" + echo " ... $stack - $status" >&2 + done + + echo "$status" + + # if status is failed or we'd rolled back, assume bad things happened + if [[ $status =~ _FAILED$ ]] || [[ $status =~ ROLLBACK ]]; then + return 1 + fi +} + +# NAME: aws_cf_tail +# DESCRIPTION: Show all events for CF stack until update completes or fails. +# USAGE: aws_cf_tail {Stack name} +# PARAMETERS: +# 1) Stack name (required) +aws_cf_tail() { + if [ -z "$1" ] ; then echo "Usage: ${FUNCNAME[0]} stack"; return 1; fi + local stack + stack="$(basename "$1" .json)" + local current + local final_line + local output + local previous + until echo "$current" | tail -1 | egrep -q "${stack}.*_(COMPLETE|FAILED)" + do + if ! output=$(cf_events "$stack"); then + # Something went wrong with cf_events (like stack not known) + return 1 + fi + if [ -z "$output" ]; then sleep 1; continue; fi + + current=$(echo "$output" | sed '$d') + final_line=$(echo "$output" | tail -1) + if [ -z "$previous" ]; then + echo "$current" + elif [ "$current" != "$previous" ]; then + comm -13 <(echo "$previous") <(echo "$current") + fi + previous="$current" + sleep 1 + done + echo "$final_line" +} + +# NAME: aws_cf_events +# DESCRIPTION: Show all events for CF stack until update completes or fails. +# USAGE: aws_cf_events {Stack name} +# PARAMETERS: +# 1) Stack name (required) +aws_cf_events() { + if [ -z "$1" ] ; then echo "Usage: ${FUNCNAME[0]} stack"; return 1; fi + local stack + stack="$(basename "$1" .json)" + shift + local output + if output=$(aws_cmd --color on cloudformation describe-stack-events --stack-name "$stack" --query 'sort_by(StackEvents, &Timestamp)[].{Resource: LogicalResourceId, Type: ResourceType, Status: ResourceStatus}' --output table "$@"); then + echo "$output" | uniq -u + else + return $? + fi +} + +# NAME: aws_get_elb_name +# DESCRIPTION: Returns the ELB to which the instance is registered. +aws_get_elb_name() { + local instance_id; instance_id=$(aws_get_instance_id) + if output=$(aws_cmd elb describe-load-balancers --query "LoadBalancerDescriptions[?contains(Instances[].InstanceId, \`${instance_id}\`)].LoadBalancerName" --output text); then + echo "$output" + else + return $? + fi +} + +# NAME: aws_elb_get_health_check +# DESCRIPTION: Returns the Elastic Load Balancer health check configuration +aws_elb_get_health_check() { + local elb; elb=$(aws_get_elb_name) + if output=$(aws_cmd elb describe-load-balancers --load-balancer-names "$elb" --query 'LoadBalancerDescriptions[].HealthCheck | [0]'); then + echo "$output" + else + return $? + fi +} + +# NAME: aws_elb_configure_health_check +# DESCRIPTION: Modifies the Elastic Load Balancer' health check configuration +# USAGE: aws_elb_configure_health_check {Config} +# PARAMETERS: +# 1) Configuration (required) +# Ex: Target=HTTP:80/,Interval=10,UnhealthyThreshold=5,HealthyThreshold=2,Timeout=5 +aws_elb_configure_health_check() { + local elb; elb=$(aws_get_elb_name) + if output=$(aws_cmd elb configure-health-check --load-balancer-name "$elb" --health-check "$1"); then + echo "$output" + else + return $? + fi +} + +# NAME: aws_ecs_list_clusters +# DESCRIPTION: Returns a list of EC2 Container Service Clusters +aws_ecs_list_clusters(){ + if output=$(aws_cmd ecs list-clusters --query 'clusterArns[]' --output text); then + echo "$output" + else + return $? + fi +} + +# NAME: aws_mount_efs +# DESCRIPTION: Mount AWS Elastic File System +# USAGE: aws_mount_efs {EFS ID} {path} +# PARAMETERS: +# 1) Elastic File System ID (required) +# 2) The local path where to mount the EFS +aws_mount_efs(){ + local id=$1 + local path=$2 + local zone; zone=$(aws_get_instance_az) + local mnt="${zone}.${id}.efs.${zone%?}.amazonaws.com:/" + + # Check arguments + if [ $# -eq 0 ] ; then echo "Usage: ${FUNCNAME[0]} EFS_ID PATH"; return 1; fi + # Check if NFS tools are installed + if ! is_cmd mount.nfs4; then echo "Install 'nfs-common' first"; return 1; fi + # Check if already mounted + if mount | grep -q "$mnt"; then echo "'${mnt}' already mounted"; return; fi + + if [ ! -d "$path" ]; then + echo "Creating '${path}' and mounting Elastic File System" + mkdir -p "$path" + else + echo "'${path}' already present" + fi + echo "Mounting Elastic File System at '${path}'" + mount -t nfs4 "$mnt" "$path" +} + From d0fbc30f1e9f66de21496c54f619779c018a48ca Mon Sep 17 00:00:00 2001 From: Vlad Ghinea Date: Sun, 17 Jan 2016 13:46:07 -0600 Subject: [PATCH 13/49] Add a private .env file --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index eb8a295..761ffd8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ +# Private +.env + # Everything in data except global.yaml /hieradata/* !/hieradata/common.yaml From 5c035312a43f817359000b22bcd2cba6019f3a7f Mon Sep 17 00:00:00 2001 From: Vlad Ghinea Date: Sun, 17 Jan 2016 13:46:42 -0600 Subject: [PATCH 14/49] Improve shebang --- bin/pre-commit-hook | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/pre-commit-hook b/bin/pre-commit-hook index eef5f8e..c0305d5 100755 --- a/bin/pre-commit-hook +++ b/bin/pre-commit-hook @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash # pre-commit git hook # # Prerequisites: From 5ea6fa7d8f8bdb7df795c8f6eac2d03caeb18a09 Mon Sep 17 00:00:00 2001 From: Vlad Ghinea Date: Sun, 17 Jan 2016 13:47:41 -0600 Subject: [PATCH 15/49] Move functions to a separate include folder --- {bin => include}/aws.sh | 0 {bin => include}/common.sh | 10 +++++++--- 2 files changed, 7 insertions(+), 3 deletions(-) rename {bin => include}/aws.sh (100%) mode change 100755 => 100644 rename {bin => include}/common.sh (75%) mode change 100755 => 100644 diff --git a/bin/aws.sh b/include/aws.sh old mode 100755 new mode 100644 similarity index 100% rename from bin/aws.sh rename to include/aws.sh diff --git a/bin/common.sh b/include/common.sh old mode 100755 new mode 100644 similarity index 75% rename from bin/common.sh rename to include/common.sh index f4b3c8f..6c17f0b --- a/bin/common.sh +++ b/include/common.sh @@ -4,9 +4,14 @@ # Immediately exit on errors set -euo pipefail -# Load environment +# Load global environment # shellcheck disable=1090 -. "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)/../environment.sh" +. "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)/../environment.sh" || true + +# Load private environment if it exists, overriding global variables. +# shellcheck disable=1090 +DOTENV="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)/../.env" +[[ -s "$DOTENV" ]] && . "$DOTENV" # FUNCTIONS ## Check if root @@ -29,4 +34,3 @@ apt_update() { echo 'Updating APT' && apt-get -qy update < /dev/null ;} ## Upgrade box apt_upgrade(){ echo 'Upgrading box' && sudo apt-get -qy upgrade < /dev/null ;} - From 6d6eee50f2a45f4d85a52cd9b6a29130276b767a Mon Sep 17 00:00:00 2001 From: Vlad Ghinea Date: Sun, 17 Jan 2016 13:55:00 -0600 Subject: [PATCH 16/49] Update README --- README.md | 31 +++++++++++++++++++------------ 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 05b87e7..6e14fae 100644 --- a/README.md +++ b/README.md @@ -8,17 +8,6 @@ This project is still in a prototype development stage. Vlad's Puppet Control Repo. ## Description -### Puppetfile -r10k needs this file to figure out what component modules you want from the -Forge. The result is a modules directory containing all the modules specified in -this file, for each environment/branch. The modules directory is listed in -environment.conf's modulepath. - -### environment.conf -This file can override several settings whenever the Puppet master is serving -nodes assigned to that environment. -[Config Files: environment.conf](https://docs.puppetlabs.com/puppet/latest/reference/config_file_environment.html) - ### bin/ Contains various executable scripts. @@ -35,11 +24,29 @@ Contains the hiera data files. It's intended to serve as a base only, for public data, with sane defaults. It should be overwritten or amended with data from private sources. +### include/ +Contains various functions that can be sourced in other scripts. + ### manifests/ Contains Puppet's manifests: - - `bootstrap.pp`: the bootstrapping manifest - `site.pp`: the main manifest +### Puppetfile +r10k needs this file to figure out what component modules you want from the +Forge. The result is a modules directory containing all the modules specified in +this file, for each environment/branch. The modules directory is listed in +environment.conf's modulepath. + +### environment.conf +This file can override several settings whenever the Puppet master is serving +nodes assigned to that environment. +[Config Files: environment.conf](https://docs.puppetlabs.com/puppet/latest/reference/config_file_environment.html) + +### environment.sh +This file contains global variables. It can be sourced by other scripts. + +[Config Files: environment.conf](https://docs.puppetlabs.com/puppet/latest/reference/config_file_environment.html) + ## Testing ### Prerequisites From 1561add7142d7de24fa9307e8dfbc1d60de17eb1 Mon Sep 17 00:00:00 2001 From: Vlad Ghinea Date: Sun, 17 Jan 2016 19:12:30 -0600 Subject: [PATCH 17/49] Add main CloudFormation template --- cfn/vgh.json | 71 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 cfn/vgh.json diff --git a/cfn/vgh.json b/cfn/vgh.json new file mode 100644 index 0000000..ca3f3ab --- /dev/null +++ b/cfn/vgh.json @@ -0,0 +1,71 @@ +{ + "Description": "VGH CloudFormation Stack", + + "Parameters": { + "KeyName": { + "Type": "AWS::EC2::KeyPair::KeyName", + "Description": "Amazon EC2 key pair" + }, + "AssetsBucket": { + "Type": "String", + "Description": "The S3 bucket containing the assets" + }, + "EnvType": { + "Type": "String", + "Default" : "production", + "Description": "Environment type", + "AllowedValues" : ["development", "test", "production"] + }, + "SSHLocation" : { + "Type": "String", + "Description" : "The IP address range that can be used to SSH to the EC2 instances", + "MinLength": "9", + "MaxLength": "18", + "Default": "0.0.0.0/0", + "AllowedPattern": "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})/(\\d{1,2})", + "ConstraintDescription": "must be a valid IP CIDR range of the form x.x.x.x/x." + }, + "VPCTemplateKey": { + "Type": "String", + "Description": "The key of the template for the VPC nested stack" + }, + "SGTemplateKey": { + "Type": "String", + "Description": "The key of the template for the SG nested stack" + }, + "IAMTemplateKey": { + "Type": "String", + "Description": "The key of the template for the IAM nested stack" + } + }, + + "Resources": { + "VPC": { + "Type": "AWS::CloudFormation::Stack", + "Properties": { + "TemplateURL": {"Ref": "VPCTemplateKey" } + } + }, + + "SG": { + "Type": "AWS::CloudFormation::Stack", + "Properties": { + "TemplateURL": {"Ref": "SGTemplateKey" }, + "Parameters": { + "VPCId": {"Fn::GetAtt": ["VPC", "Outputs.VPCId"]}, + "SSHLocation": {"Ref": "SSHLocation"} + } + } + }, + + "IAM": { + "Type": "AWS::CloudFormation::Stack", + "Properties": { + "TemplateURL": {"Ref": "IAMTemplateKey" }, + "Parameters": { + "AssetsBucket": {"Ref": "AssetsBucket"} + } + } + } + } +} From ee7d59b955eb8889adfc5061b6a803795d303483 Mon Sep 17 00:00:00 2001 From: Vlad Ghinea Date: Sun, 17 Jan 2016 19:13:00 -0600 Subject: [PATCH 18/49] Deploy to S3 with Travis --- .travis.yml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.travis.yml b/.travis.yml index f3641cb..b8ba5b7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,6 +7,15 @@ cache: directories: - dist/profile/vendor - dist/profile/spec/fixtures/modules +deploy: + provider: s3 + access_key_id: AKIAIGIJGXNDAACN2YNQ + secret_access_key: + secure: Vn+E0F4gKcwP26xzjK2wV5Y/1cea0tgP66UerxG7nwFvzvcv6/IgXmW9jDFTYnQ0g+pJuu4YhvfH8Qi59upbVjDnLsvgi4qbSsBnrRSzfsyPTTF3E6sABND/PC0ZO1DroRKQgSJ9Fq6Yd6AZRDZnwEKMq2zdb7lALJjJGb5h66EsioIoUoz2J/oct8G1tKMb2ZRS2EN4djFgVOKkt6PTH+wBm5dMCQ3msUBUAYNc4lL9VXD5Q3W04rFtWFuGmd1upk+xr1uBwbB42ytJITjZgsYi+zxpm8RYhBnx7xj/oldjmeCfR5v0O2mSRHJLBi/mZmAY5Lnycdo/8LgNsbBn1ylpuDD9aU3b96eLV29AGNOjSIkbVkasH0105UowEnJ3Q4EQ87ys5PxGH9Eat1i4DVTprLPW1G0FhiDfSwDRUJt+Q5GFdYnBOjrnuQsfPsdp782Uh7Ggv3llH+uCkLjTrQuHjtQos0xkeM8pXan3BrfB/fzQ3eMb9NnBr8xuBljlkRk5Potr/DEFZBpr552Y5A9yf/RTZF/9ZVOa/O2437GsYVB3TJN2mssxrYp488dDkLxyvSaXj3Ves9vnHO5oWqTRinhI9gz3VkUT6sFv7rD3JzogwIqfwFq2Z+a5D3WPcygYMue65pxB18WOq3OeJUyBQ8yx2VqQZQboAR7hjxE= + bucket: vladgh + acl: private + local_dir: cfn + upload-dir: cloudformation notifications: slack: secure: PO+9V7wfK1zJZTO3RgewarOo2Uk6bhPXTAUFwDhwEoBnbhOEIq1ItLkOZfXW4LgQ9WpK03x1y9LNA4xHK89Z3yxO/s+/Csf5YOcv9+CUuwQR4vjfiFmedB8DCObGKc5HdsuohJx6G/YyLag8gFTJMGIDyQRN4uodXjrK2NGuMG8QRYhq4qwYtmnw3Q4q3o9Nsso1486qTnbWuRtF78ofgVnBurNYefbOiycGRvX4on2WI8syGcBVLQWIO/9Lc3Nye3arKdvtrVKI15/QM5DpgyPCmSTeqeu122XgVP/vPqYtmBXttU3lTladAq3S5VpMo9DQlltgTLvAl4a8ma+mzqtux1K628tMsAcNLUpcsXYbbUTRQILCmIQlDomC8oQOtSWnYPCtMpTr/5pGElVlNoWUSahlZS/VV9m/1Om64lgFdxY+MI9sI8UlXi6Ny47JfpHp7xQJPe80sFVLtwtrs9ievcEyw1R+9rSL1ASs3JIevKu5q9wTFL8Dy8lzWHlwyth8kkSruzUc7b5wsLpOPhJoKXuEJjY1ac3abgBjuX06Vk0y0y/dJdPDKy3Uo+ajY4IdjoppuJPRklXv77/hU04W8XTlD5eBR+M4tWAsUZgU1GkgtoUV05GSfNwhRfpeqzqqqzVjeUDUmXvhUG1YmkNd3wdRaW6t57iCi7P6ikI= From 9b0d943773e5d8a6d593a28db411ffd1c1996a8a Mon Sep 17 00:00:00 2001 From: Vlad Ghinea Date: Sun, 17 Jan 2016 19:41:55 -0600 Subject: [PATCH 19/49] Install awscli --- .travis.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index b8ba5b7..c2041ba 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,8 @@ sudo: false language: ruby -install: cd dist/profile && bundle install --without development system_tests --path vendor +install: + - cd dist/profile && bundle install --without development system_tests --path vendor + - pip install awscli env: PUPPET_GEM_VERSION="~> 4.0" STRICT_VARIABLES=yes script: bundle exec rake test cache: From 950cfec6f8c7b43c41af03cb5ea91104fd226448 Mon Sep 17 00:00:00 2001 From: Vlad Ghinea Date: Sun, 17 Jan 2016 19:44:12 -0600 Subject: [PATCH 20/49] Add sudo --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index c2041ba..c4bc4b6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,7 +2,7 @@ sudo: false language: ruby install: - cd dist/profile && bundle install --without development system_tests --path vendor - - pip install awscli + - sudo pip install awscli env: PUPPET_GEM_VERSION="~> 4.0" STRICT_VARIABLES=yes script: bundle exec rake test cache: From 1110437226fd59b681b105a50c098407057dfbf6 Mon Sep 17 00:00:00 2001 From: Vlad Ghinea Date: Sun, 17 Jan 2016 19:46:30 -0600 Subject: [PATCH 21/49] Fix sudo RTFM --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index c4bc4b6..ba17117 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,7 +2,7 @@ sudo: false language: ruby install: - cd dist/profile && bundle install --without development system_tests --path vendor - - sudo pip install awscli + - pip install --user awscli env: PUPPET_GEM_VERSION="~> 4.0" STRICT_VARIABLES=yes script: bundle exec rake test cache: From b92e5dfe989c578f8f4e28e1d97833cda67740c8 Mon Sep 17 00:00:00 2001 From: Vlad Ghinea Date: Mon, 18 Jan 2016 09:43:50 -0600 Subject: [PATCH 22/49] Fix environment --- README.md | 6 +++--- environment.sh | 51 ++++++++++++++++++++++++++++++++++++++++++++++- include/common.sh | 9 ++++----- 3 files changed, 57 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 6e14fae..01e5dd8 100644 --- a/README.md +++ b/README.md @@ -43,9 +43,9 @@ nodes assigned to that environment. [Config Files: environment.conf](https://docs.puppetlabs.com/puppet/latest/reference/config_file_environment.html) ### environment.sh -This file contains global variables. It can be sourced by other scripts. - -[Config Files: environment.conf](https://docs.puppetlabs.com/puppet/latest/reference/config_file_environment.html) +This file contains global variables. It can be sourced by other scripts. **All +variables declared here are public**. Any sensitive information should be +placed in an `.env` file which will overwrite the information here. ## Testing ### Prerequisites diff --git a/environment.sh b/environment.sh index ff13a7e..03dfb9f 100644 --- a/environment.sh +++ b/environment.sh @@ -1,5 +1,54 @@ #!/usr/bin/env bash +# **All variables declared here are public**. Any sensitive information should +# be placed in an `.env` file which will overwrite the information here. # VARs -PATH="/opt/puppetlabs/bin:/opt/puppetlabs/puppet/bin:${PATH}" +## Paths +export PATH="/opt/puppetlabs/bin:/opt/puppetlabs/puppet/bin:/usr/local/bin:${PATH}" +export REPODIR=${REPODIR:-$(git rev-parse --show-toplevel)} + +## Git +export BRANCH; BRANCH=$(git symbolic-ref --short HEAD 2>/dev/null || true) +export SHA; SHA=$(git rev-parse --short HEAD 2>/dev/null || true) + +case "${BRANCH}" in + master) + export ENV_TYPE='production' + ;; + *) + export ENV_TYPE="$BRANCH" + ;; +esac + +## External ip +external_ip=$(dig +short myip.opendns.com @resolver1.opendns.com) + +## CloudFormation +export vgh_stack_name=${vgh_stack_name:-vgh} +export vgh_stack_file=${vgh_stack_file:-file://./cfn/vgh.json} +export vgh_env_type=${vgh_env_type:-$ENV_TYPE} +export vgh_ec2_key=${vgh_ec2_key:-key} +export vgh_ssh_location="${external_ip}/32" +export vgh_stack_capabilities=${vgh_stack_capabilities:-CAPABILITY_IAM} +export vgh_notifications_topic_arn=${vgh_notifications_topic_arn:-arn:aws:sns:us-east-1:12345:Topic} + +export vgh_assets_bucket=${vgh_assets_bucket:-vladgh} +export vgh_assets_key_prefix=${vgh_assets_key_prefix:-puppet} +export vgh_assets_s3path="${vgh_assets_bucket}/${vgh_assets_key_prefix}/${ENV_TYPE}" + +export vgh_cfn_stack_s3="s3://${vgh_assets_s3path}/cfn" +export vgh_cfn_stack_url="https://s3.amazonaws.com/${vgh_assets_s3path}/cfn" + +export vgh_stack_parameters="\ + ParameterKey=KeyName,ParameterValue=${vgh_ec2_key} \ + ParameterKey=AssetsBucket,ParameterValue=${vgh_assets_bucket} \ + ParameterKey=EnvType,ParameterValue=${vgh_env_type} \ + ParameterKey=SSHLocation,ParameterValue=${vgh_ssh_location} \ + ParameterKey=VPCTemplateKey,ParameterValue=${vgh_cfn_stack_url}/vpc.json \ + ParameterKey=SGTemplateKey,ParameterValue=${vgh_cfn_stack_url}/iam.json \ + ParameterKey=IAMTemplateKey,ParameterValue=${vgh_cfn_stack_url}/sec_grp.json" + +export vgh_group_tag=${vgh_group_tag:-vgh} +export vgh_stack_tags="\ + Key=Group,Value=${vgh_group_tag}" diff --git a/include/common.sh b/include/common.sh index 6c17f0b..8633df8 100644 --- a/include/common.sh +++ b/include/common.sh @@ -4,14 +4,13 @@ # Immediately exit on errors set -euo pipefail -# Load global environment +# Load private environment # shellcheck disable=1090 -. "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)/../environment.sh" || true +. "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)/../.env" || true -# Load private environment if it exists, overriding global variables. +# Load global environment # shellcheck disable=1090 -DOTENV="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)/../.env" -[[ -s "$DOTENV" ]] && . "$DOTENV" +. "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)/../environment.sh" || true # FUNCTIONS ## Check if root From cd6a200a288fd79aa7a53724cf6ca51d3485e17a Mon Sep 17 00:00:00 2001 From: Vlad Ghinea Date: Mon, 18 Jan 2016 09:45:05 -0600 Subject: [PATCH 23/49] Add encrypted environment --- .env.enc | Bin 0 -> 320 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 .env.enc diff --git a/.env.enc b/.env.enc new file mode 100644 index 0000000000000000000000000000000000000000..f58e40a8b261fbf2f8abad545336327fd6d00333 GIT binary patch literal 320 zcmV-G0l)rbcu)66G96hnrjozfMN#1N;$3FiJDVNcxUb1n?!5N}PnDA{zmHN;wt7Pbi`QQjSeRQpHh9zltm zFfK7x)x{g6s5k$-%k7WLnmDl$<%&d?Jaj!an`*z~{=PdIst4FoBt43-gfo+!)yo!H SB^Y!Xkg#$s2H-)I8vw>e45&r` literal 0 HcmV?d00001 From b0aac45f740e7721d88eb364116f0257226f8085 Mon Sep 17 00:00:00 2001 From: Vlad Ghinea Date: Mon, 18 Jan 2016 09:46:56 -0600 Subject: [PATCH 24/49] Use a custom script for building --- .travis.yml | 20 ++++++++------------ bin/ci.sh | 29 +++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 12 deletions(-) create mode 100755 bin/ci.sh diff --git a/.travis.yml b/.travis.yml index ba17117..0e630c3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,23 +1,19 @@ sudo: false language: ruby -install: - - cd dist/profile && bundle install --without development system_tests --path vendor - - pip install --user awscli +before_install: + - openssl aes-256-cbc -K $encrypted_4543e8d896b8_key -iv $encrypted_4543e8d896b8_iv -in .env.enc -out .env -d || true +install: bin/ci.sh install env: PUPPET_GEM_VERSION="~> 4.0" STRICT_VARIABLES=yes -script: bundle exec rake test +script: bin/ci.sh script cache: directories: - dist/profile/vendor - dist/profile/spec/fixtures/modules deploy: - provider: s3 - access_key_id: AKIAIGIJGXNDAACN2YNQ - secret_access_key: - secure: Vn+E0F4gKcwP26xzjK2wV5Y/1cea0tgP66UerxG7nwFvzvcv6/IgXmW9jDFTYnQ0g+pJuu4YhvfH8Qi59upbVjDnLsvgi4qbSsBnrRSzfsyPTTF3E6sABND/PC0ZO1DroRKQgSJ9Fq6Yd6AZRDZnwEKMq2zdb7lALJjJGb5h66EsioIoUoz2J/oct8G1tKMb2ZRS2EN4djFgVOKkt6PTH+wBm5dMCQ3msUBUAYNc4lL9VXD5Q3W04rFtWFuGmd1upk+xr1uBwbB42ytJITjZgsYi+zxpm8RYhBnx7xj/oldjmeCfR5v0O2mSRHJLBi/mZmAY5Lnycdo/8LgNsbBn1ylpuDD9aU3b96eLV29AGNOjSIkbVkasH0105UowEnJ3Q4EQ87ys5PxGH9Eat1i4DVTprLPW1G0FhiDfSwDRUJt+Q5GFdYnBOjrnuQsfPsdp782Uh7Ggv3llH+uCkLjTrQuHjtQos0xkeM8pXan3BrfB/fzQ3eMb9NnBr8xuBljlkRk5Potr/DEFZBpr552Y5A9yf/RTZF/9ZVOa/O2437GsYVB3TJN2mssxrYp488dDkLxyvSaXj3Ves9vnHO5oWqTRinhI9gz3VkUT6sFv7rD3JzogwIqfwFq2Z+a5D3WPcygYMue65pxB18WOq3OeJUyBQ8yx2VqQZQboAR7hjxE= - bucket: vladgh - acl: private - local_dir: cfn - upload-dir: cloudformation + provider: script + script: bin/ci.sh deploy + on: + all_branches: true notifications: slack: secure: PO+9V7wfK1zJZTO3RgewarOo2Uk6bhPXTAUFwDhwEoBnbhOEIq1ItLkOZfXW4LgQ9WpK03x1y9LNA4xHK89Z3yxO/s+/Csf5YOcv9+CUuwQR4vjfiFmedB8DCObGKc5HdsuohJx6G/YyLag8gFTJMGIDyQRN4uodXjrK2NGuMG8QRYhq4qwYtmnw3Q4q3o9Nsso1486qTnbWuRtF78ofgVnBurNYefbOiycGRvX4on2WI8syGcBVLQWIO/9Lc3Nye3arKdvtrVKI15/QM5DpgyPCmSTeqeu122XgVP/vPqYtmBXttU3lTladAq3S5VpMo9DQlltgTLvAl4a8ma+mzqtux1K628tMsAcNLUpcsXYbbUTRQILCmIQlDomC8oQOtSWnYPCtMpTr/5pGElVlNoWUSahlZS/VV9m/1Om64lgFdxY+MI9sI8UlXi6Ny47JfpHp7xQJPe80sFVLtwtrs9ievcEyw1R+9rSL1ASs3JIevKu5q9wTFL8Dy8lzWHlwyth8kkSruzUc7b5wsLpOPhJoKXuEJjY1ac3abgBjuX06Vk0y0y/dJdPDKy3Uo+ajY4IdjoppuJPRklXv77/hU04W8XTlD5eBR+M4tWAsUZgU1GkgtoUV05GSfNwhRfpeqzqqqzVjeUDUmXvhUG1YmkNd3wdRaW6t57iCi7P6ikI= diff --git a/bin/ci.sh b/bin/ci.sh new file mode 100755 index 0000000..bcd91b7 --- /dev/null +++ b/bin/ci.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash +# Creates CloudFormation stack + +# Load environment +# shellcheck disable=1090 +. "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)/../include/common.sh" + +case "$1" in + install) + pip install --user awscli + cd dist/profile || exit + bundle install --without development system_tests --path vendor + ;; + script) + cd dist/profile || exit + bundle exec rake test + ;; + deploy) + if aws s3 sync "${REPODIR}/cfn/" "${vgh_cfn_stack_s3:?}/" \ + --delete --acl public-read \ + --exclude "*" --include "*.json"; then + echo "Synced ${REPODIR}/cfn/ to ${vgh_cfn_stack_s3}" + else + echo "FATAL: Could not sync ${REPODIR}/cfn/ to ${vgh_cfn_stack_s3}" + exit 1 + fi + ;; +esac + From 3a0481114362552ff71d885c5d6a91e9c56fed07 Mon Sep 17 00:00:00 2001 From: Vlad Ghinea Date: Mon, 18 Jan 2016 09:47:12 -0600 Subject: [PATCH 25/49] Add cfn_create_stack command --- bin/cfn_create_stack | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100755 bin/cfn_create_stack diff --git a/bin/cfn_create_stack b/bin/cfn_create_stack new file mode 100755 index 0000000..7cdb14a --- /dev/null +++ b/bin/cfn_create_stack @@ -0,0 +1,21 @@ +#!/usr/bin/env bash +# Creates CloudFormation stack + +# Load environment +# shellcheck disable=1090 +. "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)/../include/aws.sh" + +aws_cmd cloudformation create-stack \ + --stack-name "${vgh_stack_name:?}" \ + --template-body "${vgh_stack_file:?}" \ + --parameters "${vgh_stack_parameters:?}" \ + --capabilities "${vgh_stack_capabilities:?}" \ + --notification-arns "${vgh_notifications_topic_arn:?}" \ + --tags "${vgh_stack_tags:?}" \ + --on-failure 'DELETE' + + +if aws_cfn_wait_for_stack "${vgh_stack_name:?}"; then + echo "FATAL: The stack ${vgh_stack_name:?} failed to create properly" >&2 + exit 1 +fi From 717d860177f4b5ebd62aa37a24679c99cdebd69e Mon Sep 17 00:00:00 2001 From: Vlad Ghinea Date: Mon, 18 Jan 2016 09:54:10 -0600 Subject: [PATCH 26/49] Do not fail if can not find ip --- environment.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/environment.sh b/environment.sh index 03dfb9f..5daa43b 100644 --- a/environment.sh +++ b/environment.sh @@ -22,7 +22,7 @@ case "${BRANCH}" in esac ## External ip -external_ip=$(dig +short myip.opendns.com @resolver1.opendns.com) +external_ip=$(dig +short myip.opendns.com @resolver1.opendns.com || true) ## CloudFormation export vgh_stack_name=${vgh_stack_name:-vgh} From dc4d1ddb0b5566e61fa8081fa543350de699f21b Mon Sep 17 00:00:00 2001 From: Vlad Ghinea Date: Mon, 18 Jan 2016 09:54:50 -0600 Subject: [PATCH 27/49] Use after_success instead of deploy Script support is experimental --- .travis.yml | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index 0e630c3..05d1730 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,11 +9,7 @@ cache: directories: - dist/profile/vendor - dist/profile/spec/fixtures/modules -deploy: - provider: script - script: bin/ci.sh deploy - on: - all_branches: true +after_success: bin/ci.sh deploy notifications: slack: secure: PO+9V7wfK1zJZTO3RgewarOo2Uk6bhPXTAUFwDhwEoBnbhOEIq1ItLkOZfXW4LgQ9WpK03x1y9LNA4xHK89Z3yxO/s+/Csf5YOcv9+CUuwQR4vjfiFmedB8DCObGKc5HdsuohJx6G/YyLag8gFTJMGIDyQRN4uodXjrK2NGuMG8QRYhq4qwYtmnw3Q4q3o9Nsso1486qTnbWuRtF78ofgVnBurNYefbOiycGRvX4on2WI8syGcBVLQWIO/9Lc3Nye3arKdvtrVKI15/QM5DpgyPCmSTeqeu122XgVP/vPqYtmBXttU3lTladAq3S5VpMo9DQlltgTLvAl4a8ma+mzqtux1K628tMsAcNLUpcsXYbbUTRQILCmIQlDomC8oQOtSWnYPCtMpTr/5pGElVlNoWUSahlZS/VV9m/1Om64lgFdxY+MI9sI8UlXi6Ny47JfpHp7xQJPe80sFVLtwtrs9ievcEyw1R+9rSL1ASs3JIevKu5q9wTFL8Dy8lzWHlwyth8kkSruzUc7b5wsLpOPhJoKXuEJjY1ac3abgBjuX06Vk0y0y/dJdPDKy3Uo+ajY4IdjoppuJPRklXv77/hU04W8XTlD5eBR+M4tWAsUZgU1GkgtoUV05GSfNwhRfpeqzqqqzVjeUDUmXvhUG1YmkNd3wdRaW6t57iCi7P6ikI= From e045afcad1737075bde35d0779e40841edc8e859 Mon Sep 17 00:00:00 2001 From: Vlad Ghinea Date: Mon, 18 Jan 2016 10:09:03 -0600 Subject: [PATCH 28/49] Use after_script instead of after_success after_success does not mark the build as fail --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 05d1730..be7218c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,13 +3,13 @@ language: ruby before_install: - openssl aes-256-cbc -K $encrypted_4543e8d896b8_key -iv $encrypted_4543e8d896b8_iv -in .env.enc -out .env -d || true install: bin/ci.sh install -env: PUPPET_GEM_VERSION="~> 4.0" STRICT_VARIABLES=yes script: bin/ci.sh script +after_script: bin/ci.sh deploy +env: PUPPET_GEM_VERSION="~> 4.0" STRICT_VARIABLES=yes cache: directories: - dist/profile/vendor - dist/profile/spec/fixtures/modules -after_success: bin/ci.sh deploy notifications: slack: secure: PO+9V7wfK1zJZTO3RgewarOo2Uk6bhPXTAUFwDhwEoBnbhOEIq1ItLkOZfXW4LgQ9WpK03x1y9LNA4xHK89Z3yxO/s+/Csf5YOcv9+CUuwQR4vjfiFmedB8DCObGKc5HdsuohJx6G/YyLag8gFTJMGIDyQRN4uodXjrK2NGuMG8QRYhq4qwYtmnw3Q4q3o9Nsso1486qTnbWuRtF78ofgVnBurNYefbOiycGRvX4on2WI8syGcBVLQWIO/9Lc3Nye3arKdvtrVKI15/QM5DpgyPCmSTeqeu122XgVP/vPqYtmBXttU3lTladAq3S5VpMo9DQlltgTLvAl4a8ma+mzqtux1K628tMsAcNLUpcsXYbbUTRQILCmIQlDomC8oQOtSWnYPCtMpTr/5pGElVlNoWUSahlZS/VV9m/1Om64lgFdxY+MI9sI8UlXi6Ny47JfpHp7xQJPe80sFVLtwtrs9ievcEyw1R+9rSL1ASs3JIevKu5q9wTFL8Dy8lzWHlwyth8kkSruzUc7b5wsLpOPhJoKXuEJjY1ac3abgBjuX06Vk0y0y/dJdPDKy3Uo+ajY4IdjoppuJPRklXv77/hU04W8XTlD5eBR+M4tWAsUZgU1GkgtoUV05GSfNwhRfpeqzqqqzVjeUDUmXvhUG1YmkNd3wdRaW6t57iCi7P6ikI= From 7735554968af862b006458c25b2de90aa8abf886 Mon Sep 17 00:00:00 2001 From: Vlad Ghinea Date: Mon, 18 Jan 2016 10:53:33 -0600 Subject: [PATCH 29/49] Simplify sync command --- bin/ci.sh | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/bin/ci.sh b/bin/ci.sh index bcd91b7..76f04fe 100755 --- a/bin/ci.sh +++ b/bin/ci.sh @@ -16,14 +16,9 @@ case "$1" in bundle exec rake test ;; deploy) - if aws s3 sync "${REPODIR}/cfn/" "${vgh_cfn_stack_s3:?}/" \ + aws s3 sync "${REPODIR}/cfn/" "${vgh_cfn_stack_s3:?}/" \ --delete --acl public-read \ - --exclude "*" --include "*.json"; then - echo "Synced ${REPODIR}/cfn/ to ${vgh_cfn_stack_s3}" - else - echo "FATAL: Could not sync ${REPODIR}/cfn/ to ${vgh_cfn_stack_s3}" - exit 1 - fi + --exclude "*" --include "*.json" ;; esac From 63c87135aacf80f6fdad8e1c06335c263c1dacb7 Mon Sep 17 00:00:00 2001 From: Vlad Ghinea Date: Mon, 18 Jan 2016 11:10:19 -0600 Subject: [PATCH 30/49] Debug build --- environment.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/environment.sh b/environment.sh index 5daa43b..442df15 100644 --- a/environment.sh +++ b/environment.sh @@ -11,7 +11,7 @@ export REPODIR=${REPODIR:-$(git rev-parse --show-toplevel)} ## Git export BRANCH; BRANCH=$(git symbolic-ref --short HEAD 2>/dev/null || true) export SHA; SHA=$(git rev-parse --short HEAD 2>/dev/null || true) - +echo $BRANCH case "${BRANCH}" in master) export ENV_TYPE='production' @@ -20,7 +20,7 @@ case "${BRANCH}" in export ENV_TYPE="$BRANCH" ;; esac - +echo $ENV_TYPE ## External ip external_ip=$(dig +short myip.opendns.com @resolver1.opendns.com || true) From 2e02f6733337f1a70736f61e947bfa018c5e21e0 Mon Sep 17 00:00:00 2001 From: Vlad Ghinea Date: Mon, 18 Jan 2016 11:12:48 -0600 Subject: [PATCH 31/49] Debug build --- environment.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/environment.sh b/environment.sh index 442df15..94b189c 100644 --- a/environment.sh +++ b/environment.sh @@ -9,9 +9,9 @@ export PATH="/opt/puppetlabs/bin:/opt/puppetlabs/puppet/bin:/usr/local/bin:${PAT export REPODIR=${REPODIR:-$(git rev-parse --show-toplevel)} ## Git -export BRANCH; BRANCH=$(git symbolic-ref --short HEAD 2>/dev/null || true) -export SHA; SHA=$(git rev-parse --short HEAD 2>/dev/null || true) -echo $BRANCH +export BRANCH; BRANCH=$(git symbolic-ref --short HEAD) +export SHA; SHA=$(git rev-parse --short HEAD) +echo 'branch: ' && echo $BRANCH case "${BRANCH}" in master) export ENV_TYPE='production' @@ -20,7 +20,7 @@ case "${BRANCH}" in export ENV_TYPE="$BRANCH" ;; esac -echo $ENV_TYPE +echo 'env: ' && echo $ENV_TYPE ## External ip external_ip=$(dig +short myip.opendns.com @resolver1.opendns.com || true) From 6996831122cec7e624adec0a28510d50e7c341d5 Mon Sep 17 00:00:00 2001 From: Vlad Ghinea Date: Mon, 18 Jan 2016 11:18:58 -0600 Subject: [PATCH 32/49] Debug build --- environment.sh | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/environment.sh b/environment.sh index 94b189c..942c137 100644 --- a/environment.sh +++ b/environment.sh @@ -9,8 +9,7 @@ export PATH="/opt/puppetlabs/bin:/opt/puppetlabs/puppet/bin:/usr/local/bin:${PAT export REPODIR=${REPODIR:-$(git rev-parse --show-toplevel)} ## Git -export BRANCH; BRANCH=$(git symbolic-ref --short HEAD) -export SHA; SHA=$(git rev-parse --short HEAD) +export BRANCH; BRANCH=$(git symbolic-ref --short HEAD || $TRAVIS_BRANCH) echo 'branch: ' && echo $BRANCH case "${BRANCH}" in master) From e6671ba7c86cca3a26ae392550cb1d943d623ce3 Mon Sep 17 00:00:00 2001 From: Vlad Ghinea Date: Mon, 18 Jan 2016 11:22:02 -0600 Subject: [PATCH 33/49] Revert to TRAVIS_BRANCH to find the current branch --- environment.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/environment.sh b/environment.sh index 942c137..43e18d5 100644 --- a/environment.sh +++ b/environment.sh @@ -9,8 +9,8 @@ export PATH="/opt/puppetlabs/bin:/opt/puppetlabs/puppet/bin:/usr/local/bin:${PAT export REPODIR=${REPODIR:-$(git rev-parse --show-toplevel)} ## Git -export BRANCH; BRANCH=$(git symbolic-ref --short HEAD || $TRAVIS_BRANCH) -echo 'branch: ' && echo $BRANCH +export BRANCH; BRANCH=$(git symbolic-ref --short HEAD 2>/dev/null || "$TRAVIS_BRANCH") + case "${BRANCH}" in master) export ENV_TYPE='production' @@ -19,7 +19,7 @@ case "${BRANCH}" in export ENV_TYPE="$BRANCH" ;; esac -echo 'env: ' && echo $ENV_TYPE + ## External ip external_ip=$(dig +short myip.opendns.com @resolver1.opendns.com || true) From 18572e929c28e0d3a709e1e2b4de0f605aed759d Mon Sep 17 00:00:00 2001 From: Vlad Ghinea Date: Mon, 18 Jan 2016 11:24:15 -0600 Subject: [PATCH 34/49] Fix branch --- environment.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/environment.sh b/environment.sh index 43e18d5..d3d824d 100644 --- a/environment.sh +++ b/environment.sh @@ -9,7 +9,7 @@ export PATH="/opt/puppetlabs/bin:/opt/puppetlabs/puppet/bin:/usr/local/bin:${PAT export REPODIR=${REPODIR:-$(git rev-parse --show-toplevel)} ## Git -export BRANCH; BRANCH=$(git symbolic-ref --short HEAD 2>/dev/null || "$TRAVIS_BRANCH") +export BRANCH; BRANCH=$(git symbolic-ref --short HEAD 2>/dev/null || echo "$TRAVIS_BRANCH") case "${BRANCH}" in master) From d23694fdd4e6a703a5dc5ea0afa731eb4c1a9d52 Mon Sep 17 00:00:00 2001 From: Vlad Ghinea Date: Mon, 18 Jan 2016 11:27:13 -0600 Subject: [PATCH 35/49] Cache pip dependencies --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index be7218c..8647845 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,6 +10,7 @@ cache: directories: - dist/profile/vendor - dist/profile/spec/fixtures/modules + - $HOME/.cache/pip notifications: slack: secure: PO+9V7wfK1zJZTO3RgewarOo2Uk6bhPXTAUFwDhwEoBnbhOEIq1ItLkOZfXW4LgQ9WpK03x1y9LNA4xHK89Z3yxO/s+/Csf5YOcv9+CUuwQR4vjfiFmedB8DCObGKc5HdsuohJx6G/YyLag8gFTJMGIDyQRN4uodXjrK2NGuMG8QRYhq4qwYtmnw3Q4q3o9Nsso1486qTnbWuRtF78ofgVnBurNYefbOiycGRvX4on2WI8syGcBVLQWIO/9Lc3Nye3arKdvtrVKI15/QM5DpgyPCmSTeqeu122XgVP/vPqYtmBXttU3lTladAq3S5VpMo9DQlltgTLvAl4a8ma+mzqtux1K628tMsAcNLUpcsXYbbUTRQILCmIQlDomC8oQOtSWnYPCtMpTr/5pGElVlNoWUSahlZS/VV9m/1Om64lgFdxY+MI9sI8UlXi6Ny47JfpHp7xQJPe80sFVLtwtrs9ievcEyw1R+9rSL1ASs3JIevKu5q9wTFL8Dy8lzWHlwyth8kkSruzUc7b5wsLpOPhJoKXuEJjY1ac3abgBjuX06Vk0y0y/dJdPDKy3Uo+ajY4IdjoppuJPRklXv77/hU04W8XTlD5eBR+M4tWAsUZgU1GkgtoUV05GSfNwhRfpeqzqqqzVjeUDUmXvhUG1YmkNd3wdRaW6t57iCi7P6ikI= From d64146c57270fad90a0a6509df6c85ce1d125901 Mon Sep 17 00:00:00 2001 From: Vlad Ghinea Date: Mon, 18 Jan 2016 11:33:07 -0600 Subject: [PATCH 36/49] Test cache --- bin/cfn_create_stack | 1 - 1 file changed, 1 deletion(-) diff --git a/bin/cfn_create_stack b/bin/cfn_create_stack index 7cdb14a..8e4e524 100755 --- a/bin/cfn_create_stack +++ b/bin/cfn_create_stack @@ -14,7 +14,6 @@ aws_cmd cloudformation create-stack \ --tags "${vgh_stack_tags:?}" \ --on-failure 'DELETE' - if aws_cfn_wait_for_stack "${vgh_stack_name:?}"; then echo "FATAL: The stack ${vgh_stack_name:?} failed to create properly" >&2 exit 1 From cbb175ec87b8904ce04de78a075cccdcd53bd5b2 Mon Sep 17 00:00:00 2001 From: Vlad Ghinea Date: Mon, 18 Jan 2016 11:44:23 -0600 Subject: [PATCH 37/49] Improve aws scripts --- bin/ci.sh | 4 ++-- include/aws.sh | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/bin/ci.sh b/bin/ci.sh index 76f04fe..78ebfc3 100755 --- a/bin/ci.sh +++ b/bin/ci.sh @@ -3,7 +3,7 @@ # Load environment # shellcheck disable=1090 -. "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)/../include/common.sh" +. "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)/../include/aws.sh" case "$1" in install) @@ -16,7 +16,7 @@ case "$1" in bundle exec rake test ;; deploy) - aws s3 sync "${REPODIR}/cfn/" "${vgh_cfn_stack_s3:?}/" \ + aws_cmd s3 sync "${REPODIR}/cfn/" "${vgh_cfn_stack_s3:?}/" \ --delete --acl public-read \ --exclude "*" --include "*.json" ;; diff --git a/include/aws.sh b/include/aws.sh index 8ef66b0..ed8564e 100644 --- a/include/aws.sh +++ b/include/aws.sh @@ -34,12 +34,12 @@ aws_get_instance_az() { # NAME: aws_get_instance_region # DESCRIPTION: Returns the the AWS region (defaults to us-east-1) aws_get_instance_region() { - if [ -z "${AWS_REGION}" ]; then + if [ -z "${AWS_DEFAULT_REGION}" ]; then zone=$(aws_get_instance_az) - export AWS_REGION="${zone%?}" - echo "${AWS_REGION:-us-east-1}" + export AWS_DEFAULT_REGION="${zone%?}" + echo "${AWS_DEFAULT_REGION:-us-east-1}" else - echo "$AWS_REGION" + echo "$AWS_DEFAULT_REGION" fi } From 4007e764a6feda7a6d348e7b4029f00889d07689 Mon Sep 17 00:00:00 2001 From: Vlad Ghinea Date: Mon, 18 Jan 2016 11:51:36 -0600 Subject: [PATCH 38/49] Improve AWS functions --- .env.enc | Bin 320 -> 288 bytes bin/ci.sh | 2 +- environment.sh | 3 +++ include/aws.sh | 51 +++++++++++++++++++++---------------------------- 4 files changed, 26 insertions(+), 30 deletions(-) diff --git a/.env.enc b/.env.enc index f58e40a8b261fbf2f8abad545336327fd6d00333..608aa3a55088353c5ee1746ff4e8cf2c03b72ac5 100644 GIT binary patch literal 288 zcmV+*0pI@9hRR?$)wQ+Hqgs?M=2BgX;Y~zE?#HbUUe=WrRXrvs$S5IoHnJC;YeJy) z2F%%z-#4?pb;=?+tq2P8s|u2dH0+cGjaF7p*Q7!9^YBSAVadrUOv#R&&z{(XS7H)i zGaNU+J#^=q#fYSU3{D28z>ui&uiWE5fJVYlgyq!V5gq5#3cOl4X&F7}_!>-PbMx2v z8x0?CbDXWDPy{}8(K%Jwb`P|%W-Fx! z8}XJd856VQRn`BUb1n?!5N}PnDA{zmHN;wt7Pbi`QQjSeRQpHh9zltm zFfK7x)x{g6s5k$-%k7WLnmDl$<%&d?Jaj!an`*z~{=PdIst4FoBt43-gfo+!)yo!H SB^Y!Xkg#$s2H-)I8vw>e45&r` diff --git a/bin/ci.sh b/bin/ci.sh index 78ebfc3..b8a2f13 100755 --- a/bin/ci.sh +++ b/bin/ci.sh @@ -16,7 +16,7 @@ case "$1" in bundle exec rake test ;; deploy) - aws_cmd s3 sync "${REPODIR}/cfn/" "${vgh_cfn_stack_s3:?}/" \ + aws s3 sync "${REPODIR}/cfn/" "${vgh_cfn_stack_s3:?}/" \ --delete --acl public-read \ --exclude "*" --include "*.json" ;; diff --git a/environment.sh b/environment.sh index d3d824d..cbd5a04 100644 --- a/environment.sh +++ b/environment.sh @@ -23,6 +23,9 @@ esac ## External ip external_ip=$(dig +short myip.opendns.com @resolver1.opendns.com || true) +# AWS +export AWS_DEFAULT_REGION='us-east-1' + ## CloudFormation export vgh_stack_name=${vgh_stack_name:-vgh} export vgh_stack_file=${vgh_stack_file:-file://./cfn/vgh.json} diff --git a/include/aws.sh b/include/aws.sh index ed8564e..8bcbb75 100644 --- a/include/aws.sh +++ b/include/aws.sh @@ -5,6 +5,9 @@ # shellcheck disable=1090 . "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)/common.sh" +# AWS Region +export AWS_DEFAULT_REGION="${AWS_DEFAULT_REGION:-us-east-1}" + # NAME: aws_get_metadata # DESCRIPTION: AWS MetaData Service aws_get_metadata(){ @@ -32,26 +35,16 @@ aws_get_instance_az() { } # NAME: aws_get_instance_region -# DESCRIPTION: Returns the the AWS region (defaults to us-east-1) +# DESCRIPTION: Returns the the AWS region aws_get_instance_region() { - if [ -z "${AWS_DEFAULT_REGION}" ]; then - zone=$(aws_get_instance_az) - export AWS_DEFAULT_REGION="${zone%?}" - echo "${AWS_DEFAULT_REGION:-us-east-1}" - else - echo "$AWS_DEFAULT_REGION" - fi -} - -# AWS CLI Command -aws_cmd(){ - /usr/local/bin/aws --region "$(aws_get_instance_region)" "${@}" + local zone; zone=$(aws_get_instance_az) + echo "${zone%?}" } # NAME: list_all_tags # DESCRIPTION: Returns all EC2 tags associated with the current instance list_all_tags(){ - aws_cmd --output text ec2 describe-tags \ + aws --output text ec2 describe-tags \ --filters "Name=resource-id,Values=$(aws_get_instance_id)" \ --query "Tags[*].[join(\`=\`,[Key,Value])]" 2>/dev/null | \ awk '{print tolower($0)}' | \ @@ -69,7 +62,7 @@ aws_get_tag(){ local instance_id; instance_id=$(aws_get_instance_id) local resource=${2:-$instance_id} - aws_cmd --output text ec2 describe-tags \ + aws --output text ec2 describe-tags \ --filters "Name=resource-id,Values=${resource}" "Name=key,Values=$name" \ --query "Tags[*].[Value]" 2>/dev/null } @@ -82,7 +75,7 @@ aws_get_tag(){ # 1) The instance id aws_get_instance_state_asg() { local instance_id=$1 - local state; state=$(aws_cmd autoscaling describe-auto-scaling-instances \ + local state; state=$(aws autoscaling describe-auto-scaling-instances \ --instance-ids "$instance_id" \ --query "AutoScalingInstances[?InstanceId == \`$instance_id\`].LifecycleState | [0]" \ --output text) @@ -102,7 +95,7 @@ aws_get_instance_state_asg() { # 1) The instance id aws_get_autoscaling_group_name() { local instance_id=$1 - local autoscaling_name; autoscaling_name=$(aws_cmd autoscaling \ + local autoscaling_name; autoscaling_name=$(aws autoscaling \ describe-auto-scaling-instances \ --instance-ids "$instance_id" \ --output text \ @@ -143,7 +136,7 @@ aws_autoscaling_enter_standby(){ fi echo "Putting instance $instance_id into Standby" - aws_cmd autoscaling enter-standby \ + aws autoscaling enter-standby \ --instance-ids "$instance_id" \ --auto-scaling-group-name "$asg_name" \ --should-decrement-desired-capacity @@ -187,7 +180,7 @@ aws_autoscaling_exit_standby(){ fi echo "Moving instance $instance_id out of Standby" - aws_cmd autoscaling exit-standby \ + aws autoscaling exit-standby \ --instance-ids "$instance_id" \ --auto-scaling-group-name "$asg_name" if [ $? != 0 ]; then @@ -215,7 +208,7 @@ aws_deploy_list_running_deployments() { local app=$1 local group=$2 - aws_cmd deploy list-deployments \ + aws deploy list-deployments \ --output text \ --query 'deployments' \ --application-name "$1" \ @@ -233,7 +226,7 @@ aws_deploy_group_exists() { local app=$1 local group=$2 - aws_cmd deploy get-deployment-group \ + aws deploy get-deployment-group \ --query 'deploymentGroupInfo.deploymentGroupId' --output text \ --application-name "$1" \ --deployment-group-name "$2" >/dev/null @@ -279,7 +272,7 @@ aws_deploy_create_deployment(){ aws_deploy_wait "$@" echo "Creating deployment for application '${app}', group '${group}'" - aws_cmd deploy create-deployment \ + aws deploy create-deployment \ --application-name "$app" \ --s3-location bucket="${bucket}",key="${key}",bundleType="${bundle}" \ --deployment-group-name "$group" \ @@ -306,7 +299,7 @@ aws_get_private_env(){ echo "File '${file}' already exists. Replacing" sudo rm "$file" fi - aws_cmd s3 cp "$src" "$file" || echo "Failed to get ${src}" + aws s3 cp "$src" "$file" || echo "Failed to get ${src}" if [ -s "$file" ]; then echo "Setting permissions for ${file}" sudo chmod 400 "$file" @@ -359,7 +352,7 @@ aws_cfn_wait_for_stack(){ echo "Waiting for $stack to complete ..." >&2 until [[ $status =~ _(COMPLETE|FAILED)$ ]]; do sleep 5 - status="$(aws_cmd cloudformation describe-stacks --stack-name "$1" --output text --query 'Stacks[0].StackStatus')" + status="$(aws cloudformation describe-stacks --stack-name "$1" --output text --query 'Stacks[0].StackStatus')" echo " ... $stack - $status" >&2 done @@ -416,7 +409,7 @@ aws_cf_events() { stack="$(basename "$1" .json)" shift local output - if output=$(aws_cmd --color on cloudformation describe-stack-events --stack-name "$stack" --query 'sort_by(StackEvents, &Timestamp)[].{Resource: LogicalResourceId, Type: ResourceType, Status: ResourceStatus}' --output table "$@"); then + if output=$(aws --color on cloudformation describe-stack-events --stack-name "$stack" --query 'sort_by(StackEvents, &Timestamp)[].{Resource: LogicalResourceId, Type: ResourceType, Status: ResourceStatus}' --output table "$@"); then echo "$output" | uniq -u else return $? @@ -427,7 +420,7 @@ aws_cf_events() { # DESCRIPTION: Returns the ELB to which the instance is registered. aws_get_elb_name() { local instance_id; instance_id=$(aws_get_instance_id) - if output=$(aws_cmd elb describe-load-balancers --query "LoadBalancerDescriptions[?contains(Instances[].InstanceId, \`${instance_id}\`)].LoadBalancerName" --output text); then + if output=$(aws elb describe-load-balancers --query "LoadBalancerDescriptions[?contains(Instances[].InstanceId, \`${instance_id}\`)].LoadBalancerName" --output text); then echo "$output" else return $? @@ -438,7 +431,7 @@ aws_get_elb_name() { # DESCRIPTION: Returns the Elastic Load Balancer health check configuration aws_elb_get_health_check() { local elb; elb=$(aws_get_elb_name) - if output=$(aws_cmd elb describe-load-balancers --load-balancer-names "$elb" --query 'LoadBalancerDescriptions[].HealthCheck | [0]'); then + if output=$(aws elb describe-load-balancers --load-balancer-names "$elb" --query 'LoadBalancerDescriptions[].HealthCheck | [0]'); then echo "$output" else return $? @@ -453,7 +446,7 @@ aws_elb_get_health_check() { # Ex: Target=HTTP:80/,Interval=10,UnhealthyThreshold=5,HealthyThreshold=2,Timeout=5 aws_elb_configure_health_check() { local elb; elb=$(aws_get_elb_name) - if output=$(aws_cmd elb configure-health-check --load-balancer-name "$elb" --health-check "$1"); then + if output=$(aws elb configure-health-check --load-balancer-name "$elb" --health-check "$1"); then echo "$output" else return $? @@ -463,7 +456,7 @@ aws_elb_configure_health_check() { # NAME: aws_ecs_list_clusters # DESCRIPTION: Returns a list of EC2 Container Service Clusters aws_ecs_list_clusters(){ - if output=$(aws_cmd ecs list-clusters --query 'clusterArns[]' --output text); then + if output=$(aws ecs list-clusters --query 'clusterArns[]' --output text); then echo "$output" else return $? From 626208d109f083c2e52d6cd54579b14f37e5a51e Mon Sep 17 00:00:00 2001 From: Vlad Ghinea Date: Tue, 19 Jan 2016 09:15:57 -0600 Subject: [PATCH 39/49] Fix aws command --- bin/cfn_create_stack | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/cfn_create_stack b/bin/cfn_create_stack index 8e4e524..a0dfc36 100755 --- a/bin/cfn_create_stack +++ b/bin/cfn_create_stack @@ -5,7 +5,7 @@ # shellcheck disable=1090 . "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)/../include/aws.sh" -aws_cmd cloudformation create-stack \ +aws cloudformation create-stack \ --stack-name "${vgh_stack_name:?}" \ --template-body "${vgh_stack_file:?}" \ --parameters "${vgh_stack_parameters:?}" \ From 48cc36c73b1ba20dc0b6806bbc5cb25e28124367 Mon Sep 17 00:00:00 2001 From: Vlad Ghinea Date: Tue, 19 Jan 2016 09:30:00 -0600 Subject: [PATCH 40/49] Improve wait function for CloudFormation Fail when stack was not found --- include/aws.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/aws.sh b/include/aws.sh index 8bcbb75..d262f85 100644 --- a/include/aws.sh +++ b/include/aws.sh @@ -351,9 +351,9 @@ aws_cfn_wait_for_stack(){ echo "Waiting for $stack to complete ..." >&2 until [[ $status =~ _(COMPLETE|FAILED)$ ]]; do - sleep 5 - status="$(aws cloudformation describe-stacks --stack-name "$1" --output text --query 'Stacks[0].StackStatus')" + status="$(aws cloudformation describe-stacks --stack-name "$1" --output text --query 'Stacks[0].StackStatus')" || return 1 echo " ... $stack - $status" >&2 + sleep 5 done echo "$status" From 9ccf36eb35d4c33829a3ad7f528ec903286f176f Mon Sep 17 00:00:00 2001 From: Vlad Ghinea Date: Tue, 19 Jan 2016 09:30:21 -0600 Subject: [PATCH 41/49] Fix CloudFormation parameters and tags declaration --- environment.sh | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/environment.sh b/environment.sh index cbd5a04..697765c 100644 --- a/environment.sh +++ b/environment.sh @@ -42,15 +42,23 @@ export vgh_assets_s3path="${vgh_assets_bucket}/${vgh_assets_key_prefix}/${ENV_TY export vgh_cfn_stack_s3="s3://${vgh_assets_s3path}/cfn" export vgh_cfn_stack_url="https://s3.amazonaws.com/${vgh_assets_s3path}/cfn" -export vgh_stack_parameters="\ - ParameterKey=KeyName,ParameterValue=${vgh_ec2_key} \ - ParameterKey=AssetsBucket,ParameterValue=${vgh_assets_bucket} \ - ParameterKey=EnvType,ParameterValue=${vgh_env_type} \ - ParameterKey=SSHLocation,ParameterValue=${vgh_ssh_location} \ - ParameterKey=VPCTemplateKey,ParameterValue=${vgh_cfn_stack_url}/vpc.json \ - ParameterKey=SGTemplateKey,ParameterValue=${vgh_cfn_stack_url}/iam.json \ - ParameterKey=IAMTemplateKey,ParameterValue=${vgh_cfn_stack_url}/sec_grp.json" +export vgh_stack_parameters; vgh_stack_parameters=$(cat < Date: Tue, 19 Jan 2016 09:35:16 -0600 Subject: [PATCH 42/49] Fix create stack command --- bin/cfn_create_stack | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/cfn_create_stack b/bin/cfn_create_stack index a0dfc36..990ab81 100755 --- a/bin/cfn_create_stack +++ b/bin/cfn_create_stack @@ -14,7 +14,7 @@ aws cloudformation create-stack \ --tags "${vgh_stack_tags:?}" \ --on-failure 'DELETE' -if aws_cfn_wait_for_stack "${vgh_stack_name:?}"; then +if ! aws_cfn_wait_for_stack "${vgh_stack_name:?}"; then echo "FATAL: The stack ${vgh_stack_name:?} failed to create properly" >&2 exit 1 fi From 367d86378ac77d0032a3b0161e3d80664049744c Mon Sep 17 00:00:00 2001 From: Vlad Ghinea Date: Tue, 19 Jan 2016 09:38:33 -0600 Subject: [PATCH 43/49] Fix Security Group CFN template --- cfn/sec_grp.json | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/cfn/sec_grp.json b/cfn/sec_grp.json index 708cade..582ac6e 100644 --- a/cfn/sec_grp.json +++ b/cfn/sec_grp.json @@ -5,7 +5,16 @@ "VPCId": { "Type": "AWS::EC2::VPC::Id", "Description": "The VPC ID" - } + }, + "SSHLocation" : { + "Type": "String", + "Description" : "The IP address range that can be used to SSH to the EC2 instances", + "MinLength": "9", + "MaxLength": "18", + "Default": "0.0.0.0/0", + "AllowedPattern": "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})/(\\d{1,2})", + "ConstraintDescription": "must be a valid IP CIDR range of the form x.x.x.x/x." + } }, "Resources": { @@ -42,6 +51,12 @@ "GroupId": {"Fn::GetAtt": ["InstanceSecurityGroup", "GroupId"]}, "IpProtocol": "tcp", "FromPort": "0", "ToPort": "65535", "SourceSecurityGroupId": {"Fn::GetAtt": ["ELBSecurityGroup", "GroupId"]} } }, + "InstanceSecurityGroupAllowSSH": { + "Type": "AWS::EC2::SecurityGroupIngress", + "Properties": { + "GroupId": {"Fn::GetAtt": ["InstanceSecurityGroup", "GroupId"]}, "IpProtocol": "tcp", "FromPort": "22", "ToPort": "22", "CidrIp": {"Ref": "SSHLocation"} + } + }, "DBSecurityGroup": { "DependsOn": ["InstanceSecurityGroup"], From 4eeb6c29d5fd05610b29170ad5926933ac05265d Mon Sep 17 00:00:00 2001 From: Vlad Ghinea Date: Tue, 19 Jan 2016 09:39:11 -0600 Subject: [PATCH 44/49] Remove CI user in the IAM CFN template --- cfn/iam.json | 42 ------------------------------------------ 1 file changed, 42 deletions(-) diff --git a/cfn/iam.json b/cfn/iam.json index 08b3e19..cafd691 100644 --- a/cfn/iam.json +++ b/cfn/iam.json @@ -119,40 +119,6 @@ "ManagedPolicyArns": ["arn:aws:iam::aws:policy/service-role/AWSCodeDeployRole"], "Path": "/" } - }, - - "CI": { - "Type": "AWS::IAM::User", - "Properties": { - "Policies": [ - { - "PolicyName": "CIAccess", - "PolicyDocument": { - "Version": "2012-10-17", - "Statement": [ - { - "Sid": "AllowAccessToCodeDeploy", - "Effect": "Allow", - "Action": ["codedeploy:*"], - "Resource": "arn:aws:codedeploy:*" - }, - { - "Sid": "AllowUploadingDeployments", - "Effect": "Allow", - "Action": ["s3:PutObject"], - "Resource": [{"Fn::Join": ["", ["arn:aws:s3:::", {"Ref": "AssetsBucket"}, "/deploy/*"]]}] - } - ] - } - } - ] - } - }, - "CIAccessKey": { - "Type": "AWS::IAM::AccessKey", - "Properties": { - "UserName": {"Ref": "CI"} - } } }, @@ -164,14 +130,6 @@ "CodeDeployServiceRoleARN": { "Description": "The CodeDeploy service role ARN", "Value": {"Fn::GetAtt": ["CodeDeployRole", "Arn"]} - }, - "CIAccessKey": { - "Description": "The CI Access Key", - "Value": {"Ref": "CIAccessKey" } - }, - "CISecretKey": { - "Description": "The CI Secret Key", - "Value": {"Fn::GetAtt": [ "CIAccessKey", "SecretAccessKey" ]} } } } From 9e485ff73c6af134185d50cba46adb01f0d9ad48 Mon Sep 17 00:00:00 2001 From: Vlad Ghinea Date: Tue, 19 Jan 2016 09:41:55 -0600 Subject: [PATCH 45/49] Add a delete stack command --- bin/cfn_delete_stack | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100755 bin/cfn_delete_stack diff --git a/bin/cfn_delete_stack b/bin/cfn_delete_stack new file mode 100755 index 0000000..6a360b3 --- /dev/null +++ b/bin/cfn_delete_stack @@ -0,0 +1,13 @@ +#!/usr/bin/env bash +# Creates CloudFormation stack + +# Load environment +# shellcheck disable=1090 +. "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)/../include/aws.sh" + +aws cloudformation delete-stack --stack-name "${vgh_stack_name:?}" + +if ! aws_cfn_wait_for_stack "${vgh_stack_name:?}"; then + echo "FATAL: The stack ${vgh_stack_name:?} failed to delete properly" >&2 + exit 1 +fi From 93f245925352f508733b83e0465b3666dba725b5 Mon Sep 17 00:00:00 2001 From: Vlad Ghinea Date: Tue, 19 Jan 2016 18:28:47 -0600 Subject: [PATCH 46/49] Remove stack notifications --- .env.enc | Bin 288 -> 256 bytes bin/cfn_create_stack | 1 - environment.sh | 1 - 3 files changed, 2 deletions(-) diff --git a/.env.enc b/.env.enc index 608aa3a55088353c5ee1746ff4e8cf2c03b72ac5..f11bd252aebbf5f6e8a88f92d319292503f3beb6 100644 GIT binary patch literal 256 zcmV+b0ssC!TYRt(d~Ej#HWy;837`dHQri+gCjDN|A^)gupjNnNNYHrWia6P#h4z#I zUT9$C+B3VC_g+}Ns_R$+8|k7xlH|}krp_J0f-?MXGCHD|mOP~c`z{Fw`ss5k_@5nr zTEu$ofF9+?r8Ptp86%V;O@uiPxM-1xBmGEYQa`a2P?DtfXBM7_ZR zh7?_xtj*xY@u)r%9&74pMyhiti$L`esdbm#8t?4afo>eO)IYTrXPnq8pQ(!Z;ad)f z>7~{BiS>w?sYcNv0DejS7IB;M_*8hsX>P&ui&uiWE5fJVYlgyq!V5gq5#3cOl4X&F7}_!>-PbMx2v z8x0?CbDXWDPy{}8(K%Jwb`P|%W-Fx! z8}XJd856VQRn`B Date: Tue, 19 Jan 2016 18:30:41 -0600 Subject: [PATCH 47/49] Upgrade aws cli if needed --- bin/ci.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/ci.sh b/bin/ci.sh index b8a2f13..30e65c7 100755 --- a/bin/ci.sh +++ b/bin/ci.sh @@ -7,7 +7,7 @@ case "$1" in install) - pip install --user awscli + pip install --user --upgrade awscli cd dist/profile || exit bundle install --without development system_tests --path vendor ;; From 5c3301959281853186fd8433b491bb659732a3fb Mon Sep 17 00:00:00 2001 From: Vlad Ghinea Date: Wed, 20 Jan 2016 07:06:25 -0600 Subject: [PATCH 48/49] Add a load balancer --- cfn/vgh.json | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/cfn/vgh.json b/cfn/vgh.json index ca3f3ab..438d1d6 100644 --- a/cfn/vgh.json +++ b/cfn/vgh.json @@ -40,6 +40,31 @@ }, "Resources": { + "ELB": { + "Type": "AWS::ElasticLoadBalancing::LoadBalancer", + "Properties": { + "SecurityGroups": [{"Fn::GetAtt": ["SG", "Outputs.ELBSecurityGroup"]}], + "Subnets": [ + {"Fn::GetAtt": ["VPC", "Outputs.Subnet1"]}, + {"Fn::GetAtt": ["VPC", "Outputs.Subnet2"]}, + {"Fn::GetAtt": ["VPC", "Outputs.Subnet3"]}, + {"Fn::GetAtt": ["VPC", "Outputs.Subnet4"]} + ], + "CrossZone": "true", + "Listeners": [ + {"LoadBalancerPort": "80", "InstancePort": "80", "Protocol": "HTTP"} + ], + "HealthCheck": { + "Target": "HTTP:80/", + "HealthyThreshold": "2", + "UnhealthyThreshold": "6", + "Interval": "10", + "Timeout": "5" + }, + "ConnectionDrainingPolicy": {"Enabled": "true", "Timeout": "60"} + } + }, + "VPC": { "Type": "AWS::CloudFormation::Stack", "Properties": { @@ -67,5 +92,11 @@ } } } + }, + "Outputs": { + "URL": { + "Description": "URL - The Load Balancer Public DNS", + "Value": {"Fn::Join": ["", ["http://", {"Fn::GetAtt": ["ELB", "DNSName"]}]]} + } } } From aeea06c28d5d2d4ba0e45e2c7b87258a26a755cd Mon Sep 17 00:00:00 2001 From: Vlad Ghinea Date: Wed, 20 Jan 2016 07:19:50 -0600 Subject: [PATCH 49/49] Ignore external ip error --- environment.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/environment.sh b/environment.sh index 14c7974..a44ad5e 100644 --- a/environment.sh +++ b/environment.sh @@ -21,7 +21,7 @@ case "${BRANCH}" in esac ## External ip -external_ip=$(dig +short myip.opendns.com @resolver1.opendns.com || true) +external_ip=$(dig +short myip.opendns.com @resolver1.opendns.com 2>/dev/null || true) # AWS export AWS_DEFAULT_REGION='us-east-1'