cumulus-aws 0.11.1

Sign up to get free protection for your applications and to get access to all the features.
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