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 +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
|