cloud-maker 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -4,6 +4,17 @@ require 'cloud-maker'
4
4
  class CloudMakerCLI < Thor
5
5
  include Thor::Actions
6
6
 
7
+ CONFIG_PATH = '~/.cloud-maker.yaml'
8
+
9
+ def initialize(*args)
10
+ super
11
+ begin
12
+ @global_options = YAML::load(File.open(File.expand_path(CONFIG_PATH)))
13
+ rescue
14
+ @global_options = {}
15
+ end
16
+ end
17
+
7
18
  map "--version" => :version, "-v" => :version
8
19
 
9
20
  desc "user_data [INSTANCE_CONFIG_YAML]", "Generate the Cloud Init user data for an instance described by INSTANCE_CONFIG_YAML"
@@ -28,6 +39,37 @@ class CloudMakerCLI < Thor
28
39
  say "cloud-maker #{Gem::Specification.find_by_name('cloud-maker').version.version}"
29
40
  end
30
41
 
42
+ desc "authorize_github", "Fetch an API authorization token from github."
43
+ def authorize_github
44
+ say "This will generate an OAuth token for use when fetching #{"github://".cyan} urls. It will be saved in #{CONFIG_PATH.cyan}."
45
+ say "You can remove this authorization from the GitHub interface if needed."
46
+ say "Your password will not be saved to disk.".green
47
+ user = ask("What is your GitHub #{"username".cyan}?")
48
+ password = ask("What is your GitHub #{"password".cyan}?")
49
+ begin
50
+ response = JSON.parse(RestClient::Request.new(
51
+ :method => "POST",
52
+ :url => 'https://api.github.com/authorizations',
53
+ :user => user,
54
+ :password => password,
55
+ :payload => {
56
+ :note => "CloudMaker",
57
+ :note_url => "https://github.com/airbnb/cloud-maker",
58
+ :scopes => ["repo"]
59
+ }.to_json,
60
+ :headers => {
61
+ :content_type => 'application/json'
62
+ }
63
+ ).execute)
64
+ @global_options['github_token'] = response["token"]
65
+ save_options!
66
+ say "Your GitHub credentials have been successfully updated.".green
67
+ rescue Exception => e
68
+ say e.to_s.red
69
+ say "Your GitHub credentials could not be updated.".red
70
+ end
71
+ end
72
+
31
73
  desc "terminate [AWS_INSTANCE_ID]", "Terminate the specified AWS instance"
32
74
  method_option :aws_access_key_id,
33
75
  :desc => "Your AWS access key id",
@@ -167,8 +209,32 @@ class CloudMakerCLI < Thor
167
209
  puts
168
210
  end
169
211
 
212
+ def save_options!
213
+ path = File.expand_path(CONFIG_PATH)
214
+ File.open(path, 'w+') {|f| f.write(@global_options.to_yaml)}
215
+ end
216
+
170
217
  def build_config(instance_config_yaml, options)
171
- config = CloudMaker::Config.from_yaml(instance_config_yaml, 'import_ec2' => true)
218
+ begin
219
+ config = CloudMaker::Config.from_yaml(instance_config_yaml, 'import_ec2' => true, 'github_token' => @global_options['github_token'])
220
+ rescue CloudMaker::Config::GitHubContentNotFound => e
221
+ say e.to_s.red
222
+ if @global_options['github_token']
223
+ say "You do have GitHub OAuth configured, however you can redo the configuration if the current OAuth token is no longer valid.".yellow
224
+ else
225
+ say "You don't have a GitHub OAuth token configured, if this is a private repo doing so is necessary.".yellow
226
+ end
227
+ if yes?("Would you like to configure OAuth and retry now?")
228
+ authorize_github
229
+ retry
230
+ else
231
+ exit 1
232
+ end
233
+ rescue Exception => e
234
+ say e.to_s.red
235
+ exit 1
236
+ end
237
+
172
238
  options.set.each_pair {|key, val| config[key] = val}
173
239
 
174
240
  config['tags'] ||= {}
@@ -1,8 +1,10 @@
1
+ require 'json'
1
2
  require 'yaml'
2
3
  require 'thor'
3
4
  require 'colorize'
4
5
  require 'deep_merge'
5
6
  require 'aws-sdk'
7
+ require 'rest-client'
6
8
 
7
9
  require 'cloud_maker/config'
8
10
  require 'cloud_maker/ec2'
@@ -1,5 +1,19 @@
1
1
  module CloudMaker
2
2
  class Config
3
+ class ContentNotFound < RuntimeError
4
+ attr_accessor :path
5
+ def initialize(message, path)
6
+ super(message)
7
+ self.path = path
8
+ end
9
+ end
10
+ class GitHubContentNotFound < ContentNotFound
11
+ end
12
+ class HTTPContentNotFound < ContentNotFound
13
+ end
14
+ class FileContentNotFound < ContentNotFound
15
+ end
16
+
3
17
  # Public: Gets/Sets the CloudMaker specific properties. The options hash
4
18
  # is formatted as:
5
19
  # {
@@ -84,9 +98,23 @@ module CloudMaker
84
98
  self.cloud_config = cloud_config
85
99
 
86
100
  self.import(self.class.new(EC2::CLOUD_MAKER_CONFIG)) if (extra_options['import_ec2'])
101
+
102
+ # It's important here that reverse duplicates the imports array as executing the import will
103
+ # add the imported configs imports to the list and we do NOT want to reimport those as well.
87
104
  self.imports.reverse.each do |import_path|
88
- self.import(self.class.from_yaml(import_path))
105
+ self.import(self.class.from_yaml(import_path, self.extra_options))
89
106
  end
107
+ self['tags'] ||= {}
108
+ self['tags']['cloud_maker_config'] = self.config_name
109
+ end
110
+
111
+ def config_name
112
+ files = self.imports.dup
113
+ files.push self.extra_options['config_path'] if self.extra_options['config_path']
114
+
115
+ files.reverse.map { |import|
116
+ import[import.rindex('/')+1..-1].gsub(/\..*/, '').gsub(/[^\w]/, '-')
117
+ }.join(':')
90
118
  end
91
119
 
92
120
  # Public: Check if the CloudMaker config is in a valid state.
@@ -208,14 +236,42 @@ module CloudMaker
208
236
  # options - Any options to pass through as options to CloudMaker::Config::initialize
209
237
  #
210
238
  # Returns a new Config
211
- # Raises: Exception if the file doesn't exist.
239
+ # Raises: GitHubContentNotFound, HTTPContentNotFound, or FileContentNotFound if the file doesn't exist.
212
240
  # Raises: SyntaxError if the YAML file is invalid.
213
241
  def from_yaml(instance_config_yaml, options={})
214
- begin
215
- full_path = File.expand_path(instance_config_yaml)
216
- cloud_yaml = File.open(full_path, "r") #Right_AWS will base64 encode this for us
217
- rescue
218
- raise "ERROR: The path to the CloudMaker config is incorrect"
242
+ if instance_config_yaml =~ /\Agithub:\/\//
243
+ begin
244
+ user, repo, *path = instance_config_yaml.split('/')[2..-1]
245
+ begin
246
+ if options['github_token']
247
+ response = RestClient.get(
248
+ "https://api.github.com/repos/#{user}/#{repo}/contents/#{path.join('/')}",
249
+ "Authorization" => "token #{options['github_token']}"
250
+ )
251
+ else
252
+ response = RestClient.get("https://api.github.com/repos/#{user}/#{repo}/contents/#{path.join('/')}")
253
+ end
254
+ cloud_yaml = Base64.decode64(JSON.parse(response)['content'])
255
+ rescue
256
+ raise GitHubContentNotFound.new(
257
+ "Unable to access the configuration #{instance_config_yaml} from GitHub.",
258
+ instance_config_yaml
259
+ )
260
+ end
261
+ end
262
+ elsif instance_config_yaml =~ /\Ahttps?:\/\//
263
+ begin
264
+ cloud_yaml = RestClient.get(instance_config_yaml)
265
+ rescue
266
+ raise HTTPContentNotFound.new("Unable to access the configuration via HTTP from #{instance_config_yaml}.", instance_config_yaml)
267
+ end
268
+ else
269
+ begin
270
+ full_path = File.expand_path(instance_config_yaml)
271
+ cloud_yaml = File.open(full_path, "r") #Right_AWS will base64 encode this for us
272
+ rescue
273
+ raise FileContentNotFound.new("Unable to access the configuration via your local file system from #{full_path}.", instance_config_yaml)
274
+ end
219
275
  end
220
276
 
221
277
  # loading a blank config file returns false, it's an odd degenerate case but handling
@@ -340,6 +396,7 @@ module CloudMaker
340
396
  def import(cloud_maker_config)
341
397
  self.options = cloud_maker_config.options.deep_merge!(self.options)
342
398
  self.includes = cloud_maker_config.includes.concat(self.includes).uniq
399
+ self.imports = cloud_maker_config.imports.concat(self.imports).uniq
343
400
  self.cloud_config = cloud_maker_config.cloud_config.deep_merge!(self.cloud_config)
344
401
  self.extra_options = cloud_maker_config.extra_options.deep_merge!(self.extra_options)
345
402
  end
@@ -100,7 +100,6 @@ module CloudMaker
100
100
  else
101
101
  region = ec2 # .instances.create will just put things in the default region
102
102
  end
103
-
104
103
  instance = region.instances.create(
105
104
  :image_id => cloud_maker_config['ami'],
106
105
  :security_groups => cloud_maker_config['security_group'],
@@ -110,7 +109,7 @@ module CloudMaker
110
109
  :user_data => user_data
111
110
  )
112
111
 
113
- instance.tags.set(cloud_maker_config["tags"]) if cloud_maker_config["tags"]
112
+ instance.tags.set(cloud_maker_config['tags']) if cloud_maker_config['tags']
114
113
  instance.associate_elastic_ip(cloud_maker_config["elastic_ip"]) if cloud_maker_config["elastic_ip"]
115
114
 
116
115
  archiver = S3Archiver.new(
@@ -174,7 +173,8 @@ module CloudMaker
174
173
  :private_ip_address => instance.private_ip_address,
175
174
  :key_name => instance.key_name,
176
175
  :owner_id => instance.owner_id,
177
- :status => instance.status
176
+ :status => instance.status,
177
+ :tags => instance.tags.inject({}) {|hash, tag| hash[tag.first] = tag.last;hash}
178
178
  }
179
179
  end
180
180
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cloud-maker
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2012-08-15 00:00:00.000000000 Z
13
+ date: 2012-08-17 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: colorize
@@ -128,5 +128,5 @@ rubyforge_project:
128
128
  rubygems_version: 1.8.24
129
129
  signing_key:
130
130
  specification_version: 3
131
- summary: Launch and perform initial configuration of cloud servers.
131
+ summary: Extends Ubuntu CloudInit to launch and configure cloud servers.
132
132
  test_files: []