kozo 0.2.0 → 0.3.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/.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")))
|