biosphere 0.0.14 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/bin/biosphere +172 -111
- data/lib/biosphere.rb +5 -0
- data/lib/biosphere/deployment.rb +165 -0
- data/lib/biosphere/kube.rb +26 -8
- data/lib/biosphere/node.rb +52 -9
- data/lib/biosphere/s3.rb +7 -5
- data/lib/biosphere/settings.rb +72 -0
- data/lib/biosphere/state.rb +60 -0
- data/lib/biosphere/suite.rb +99 -101
- data/lib/biosphere/terraformproxy.rb +44 -126
- data/lib/biosphere/version.rb +1 -1
- metadata +33 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8a63904122980f99e791afdbd4e49ad165ef26ac
|
4
|
+
data.tar.gz: d7d5ea118f8321eb6cac9173d633b2bd5ca49522
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
14
|
-
if path[-1] != '/'
|
15
|
-
path += '/'
|
16
|
-
end
|
17
|
-
return path
|
18
|
-
end
|
13
|
+
def self.parse(args)
|
19
14
|
|
20
|
-
|
15
|
+
options = OpenStruct.new
|
16
|
+
options.build_dir = "build"
|
17
|
+
options.src = "./"
|
18
|
+
options.version = ::Biosphere::Version
|
21
19
|
|
22
|
-
|
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
|
-
|
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
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
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
|
-
|
43
|
-
|
44
|
-
|
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
|
-
|
48
|
-
|
49
|
-
|
44
|
+
opts.on("--build-dir PATH", "Directory where to build json files") do |path|
|
45
|
+
options.build_dir = path
|
46
|
+
end
|
50
47
|
|
51
|
-
|
52
|
-
|
53
|
-
|
48
|
+
opts.on_tail("-v", "--version", "Show version") do
|
49
|
+
puts options.version
|
50
|
+
exit
|
51
|
+
end
|
54
52
|
|
55
|
-
|
53
|
+
end
|
56
54
|
|
57
|
-
|
58
|
-
|
59
|
-
|
55
|
+
opt_parser.parse!(args)
|
56
|
+
options
|
57
|
+
end
|
60
58
|
|
61
59
|
end
|
62
60
|
|
63
61
|
if !STDOUT.isatty
|
64
|
-
|
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
|
-
|
71
|
-
|
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
|
-
|
76
|
-
|
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.
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
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.
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
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
|
-
|
115
|
+
suite.evaluate_resources()
|
106
116
|
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
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
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
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
|
-
|
119
|
-
|
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
|
-
|
123
|
-
|
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
|
-
|
127
|
-
|
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
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
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
|
-
|
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
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
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
|
-
|
224
|
+
s3.set_lock()
|
164
225
|
|
165
226
|
elsif ARGV[0] == "unlock"
|
166
|
-
|
227
|
+
s3.release_lock()
|
167
228
|
|
168
229
|
else
|
169
|
-
|
170
|
-
|
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
|