kozo 0.2.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +2 -0
- data/CHANGELOG.md +15 -3
- data/Gemfile +1 -3
- data/Gemfile.lock +44 -51
- data/README.md +2 -0
- data/kozo.gemspec +2 -2
- data/lib/core_ext/boolean.rb +8 -0
- data/lib/core_ext/date.rb +15 -0
- data/lib/core_ext/enumerable.rb +27 -0
- data/lib/core_ext/float.rb +15 -0
- data/lib/core_ext/hash.rb +23 -0
- data/lib/core_ext/integer.rb +15 -0
- data/lib/core_ext/nil_class.rb +8 -0
- data/lib/core_ext/string.rb +11 -0
- data/lib/core_ext/symbol.rb +11 -0
- data/lib/core_ext/time.rb +16 -0
- data/lib/kozo/backend.rb +2 -13
- data/lib/kozo/backends/git.rb +1 -1
- data/lib/kozo/backends/local.rb +12 -4
- data/lib/kozo/cli.rb +11 -3
- data/lib/kozo/command.rb +6 -15
- data/lib/kozo/commands/apply.rb +2 -2
- data/lib/kozo/commands/import.rb +4 -2
- data/lib/kozo/commands/plan.rb +16 -7
- data/lib/kozo/commands/state/delete.rb +39 -0
- data/lib/kozo/commands/state/list.rb +21 -0
- data/lib/kozo/commands/state/show.rb +30 -0
- data/lib/kozo/commands/state/upgrade.rb +45 -0
- data/lib/kozo/commands/state.rb +2 -38
- data/lib/kozo/concerns/attributes.rb +59 -32
- data/lib/kozo/concerns/read_write.rb +45 -0
- data/lib/kozo/configuration.rb +28 -17
- data/lib/kozo/dsl.rb +15 -0
- data/lib/kozo/error.rb +13 -2
- data/lib/kozo/inflector.rb +11 -0
- data/lib/kozo/logger.rb +13 -7
- data/lib/kozo/operation.rb +0 -25
- data/lib/kozo/operations/create.rb +12 -6
- data/lib/kozo/operations/destroy.rb +12 -6
- data/lib/kozo/operations/show.rb +17 -0
- data/lib/kozo/operations/update.rb +17 -0
- data/lib/kozo/options.rb +24 -0
- data/lib/kozo/providers/dummy/resources/dummy.rb +11 -0
- data/lib/kozo/providers/hcloud/provider.rb +1 -1
- data/lib/kozo/providers/hcloud/resource.rb +30 -7
- data/lib/kozo/providers/hcloud/resources/server.rb +16 -8
- data/lib/kozo/providers/hcloud/resources/ssh_key.rb +6 -1
- data/lib/kozo/reference.rb +58 -0
- data/lib/kozo/resource.rb +35 -7
- data/lib/kozo/state.rb +11 -5
- data/lib/kozo/type.rb +3 -3
- data/lib/kozo/types/boolean.rb +0 -4
- data/lib/kozo/types/date.rb +0 -4
- data/lib/kozo/types/float.rb +0 -4
- data/lib/kozo/types/hash.rb +0 -4
- data/lib/kozo/types/integer.rb +0 -4
- data/lib/kozo/types/reference.rb +17 -0
- data/lib/kozo/types/string.rb +0 -4
- data/lib/kozo/types/time.rb +0 -4
- data/lib/kozo/upgrade/1_initial.rb +9 -0
- data/lib/kozo/upgrade/2_remove_kozo_version.rb +11 -0
- data/lib/kozo/upgrade.rb +15 -0
- data/lib/kozo/version.rb +1 -1
- data/lib/kozo.rb +7 -1
- metadata +42 -19
- data/lib/kozo/concerns/mark.rb +0 -25
data/lib/kozo/operations/show.rb
CHANGED
@@ -7,6 +7,23 @@ module Kozo
|
|
7
7
|
self.display_symbol = nil
|
8
8
|
|
9
9
|
def apply(state); end
|
10
|
+
|
11
|
+
def to_s
|
12
|
+
l = resource.attribute_names.map(&:length).max || 1
|
13
|
+
|
14
|
+
attrs = resource
|
15
|
+
.attributes
|
16
|
+
.map { |k, v| " r.#{k.to_s.ljust(l)} = #{v.as_s.indent(2)[2..]}" }
|
17
|
+
.join("\n")
|
18
|
+
|
19
|
+
<<~DSL.chomp
|
20
|
+
#{"# #{resource.address}:".bold}
|
21
|
+
resource "#{resource.resource_name}", "#{resource.state_name}" do |r|
|
22
|
+
#{attrs}
|
23
|
+
end
|
24
|
+
|
25
|
+
DSL
|
26
|
+
end
|
10
27
|
end
|
11
28
|
end
|
12
29
|
end
|
@@ -14,6 +14,23 @@ module Kozo
|
|
14
14
|
state.resources.delete(resource)
|
15
15
|
state.resources << resource
|
16
16
|
end
|
17
|
+
|
18
|
+
def to_s
|
19
|
+
l = resource.attribute_names.map(&:length).max || 1
|
20
|
+
|
21
|
+
attrs = resource
|
22
|
+
.attributes
|
23
|
+
.map { |k, v| " #{resource.changes.key?(k) ? display_symbol : ' '} r.#{k.to_s.ljust(l)} = #{resource.changes.key?(k) ? "#{resource.changes[k].first.as_s.indent(4)[4..]} -> #{v.as_s.indent(4)[4..]}" : v.as_s.indent(4)[4..]}" }
|
24
|
+
.join("\n")
|
25
|
+
|
26
|
+
<<~DSL.chomp
|
27
|
+
#{"# #{resource.address}:".bold}
|
28
|
+
#{display_symbol} resource "#{resource.resource_name}", "#{resource.state_name}" do |r|
|
29
|
+
#{attrs}
|
30
|
+
end
|
31
|
+
|
32
|
+
DSL
|
33
|
+
end
|
17
34
|
end
|
18
35
|
end
|
19
36
|
end
|
data/lib/kozo/options.rb
CHANGED
@@ -28,6 +28,30 @@ module Kozo
|
|
28
28
|
verbose.present?
|
29
29
|
end
|
30
30
|
|
31
|
+
def debug=(value)
|
32
|
+
@debug = value.present?
|
33
|
+
end
|
34
|
+
|
35
|
+
def debug
|
36
|
+
@debug ||= false
|
37
|
+
end
|
38
|
+
|
39
|
+
def debug?
|
40
|
+
debug.present?
|
41
|
+
end
|
42
|
+
|
43
|
+
def dry_run=(value)
|
44
|
+
@dry_run = value.present?
|
45
|
+
end
|
46
|
+
|
47
|
+
def dry_run
|
48
|
+
@dry_run ||= false
|
49
|
+
end
|
50
|
+
|
51
|
+
def dry_run?
|
52
|
+
dry_run.present?
|
53
|
+
end
|
54
|
+
|
31
55
|
def [](key)
|
32
56
|
send(key)
|
33
57
|
end
|
@@ -7,8 +7,19 @@ module Kozo
|
|
7
7
|
class Dummy < Resource
|
8
8
|
self.resource_name = "dummy"
|
9
9
|
|
10
|
+
self.creatable_attribute_names = [:name, :labels, :description]
|
11
|
+
self.updatable_attribute_names = [:name, :labels, :description]
|
12
|
+
|
10
13
|
attribute :name
|
11
14
|
attribute :description
|
15
|
+
|
16
|
+
attribute :labels, multiple: true
|
17
|
+
|
18
|
+
readonly
|
19
|
+
|
20
|
+
attribute :location
|
21
|
+
|
22
|
+
attribute :locked, type: :boolean, default: false
|
12
23
|
end
|
13
24
|
end
|
14
25
|
end
|
@@ -11,7 +11,7 @@ module Kozo
|
|
11
11
|
self.provider_name = "hcloud"
|
12
12
|
|
13
13
|
def setup
|
14
|
-
::HCloud::Client.connection = ::HCloud::Client.new(access_token: key)
|
14
|
+
::HCloud::Client.connection = ::HCloud::Client.new(access_token: key, logger: Kozo.debug_logger)
|
15
15
|
end
|
16
16
|
|
17
17
|
def ==(other)
|
@@ -6,43 +6,66 @@ module Kozo
|
|
6
6
|
class Resource < Kozo::Resource
|
7
7
|
self.provider_name = "hcloud"
|
8
8
|
|
9
|
+
readonly
|
10
|
+
|
9
11
|
attribute :id, type: :integer
|
10
12
|
|
13
|
+
readwrite
|
14
|
+
|
11
15
|
protected
|
12
16
|
|
13
17
|
def refresh
|
18
|
+
raise NotPersisted, "resource is not yet persisted" unless id
|
19
|
+
|
14
20
|
resource = resource_class.find(id)
|
15
21
|
|
16
|
-
|
22
|
+
# Set local attributes from remote resource
|
23
|
+
readable_attribute_names
|
17
24
|
.excluding(:id)
|
18
25
|
.each { |attr| send(:"#{attr}=", resource.send(attr)) }
|
26
|
+
rescue ::HCloud::Errors::NotFound => e
|
27
|
+
raise StateError, "#{address}: #{e.message}"
|
19
28
|
end
|
20
29
|
|
21
30
|
def create
|
22
|
-
resource = resource_class.new(
|
31
|
+
resource = resource_class.new(creatable_attributes)
|
23
32
|
resource.create
|
24
33
|
|
25
|
-
|
34
|
+
# Set local attributes from remote resource
|
35
|
+
readable_attribute_names
|
26
36
|
.each { |attr| send(:"#{attr}=", resource.send(attr)) }
|
37
|
+
rescue ::HCloud::Errors::UniquenessError => e
|
38
|
+
raise ResourceError, "#{address}: #{e.message}"
|
27
39
|
end
|
28
40
|
|
29
41
|
def update
|
42
|
+
raise NotPersisted unless id
|
43
|
+
|
30
44
|
resource = resource_class.find(id)
|
31
45
|
|
32
|
-
|
33
|
-
|
46
|
+
# Set remote attributes from local resource
|
47
|
+
updatable_attribute_names
|
34
48
|
.each { |attr| resource.send(:"#{attr}=", send(attr)) }
|
35
49
|
|
36
50
|
resource.update
|
37
51
|
|
38
|
-
|
52
|
+
readable_attribute_names
|
39
53
|
.excluding(:id)
|
40
54
|
.each { |attr| send(:"#{attr}=", resource.send(attr)) }
|
55
|
+
rescue ::HCloud::Errors::NotFound => e
|
56
|
+
raise StateError, "#{address}: #{e.message}"
|
57
|
+
rescue ::HCloud::Errors::UniquenessError => e
|
58
|
+
raise ResourceError, "#{address}: #{e.message}"
|
41
59
|
end
|
42
60
|
|
43
61
|
def destroy
|
44
|
-
|
62
|
+
# Use rid, because id will have been blanked
|
63
|
+
raise NotPersisted unless rid
|
64
|
+
|
65
|
+
resource = resource_class.find(rid)
|
45
66
|
resource.delete
|
67
|
+
rescue ::HCloud::Errors::NotFound => e
|
68
|
+
raise StateError, "#{address}: #{e.message}"
|
46
69
|
end
|
47
70
|
|
48
71
|
private
|
@@ -7,21 +7,29 @@ module Kozo
|
|
7
7
|
class Server < Resource
|
8
8
|
self.resource_name = "hcloud_server"
|
9
9
|
|
10
|
+
self.creatable_attribute_names = [:name, :image, :server_type, :location, :datacenter, :user_data, :ssh_keys, :labels]
|
11
|
+
self.updatable_attribute_names = [:name, :ssh_keys, :labels]
|
12
|
+
|
10
13
|
attribute :name
|
11
|
-
attribute :image
|
12
|
-
attribute :server_type
|
13
|
-
attribute :location
|
14
|
-
attribute :datacenter
|
15
14
|
|
16
|
-
attribute :
|
15
|
+
attribute :image, wrapped: true
|
16
|
+
attribute :server_type, wrapped: true
|
17
|
+
attribute :location, wrapped: true
|
18
|
+
attribute :datacenter, wrapped: true
|
17
19
|
|
18
20
|
attribute :labels, type: :hash
|
19
21
|
|
20
|
-
|
22
|
+
readonly
|
23
|
+
|
24
|
+
attribute :locked, type: :boolean
|
21
25
|
|
22
|
-
attribute :
|
26
|
+
attribute :created, type: :time
|
27
|
+
|
28
|
+
writeonly
|
29
|
+
|
30
|
+
attribute :user_data
|
23
31
|
|
24
|
-
attribute :
|
32
|
+
attribute :ssh_keys, multiple: true, type: :reference
|
25
33
|
end
|
26
34
|
end
|
27
35
|
end
|
@@ -7,11 +7,16 @@ module Kozo
|
|
7
7
|
class SSHKey < Resource
|
8
8
|
self.resource_name = "hcloud_ssh_key"
|
9
9
|
|
10
|
+
self.creatable_attribute_names = [:name, :public_key, :labels]
|
11
|
+
self.updatable_attribute_names = [:name, :labels]
|
12
|
+
|
10
13
|
attribute :name
|
11
14
|
attribute :public_key
|
12
15
|
attribute :labels, type: :hash
|
13
16
|
|
14
|
-
|
17
|
+
readonly
|
18
|
+
|
19
|
+
attribute :created, type: :time
|
15
20
|
end
|
16
21
|
end
|
17
22
|
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Kozo
|
4
|
+
class Reference
|
5
|
+
attr_reader :resource_class, :state_name, :id
|
6
|
+
|
7
|
+
def initialize(resource_class: nil, id: nil)
|
8
|
+
@resource_class = resource_class
|
9
|
+
@id = id
|
10
|
+
end
|
11
|
+
|
12
|
+
def method_missing(method_name, *_arguments)
|
13
|
+
# Raise NoMethodError when `state_name` was already set (usually because of a programming error)
|
14
|
+
return super if @state_name
|
15
|
+
|
16
|
+
@state_name = method_name.to_s
|
17
|
+
|
18
|
+
self
|
19
|
+
end
|
20
|
+
|
21
|
+
def resolve(configuration)
|
22
|
+
raise StateError, "no state name specified" unless state_name
|
23
|
+
|
24
|
+
resource = configuration
|
25
|
+
.state
|
26
|
+
.resources
|
27
|
+
.find { |r| r.address == address }
|
28
|
+
|
29
|
+
raise StateError, "no such resource address: #{address}" unless resource
|
30
|
+
|
31
|
+
@id = resource.id
|
32
|
+
end
|
33
|
+
|
34
|
+
def respond_to_missing?(_method_name, _include_private = false)
|
35
|
+
true
|
36
|
+
end
|
37
|
+
|
38
|
+
def to_h
|
39
|
+
{
|
40
|
+
id: id,
|
41
|
+
}
|
42
|
+
end
|
43
|
+
|
44
|
+
def as_h
|
45
|
+
id
|
46
|
+
end
|
47
|
+
|
48
|
+
def as_s
|
49
|
+
id&.as_s || "(known after apply)"
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def address
|
55
|
+
"#{resource_class.resource_name}.#{state_name}"
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
data/lib/kozo/resource.rb
CHANGED
@@ -3,24 +3,36 @@
|
|
3
3
|
module Kozo
|
4
4
|
class Resource
|
5
5
|
include Attributes
|
6
|
-
|
7
6
|
include Assignment
|
8
|
-
include Mark
|
9
7
|
include Track
|
8
|
+
include ReadWrite
|
10
9
|
|
11
10
|
attr_accessor :provider, :state_name
|
12
11
|
|
12
|
+
readonly
|
13
|
+
|
13
14
|
attribute :id
|
14
15
|
|
16
|
+
readwrite
|
17
|
+
|
15
18
|
class_attribute :resource_name, :provider_name
|
16
19
|
|
20
|
+
class_attribute :creatable_attribute_names,
|
21
|
+
:updatable_attribute_names,
|
22
|
+
default: []
|
23
|
+
|
17
24
|
def address
|
18
25
|
"#{resource_name}.#{state_name}"
|
19
26
|
end
|
20
27
|
|
28
|
+
# Catchall for id, also works when the resource will be deleted
|
29
|
+
def rid
|
30
|
+
id || id_was
|
31
|
+
end
|
32
|
+
|
21
33
|
def ==(other)
|
22
34
|
self.class == other.class &&
|
23
|
-
|
35
|
+
rid == other.rid &&
|
24
36
|
state_name == other.state_name
|
25
37
|
end
|
26
38
|
|
@@ -30,10 +42,26 @@ module Kozo
|
|
30
42
|
[self.class, id].hash
|
31
43
|
end
|
32
44
|
|
45
|
+
def to_dsl(prefix = nil)
|
46
|
+
l = attribute_names.map(&:length).max || 1
|
47
|
+
|
48
|
+
attribute_dsl = attributes
|
49
|
+
.map { |k, v| " #{changes.key?(k) ? prefix : ' '}r.#{k.to_s.ljust(l)} = #{v.as_s.indent(4)[4..]}" }
|
50
|
+
.join("\n")
|
51
|
+
|
52
|
+
<<~DSL.chomp
|
53
|
+
#{"# #{address}:".bold}
|
54
|
+
#{prefix}resource "#{resource_name}", "#{state_name}" do |r|
|
55
|
+
#{attribute_dsl}
|
56
|
+
end
|
57
|
+
|
58
|
+
DSL
|
59
|
+
end
|
60
|
+
|
33
61
|
def to_h
|
34
62
|
{
|
35
63
|
meta: meta,
|
36
|
-
data:
|
64
|
+
data: readable_attributes,
|
37
65
|
}
|
38
66
|
end
|
39
67
|
|
@@ -62,7 +90,7 @@ module Kozo
|
|
62
90
|
def create!
|
63
91
|
Kozo.logger.info "#{address}: creating resource"
|
64
92
|
|
65
|
-
create
|
93
|
+
create unless Kozo.options.dry_run?
|
66
94
|
|
67
95
|
Kozo.logger.info "#{address}: created resource"
|
68
96
|
end
|
@@ -73,7 +101,7 @@ module Kozo
|
|
73
101
|
def update!
|
74
102
|
Kozo.logger.info "#{address}: updating resource"
|
75
103
|
|
76
|
-
update
|
104
|
+
update unless Kozo.options.dry_run?
|
77
105
|
|
78
106
|
Kozo.logger.info "#{address}: updated resource"
|
79
107
|
end
|
@@ -84,7 +112,7 @@ module Kozo
|
|
84
112
|
def destroy!
|
85
113
|
Kozo.logger.info "#{address}: destroying resource"
|
86
114
|
|
87
|
-
destroy
|
115
|
+
destroy unless Kozo.options.dry_run?
|
88
116
|
|
89
117
|
Kozo.logger.info "#{address}: destroyed resource"
|
90
118
|
end
|
data/lib/kozo/state.rb
CHANGED
@@ -2,12 +2,19 @@
|
|
2
2
|
|
3
3
|
module Kozo
|
4
4
|
class State
|
5
|
-
VERSION =
|
5
|
+
VERSION = 2
|
6
6
|
|
7
|
-
attr_accessor :resources
|
7
|
+
attr_accessor :resources, :version
|
8
8
|
|
9
|
-
def initialize(resources =
|
10
|
-
@resources =
|
9
|
+
def initialize(resources = [], version = VERSION, verify: true)
|
10
|
+
@resources = Array(resources)
|
11
|
+
@version = version
|
12
|
+
|
13
|
+
raise StateError, "unexpected version in state: got #{version}, expected #{State::VERSION}\nRun `#{File.basename($PROGRAM_NAME)} state upgrade` to upgrade your state file" unless compatible? || !verify
|
14
|
+
end
|
15
|
+
|
16
|
+
def compatible?
|
17
|
+
version == State::VERSION
|
11
18
|
end
|
12
19
|
|
13
20
|
def ==(other)
|
@@ -17,7 +24,6 @@ module Kozo
|
|
17
24
|
def to_h
|
18
25
|
{
|
19
26
|
version: VERSION,
|
20
|
-
kozo_version: Kozo::VERSION,
|
21
27
|
resources: resources.map(&:to_h),
|
22
28
|
}
|
23
29
|
end
|
data/lib/kozo/type.rb
CHANGED
data/lib/kozo/types/boolean.rb
CHANGED
data/lib/kozo/types/date.rb
CHANGED
data/lib/kozo/types/float.rb
CHANGED
data/lib/kozo/types/hash.rb
CHANGED
data/lib/kozo/types/integer.rb
CHANGED
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Kozo
|
4
|
+
module Types
|
5
|
+
class Reference < Type
|
6
|
+
def self.cast(value)
|
7
|
+
return value if value.is_a? Kozo::Reference
|
8
|
+
return unless value.is_a? Resource
|
9
|
+
|
10
|
+
# TODO: infer configuration
|
11
|
+
Kozo::Reference
|
12
|
+
.new(resource_class: value.class, id: value.id)
|
13
|
+
.send(value.state_name)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
data/lib/kozo/types/string.rb
CHANGED
data/lib/kozo/types/time.rb
CHANGED
data/lib/kozo/upgrade.rb
ADDED
data/lib/kozo/version.rb
CHANGED
data/lib/kozo.rb
CHANGED
@@ -1,11 +1,12 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "active_support/all"
|
4
|
-
require "active_model"
|
5
4
|
require "colorize"
|
6
5
|
require "dinja"
|
7
6
|
require "zeitwerk"
|
8
7
|
|
8
|
+
require "kozo/inflector"
|
9
|
+
|
9
10
|
require "byebug" if ENV["ENV"] == "development"
|
10
11
|
|
11
12
|
module Kozo
|
@@ -28,8 +29,13 @@ module Kozo
|
|
28
29
|
@logger ||= Logger.new
|
29
30
|
end
|
30
31
|
|
32
|
+
def debug_logger
|
33
|
+
@debug_logger ||= Logger::Debug.new
|
34
|
+
end
|
35
|
+
|
31
36
|
def setup
|
32
37
|
@loader = Zeitwerk::Loader.for_gem
|
38
|
+
loader.inflector = Kozo::Inflector.new(__FILE__)
|
33
39
|
|
34
40
|
# Register inflections
|
35
41
|
instance_eval(File.read(root.join("config/inflections.rb")))
|