lucid-cumulus 0.11.2 → 0.11.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,20 @@
1
+ module Cumulus
2
+ module AutoScaling
3
+ require "common/Commands"
4
+ class Commands < Cumulus::Common::Commands
5
+
6
+ def self.banner_message
7
+ format_message [
8
+ "autoscaling: Manage AutoScaling groups.",
9
+ "\tCompiles AutoScaling groups, scaling policies, and alarms that are defined in configuration files and syncs the resulting AutoScaling groups with AWS.",
10
+ ]
11
+ end
12
+
13
+ def self.manager
14
+ require "autoscaling/manager/Manager"
15
+ Cumulus::AutoScaling::Manager.new
16
+ end
17
+
18
+ end
19
+ end
20
+ end
@@ -1,17 +1,35 @@
1
1
  require "aws-sdk"
2
+ require "json"
3
+ require "deepsort"
2
4
 
3
5
  module AwsExtensions
4
6
  module S3
5
7
  module BucketPolicy
6
8
  # Public: Method that will either return the bucket policy, or an empty
7
- # string if there is no policy. We have to do this because catching an
8
- # exception is the ONLY way to determine if there is a policy.
9
+ # string if there is no policy.
9
10
  #
10
11
  # Returns the policy as a string.
11
12
  def policy_string
12
- policy.string
13
+ # fetch the sorted policy
14
+ hash = policy_hash
15
+ # check if policy exists
16
+ unless hash.nil?
17
+ # convert the policy to string
18
+ JSON.generate(hash)
19
+ else
20
+ # if no policy exists, return an empty string
21
+ ""
22
+ end
23
+ end
24
+
25
+ # Public: Method returns the bucket policy as a sorted hash
26
+ # if no policy exists, returns nil
27
+ def policy_hash
28
+ # rescue and ignore all excpetions related to no policy existing
29
+ # We have to do this because catching an exception is the ONLY way to determine if there is a policy.
30
+ JSON.parse(policy.string).deep_sort
13
31
  rescue Aws::S3::Errors::NoSuchBucketPolicy
14
- ""
32
+ nil
15
33
  end
16
34
  end
17
35
  end
@@ -0,0 +1,51 @@
1
+ module Cumulus
2
+ module CloudFront
3
+ require "common/Commands"
4
+ class Commands < Cumulus::Common::Commands
5
+
6
+ def self.banner_message
7
+ format_message [
8
+ "cloudfront: Manage CloudFront",
9
+ "\tDiff and sync CloudFront configuration with AWS.",
10
+ ]
11
+ end
12
+
13
+ def self.command_details
14
+ format_message [
15
+ ["diff", "print out differences between local configuration and AWS (supplying the id of the distribution will diff only that distribution)"],
16
+ ["invalidate", "create an invalidation. Must supply the name of the invalidation to run. Specifying 'list' as an argument lists the local invalidation configurations"],
17
+ ["list", "list the locally defined distributions"],
18
+ ["migrate", "produce Cumulus CloudFront distribution configuration from current AWS configuration"],
19
+ ["sync", "sync local cloudfront distribution configuration with AWS (supplying the id of the distribution will sync only that distribution)"],
20
+ ]
21
+ end
22
+
23
+ def self.manager
24
+ require "cloudfront/manager/Manager"
25
+ Cumulus::CloudFront::Manager.new
26
+ end
27
+
28
+ def self.valid_options
29
+ [["diff", "invalidate", "list", "migrate", "sync"]]
30
+ end
31
+
32
+ def self.execute(arguments)
33
+ if arguments[0] == "invalidate"
34
+ if arguments.size != 2
35
+ puts "Specify one invalidation to run"
36
+ exit
37
+ else
38
+ if arguments[1] == "list"
39
+ manager.list_invalidations
40
+ else
41
+ manager.invalidate(arguments[1])
42
+ end
43
+ end
44
+ else
45
+ super(arguments)
46
+ end
47
+ end
48
+
49
+ end
50
+ end
51
+ end
@@ -27,7 +27,7 @@ module Cumulus
27
27
  # json - indicates if the resources are in json format
28
28
  # loader - the function that will handle the read json
29
29
  def self.resource(file, dir, json = true, &loader)
30
- name = file.end_with?(".json") ? file.chomp(".json") : file
30
+ name = file.chomp(".json")
31
31
 
32
32
  begin
33
33
  contents = load_file(file, dir)
@@ -46,12 +46,14 @@ module Cumulus
46
46
  #
47
47
  # file - the name of the file to load
48
48
  # dir - the directory the file is located in
49
- # vars - the variables to apply to the template
49
+ # vars - the variables to apply to the template (can be nil)
50
50
  # loader - the function that will handle the read json
51
51
  def self.template(file, dir, vars, &loader)
52
52
  template = load_file(file, dir)
53
- vars.each do |key, value|
54
- template.gsub!("{{#{key}}}", "#{value}")
53
+ if vars
54
+ vars.each do |key, value|
55
+ template.gsub!("{{#{key}}}", "#{value}")
56
+ end
55
57
  end
56
58
  json = JSON.parse(template)
57
59
 
@@ -0,0 +1,131 @@
1
+ module Cumulus
2
+ module Common
3
+ # Public: Base class for the command line parser class.
4
+ #
5
+ # Classes that extend this class must provide the following methods:
6
+ #
7
+ # self.manager - returns the manager for the AWS module that is being used.
8
+ #
9
+ # Additionally, the following methods can be set to change the behavior of the parser:
10
+ #
11
+ # self.usage_message - returns the usage instructions.
12
+ # self.banner_message - returns the title, purpose, and behavior of the module. This is displayed in the help message.
13
+ # self.command_details - returns basic instructions on how to use each module command. This is displayed in the help message.
14
+ # self.valid_options - returns an array of the valid arguments where each argument is an array of valid commands.
15
+ # self.verify - returns true/false of whether or not the arguments passed in are valid (proper order, right commands, etc).
16
+ # self.execute - runs the correct method on the manager based on the array of arguments passed in.
17
+ #
18
+ # For your convenience, many of the help and usage messages can be formatted correctly with the self.format_message method.
19
+ # To use this method, pass in the message as an array of 'lines' where each line is either a string, or a command-instruction pair.
20
+ #
21
+ # The following super class is one example of what each method could look like. Change them in inherited classes as necessary.
22
+ class Commands
23
+ def self.usage_message
24
+ options = valid_options
25
+ options[0] = options[0].push("help")
26
+ "Usage: cumulus #{manager_name}" + options.reduce(String.new) do |memo, param_list|
27
+ memo + " [" + param_list.join("|") + "]"
28
+ end + " <asset>"
29
+ end
30
+
31
+ def self.banner_message
32
+ format_message [
33
+ "#{manager_name}: Manage #{manager_name.upcase}s.",
34
+ "\tCompiles #{manager_name.upcase}s that are defined with configuration files and syncs the resulting #{manager_name.upcase} assets with AWS.",
35
+ ]
36
+ end
37
+
38
+ def self.command_details
39
+ format_message [
40
+ ["diff", "get a list of resources that have different definitions locally than in AWS (supplying the name of the group will diff only that group)"],
41
+ ["list", "list the resources defined in configuration"],
42
+ ["migrate", "create resource configuration files that match the definitions in AWS"],
43
+ ["sync", "sync the local resource definition with AWS (supplying the name of the resource will sync only that group). Also adds and removes users from groups"],
44
+ ]
45
+ end
46
+
47
+ def self.help_message
48
+ format_message [
49
+ "#{banner_message}",
50
+ "",
51
+ "#{usage_message}",
52
+ "",
53
+ "Commands",
54
+ "#{command_details}"
55
+ ]
56
+ end
57
+
58
+ def self.manager
59
+ require "common/manager/Manager"
60
+ Cumulus::Common::Manager.new
61
+ end
62
+
63
+ # Retrieves the AWS module name by checking what ruby module the class is in.
64
+ def self.manager_name
65
+ manager.class.to_s.split("::")[1].downcase
66
+ end
67
+
68
+ def self.valid_options
69
+ # The first array is the list of all possible first commands
70
+ # The second array is the list of all possible second commands and so on.
71
+ [["diff", "list", "migrate", "sync"]]
72
+ end
73
+
74
+ def self.verify(arguments)
75
+ (arguments.size >= valid_options.size) and
76
+ (valid_options.size < 1 or valid_options[0].include?(arguments[0])) and
77
+ (valid_options.size < 2 or valid_options[1].include?(arguments[1])) and
78
+ (valid_options.size < 3 or valid_options[2].include?(arguments[2]))
79
+ end
80
+
81
+ def self.execute(arguments)
82
+ if arguments[0] == "diff" and arguments.size == 2
83
+ manager.diff_one(arguments[1])
84
+ elsif arguments[0] == "sync" and arguments.size == 2
85
+ manager.sync_one(arguments[1])
86
+ else
87
+ manager.method(arguments[0]).call
88
+ end
89
+ end
90
+
91
+ # use this helper function to format help messages
92
+ def self.format_message(message, args = Hash.new)
93
+ # default pad is the size of the smallest command
94
+ pad = args.key?(:padding) ? args[:padding] : message.reduce(0) do |memo, line|
95
+ if line.class == Array && line.first.size > memo
96
+ line.first.size
97
+ else
98
+ memo
99
+ end
100
+ end
101
+
102
+ message = message.reduce(String.new) do |memo, line|
103
+ if line.class == Array
104
+ memo + "\t%-#{pad}s - %s\n" % line
105
+ else
106
+ memo + line + "\n"
107
+ end
108
+ end.chomp
109
+
110
+ message = "\t"*args[:indent] + message.gsub("\n", "\n" + "\t"*args[:indent]) if args.key?(:indent)
111
+
112
+ message
113
+ end
114
+
115
+ # the main function called by the command line parser. DON'T OVERRIDE
116
+ def self.parse(arguments)
117
+ if arguments.size >= 1 and arguments[0] == "help"
118
+ puts help_message
119
+ exit
120
+ end
121
+
122
+ if !verify(arguments)
123
+ puts usage_message
124
+ exit
125
+ end
126
+
127
+ execute(arguments)
128
+ end
129
+ end
130
+ end
131
+ end
@@ -69,10 +69,10 @@ module Cumulus
69
69
  value
70
70
  end
71
71
  rescue KeyError => e
72
- puts "Your configuration file is missing $.#{key}."
73
72
  if allow_missing
74
73
  nil
75
74
  else
75
+ puts "Your configuration file is missing $.#{key}."
76
76
  exit
77
77
  end
78
78
  end
@@ -91,15 +91,16 @@ module Cumulus
91
91
  # Internal: Constructor. Sets up the `instance` variable, which is the access
92
92
  # point for the Singleton.
93
93
  #
94
+ # json - a Hash containing the json configuration. If nil, this value will be read in from the default location
94
95
  # conf_dir - The String path to the directory the configuration can be found in
95
96
  # profile - The String profile name that will be used to make AWS API calls
96
97
  # assume_role - The ARN of the role to assume when making AWS API calls
97
98
  # autoscaling_force_size
98
99
  # - Determines whether autoscaling should use configured values for
99
100
  # min/max/desired group size
100
- def initialize(conf_dir, profile, assume_role, autoscaling_force_size)
101
- Config.conf_dir = conf_dir;
102
- Config.json = JSON.parse(File.read(absolute_path("configuration.json")))
101
+ def initialize(conf_dir, profile, assume_role, autoscaling_force_size, json = nil)
102
+ Config.conf_dir = conf_dir
103
+ Config.json = json.nil? ? JSON.parse(File.read(absolute_path("configuration.json"))) : json
103
104
  @colors_enabled = conf "colors-enabled"
104
105
  @iam = IamConfig.new
105
106
  @autoscaling = AutoScalingConfig.new(autoscaling_force_size)
@@ -126,6 +127,7 @@ module Cumulus
126
127
  :region => region,
127
128
  :profile => profile,
128
129
  :credentials => credentials,
130
+ :stub_responses => conf("stub_aws_responses", true)
129
131
  }.reject { |_, v| v.nil? }
130
132
  end
131
133
 
@@ -140,8 +142,22 @@ module Cumulus
140
142
  # - Determines whether autoscaling should use configured values for
141
143
  # min/max/desired group size
142
144
  def init(conf_dir, profile, assume_role, autoscaling_force_size)
143
- instance = new(conf_dir, profile, assume_role, autoscaling_force_size)
144
- @@instance = instance
145
+ @@instance = new(conf_dir, profile, assume_role, autoscaling_force_size)
146
+ end
147
+
148
+ # Private: Initialize the Configuration Singleton. Must be called before any
149
+ # access to `Configuration.instance` is used. Used by the tests to pass in json
150
+ # rather than a file
151
+ #
152
+ # json - the Hash containing the json configuration
153
+ # conf_dir - The String path to the directory the configuration can be found in
154
+ # profile - The String profile name that will be used to make AWS API calls
155
+ # assume_role - The ARN of the role to assume when making AWS API calls
156
+ # autoscaling_force_size
157
+ # - Determines whether autoscaling should use configured values for
158
+ # min/max/desired group size
159
+ def init_from_hash(json, conf_dir, profile, assume_role, autoscaling_force_size)
160
+ @@instance = new(conf_dir, profile, assume_role, autoscaling_force_size, json)
145
161
  end
146
162
 
147
163
  # Public: The Singleton instance of Configuration.
@@ -152,6 +168,7 @@ module Cumulus
152
168
  end
153
169
 
154
170
  private :new
171
+ private :init_from_hash
155
172
  end
156
173
 
157
174
  # Public: Inner class that contains IAM configuration options
@@ -0,0 +1,51 @@
1
+ module Cumulus
2
+ module EC2
3
+ require "common/Commands"
4
+ class Commands < Cumulus::Common::Commands
5
+
6
+ def self.command_details
7
+ format_message([
8
+ "ebs - Manage EBS volumes in groups",
9
+ ["diff", "get a list of groups that have different definitions locally than in AWS (supplying the name of the group will diff only that group)"],
10
+ ["list", "list the groups defined in configuration"],
11
+ ["migrate", "create group configuration files that match the definitions in AWS"],
12
+ ["sync", "sync the local group definition with AWS (supplying the name of the group will sync only that group). Also creates volumes in a group"],
13
+ "instances - Manage EC2 instances",
14
+ ["diff", "get a list of instances that have different definitions locally than in AWS (supplying the name of the instance will diff only that instance)"],
15
+ ["list", "list the instances defined in configuration"],
16
+ ["migrate", "create instances configuration files that match the definitions in AWS"],
17
+ ["sync", "sync the local instance definition with AWS (supplying the name of the instance will sync only that instance)"],
18
+ ], indent: 1)
19
+ end
20
+
21
+ def self.manager_name
22
+ "ec2"
23
+ end
24
+
25
+ def self.valid_options
26
+ [["ebs", "instances"], ["diff", "list", "migrate", "sync"]]
27
+ end
28
+
29
+ def self.execute(arguments)
30
+ manager = if arguments[0] == "ebs"
31
+ require "ec2/managers/EbsManager"
32
+ Cumulus::EC2::EbsManager.new
33
+ elsif arguments[0] == "instances"
34
+ require "ec2/managers/InstanceManager"
35
+ Cumulus::EC2::InstanceManager.new
36
+ else
37
+ nil
38
+ end
39
+
40
+ if arguments[1] == "diff" and arguments.size == 3
41
+ manager.diff_one(arguments[2])
42
+ elsif arguments[1] == "sync" and arguments.size == 3
43
+ manager.sync_one(arguments[2])
44
+ else
45
+ manager.method(arguments[1]).call
46
+ end
47
+ end
48
+
49
+ end
50
+ end
51
+ end
@@ -187,7 +187,7 @@ module Cumulus
187
187
  if !group_diffs.empty?
188
188
  [group_name, EbsGroupDiff.modified(aws_config, group_config, group_diffs)]
189
189
  end
190
- end].reject { |k, v| v.nil? }
190
+ end.reject { |v| v.nil? }]
191
191
 
192
192
  ebs_changes = Common::ListChange.new(added_groups, removed_groups, changed_groups)
193
193
  if !ebs_changes.empty?
@@ -0,0 +1,40 @@
1
+ module Cumulus
2
+ module ELB
3
+ require "common/Commands"
4
+ class Commands < Cumulus::Common::Commands
5
+
6
+ def self.command_details
7
+ format_message [
8
+ ["diff", "print out differences between local configuration and AWS (supplying the name of the elb will diff only that elb)"],
9
+ ["list", "list the locally defined ELBs"],
10
+ ["sync", "sync local ELB definitions with AWS (supplying the name of the elb will sync only that elb)"],
11
+ ["migrate", "migrate AWS configuration to Cumulus"],
12
+ format_message([
13
+ ["default-policies", "migrate default ELB policies from AWS to Cumulus"],
14
+ ["elbs", "migrate the current ELB configuration from AWS to Cumulus"],
15
+ ], indent: 1),
16
+ ]
17
+ end
18
+
19
+ def self.manager
20
+ require "elb/manager/Manager"
21
+ Cumulus::ELB::Manager.new
22
+ end
23
+
24
+ def self.execute(arguments)
25
+ if arguments[0] == "migrate"
26
+ if arguments[1] == "default-policies"
27
+ manager.migrate_default_policies
28
+ elsif arguments[1] == "elbs"
29
+ manager.migrate_elbs
30
+ else
31
+ puts "Usage: cumulus elb migrate [default-policies|elbs]"
32
+ end
33
+ else
34
+ super(arguments)
35
+ end
36
+ end
37
+
38
+ end
39
+ end
40
+ end