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.
@@ -114,7 +114,7 @@ class Biosphere
114
114
 
115
115
  end
116
116
 
117
- end
117
+ end # end of class Client
118
118
 
119
119
  def kube_test(str)
120
120
  return str
@@ -182,6 +182,29 @@ class Biosphere
182
182
  end
183
183
  end
184
184
 
185
+ #
186
+ # Applies seleted properties from the current resource (as fetched from apiserver) to the new_version (as read from manifest file)
187
+ #
188
+ #
189
+ def self.kube_merge_resource_for_put!(current, new_version)
190
+ if current[:metadata]
191
+ new_version[:metadata][:selfLink] = current[:metadata][:selfLink] if current[:metadata][:selfLink]
192
+ new_version[:metadata][:uid] = current[:metadata][:uid] if current[:metadata][:uid]
193
+ new_version[:metadata][:resourceVersion] = current[:metadata][:resourceVersion] if current[:metadata][:resourceVersion]
194
+ end
195
+
196
+ if current[:spec]
197
+ new_version[:spec] = {} if !new_version[:spec]
198
+
199
+ # handle spec.clusterIP
200
+ if new_version[:spec][:clusterIP] && new_version[:spec][:clusterIP] != current[:spec][:clusterIP]
201
+ raise ArgumentError, "Tried to modify spec.clusterIP from #{current[:spec][:clusterIP]} to #{new_version[:spec][:clusterIP]} but the field is immutable"
202
+ end
203
+ new_version[:spec][:clusterIP] = current[:spec][:clusterIP] if current[:spec][:clusterIP]
204
+
205
+ end
206
+ return new_version
207
+ end
185
208
 
186
209
  def kube_apply_resource(client, resource)
187
210
  name = resource[:metadata][:name]
@@ -206,15 +229,10 @@ class Biosphere
206
229
  puts "Updating resource #{response[:resource]}"
207
230
 
208
231
  # Get the current full resource from apiserver
209
- update_resource = response[:body]
232
+ current_resource = response[:body]
210
233
 
211
- # Merge the updates on top of it
212
- # TODO: this doesn't remove fields from the update_resource which have been removed from the to-be-updated resource
213
- update_resource.merge(resource)
234
+ update_resource = Kube.kube_merge_resource_for_put!(current_resource, resource)
214
235
 
215
- # Remove fields which apiserver refuses to accept in PUT requests
216
- update_resource.delete(:apiVersion)
217
-
218
236
  begin
219
237
  responses << client.put(update_resource)
220
238
  rescue RestClient::Exception => e
@@ -2,34 +2,77 @@ require 'pp'
2
2
  require 'awesome_print'
3
3
 
4
4
  class Biosphere
5
+
5
6
  class Node
7
+
8
+ class Attribute < Hash
9
+ def deep_set(*args)
10
+ #puts "deep_set: #{args}"
11
+ raise ArgumentError, "must pass at least one key, and a value" if args.length < 2
12
+ value = args.pop
13
+ args = args.first if args.length == 1 && args.first.kind_of?(Array)
14
+
15
+ key = args.first
16
+ raise ArgumentError, "must be a number" if self.kind_of?(Array) && !key.kind_of?(Numeric)
17
+
18
+ if args.length == 1
19
+ self[key] = value
20
+ else
21
+ child = self[key]
22
+ unless child.respond_to?(:store_path)
23
+ self[key] = self.class.new
24
+ child = self[key]
25
+ end
26
+ child.deep_set(args[1..-1].push, value)
27
+ end
28
+ end
29
+ end
30
+
6
31
  attr_reader :data
7
- def initialize(from_string = nil)
8
- if from_string
9
- blob = Marshal.load(from_string)
10
- @data = blob.data
32
+ def initialize(from = nil)
33
+ if from && from.is_a?(String)
34
+ blob = Marshal.load(from)
35
+ if blob.class == Biosphere::Node
36
+ raise "Tried to load old state format. Unfortunately we are not backwards compatible"
37
+ end
38
+ @data = blob
39
+ elsif from
40
+ @data = from
11
41
  else
12
- @data = {}
42
+ @data = Attribute.new
13
43
  end
14
44
  end
15
45
 
46
+ def data
47
+ return @data
48
+ end
49
+
50
+ def data=(s)
51
+ @data = s
52
+ end
53
+
16
54
  def include?(symbol)
17
55
  @data.include?(symbol)
18
56
  end
19
57
 
58
+ def deep_set(*args)
59
+ @data.deep_set(*args)
60
+ end
61
+
20
62
  def []=(symbol, *args)
21
63
  @data[symbol] = args[0]
22
64
  end
23
65
 
24
66
  def [](symbol, *args)
25
- if !@data[symbol]
26
- @data[symbol] = Node.new
27
- end
28
67
  return @data[symbol]
29
68
  end
30
69
 
70
+ def merge!(obj)
71
+ @data.deep_merge!(obj)
72
+ end
73
+
31
74
  def save()
32
- return Marshal.dump(self)
75
+ return Marshal.dump(@data)
33
76
  end
34
77
 
35
78
  def values
data/lib/biosphere/s3.rb CHANGED
@@ -8,13 +8,14 @@ class S3
8
8
  end
9
9
 
10
10
  def save(path_to_file)
11
- puts "\nSaving #{path_to_file} to S3"
12
11
  filename = path_to_file.split('/')[-1]
12
+ key = "#{@main_key}/#{filename}"
13
+ puts "Saving #{path_to_file} to S3 #{key}"
13
14
  begin
14
15
  File.open(path_to_file, 'rb') do |file|
15
16
  @client.put_object({
16
17
  :bucket => @bucket_name,
17
- :key => "#{@main_key}/#{filename}",
18
+ :key => key,
18
19
  :body => file
19
20
  })
20
21
  end
@@ -27,17 +28,18 @@ class S3
27
28
 
28
29
  def retrieve(path_to_file)
29
30
  filename = path_to_file.split('/')[-1]
30
- puts "\nFetching #{filename} from S3"
31
+ key = "#{@main_key}/#{filename}"
32
+ puts "Fetching #{filename} from S3 from #{key}"
31
33
  begin
32
34
  resp = @client.get_object({
33
35
  :bucket => @bucket_name,
34
- :key => "#{@main_key}/#{filename}"
36
+ :key => key
35
37
  })
36
38
  File.open(path_to_file, 'w') do |f|
37
39
  f.puts(resp.body.read)
38
40
  end
39
41
  rescue Aws::S3::Errors::NoSuchKey
40
- puts "\nCouldn't find remote file #{filename} from S3."
42
+ puts "Couldn't find remote file #{filename} from S3 at #{key}"
41
43
  rescue
42
44
  puts "\nError occurred while fetching the remote state, can't continue."
43
45
  puts "Error: #{$!}"
@@ -0,0 +1,72 @@
1
+ require 'set'
2
+ require 'deep_dup'
3
+
4
+ class Biosphere
5
+
6
+ class Settings
7
+
8
+ class << self
9
+ def class_attribute(*attrs)
10
+ singleton_class.class_eval do
11
+ attr_accessor(*attrs)
12
+ end
13
+
14
+ class_attributes.merge(attrs)
15
+ end
16
+
17
+ def class_attributes
18
+ @class_attributes ||= ::Set.new
19
+ end
20
+
21
+ def inherited(subclass)
22
+ class_attributes.each do |attr|
23
+ value = send(attr)
24
+ value = DeepDup.deep_dup(value) # rescue value
25
+ subclass.class_attribute attr
26
+ subclass.send("#{attr}=", value)
27
+
28
+ p = path
29
+ if p != ""
30
+ p = p + "/"
31
+ end
32
+ subclass.send("path=", p + subclass.name.split('::').last)
33
+ end
34
+ end
35
+
36
+ def settings(settings=nil)
37
+ if settings
38
+ c = @settings_hash ||= ::Hash.new
39
+ c = DeepDup.deep_dup(c)
40
+ c.deep_merge!(settings)
41
+ @settings_hash = c
42
+ end
43
+ return @settings_hash
44
+ end
45
+
46
+ def path()
47
+ return @path
48
+ end
49
+ end
50
+
51
+ class_attribute :settings_hash, :path
52
+
53
+ attr_accessor :settings, :path
54
+
55
+ def initialize(settings = {})
56
+ @settings = DeepDup.deep_dup(self.class.settings_hash)
57
+ @path = self.class.path
58
+ if settings
59
+ @settings.deep_merge!(settings)
60
+ end
61
+ end
62
+
63
+ def [](key)
64
+ return @settings[key]
65
+ end
66
+
67
+ settings({})
68
+
69
+ self.path = ""
70
+
71
+ end
72
+ end
@@ -0,0 +1,60 @@
1
+ require 'pp'
2
+ require 'ipaddress'
3
+ require 'biosphere/node'
4
+ require 'deep_merge'
5
+
6
+ class Biosphere
7
+ class State
8
+ attr_accessor :filename, :node
9
+
10
+ def initialize(filename = nil)
11
+ if filename
12
+ load(filename)
13
+ else
14
+ self.reset()
15
+ end
16
+ end
17
+
18
+ def reset()
19
+ @node = Node.new
20
+ end
21
+
22
+ def load(filename=nil)
23
+ if filename
24
+ @filename = filename
25
+ end
26
+ data = Marshal.load(File.read(@filename))
27
+ puts "Loading data from file #{@filename}: #{data}"
28
+ load_from_structure!(data)
29
+ end
30
+
31
+ def node(name=nil)
32
+ if name
33
+ return @node[name]
34
+ else
35
+ return @node
36
+ end
37
+ end
38
+
39
+ def merge!(settings)
40
+ @node.merge!(settings)
41
+ end
42
+
43
+ def save(filename=nil)
44
+ if !filename && @filename
45
+ filename = @filename
46
+ end
47
+ str = Marshal.dump(@node)
48
+ File.write(filename, str)
49
+ puts "Saving state to #{filename}"
50
+ end
51
+
52
+ def load_from_structure!(structure)
53
+ if @node
54
+ @node.data.deep_merge(structure.data)
55
+ else
56
+ @node = structure
57
+ end
58
+ end
59
+ end
60
+ end
@@ -3,105 +3,103 @@ require 'ipaddress'
3
3
  require 'biosphere/node'
4
4
 
5
5
  class Biosphere
6
- class Suite
7
-
8
- attr_accessor :files
9
- attr_accessor :actions
10
-
11
- def initialize(directory)
12
-
13
- @files = {}
14
- @actions = {}
15
- @directory = directory
16
-
17
- files = Dir::glob("#{directory}/*.rb")
18
-
19
- @plan_proxy = PlanProxy.new
20
-
21
- for file in files
22
- proxy = Biosphere::TerraformProxy.new(file, @plan_proxy)
23
-
24
- @files[file[directory.length+1..-1]] = proxy
25
- end
26
-
27
- if File.exists?(directory + "/state.node")
28
- @plan_proxy.node = Marshal.load(File.read(directory + "/state.node"))
29
- end
30
- end
31
-
32
- def node(name=nil)
33
- if name
34
- return @plan_proxy.node[name]
35
- else
36
- return @plan_proxy.node
37
- end
38
- end
39
-
40
-
41
- def evaluate_resources()
42
- @files.each do |file_name, proxy|
43
- proxy.evaluate_resources()
44
- end
45
-
46
- end
47
-
48
- def load_all()
49
- @files.each do |file_name, proxy|
50
- proxy.load_from_file()
51
-
52
- proxy.actions.each do |key, value|
53
-
54
- if @actions[key]
55
- raise "Action '#{key}' already defined at #{value[:location]}"
56
- end
57
- @actions[key] = value
58
- end
59
- end
60
-
61
- return @files.length
62
- end
63
-
64
- def save_node(filename = "state.node")
65
- str = Marshal.dump(@plan_proxy.node)
66
- File.write(@directory + "/" + filename, str)
67
- end
68
-
69
-
70
- def evaluate_plans()
71
- @files.each do |file_name, proxy|
72
- puts "evaluating plans for #{file_name}"
73
-
74
- proxy.evaluate_plans()
75
- end
76
- end
77
-
78
- def call_action(name, context)
79
- found = false
80
- @files.each do |file_name, proxy|
81
- if proxy.actions[name]
82
- found = true
83
- proxy.call_action(name, context)
84
- end
85
- end
86
-
87
- return found
88
- end
89
-
90
- def write_json_to(destination_dir)
91
- if !File.directory?(destination_dir)
92
- Dir.mkdir(destination_dir)
93
- end
94
-
95
- @files.each do |file_name, proxy|
96
- json_name = file_name[0..-4] + ".json.tf"
97
- str = proxy.to_json(true) + "\n"
98
- destination_name = destination_dir + "/" + json_name
99
- File.write(destination_name, str)
100
-
101
- yield file_name, destination_name, str, proxy if block_given?
102
- end
103
-
104
- end
105
-
106
- end
6
+ class Suite
7
+
8
+ attr_accessor :files
9
+ attr_accessor :actions
10
+ attr_reader :deployments, :biosphere_settings, :state
11
+
12
+ def initialize(state)
13
+
14
+ @files = {}
15
+ @actions = {}
16
+ @state = state
17
+ @deployments = {}
18
+ @biosphere_settings = {}
19
+ @biosphere_path = ""
20
+ end
21
+
22
+ def register(deployment)
23
+ if @deployments[deployment.name]
24
+ raise RuntimeException.new("Deployment #{deployment.name} already registered")
25
+ end
26
+ @deployments[deployment.name] = deployment
27
+ if !@state.node[:deployments]
28
+ @state.node[:deployments] = Node::Attribute.new
29
+ end
30
+ @state.node[:deployments][deployment.name] = Node::Attribute.new
31
+ deployment.node = Node.new(@state.node[:deployments][deployment.name])
32
+ #@state.node.deep_set(:deployments, deployment.name, deployment.node.data)
33
+ deployment.state = @state
34
+
35
+ if deployment._settings[:biosphere]
36
+ @biosphere_settings.deep_merge!(deployment._settings[:biosphere])
37
+ end
38
+
39
+ end
40
+
41
+ def evaluate_resources()
42
+ @deployments.each do |name, deployment|
43
+ deployment.evaluate_resources()
44
+ end
45
+ end
46
+
47
+ def node
48
+ @state.node
49
+ end
50
+
51
+ def load_all(directory)
52
+ @directory = directory
53
+ files = Dir::glob("#{directory}/*.rb")
54
+
55
+ for file in files
56
+ proxy = Biosphere::TerraformProxy.new(file, self)
57
+
58
+ @files[file[directory.length+1..-1]] = proxy
59
+ end
60
+
61
+ @files.each do |file_name, proxy|
62
+ proxy.load_from_file()
63
+
64
+ proxy.actions.each do |key, value|
65
+
66
+ if @actions[key]
67
+ raise "Action '#{key}' already defined at #{value[:location]}"
68
+ end
69
+ @actions[key] = value
70
+ end
71
+ end
72
+
73
+ return @files.length
74
+ end
75
+
76
+ def call_action(name, context)
77
+ found = false
78
+ @files.each do |file_name, proxy|
79
+ if proxy.actions[name]
80
+ found = true
81
+ proxy.call_action(name, context)
82
+ end
83
+ end
84
+
85
+ return found
86
+ end
87
+
88
+ def write_json_to(destination_dir)
89
+ if !File.directory?(destination_dir)
90
+ Dir.mkdir(destination_dir)
91
+ end
92
+
93
+ @deployments.each do |name, deployment|
94
+ json_name = deployment.name + ".json.tf"
95
+ str = deployment.to_json(true) + "\n"
96
+ destination_name = destination_dir + "/" + json_name
97
+ File.write(destination_name, str)
98
+
99
+ yield deployment.name, destination_name, str, deployment if block_given?
100
+ end
101
+
102
+ end
103
+
104
+ end
107
105
  end