biosphere 0.0.14 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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