biosphere 0.0.14 → 0.1.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: ca246e427a415272896db19c4a1ba36cba31e831
4
- data.tar.gz: 13934dc8a5d8655c30df4147db315659ce5c53e5
3
+ metadata.gz: 8a63904122980f99e791afdbd4e49ad165ef26ac
4
+ data.tar.gz: d7d5ea118f8321eb6cac9173d633b2bd5ca49522
5
5
  SHA512:
6
- metadata.gz: ed071e46536020e52696368e6e7e994745144229d873eeac4ce68e1c0aa909df7d07b91169da69ad2de506e38a8cb27d0169b62ea91a8748ad7babe0b74355cd
7
- data.tar.gz: 81b5e1dfdf363a8ecbfb58e0086e8759966cd26a7ad17d61b467ac42cf52f880aff29b28a398b4f3faf4a8462952304a434257c8a936b4cacc5fb16deaf9a2a3
6
+ metadata.gz: 4b3666b2efec558ca7d2d29356d8194078534dc777bbe02d26aae34a7db284d900b0e6ae96e2e0cb419cebaa3996f06baa78dc72de6e7f0f83020bc097461da0
7
+ data.tar.gz: 1af9536e8c8cbe9b0ebed3e93fbf0e2be42b6333640dc73ef56ac60fecb5aadd501998e5b09bd2443642c62c87b270d1f779c5a6d23a2e20061047ad3b5f3c7a
data/bin/biosphere CHANGED
@@ -10,163 +10,224 @@ require 'biosphere/s3.rb'
10
10
 
11
11
  class BiosphereOpts
12
12
 
13
- def self.normalize_path(path)
14
- if path[-1] != '/'
15
- path += '/'
16
- end
17
- return path
18
- end
13
+ def self.parse(args)
19
14
 
20
- def self.parse(args)
15
+ options = OpenStruct.new
16
+ options.build_dir = "build"
17
+ options.src = "./"
18
+ options.version = ::Biosphere::Version
21
19
 
22
- options = OpenStruct.new
23
- options.build_dir = "build/"
24
- options.src = "./"
25
- options.version = ::Biosphere::Version
20
+ opt_parser = OptionParser.new do |opts|
26
21
 
27
- opt_parser = OptionParser.new do |opts|
28
22
 
23
+ opts.banner = "Usage: \"biosphere [options] <action>\""
29
24
 
30
- opts.banner = "Usage: \"biosphere [options] <action>\""
25
+ opts.separator ""
26
+ opts.separator "Commands:"
27
+ opts.separator "\tbuild\tWrite tf files as json into build directory"
28
+ opts.separator "\tplan\tRun the planning phase"
29
+ opts.separator "\tcommit\tCommit changes and update the infrastructure"
30
+ opts.separator "\tlock\tAcquire lock for remote state"
31
+ opts.separator "\tunlock\tRelease lock for remote state"
32
+ opts.separator "\taction [action]\tCall an action defined in the application .rb files"
33
+ opts.separator ""
31
34
 
32
- opts.separator ""
33
- opts.separator "Commands:"
34
- opts.separator "\tbuild\tWrite tf files as json into build directory"
35
- opts.separator "\tplan\tRun the planning phase"
36
- opts.separator "\tcommit\tCommit changes and update the infrastructure"
37
- opts.separator "\tlock\tAcquire lock for remote state"
38
- opts.separator "\tunlock\tRelease lock for remote state"
39
- opts.separator "\taction [action]\tCall an action defined in the application .rb files"
40
- opts.separator ""
35
+ opts.on_tail("-h", "--help", "Show this message") do
36
+ puts opts
37
+ exit
38
+ end
41
39
 
42
- opts.on_tail("-h", "--help", "Show this message") do
43
- puts opts
44
- exit
45
- end
40
+ opts.on("--src PATH", "Directory where the application .rb files are") do |path|
41
+ options.src = path
42
+ end
46
43
 
47
- opts.on("--src PATH", "Directory where the application .rb files are") do |path|
48
- options.src = normalize_path(path)
49
- end
44
+ opts.on("--build-dir PATH", "Directory where to build json files") do |path|
45
+ options.build_dir = path
46
+ end
50
47
 
51
- opts.on("--build-dir PATH", "Directory where to build json files") do |path|
52
- options.build_dir = normalize_path(path)
53
- end
48
+ opts.on_tail("-v", "--version", "Show version") do
49
+ puts options.version
50
+ exit
51
+ end
54
52
 
55
- end
53
+ end
56
54
 
57
- opt_parser.parse!(args)
58
- options
59
- end
55
+ opt_parser.parse!(args)
56
+ options
57
+ end
60
58
 
61
59
  end
62
60
 
63
61
  if !STDOUT.isatty
64
- String.disable_colorization true
62
+ String.disable_colorization true
65
63
  end
66
64
 
67
65
  options = BiosphereOpts.parse(ARGV)
68
66
 
69
67
  if ARGV.length == 0
70
- STDERR.puts "No action spesified. Use -h to get help."
71
- exit -1
68
+ STDERR.puts "No action spesified. Use -h to get help."
69
+ exit -1
72
70
  end
73
71
 
74
72
  if !File.directory?(options.src)
75
- STDERR.puts "Directory #{options.build_dir} is not a directory or it doesn't exists."
76
- exit -1
73
+ STDERR.puts "Directory #{options.build_dir} is not a directory or it doesn't exists."
74
+ exit -1
77
75
  end
78
76
 
79
- if options.src
80
- suite = Biosphere::Suite.new(options.src)
81
- if options.src == "./"
82
- STDERR.puts "Loading suite from current directory (#{File.expand_path(options.src)}). Use --src to change the path"
83
- end
84
-
85
- if suite.load_all() == 0
86
- STDERR.puts "No files found. Are you in the right directory where your biosphere .rb files are?"
87
- exit -1
88
- end
89
-
90
- if suite.node[:settings][:s3_bucket].nil? || suite.node[:settings][:s3_bucket].empty? || suite.node[:settings][:cluster_name].nil? || suite.node[:settings][:cluster_name].empty?
91
- puts "\nNo S3 bucket or cluster name defined in configuration, can't continue"
92
- exit 1
93
- end
94
- s3 = S3.new(suite.node[:settings][:s3_bucket], suite.node[:settings][:cluster_name])
77
+ if options.build_dir
78
+ if !File.directory?(options.build_dir)
79
+ STDERR.puts "Creating build directory #{options.build_dir} because it was missing"
80
+ Dir.mkdir(options.build_dir)
81
+ end
95
82
  end
96
83
 
97
- if options.build_dir
98
- if !File.directory?(options.build_dir)
99
- STDERR.puts "Creating build directory #{options.build_dir} because it was missing"
100
- Dir.mkdir(options.build_dir)
101
- end
84
+ if options.src
85
+ state = Biosphere::State.new
86
+ suite = Biosphere::Suite.new(state)
87
+
88
+ if options.src == "./"
89
+ STDERR.puts "Loading suite from current directory (#{File.expand_path(options.src)}). Use --src to change the path"
90
+ end
91
+
92
+ if suite.load_all(options.src) == 0
93
+ STDERR.puts "No files found. Are you in the right directory where your biosphere .rb files are?"
94
+ exit -1
95
+ end
96
+
97
+ if suite.biosphere_settings[:s3_bucket].nil? || suite.biosphere_settings[:s3_bucket].empty? ||
98
+ suite.biosphere_settings[:state_name].nil? || suite.biosphere_settings[:state_name].empty?
99
+ puts "\nNo S3 bucket or cluster name defined in configuration, can't continue"
100
+ exit 1
101
+ end
102
+ s3 = S3.new(suite.biosphere_settings[:s3_bucket], suite.biosphere_settings[:state_name])
103
+ s3.retrieve("#{options.build_dir}/state.node")
104
+
105
+ # This will update the state which is already passed to the suite.
106
+ state.filename = "#{options.build_dir}/state.node"
107
+ if File.exists?(state.filename)
108
+ puts "Loading state from #{state.filename}"
109
+ state.load()
110
+ end
111
+
102
112
  end
103
113
 
104
114
  if ARGV[0] == "build" && options.src
105
- suite.evaluate_resources()
115
+ suite.evaluate_resources()
106
116
 
107
- if !File.directory?(options.build_dir)
108
- STDERR.puts "Directory #{options.build_dir} is not a directory or it doesn't exists."
109
- exit -1
110
- end
117
+ if !File.directory?(options.build_dir)
118
+ STDERR.puts "Directory #{options.build_dir} is not a directory or it doesn't exists."
119
+ exit -1
120
+ end
111
121
 
112
- count = 0
113
- suite.write_json_to(options.build_dir) do |file_name, destination, str, proxy|
114
- puts "Wrote #{str.length} bytes from #{file_name} to #{destination} (#{proxy.export["resource"].length} resources)"
115
- count = count + 1
116
- end
122
+ count = 0
123
+ suite.write_json_to(options.build_dir) do |file_name, destination, str, deployment|
124
+ puts "Wrote #{str.length} bytes from #{file_name} to #{destination} (#{deployment.export["resource"].length} resources)"
125
+ count = count + 1
126
+ end
117
127
 
118
- puts "Wrote #{count} files into #{options.build_dir}"
119
- suite.save_node()
128
+ puts "Wrote #{count} files into #{options.build_dir}"
129
+ state.save()
130
+ s3.save("#{options.build_dir}/state.node")
120
131
 
121
132
  elsif ARGV[0] == "plan" && options.src
122
- suite.evaluate_plans()
123
- ap suite.node, :indent=>-4
133
+ suite.evaluate_plans()
134
+ ap suite.node, :indent=>-4
135
+
136
+ elsif ARGV[0] == "state" && options.src
137
+ ap suite.state.node, :indent=>-4
124
138
 
125
139
  elsif ARGV[0] == "action" && options.src
126
- s3.retrieve("#{options.build_dir}terraform.tfstate")
127
- s3.retrieve("#{options.build_dir}state.node")
128
- context = Biosphere::ActionContext.new()
129
- context.build_directory = options.build_dir
140
+ context = Biosphere::ActionContext.new()
141
+ context.build_directory = options.build_dir
130
142
 
131
- if suite.call_action(ARGV[1], context)
132
- STDERR.puts "Executing action #{ARGV[1]}"
133
- else
134
- STDERR.puts "Could not find action #{ARGV[1]}"
135
- end
143
+ if suite.call_action(ARGV[1], context)
144
+ STDERR.puts "Executing action #{ARGV[1]}"
145
+ else
146
+ STDERR.puts "Could not find action #{ARGV[1]}"
147
+ end
148
+ state.save()
149
+ s3.save("#{options.build_dir}/state.node")
136
150
 
137
- suite.save_node()
151
+ elsif ARGV[0] == "deployment" && options.src
152
+
153
+ suite.deployments.each do |name, deployment|
154
+ puts "Deployment: #{name}"
155
+ end
138
156
 
139
157
  elsif ARGV[0] == "commit" && options.src
140
- s3.set_lock()
141
- s3.retrieve("#{options.build_dir}terraform.tfstate")
142
- tf_plan = %x( terraform plan -state=#{options.build_dir}terraform.tfstate #{options.build_dir} )
143
- puts "\n" + tf_plan
144
- answer = ""
145
- while answer.empty? || (answer != "y" && answer != "n")
146
- print "\nDoes the plan look reasonable? (Answering yes will apply the changes) y/n: "
147
- answer = STDIN.gets.chomp
148
- end
149
-
150
- if answer == "n"
151
- puts "\nOk, will not proceed with commit"
152
- elsif answer == "y"
153
- puts "\nApplying the changes (this may take several minutes)"
154
- tf_apply = %x( terraform apply -state=#{options.build_dir}terraform.tfstate #{options.build_dir})
155
- puts "\n" + tf_apply
156
- s3.save("#{options.build_dir}terraform.tfstate")
157
- s3.save("#{options.src}state.node")
158
- end
159
-
160
- s3.release_lock()
158
+
159
+ if !ARGV[1]
160
+ puts "Please specify deployment name as the second parameter."
161
+ puts "Available deployments:"
162
+ suite.deployments.each do |name, deployment|
163
+ puts "\t#{name}"
164
+ end
165
+ exit -1
166
+ end
167
+ deployment = ARGV[1]
168
+
169
+ s3.set_lock()
170
+ s3.retrieve("#{options.build_dir}/#{deployment}.tfstate")
171
+ tf_plan = %x( terraform plan -state=#{options.build_dir}/#{deployment}.tfstate #{options.build_dir} )
172
+ puts "\n" + tf_plan
173
+ answer = ""
174
+ while answer.empty? || (answer != "y" && answer != "n")
175
+ print "\nDoes the plan look reasonable? (Answering yes will apply the changes) y/n: "
176
+ answer = STDIN.gets.chomp
177
+ end
178
+
179
+ if answer == "n"
180
+ puts "\nOk, will not proceed with commit"
181
+ elsif answer == "y"
182
+ puts "\nApplying the changes (this may take several minutes)"
183
+ tf_apply = %x( terraform apply -state=#{options.build_dir}/#{deployment}.tfstate #{options.build_dir})
184
+ puts "\n" + tf_apply
185
+ s3.save("#{options.build_dir}/#{deployment}.tfstate")
186
+ s3.save("#{options.build_dir}/state.node")
187
+ end
188
+
189
+ s3.release_lock()
190
+
191
+ elsif ARGV[0] == "destroy" && options.src
192
+
193
+ if !ARGV[1]
194
+ puts "Please specify deployment name as the second parameter."
195
+ puts "Available deployments:"
196
+ suite.deployments.each do |name, deployment|
197
+ puts "\t#{name}"
198
+ end
199
+ exit -1
200
+ end
201
+ deployment = ARGV[1]
202
+
203
+ s3.set_lock()
204
+ s3.retrieve("#{options.build_dir}/#{deployment}.tfstate")
205
+ answer = ""
206
+ while answer.empty? || (answer != "y" && answer != "n")
207
+ print "\nYou are about to destroy deployment #{deployment}? (Answering yes will nuke it from the orbit) y/n: "
208
+ answer = STDIN.gets.chomp
209
+ end
210
+
211
+ if answer == "n"
212
+ puts "\nAborted!"
213
+ elsif answer == "y"
214
+ puts "\nDestroying deployment #{deployment} (this may take several minutes)"
215
+ tf_apply = %x( terraform destroy -state=#{options.build_dir}/#{deployment}.tfstate #{options.build_dir})
216
+ puts "\n" + tf_apply
217
+ s3.save("#{options.build_dir}/#{deployment}.tfstate")
218
+ s3.save("#{options.build_dir}/state.node")
219
+ end
220
+
221
+ s3.release_lock()
161
222
 
162
223
  elsif ARGV[0] == "lock"
163
- s3.set_lock()
224
+ s3.set_lock()
164
225
 
165
226
  elsif ARGV[0] == "unlock"
166
- s3.release_lock()
227
+ s3.release_lock()
167
228
 
168
229
  else
169
- STDERR.puts "\nERROR: Unknown command #{ARGV[0]}. Maybe you wanted to do: \"biosphere action #{ARGV[0]}\"?"
170
- exit -1
230
+ STDERR.puts "\nERROR: Unknown command #{ARGV[0]}. Maybe you wanted to do: \"biosphere action #{ARGV[0]}\"?"
231
+ exit -1
171
232
  end
172
233
 
data/lib/biosphere.rb CHANGED
@@ -1,10 +1,15 @@
1
1
  class Biosphere
2
2
 
3
3
  end
4
+ require 'deep_merge'
4
5
 
5
6
  require "biosphere/ipaddressallocator"
6
7
  require "biosphere/version"
8
+ require "biosphere/settings"
7
9
  require "biosphere/node"
10
+ require "biosphere/state"
11
+ require "biosphere/deployment"
8
12
  require "biosphere/terraformproxy"
9
13
  require "biosphere/suite"
14
+ require "biosphere/s3"
10
15
 
@@ -0,0 +1,165 @@
1
+ require 'pp'
2
+ require 'awesome_print'
3
+
4
+ class Biosphere
5
+
6
+ class Deployment
7
+
8
+ attr_reader :export, :name, :_settings
9
+ attr_accessor :state, :node
10
+ def initialize(*args)
11
+
12
+ @parent = nil
13
+ @name = "unnamed"
14
+ if args[0].kind_of?(::Biosphere::Deployment) || args[0].kind_of?(::Biosphere::Suite)
15
+ @parent = args.shift
16
+ elsif args[0].kind_of?(String)
17
+ @name = args.shift
18
+ end
19
+
20
+ settings = {}
21
+ @_settings = {}
22
+ if args[0].kind_of?(Hash)
23
+ settings = args.shift
24
+ @_settings = settings
25
+ elsif args[0].kind_of?(::Biosphere::Settings)
26
+ @_settings = args.shift
27
+ settings = @_settings.settings
28
+ end
29
+
30
+ @export = {
31
+ "provider" => {},
32
+ "resource" => {},
33
+ "variable" => {},
34
+ "output" => {}
35
+ }
36
+
37
+ if @parent.is_a?(::Biosphere::Suite)
38
+ if settings[:deployment_name]
39
+ @name = settings[:deployment_name]
40
+ else
41
+ puts "\nYou need to specify :deployment_name in the Deployment settings. For example:"
42
+ puts "cluster = AdsDeliveryCluster.new(suite, MyDeliveryTestSettings.new({deployment_name: \"my-delivery-test-cluster\"})\n\n"
43
+ raise RuntimeError.new "No :deployment_name specified in Deployment settings"
44
+ end
45
+
46
+ @parent.register(self)
47
+ elsif @parent
48
+ @node = @parent.node
49
+ @state = @parent.state
50
+ @export = @parent.export
51
+ @parent.register(self)
52
+
53
+ else
54
+ @node = Node.new
55
+ end
56
+
57
+ @delayed = []
58
+ @resources = []
59
+ @actions = {}
60
+ @deployments = []
61
+
62
+ self.setup(settings)
63
+
64
+ end
65
+
66
+ def setup(settings)
67
+ end
68
+
69
+ def node
70
+ return @node
71
+ end
72
+
73
+ def state
74
+ if @state != nil
75
+ return @state
76
+ end
77
+
78
+ if @parent
79
+ return @parent.state
80
+ end
81
+ end
82
+
83
+ def register(deployment)
84
+ @deployments << deployment
85
+ end
86
+
87
+ def variable(name, value)
88
+ @export["variable"][name] = {
89
+ "default" => value
90
+ }
91
+ end
92
+
93
+ def provider(name, spec={})
94
+ @export["provider"][name.to_s] = spec
95
+ end
96
+
97
+ def delayed(&block)
98
+ delayed_call = {
99
+ :block => block
100
+ }
101
+ @delayed << delayed_call
102
+ end
103
+
104
+ def resource(type, name, &block)
105
+ @export["resource"][type.to_s] ||= {}
106
+ if @export["resource"][type.to_s][name.to_s]
107
+ throw "Tried to create a resource of type #{type} called '#{name}' when one already exists"
108
+ end
109
+
110
+ spec = {}
111
+ resource = {
112
+ :name => name,
113
+ :type => type,
114
+ :location => caller[0] + "a"
115
+ }
116
+
117
+ if block_given?
118
+ resource[:block] = block
119
+ else
120
+ STDERR.puts("WARNING: No block set for resource call '#{type}', '#{name}' at #{caller[0]}")
121
+ end
122
+
123
+ @resources << resource
124
+
125
+ end
126
+
127
+ def output(name, value)
128
+ @export["output"][name] = {
129
+ "value" => value
130
+ }
131
+ end
132
+
133
+ def evaluate_resources()
134
+
135
+ # Call first sub-deployments
136
+ @deployments.each do |deployment|
137
+ deployment.evaluate_resources()
138
+ end
139
+
140
+ # Then all delayed calls
141
+ @delayed.each do |delayed_call|
142
+ proxy = ResourceProxy.new(self)
143
+ proxy.instance_eval(&delayed_call[:block])
144
+ end
145
+
146
+ # And finish with our own resources
147
+ @resources.each do |resource|
148
+ proxy = ResourceProxy.new(self)
149
+ proxy.instance_eval(&resource[:block])
150
+
151
+ @export["resource"][resource[:type].to_s][resource[:name].to_s] = proxy.output
152
+ end
153
+
154
+ end
155
+
156
+ def to_json(pretty=false)
157
+ if pretty
158
+ return JSON.pretty_generate(@export)
159
+ else
160
+ return JSON.generate(@export)
161
+ end
162
+ end
163
+
164
+ end
165
+ end