cumulus-aws 0.11.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (173) hide show
  1. checksums.yaml +15 -0
  2. data/.gitignore +3 -0
  3. data/.travis.yml +12 -0
  4. data/Gemfile +4 -0
  5. data/Gemfile.lock +29 -0
  6. data/LICENSE +202 -0
  7. data/README.md +41 -0
  8. data/autocomplete +137 -0
  9. data/bin/cumulus +658 -0
  10. data/cumulus +2 -0
  11. data/cumulus-aws.gemspec +20 -0
  12. data/lib/autoscaling/AutoScaling.rb +40 -0
  13. data/lib/autoscaling/loader/Loader.rb +56 -0
  14. data/lib/autoscaling/manager/Manager.rb +360 -0
  15. data/lib/autoscaling/models/AlarmConfig.rb +165 -0
  16. data/lib/autoscaling/models/AlarmDiff.rb +172 -0
  17. data/lib/autoscaling/models/AutoScalingDiff.rb +178 -0
  18. data/lib/autoscaling/models/GroupConfig.rb +330 -0
  19. data/lib/autoscaling/models/PolicyConfig.rb +135 -0
  20. data/lib/autoscaling/models/PolicyDiff.rb +73 -0
  21. data/lib/autoscaling/models/ScheduledActionDiff.rb +53 -0
  22. data/lib/autoscaling/models/ScheduledConfig.rb +96 -0
  23. data/lib/aws_extensions/ec2/DhcpOptions.rb +41 -0
  24. data/lib/aws_extensions/ec2/Instance.rb +29 -0
  25. data/lib/aws_extensions/ec2/NetworkAcl.rb +25 -0
  26. data/lib/aws_extensions/ec2/NetworkInterface.rb +14 -0
  27. data/lib/aws_extensions/ec2/RouteTable.rb +26 -0
  28. data/lib/aws_extensions/ec2/SecurityGroup.rb +16 -0
  29. data/lib/aws_extensions/ec2/Subnet.rb +28 -0
  30. data/lib/aws_extensions/ec2/Volume.rb +24 -0
  31. data/lib/aws_extensions/ec2/Vpc.rb +14 -0
  32. data/lib/aws_extensions/ec2/VpcEndpoint.rb +11 -0
  33. data/lib/aws_extensions/elb/BackendServerDescription.rb +12 -0
  34. data/lib/aws_extensions/elb/PolicyDescription.rb +14 -0
  35. data/lib/aws_extensions/kinesis/StreamDescription.rb +12 -0
  36. data/lib/aws_extensions/route53/AliasTarget.rb +21 -0
  37. data/lib/aws_extensions/s3/Bucket.rb +33 -0
  38. data/lib/aws_extensions/s3/BucketAcl.rb +28 -0
  39. data/lib/aws_extensions/s3/BucketCors.rb +17 -0
  40. data/lib/aws_extensions/s3/BucketLifecycle.rb +21 -0
  41. data/lib/aws_extensions/s3/BucketLogging.rb +18 -0
  42. data/lib/aws_extensions/s3/BucketNotification.rb +23 -0
  43. data/lib/aws_extensions/s3/BucketPolicy.rb +18 -0
  44. data/lib/aws_extensions/s3/BucketTagging.rb +15 -0
  45. data/lib/aws_extensions/s3/BucketVersioning.rb +14 -0
  46. data/lib/aws_extensions/s3/BucketWebsite.rb +49 -0
  47. data/lib/aws_extensions/s3/CORSRule.rb +27 -0
  48. data/lib/aws_extensions/s3/ReplicationConfiguration.rb +22 -0
  49. data/lib/cloudfront/CloudFront.rb +83 -0
  50. data/lib/cloudfront/loader/Loader.rb +31 -0
  51. data/lib/cloudfront/manager/Manager.rb +183 -0
  52. data/lib/cloudfront/models/CacheBehaviorConfig.rb +237 -0
  53. data/lib/cloudfront/models/CacheBehaviorDiff.rb +211 -0
  54. data/lib/cloudfront/models/CustomOriginConfig.rb +51 -0
  55. data/lib/cloudfront/models/CustomOriginDiff.rb +74 -0
  56. data/lib/cloudfront/models/DistributionConfig.rb +183 -0
  57. data/lib/cloudfront/models/DistributionDiff.rb +131 -0
  58. data/lib/cloudfront/models/InvalidationConfig.rb +37 -0
  59. data/lib/cloudfront/models/OriginConfig.rb +144 -0
  60. data/lib/cloudfront/models/OriginDiff.rb +86 -0
  61. data/lib/cloudfront/models/OriginSslProtocols.rb +28 -0
  62. data/lib/cloudfront/models/OriginSslProtocolsDiff.rb +39 -0
  63. data/lib/common/BaseLoader.rb +80 -0
  64. data/lib/common/manager/Manager.rb +148 -0
  65. data/lib/common/models/Diff.rb +114 -0
  66. data/lib/common/models/ListChange.rb +21 -0
  67. data/lib/common/models/TagsDiff.rb +55 -0
  68. data/lib/common/models/UTCTimeSource.rb +17 -0
  69. data/lib/conf/Configuration.rb +365 -0
  70. data/lib/ec2/EC2.rb +503 -0
  71. data/lib/ec2/IPProtocolMapping.rb +165 -0
  72. data/lib/ec2/loaders/EbsLoader.rb +19 -0
  73. data/lib/ec2/loaders/InstanceLoader.rb +32 -0
  74. data/lib/ec2/managers/EbsManager.rb +176 -0
  75. data/lib/ec2/managers/InstanceManager.rb +509 -0
  76. data/lib/ec2/models/EbsGroupConfig.rb +133 -0
  77. data/lib/ec2/models/EbsGroupDiff.rb +48 -0
  78. data/lib/ec2/models/InstanceConfig.rb +202 -0
  79. data/lib/ec2/models/InstanceDiff.rb +95 -0
  80. data/lib/elb/ELB.rb +148 -0
  81. data/lib/elb/loader/Loader.rb +65 -0
  82. data/lib/elb/manager/Manager.rb +581 -0
  83. data/lib/elb/models/AccessLogConfig.rb +82 -0
  84. data/lib/elb/models/AccessLogDiff.rb +47 -0
  85. data/lib/elb/models/HealthCheckConfig.rb +91 -0
  86. data/lib/elb/models/HealthCheckDiff.rb +50 -0
  87. data/lib/elb/models/ListenerConfig.rb +99 -0
  88. data/lib/elb/models/ListenerDiff.rb +91 -0
  89. data/lib/elb/models/LoadBalancerConfig.rb +239 -0
  90. data/lib/elb/models/LoadBalancerDiff.rb +265 -0
  91. data/lib/iam/IAM.rb +36 -0
  92. data/lib/iam/loader/Loader.rb +117 -0
  93. data/lib/iam/manager/IamGroups.rb +98 -0
  94. data/lib/iam/manager/IamResource.rb +288 -0
  95. data/lib/iam/manager/IamRoles.rb +112 -0
  96. data/lib/iam/manager/IamUsers.rb +54 -0
  97. data/lib/iam/manager/Manager.rb +29 -0
  98. data/lib/iam/migration/AssumeRoleUnifier.rb +34 -0
  99. data/lib/iam/migration/PolicyUnifier.rb +90 -0
  100. data/lib/iam/models/GroupConfig.rb +40 -0
  101. data/lib/iam/models/IamDiff.rb +132 -0
  102. data/lib/iam/models/PolicyConfig.rb +67 -0
  103. data/lib/iam/models/ResourceWithPolicy.rb +208 -0
  104. data/lib/iam/models/RoleConfig.rb +53 -0
  105. data/lib/iam/models/StatementConfig.rb +35 -0
  106. data/lib/iam/models/UserConfig.rb +21 -0
  107. data/lib/kinesis/Kinesis.rb +94 -0
  108. data/lib/kinesis/loader/Loader.rb +19 -0
  109. data/lib/kinesis/manager/Manager.rb +206 -0
  110. data/lib/kinesis/models/StreamConfig.rb +75 -0
  111. data/lib/kinesis/models/StreamDiff.rb +58 -0
  112. data/lib/lambda/Lambda.rb +41 -0
  113. data/lib/route53/loader/Loader.rb +32 -0
  114. data/lib/route53/manager/Manager.rb +241 -0
  115. data/lib/route53/models/AliasTarget.rb +86 -0
  116. data/lib/route53/models/RecordConfig.rb +178 -0
  117. data/lib/route53/models/RecordDiff.rb +140 -0
  118. data/lib/route53/models/Vpc.rb +24 -0
  119. data/lib/route53/models/ZoneConfig.rb +156 -0
  120. data/lib/route53/models/ZoneDiff.rb +118 -0
  121. data/lib/s3/S3.rb +89 -0
  122. data/lib/s3/loader/Loader.rb +66 -0
  123. data/lib/s3/manager/Manager.rb +296 -0
  124. data/lib/s3/models/BucketConfig.rb +321 -0
  125. data/lib/s3/models/BucketDiff.rb +167 -0
  126. data/lib/s3/models/GrantConfig.rb +189 -0
  127. data/lib/s3/models/GrantDiff.rb +50 -0
  128. data/lib/s3/models/LifecycleConfig.rb +142 -0
  129. data/lib/s3/models/LifecycleDiff.rb +46 -0
  130. data/lib/s3/models/LoggingConfig.rb +81 -0
  131. data/lib/s3/models/NotificationConfig.rb +157 -0
  132. data/lib/s3/models/NotificationDiff.rb +62 -0
  133. data/lib/s3/models/ReplicationConfig.rb +133 -0
  134. data/lib/s3/models/ReplicationDiff.rb +60 -0
  135. data/lib/s3/models/WebsiteConfig.rb +107 -0
  136. data/lib/security/SecurityGroups.rb +39 -0
  137. data/lib/security/loader/Loader.rb +94 -0
  138. data/lib/security/manager/Manager.rb +246 -0
  139. data/lib/security/models/RuleConfig.rb +161 -0
  140. data/lib/security/models/RuleDiff.rb +72 -0
  141. data/lib/security/models/RuleMigration.rb +127 -0
  142. data/lib/security/models/SecurityGroupConfig.rb +172 -0
  143. data/lib/security/models/SecurityGroupDiff.rb +112 -0
  144. data/lib/sns/SNS.rb +40 -0
  145. data/lib/sqs/SQS.rb +62 -0
  146. data/lib/sqs/loader/Loader.rb +34 -0
  147. data/lib/sqs/manager/Manager.rb +128 -0
  148. data/lib/sqs/models/DeadLetterConfig.rb +70 -0
  149. data/lib/sqs/models/DeadLetterDiff.rb +35 -0
  150. data/lib/sqs/models/QueueConfig.rb +115 -0
  151. data/lib/sqs/models/QueueDiff.rb +89 -0
  152. data/lib/util/Colors.rb +111 -0
  153. data/lib/util/StatusCodes.rb +51 -0
  154. data/lib/vpc/loader/Loader.rb +73 -0
  155. data/lib/vpc/manager/Manager.rb +954 -0
  156. data/lib/vpc/models/AclEntryConfig.rb +150 -0
  157. data/lib/vpc/models/AclEntryDiff.rb +54 -0
  158. data/lib/vpc/models/DhcpConfig.rb +100 -0
  159. data/lib/vpc/models/DhcpDiff.rb +90 -0
  160. data/lib/vpc/models/EndpointConfig.rb +76 -0
  161. data/lib/vpc/models/EndpointDiff.rb +69 -0
  162. data/lib/vpc/models/NetworkAclConfig.rb +87 -0
  163. data/lib/vpc/models/NetworkAclDiff.rb +116 -0
  164. data/lib/vpc/models/RouteConfig.rb +82 -0
  165. data/lib/vpc/models/RouteDiff.rb +50 -0
  166. data/lib/vpc/models/RouteTableConfig.rb +92 -0
  167. data/lib/vpc/models/RouteTableDiff.rb +101 -0
  168. data/lib/vpc/models/SubnetConfig.rb +113 -0
  169. data/lib/vpc/models/SubnetDiff.rb +78 -0
  170. data/lib/vpc/models/VpcConfig.rb +173 -0
  171. data/lib/vpc/models/VpcDiff.rb +315 -0
  172. data/rakefile.rb +8 -0
  173. metadata +245 -0
@@ -0,0 +1,165 @@
1
+ module Cumulus
2
+ module EC2
3
+ class IPProtocolMapping
4
+ @@protocol_keyword = Hash[{
5
+ "-1" => "all",
6
+ "0" => "HOPOPT",
7
+ "1" => "ICMP",
8
+ "2" => "IGMP",
9
+ "3" => "GGP",
10
+ "4" => "IP-in-IP",
11
+ "5" => "ST",
12
+ "6" => "TCP",
13
+ "7" => "CBT",
14
+ "8" => "EGP",
15
+ "9" => "IGP",
16
+ "10" => "BBN-RCC-MON",
17
+ "11" => "NVP-II",
18
+ "12" => "PUP",
19
+ "13" => "ARGUS",
20
+ "14" => "EMCON",
21
+ "15" => "XNET",
22
+ "16" => "CHAOS",
23
+ "17" => "UDP",
24
+ "18" => "MUX",
25
+ "19" => "DCN-MEAS",
26
+ "20" => "HMP",
27
+ "21" => "PRM",
28
+ "22" => "XNS-IDP",
29
+ "23" => "TRUNK-1",
30
+ "24" => "TRUNK-2",
31
+ "25" => "LEAF-1",
32
+ "26" => "LEAF-2",
33
+ "27" => "RDP",
34
+ "28" => "IRTP",
35
+ "29" => "ISO-TP4",
36
+ "30" => "NETBLT",
37
+ "31" => "MFE-NSP",
38
+ "32" => "MERIT-INP",
39
+ "33" => "DCCP",
40
+ "34" => "3PC",
41
+ "35" => "IDPR",
42
+ "36" => "XTP",
43
+ "37" => "DDP",
44
+ "38" => "IDPR-CMTP",
45
+ "39" => "TP++",
46
+ "40" => "IL",
47
+ "41" => "IPv6",
48
+ "42" => "SDRP",
49
+ "43" => "IPv6-Route",
50
+ "44" => "IPv6-Frag",
51
+ "45" => "IDRP",
52
+ "46" => "RSVP",
53
+ "47" => "GRE",
54
+ "48" => "MHRP",
55
+ "49" => "BNA",
56
+ "50" => "ESP",
57
+ "51" => "AH",
58
+ "52" => "I-NLSP",
59
+ "53" => "SWIPE",
60
+ "54" => "NARP",
61
+ "55" => "MOBILE",
62
+ "56" => "TLSP",
63
+ "57" => "SKIP",
64
+ "58" => "IPv6-ICMP",
65
+ "59" => "IPv6-NoNxt",
66
+ "60" => "IPv6-Opts",
67
+ "61" => "61",
68
+ "62" => "CFTP",
69
+ "63" => "63",
70
+ "64" => "SAT-EXPAK",
71
+ "65" => "KRYPTOLAN",
72
+ "66" => "RVD",
73
+ "67" => "IPPC",
74
+ "68" => "68",
75
+ "69" => "SAT-MON",
76
+ "70" => "VISA",
77
+ "71" => "IPCU",
78
+ "72" => "CPNX",
79
+ "73" => "CPHB",
80
+ "74" => "WSN",
81
+ "75" => "PVP",
82
+ "76" => "BR-SAT-MON",
83
+ "77" => "SUN-ND",
84
+ "78" => "WB-MON",
85
+ "79" => "WB-EXPAK",
86
+ "80" => "ISO-IP",
87
+ "81" => "VMTP",
88
+ "82" => "SECURE-VMTP",
89
+ "83" => "VINES",
90
+ "85" => "NSFNET-IGP",
91
+ "86" => "DGP",
92
+ "87" => "TCF",
93
+ "88" => "EIGRP",
94
+ "89" => "OSPF",
95
+ "90" => "Sprite-RPC",
96
+ "91" => "LARP",
97
+ "92" => "MTP",
98
+ "93" => "AX.25",
99
+ "94" => "IPIP",
100
+ "95" => "MICP",
101
+ "96" => "SCC-SP",
102
+ "97" => "ETHERIP",
103
+ "98" => "ENCAP",
104
+ "99" => "99",
105
+ "100" => "GMTP",
106
+ "101" => "IFMP",
107
+ "102" => "PNNI",
108
+ "103" => "PIM",
109
+ "104" => "ARIS",
110
+ "105" => "SCPS",
111
+ "106" => "QNX",
112
+ "107" => "A/N",
113
+ "108" => "IPComp",
114
+ "109" => "SNP",
115
+ "110" => "Compaq-Peer",
116
+ "111" => "IPX-in-IP",
117
+ "112" => "VRRP",
118
+ "113" => "PGM",
119
+ "114" => "114",
120
+ "115" => "L2TP",
121
+ "116" => "DDX",
122
+ "117" => "IATP",
123
+ "118" => "STP",
124
+ "119" => "SRP",
125
+ "120" => "UTI",
126
+ "121" => "SMP",
127
+ "122" => "SM",
128
+ "123" => "PTP",
129
+ "124" => "IS-IS over IPv4",
130
+ "125" => "FIRE",
131
+ "126" => "CRTP",
132
+ "127" => "CRUDP",
133
+ "128" => "SSCOPMCE",
134
+ "129" => "IPLT",
135
+ "130" => "SPS",
136
+ "131" => "PIPE",
137
+ "132" => "SCTP",
138
+ "133" => "FC",
139
+ "134" => "RSVP-E2E-IGNORE",
140
+ "135" => "Mobility Header",
141
+ "136" => "UDPLite",
142
+ "137" => "MPLS-in-IP",
143
+ "138" => "manet",
144
+ "139" => "HIP",
145
+ "140" => "Shim6",
146
+ "141" => "WESP",
147
+ "142" => "ROHC"
148
+ }.map { |protocol, keyword| [protocol, keyword.downcase] }]
149
+
150
+ @@keyword_protocol = @@protocol_keyword.invert
151
+
152
+ @@special_cases = {
153
+ "84" => ["ttp", "iptm"]
154
+ }
155
+
156
+ def self.keyword(protocol)
157
+ @@protocol_keyword[protocol] || @@special_cases[protocol].first
158
+ end
159
+
160
+ def self.protocol(keyword)
161
+ @@keyword_protocol[keyword.downcase] || @@special_cases.select { |k, v| v.include? keyword.downcase }.map { |k, _| k }.first
162
+ end
163
+ end
164
+ end
165
+ end
@@ -0,0 +1,19 @@
1
+ require "common/BaseLoader"
2
+ require "conf/Configuration"
3
+ require "ec2/models/EbsGroupConfig"
4
+
5
+ module Cumulus
6
+ module EC2
7
+ module EbsLoader
8
+
9
+ include Common::BaseLoader
10
+
11
+ @@groups_dir = Configuration.instance.ec2.ebs_directory
12
+
13
+ def self.groups
14
+ Common::BaseLoader::resources(@@groups_dir, &EbsGroupConfig.method(:new))
15
+ end
16
+
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,32 @@
1
+ require "common/BaseLoader"
2
+ require "conf/Configuration"
3
+
4
+ require "base64"
5
+
6
+ module Cumulus
7
+ module EC2
8
+ module InstanceLoader
9
+
10
+ include Common::BaseLoader
11
+
12
+ @@instances_dir = Configuration.instance.ec2.instances_directory
13
+ @@user_data_dir = Configuration.instance.ec2.user_data_directory
14
+
15
+ def self.instances
16
+ Common::BaseLoader::resources(@@instances_dir, &InstanceConfig.method(:new))
17
+ end
18
+
19
+ def self.user_data(file)
20
+ Common::BaseLoader::load_file(file, @@user_data_dir)
21
+ end
22
+
23
+ # Public: Returns a Hash of user data file name to base64 of its contents.
24
+ def self.user_data_base64
25
+ @user_data_base64 ||= Hash[Common::BaseLoader::resources(@@user_data_dir, false, &Proc.new do |name, contents|
26
+ [name, Base64.encode64(contents)]
27
+ end)]
28
+ end
29
+
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,176 @@
1
+ require "common/manager/Manager"
2
+ require "conf/Configuration"
3
+ require "ec2/EC2"
4
+ require "ec2/loaders/EbsLoader"
5
+ require "ec2/models/EbsGroupConfig"
6
+ require "ec2/models/EbsGroupDiff"
7
+ require "util/StatusCodes"
8
+
9
+ require "aws-sdk"
10
+
11
+ module Cumulus
12
+ module EC2
13
+ class EbsManager < Common::Manager
14
+
15
+ def initialize
16
+ super()
17
+ @create_asset = true
18
+ @client = Aws::EC2::Client.new(Configuration.instance.client)
19
+ end
20
+
21
+ def resource_name
22
+ "EBS Volume Group"
23
+ end
24
+
25
+ def local_resources
26
+ @local_resources ||= Hash[EbsLoader.groups.map { |local| [local.name, local] }]
27
+ end
28
+
29
+ def aws_resources
30
+ @aws_resources ||= EC2::group_ebs_volumes
31
+ end
32
+
33
+ def unmanaged_diff(aws)
34
+ EbsGroupDiff.unmanaged(aws)
35
+ end
36
+
37
+ def added_diff(local)
38
+ EbsGroupDiff.added(local)
39
+ end
40
+
41
+ def diff_resource(local, aws)
42
+ local.diff(aws)
43
+ end
44
+
45
+ EbsMigrationData = Struct.new(:cumulus_group, :set_group_vols)
46
+ def migrate
47
+ puts Colors.blue("Migrating EBS Volume Groups...")
48
+
49
+ # Create the directories
50
+ ec2_dir = "#{@migration_root}/ec2"
51
+ ebs_dir = "#{ec2_dir}/ebs"
52
+
53
+ if !Dir.exists?(@migration_root)
54
+ Dir.mkdir(@migration_root)
55
+ end
56
+ if !Dir.exists?(ec2_dir)
57
+ Dir.mkdir(ec2_dir)
58
+ end
59
+ if !Dir.exists?(ebs_dir)
60
+ Dir.mkdir(ebs_dir)
61
+ end
62
+
63
+ puts "Would you like Cumulus to automatically update your volumes to have a Group tag that matches the name of the instance they are attached to? (y/n)"
64
+ update_tags = (STDIN.getc.downcase[0] == "y")
65
+
66
+ # Migrate any volumes that have already been grouped
67
+ vol_groups = Hash[EC2.group_ebs_volumes.map { |group_name, cumulus_group| [group_name, EbsMigrationData.new(cumulus_group, nil)] }]
68
+
69
+ # Use the instance name to group volumes if they do not have a group. Anything
70
+ # not attached should not get migrated
71
+ migratable_vols = EC2.ebs_volumes.select { |vol| vol.group.nil? and !vol.attachments.empty? and vol.attached? }
72
+ instance_grouped = migratable_vols.group_by do |vol|
73
+ attachments = vol.attachments.select { |att| att.state == "attached" || att.state == "attaching" }
74
+ attachments.first.instance_id
75
+ end
76
+
77
+ instance_grouped.each do |instance_id, vols|
78
+ instance_name = EC2.id_instances[instance_id].name || instance_id
79
+ vol_groups[instance_name] = EbsMigrationData.new(EbsGroupConfig.new(instance_name).populate!(vols), vols)
80
+ end
81
+
82
+ vol_groups.each do |group_name, data|
83
+ puts "Migrating group #{group_name}"
84
+
85
+ if update_tags and data.set_group_vols
86
+ data.set_group_vols.each do |vol|
87
+ if vol.group.nil?
88
+ set_group_name(vol.volume_id, group_name)
89
+ end
90
+ end
91
+ end
92
+
93
+ json = JSON.pretty_generate(data.cumulus_group.to_hash)
94
+ File.open("#{ebs_dir}/#{group_name}.json", "w") { |f| f.write(json) }
95
+ end
96
+
97
+ end
98
+
99
+ def create(local)
100
+ local.volume_groups.each do |vg|
101
+ vg.count.times do
102
+ create_volume(vg, local.availability_zone, local.name)
103
+ end
104
+ end
105
+ end
106
+
107
+ def update(local, diffs)
108
+
109
+ # If they tried to update AZ, use the old value
110
+ availability_zone = (diffs.select { |d| d.type == EbsGroupChange::AZ }.first.aws rescue local.availability_zone)
111
+
112
+ diffs.each do |diff|
113
+ case diff.type
114
+ when EbsGroupChange::AZ
115
+ puts Colors.blue("Availability zone cannot be updated")
116
+ when EbsGroupChange::VG_REMOVED
117
+ puts Colors.blue("Cumulus does not delete or detach volumes. Manually update #{diff.local.description}")
118
+ when EbsGroupChange::VG_ADDED
119
+ added_vg = diff.local
120
+ puts Colors.blue("Creating #{added_vg.count} x #{added_vg.description}...")
121
+ added_vg.count.times do
122
+ create_volume(added_vg, availability_zone, local.name)
123
+ end
124
+ when EbsGroupChange::VG_COUNT
125
+ if diff.local.count < diff.aws.count
126
+ puts Colors.blue("Cumulus will not delete or detach volumes. Manually update #{diff.local.description}")
127
+ else
128
+ num_added = diff.local.count - diff.aws.count
129
+ puts Colors.blue("Adding #{num_added} x #{diff.local.description}...")
130
+ num_added.times do
131
+ create_volume(diff.local, availability_zone, local.name)
132
+ end
133
+ end
134
+ end
135
+ end
136
+ end
137
+
138
+ private
139
+
140
+ # Internal: Sets the Group tag for an ebs volume
141
+ def set_group_name(volume_id, group_name)
142
+ @client.create_tags({
143
+ resources: [volume_id],
144
+ tags: [
145
+ {
146
+ key: "Group",
147
+ value: group_name
148
+ }
149
+ ]
150
+ })
151
+ end
152
+
153
+ # Internal: Creates a volume then sets the group name
154
+ #
155
+ # vg - the VolumeGroup config to use for volume attributes
156
+ # az - the availability zone to create the volume in
157
+ # group_name - the name of the group the volume belongs to
158
+ def create_volume(vg, az, group_name)
159
+ resp = @client.create_volume({
160
+ size: vg.size,
161
+ availability_zone: az,
162
+ volume_type: vg.type,
163
+ iops: vg.iops,
164
+ encrypted: vg.encrypted,
165
+ kms_key_id: vg.kms_key
166
+ })
167
+
168
+ set_group_name(resp.volume_id, group_name)
169
+ rescue => e
170
+ puts "Failed to create a volume of #{vg.description}: #{e}"
171
+ exit StatusCodes::EXCEPTION
172
+ end
173
+
174
+ end
175
+ end
176
+ end
@@ -0,0 +1,509 @@
1
+ require "autoscaling/AutoScaling"
2
+ require "common/manager/Manager"
3
+ require "conf/Configuration"
4
+ require "ec2/EC2"
5
+ require "ec2/loaders/InstanceLoader"
6
+ require "ec2/models/InstanceConfig"
7
+ require "ec2/models/InstanceDiff"
8
+ require "iam/IAM"
9
+ require "util/StatusCodes"
10
+
11
+ require "aws-sdk"
12
+ require "base64"
13
+
14
+ module Cumulus
15
+ module EC2
16
+ class InstanceManager < Common::Manager
17
+
18
+ def initialize
19
+ super()
20
+ @create_asset = true
21
+ @client = Aws::EC2::Client.new(Configuration.instance.client)
22
+ @device_name_base = Configuration.instance.ec2.volume_mount_base
23
+ @device_name_start = Configuration.instance.ec2.volume_mount_start
24
+ @device_name_end = Configuration.instance.ec2.volume_mount_end
25
+ end
26
+
27
+ def resource_name
28
+ "EC2 Instance"
29
+ end
30
+
31
+ def local_resources
32
+ @local_resources ||= Hash[InstanceLoader.instances.map { |local| [local.name, local] }]
33
+ end
34
+
35
+ def aws_resources
36
+ @aws_resources ||=
37
+ EC2::named_instances
38
+ .reject { |name, i| AutoScaling::instance_ids.include?(i.instance_id) }
39
+ .select { |name, i| !Configuration.instance.ec2.ignore_unmanaged_instances || local_resources.has_key?(name) }
40
+ end
41
+
42
+ def unmanaged_diff(aws)
43
+ InstanceDiff.unmanaged(aws)
44
+ end
45
+
46
+ def added_diff(local)
47
+ InstanceDiff.added(local)
48
+ end
49
+
50
+ def diff_resource(local, aws)
51
+ puts Colors.blue("Processing #{local.name}...")
52
+ instance_attributes = EC2::id_instance_attributes(aws.instance_id)
53
+ user_data_file = InstanceLoader.user_data_base64.key(instance_attributes.user_data)
54
+ cumulus_version = InstanceConfig.new(local.name).populate!(aws, user_data_file, instance_attributes.tags)
55
+
56
+ local.diff(cumulus_version)
57
+ end
58
+
59
+ def migrate
60
+ puts Colors.blue("Migrating Instances...")
61
+
62
+ # Create the directories
63
+ ec2_dir = "#{@migration_root}/ec2"
64
+ instances_dir = "#{ec2_dir}/instances"
65
+ user_data_dir = "#{ec2_dir}/user-data-scripts"
66
+
67
+ if !Dir.exists?(@migration_root)
68
+ Dir.mkdir(@migration_root)
69
+ end
70
+ if !Dir.exists?(ec2_dir)
71
+ Dir.mkdir(ec2_dir)
72
+ end
73
+ if !Dir.exists?(instances_dir)
74
+ Dir.mkdir(instances_dir)
75
+ end
76
+ if !Dir.exists?(user_data_dir)
77
+ Dir.mkdir(user_data_dir)
78
+ end
79
+
80
+ # Only migrate instances not in an autoscaling group
81
+ migratable_instances = EC2::named_instances.reject { |name, i| AutoScaling::instance_ids.include?(i.instance_id) }
82
+ puts "Will migrate #{migratable_instances.length} instances"
83
+
84
+ migratable_instances.each do |name, instance|
85
+ puts "Migrating #{name}..."
86
+
87
+ instance_attributes = EC2::id_instance_attributes(instance.instance_id)
88
+
89
+ # If there was user data set, migrate that too
90
+ if instance_attributes.user_data
91
+ user_data_file = "#{name}.sh"
92
+ file_location = "#{user_data_dir}/#{user_data_file}"
93
+ puts "Migrating user data script to #{file_location}"
94
+ file_contents = Base64.decode64(instance_attributes.user_data)
95
+ File.open(file_location, "w") { |f| f.write(file_contents) }
96
+ end
97
+
98
+ cumulus_instance = InstanceConfig.new(name).populate!(instance, user_data_file, instance_attributes.tags)
99
+
100
+ json = JSON.pretty_generate(cumulus_instance.to_hash)
101
+ File.open("#{instances_dir}/#{name}.json", "w") { |f| f.write(json) }
102
+ end
103
+
104
+ end
105
+
106
+ def create(local)
107
+ #######################################
108
+ # Make sure required attributes are set
109
+ #######################################
110
+
111
+ # Check for image
112
+ errors = []
113
+ image_id = local.image || Configuration.instance.ec2.default_image_id
114
+ if image_id.nil?
115
+ errors << "image is required"
116
+ end
117
+
118
+ # Check for IAM profile
119
+ profile_arn = if local.profile.nil?
120
+ errors << "profile is required"
121
+ nil
122
+ else
123
+ arn = IAM::get_instance_profile_arn(local.profile)
124
+ if arn.nil?
125
+ errors << "no profile named #{local.profile} exists"
126
+ end
127
+ arn
128
+ end
129
+
130
+ # Check for security groups
131
+ if local.security_groups.empty?
132
+ errors << "security-groups is required"
133
+ end
134
+
135
+ # Check for subnet
136
+ if local.subnet.nil?
137
+ errors << "subnet is required"
138
+ end
139
+
140
+ aws_subnet = EC2::named_subnets[local.subnet]
141
+ if aws_subnet.nil?
142
+ errors << "subnet #{local.subnet} does not exist"
143
+ end
144
+
145
+ # Get the vpc id from the subnet
146
+ vpc_id = aws_subnet.vpc_id
147
+
148
+ security_group_ids = local.security_groups.map do |sg|
149
+ sg_id = SecurityGroups.vpc_security_group_id_names[vpc_id].key(sg)
150
+ if sg_id.nil?
151
+ errors << "security group #{sg} does not exist"
152
+ end
153
+ sg_id
154
+ end
155
+
156
+ # Check for type
157
+ if local.type.nil?
158
+ errors << "type is required"
159
+ end
160
+
161
+ # Make sure the placement group exists
162
+ if !local.placement_group.nil? and EC2::named_placement_groups[local.placement_group].nil?
163
+ errors << "placement group #{local.placement_group} does not exist"
164
+ end
165
+
166
+ availability_zone = if aws_subnet then aws_subnet.availability_zone end
167
+
168
+ # Check for volume groups
169
+ volumes = if !local.volume_groups.empty?
170
+ # Try to get the volumes for each volume group, make sure they are in the right AZ
171
+ local.volume_groups.map do |vg|
172
+ vols = EC2::group_ebs_volumes_aws[vg]
173
+ if vols.nil?
174
+ errors << "volume group #{vg} does not exist"
175
+ elsif vols.empty?
176
+ errors << "could not find volumes for group #{vg}"
177
+ else
178
+ if availability_zone and vols.any? { |vol| vol.availability_zone != availability_zone }
179
+ errors << "not all volumes in #{vg} are in the correct availability zone: #{availability_zone}"
180
+ end
181
+ end
182
+ vols
183
+ end.flatten
184
+ else
185
+ puts "Warning: Only the root volume will be attached to the instance"
186
+ []
187
+ end
188
+
189
+ # Make sure there are not more volumes than device names for volumes
190
+ if (@device_name_end.ord - @device_name_start.ord + 1) < volumes.length
191
+ errors << "cannot attach more volumes than there are names for between #{@device_name_base}#{@device_name_start} and #{@device_name_base}#{@device_name_end}"
192
+ end
193
+
194
+ if !errors.empty?
195
+ puts "Could not create #{local.name}:"
196
+ errors.each { |e| puts "\t#{e}"}
197
+ exit StatusCodes::EXCEPTION
198
+ end
199
+
200
+ created_instance = @client.run_instances({
201
+ image_id: image_id,
202
+ min_count: 1,
203
+ max_count: 1,
204
+ key_name: local.key_name,
205
+ security_group_ids: if local.network_interfaces == 0 then security_group_ids end,
206
+ user_data: if local.user_data then Base64.encode64(InstanceLoader.user_data(local.user_data)) end,
207
+ instance_type: local.type,
208
+ subnet_id: if local.network_interfaces == 0 then aws_subnet.subnet_id end,
209
+ placement: {
210
+ availability_zone: availability_zone,
211
+ group_name: local.placement_group,
212
+ tenancy: local.tenancy,
213
+ },
214
+ monitoring: {
215
+ enabled: local.monitoring
216
+ },
217
+ private_ip_address: if local.network_interfaces == 0 then local.private_ip_address end,
218
+ network_interfaces: Array.new(local.network_interfaces) do |index|
219
+ {
220
+ subnet_id: aws_subnet.subnet_id,
221
+ groups: security_group_ids,
222
+ delete_on_termination: true,
223
+ device_index: index,
224
+ private_ip_addresses: if local.network_interfaces == 1 and local.private_ip_address
225
+ [
226
+ {
227
+ private_ip_address: local.private_ip_address,
228
+ primary: true
229
+ }
230
+ ]
231
+ end
232
+ }
233
+ end,
234
+ iam_instance_profile: {
235
+ arn: profile_arn
236
+ },
237
+ ebs_optimized: local.ebs_optimized
238
+ }).instances.first
239
+
240
+ # Wait until the instance is running then attach volumes
241
+ print "Waiting for instance to run"
242
+ @client.wait_until(:instance_running, {
243
+ instance_ids: [created_instance.instance_id]
244
+ }) do |waiter|
245
+ waiter.before_wait { print "." }
246
+ end
247
+ puts ""
248
+
249
+ if !local.tags.empty?
250
+ set_tags(created_instance.instance_id, local.tags)
251
+ end
252
+ set_name(created_instance.instance_id, local.name)
253
+
254
+ if created_instance.source_dest_check != local.source_dest_check
255
+ set_instance_source_dest_check(created_instance.instance_id, local.source_dest_check)
256
+ end
257
+
258
+ # If there are multiple network interfaces, source dest check must be set on each one
259
+ if created_instance.network_interfaces.length > 1
260
+ created_instance.network_interfaces.each do |interface|
261
+ set_interface_source_dest_check(interface.network_interface_id, local.source_dest_check)
262
+ end
263
+ else
264
+ set_instance_source_dest_check(created_instance.instance_id, local.source_dest_check)
265
+ end
266
+
267
+ # Attach volume groups
268
+ attach_volumes(created_instance.instance_id, volumes, @device_name_start)
269
+
270
+ end
271
+
272
+ def update(local, diffs)
273
+ aws_instance = EC2::named_instances[local.name]
274
+
275
+ diffs.each do |diff|
276
+ case diff.type
277
+ when InstanceChange::PROFILE,
278
+ InstanceChange::SUBNET,
279
+ InstanceChange::TYPE,
280
+ InstanceChange::TENANCY
281
+
282
+ puts Colors.red("Cannot change #{diff.asset_type}")
283
+ when InstanceChange::EBS
284
+ if !aws_instance.stopped?
285
+ puts Colors.red("Cannot update EBS Optimized unless the instance is stopped")
286
+ else
287
+ puts "Setting EBS Optimized to #{local.ebs_optimized}..."
288
+ set_ebs_optimized(aws_instance.instance_id, local.ebs_optimized)
289
+ end
290
+ when InstanceChange::MONITORING
291
+ if local.monitoring
292
+ puts "Enabling monitoring..."
293
+ set_monitoring(aws_instance.instance_id, true)
294
+ else
295
+ puts "Disabling monitoring..."
296
+ set_monitoring(aws_instance.instance_id, false)
297
+ end
298
+ when InstanceChange::SECURITY_GROUPS
299
+ puts "Updating Security Groups..."
300
+
301
+ # If there are multiple network interfaces, security groups must be set on each one
302
+ if aws_instance.network_interfaces.length > 1
303
+ aws_instance.network_interfaces.each do |interface|
304
+ set_interface_security_groups(aws_instance.vpc_id, interface.network_interface_id, local.security_groups)
305
+ end
306
+ else
307
+ set_instance_security_groups(aws_instance.vpc_id, aws_instance.instance_id, local.security_groups)
308
+ end
309
+
310
+ when InstanceChange::SDCHECK
311
+ puts "Setting Source Dest Check to #{local.source_dest_check}..."
312
+
313
+ # If there are multiple network interfaces, source dest check must be set on each one
314
+ if aws_instance.network_interfaces.length > 1
315
+ aws_instance.network_interfaces.each do |interface|
316
+ set_interface_source_dest_check(interface.network_interface_id, local.source_dest_check)
317
+ end
318
+ else
319
+ set_instance_source_dest_check(aws_instance.instance_id, local.source_dest_check)
320
+ end
321
+ when InstanceChange::INTERFACES
322
+ if diff.aws > diff.local
323
+ puts Colors.red("Cumulus will not detach or delete network interfaces. You must do so manually and update the config")
324
+ else
325
+ # Figure out highest device index for current interfaces
326
+ highest_device_index = (aws_instance.network_interfaces.map(&:attachment).map(&:device_index).max || 0) + 1
327
+
328
+ (diff.local - diff.aws).times do |i|
329
+ puts "Creating network interface..."
330
+ interface_id = create_network_interface(aws_instance.vpc_id, aws_instance.subnet_id, local.security_groups)
331
+ set_interface_source_dest_check(interface_id, local.source_dest_check)
332
+
333
+ puts "Attaching network interface..."
334
+ attachment_id = attach_network_interface(aws_instance.instance_id, interface_id, highest_device_index + i)
335
+ set_delete_on_terminate(interface_id, attachment_id)
336
+ end
337
+ end
338
+ when InstanceChange::VOLUME_GROUPS
339
+ # Figure out the highest device name for already attached volumes
340
+ last_device_name = aws_instance.nonroot_devices.map(&:device_name).sort.last
341
+ start_attaching_at = if last_device_name then (last_device_name[-1].ord + 1).chr else @start_device_letter end
342
+
343
+ # Figure out which volumes in the group are not attached
344
+ volumes_to_attach = diff.local.map { |group_name, group_config| EC2::group_ebs_volumes_aws[group_name] }.flatten.select(&:detached?)
345
+
346
+ # Make sure there are not more volumes than device names for volumes
347
+ if start_attaching_at.ord > @device_name_end.ord
348
+ puts Colors.red("Cannot attach volumes past #{@device_name_base}#{@device_name_end}")
349
+ elsif (@device_name_end.ord - start_attaching_at.ord + 1) < volumes_to_attach.length
350
+ puts Colors.red("Cannot attach more volumes than there are names for between #{@device_name_base}#{start_attaching_at} and #{@device_name_base}#{@device_name_end}")
351
+ else
352
+ attach_volumes(aws_instance.instance_id, volumes_to_attach, start_attaching_at)
353
+ end
354
+ when InstanceChange::TAGS
355
+ puts "Updating tags..."
356
+
357
+ if !diff.tags_to_remove.empty?
358
+ delete_tags(aws_instance.instance_id, diff.tags_to_remove)
359
+ end
360
+
361
+ if !diff.tags_to_add.empty?
362
+ set_tags(aws_instance.instance_id, diff.tags_to_add)
363
+ end
364
+
365
+ end
366
+ end
367
+ end
368
+
369
+ private
370
+
371
+ def set_name(instance_id, name)
372
+ @client.create_tags({
373
+ resources: [instance_id],
374
+ tags: [
375
+ {
376
+ key: "Name",
377
+ value: name
378
+ }
379
+ ]
380
+ })
381
+ end
382
+
383
+ def set_tags(instance_id, tags)
384
+ @client.create_tags({
385
+ resources: [instance_id],
386
+ tags: tags.map do |key, val|
387
+ {
388
+ key: key,
389
+ value: val
390
+ }
391
+ end
392
+ })
393
+ end
394
+
395
+ def delete_tags(instance_id, tags)
396
+ @client.delete_tags({
397
+ resources: [instance_id],
398
+ tags: tags.map do |key, val|
399
+ {
400
+ key: key,
401
+ value: val
402
+ }
403
+ end
404
+ })
405
+ end
406
+
407
+ def set_instance_source_dest_check(instance_id, source_dest_check)
408
+ @client.modify_instance_attribute({
409
+ instance_id: instance_id,
410
+ source_dest_check: {
411
+ value: source_dest_check
412
+ }
413
+ })
414
+ end
415
+
416
+ def set_interface_source_dest_check(interface_id, source_dest_check)
417
+ @client.modify_network_interface_attribute({
418
+ network_interface_id: interface_id,
419
+ source_dest_check: {
420
+ value: source_dest_check
421
+ }
422
+ })
423
+ end
424
+
425
+ def set_ebs_optimized(instance_id, optimized)
426
+ @client.modify_instance_attribute({
427
+ instance_id: instance_id,
428
+ ebs_optimized: {
429
+ value: optimized
430
+ }
431
+ })
432
+ end
433
+
434
+ def set_instance_security_groups(vpc_id, instance_id, sg_names)
435
+ @client.modify_instance_attribute({
436
+ instance_id: instance_id,
437
+ groups: sg_names.map { |sg| SecurityGroups.vpc_security_group_id_names[vpc_id].key(sg) }
438
+ })
439
+ end
440
+
441
+ def set_interface_security_groups(vpc_id, interface_id, sg_names)
442
+ @client.modify_network_interface_attribute({
443
+ network_interface_id: interface_id,
444
+ groups: sg_names.map { |sg| SecurityGroups.vpc_security_group_id_names[vpc_id].key(sg) }
445
+ })
446
+ end
447
+
448
+ def set_monitoring(instance_id, monitoring)
449
+ if monitoring
450
+ @client.monitor_instances({
451
+ instance_ids: [instance_id]
452
+ })
453
+ else
454
+ @client.unmonitor_instances({
455
+ instance_ids: [instance_id]
456
+ })
457
+ end
458
+ end
459
+
460
+ def create_network_interface(vpc_id, subnet_id, sg_names)
461
+ @client.create_network_interface({
462
+ subnet_id: subnet_id,
463
+ groups: sg_names.map { |sg| SecurityGroups.vpc_security_group_id_names[vpc_id].key(sg) }
464
+ }).network_interface.network_interface_id
465
+ end
466
+
467
+ def attach_network_interface(instance_id, interface_id, device_index)
468
+ @client.attach_network_interface({
469
+ network_interface_id: interface_id,
470
+ instance_id: instance_id,
471
+ device_index: device_index
472
+ }).attachment_id
473
+ end
474
+
475
+ def set_delete_on_terminate(interface_id, attachment_id)
476
+ @client.modify_network_interface_attribute({
477
+ network_interface_id: interface_id,
478
+ attachment: {
479
+ attachment_id: attachment_id,
480
+ delete_on_termination: true
481
+ }
482
+ })
483
+ end
484
+
485
+ def attach_volumes(instance_id, volumes, start_device_letter)
486
+
487
+ device_letter = start_device_letter
488
+
489
+ # sort volumes by size then map to id
490
+ volume_ids = volumes.sort_by(&:size).map(&:volume_id)
491
+
492
+ volume_ids.each do |vol_id|
493
+ device_name = "#{@device_name_base}#{device_letter}"
494
+ puts "Attaching volume to #{device_name}..."
495
+
496
+ @client.attach_volume({
497
+ instance_id: instance_id,
498
+ volume_id: vol_id,
499
+ device: device_name
500
+ })
501
+
502
+ device_letter = (device_letter.ord + 1).chr
503
+ end
504
+
505
+ end
506
+
507
+ end
508
+ end
509
+ end