kozo 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +7 -2
- data/.kzignore +1 -0
- data/.overcommit.yml +1 -0
- data/.rspec +1 -0
- data/.rubocop.yml +44 -5
- data/CHANGELOG.md +5 -0
- data/Gemfile +3 -1
- data/Gemfile.lock +156 -48
- data/LICENSE.md +1 -1
- data/README.md +28 -0
- data/Rakefile +3 -7
- data/bin/kozo +6 -2
- data/config/application.rb +3 -0
- data/config/dependencies.rb +4 -25
- data/kozo.gemspec +20 -5
- data/lib/core_ext/boolean.rb +12 -0
- data/lib/core_ext/nil_class.rb +11 -0
- data/lib/core_ext/string.rb +25 -0
- data/lib/kozo/backend.rb +81 -0
- data/lib/kozo/backends/git.rb +50 -0
- data/lib/kozo/backends/local.rb +41 -15
- data/lib/kozo/backends/memory.rb +26 -0
- data/lib/kozo/cli.rb +30 -9
- data/lib/kozo/command.rb +32 -0
- data/lib/kozo/commands/apply.rb +44 -0
- data/lib/kozo/commands/console.rb +15 -0
- data/lib/kozo/commands/import.rb +47 -0
- data/lib/kozo/commands/init.rb +15 -0
- data/lib/kozo/commands/plan.rb +29 -3
- data/lib/kozo/commands/refresh.rb +21 -0
- data/lib/kozo/commands/show.rb +15 -0
- data/lib/kozo/commands/state.rb +64 -0
- data/lib/kozo/commands/validate.rb +13 -0
- data/lib/kozo/commands/version.rb +13 -0
- data/lib/kozo/concerns/assignment.rb +17 -0
- data/lib/kozo/concerns/attributes.rb +99 -0
- data/lib/kozo/concerns/mark.rb +25 -0
- data/lib/kozo/concerns/track.rb +47 -0
- data/lib/kozo/configuration.rb +34 -7
- data/lib/kozo/dsl.rb +18 -12
- data/lib/kozo/error.rb +13 -0
- data/lib/kozo/logger.rb +8 -4
- data/lib/kozo/operation.rb +42 -0
- data/lib/kozo/operations/create.rb +29 -0
- data/lib/kozo/operations/destroy.rb +29 -0
- data/lib/kozo/operations/show.rb +12 -0
- data/lib/kozo/operations/update.rb +19 -0
- data/lib/kozo/options.rb +9 -1
- data/lib/kozo/parser.rb +35 -0
- data/lib/kozo/provider.rb +7 -1
- data/lib/kozo/providers/dummy/dependencies.rb +9 -0
- data/lib/kozo/providers/{null → dummy}/provider.rb +2 -6
- data/lib/kozo/providers/dummy/resource.rb +19 -0
- data/lib/kozo/providers/dummy/resources/dummy.rb +16 -0
- data/lib/kozo/providers/hcloud/dependencies.rb +13 -0
- data/lib/kozo/providers/hcloud/provider.rb +7 -1
- data/lib/kozo/providers/hcloud/resource.rb +46 -1
- data/lib/kozo/providers/hcloud/resources/server.rb +29 -0
- data/lib/kozo/providers/hcloud/resources/ssh_key.rb +6 -2
- data/lib/kozo/resource.rb +110 -3
- data/lib/kozo/state.rb +25 -0
- data/lib/kozo/type.rb +15 -0
- data/lib/kozo/types/boolean.rb +30 -0
- data/lib/kozo/types/date.rb +20 -0
- data/lib/kozo/types/float.rb +17 -0
- data/lib/kozo/types/hash.rb +20 -0
- data/lib/kozo/types/integer.rb +17 -0
- data/lib/kozo/types/string.rb +17 -0
- data/lib/kozo/types/time.rb +18 -0
- data/lib/kozo/version.rb +1 -1
- data/lib/kozo.rb +13 -7
- metadata +195 -31
- data/.github/dependabot.yml +0 -14
- data/.github/workflows/ci.yml +0 -81
- data/bin/bundle +0 -118
- data/bin/console +0 -7
- data/bin/rspec +0 -28
- data/bin/version +0 -62
- data/lib/kozo/backends/base.rb +0 -31
- data/lib/kozo/commands/base.rb +0 -21
- data/lib/kozo/container.rb +0 -35
- data/lib/kozo/environment.rb +0 -27
- data/lib/kozo/providers/null/resource.rb +0 -11
- data/lib/kozo/providers/null/resources/null.rb +0 -13
- data/log/.keep +0 -0
data/lib/kozo/backend.rb
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Kozo
|
4
|
+
class Backend
|
5
|
+
attr_accessor :configuration, :directory
|
6
|
+
|
7
|
+
def initialize(configuration, directory)
|
8
|
+
@configuration = configuration
|
9
|
+
@directory = directory
|
10
|
+
end
|
11
|
+
|
12
|
+
def state
|
13
|
+
@state ||= State
|
14
|
+
.new(resources)
|
15
|
+
end
|
16
|
+
|
17
|
+
def state=(value)
|
18
|
+
@state = value
|
19
|
+
|
20
|
+
self.data = value.to_h
|
21
|
+
end
|
22
|
+
|
23
|
+
def validate!
|
24
|
+
state_version = data.fetch(:version)
|
25
|
+
kozo_version = data.fetch(:kozo_version)
|
26
|
+
|
27
|
+
raise StateError, "invalid version in state: got #{state_version}, expected #{State::VERSION}" unless state_version == State::VERSION
|
28
|
+
|
29
|
+
return if kozo_version == Kozo::VERSION
|
30
|
+
|
31
|
+
raise StateError, "invalid kozo version in state: got #{kozo_version}, expected #{Kozo::VERSION}"
|
32
|
+
end
|
33
|
+
|
34
|
+
##
|
35
|
+
# Create necessary backend files/structures if they do not exist
|
36
|
+
#
|
37
|
+
def initialize!
|
38
|
+
raise NotImplementedError
|
39
|
+
end
|
40
|
+
|
41
|
+
def ==(other)
|
42
|
+
directory == other.directory
|
43
|
+
end
|
44
|
+
|
45
|
+
protected
|
46
|
+
|
47
|
+
##
|
48
|
+
# Read state from backend
|
49
|
+
#
|
50
|
+
# @return Hash
|
51
|
+
#
|
52
|
+
def data
|
53
|
+
raise NotImplementedError
|
54
|
+
end
|
55
|
+
|
56
|
+
##
|
57
|
+
# Write state to backend
|
58
|
+
#
|
59
|
+
# @param value Hash
|
60
|
+
#
|
61
|
+
def data=(value)
|
62
|
+
raise NotImplementedError
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
def resources
|
68
|
+
data
|
69
|
+
.fetch(:resources, [])
|
70
|
+
.map do |hash|
|
71
|
+
provider = configuration.providers[hash.dig(:meta, :provider)]
|
72
|
+
|
73
|
+
raise StateError, "provider #{hash.dig(:meta, :provider)} not configured" unless provider
|
74
|
+
|
75
|
+
Resource
|
76
|
+
.from_h(hash)
|
77
|
+
.tap { |r| r.provider = provider }
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "git"
|
4
|
+
|
5
|
+
module Kozo
|
6
|
+
module Backends
|
7
|
+
class Git < Local
|
8
|
+
attr_writer :repository
|
9
|
+
attr_accessor :remote
|
10
|
+
|
11
|
+
def initialize!
|
12
|
+
Kozo.logger.debug "Initializing local state in #{repository_path}"
|
13
|
+
|
14
|
+
return if Dir.exist?(repository_path)
|
15
|
+
|
16
|
+
# Initialize git repository
|
17
|
+
@git = ::Git.init(repository_path)
|
18
|
+
git.add_remote("origin", remote) if remote
|
19
|
+
|
20
|
+
super
|
21
|
+
end
|
22
|
+
|
23
|
+
def data=(value)
|
24
|
+
super
|
25
|
+
|
26
|
+
git.add(file)
|
27
|
+
git.commit("Update Kozo resource state")
|
28
|
+
git.push(git.remote("origin")) if remote
|
29
|
+
end
|
30
|
+
|
31
|
+
def repository
|
32
|
+
@repository ||= "kozo.git"
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def repository_path
|
38
|
+
@repository_path ||= File.join(directory, repository)
|
39
|
+
end
|
40
|
+
|
41
|
+
def path
|
42
|
+
@path ||= File.join(directory, repository, file)
|
43
|
+
end
|
44
|
+
|
45
|
+
def git
|
46
|
+
@git ||= Git.open(path)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
data/lib/kozo/backends/local.rb
CHANGED
@@ -1,33 +1,59 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "
|
3
|
+
require "fileutils"
|
4
|
+
require "yaml"
|
4
5
|
|
5
6
|
module Kozo
|
6
7
|
module Backends
|
7
|
-
class Local <
|
8
|
-
|
9
|
-
|
8
|
+
class Local < Kozo::Backend
|
9
|
+
attr_writer :file
|
10
|
+
attr_accessor :backups
|
10
11
|
|
11
|
-
|
12
|
+
def initialize(configuration, directory)
|
13
|
+
super
|
12
14
|
|
13
|
-
|
14
|
-
rescue JSON::ParserError => e
|
15
|
-
Kozo.logger.fatal "Could not read state file: #{e.message}"
|
15
|
+
@directory ||= Kozo.options.directory
|
16
16
|
end
|
17
17
|
|
18
|
-
def
|
19
|
-
Kozo.logger.debug "
|
20
|
-
|
18
|
+
def initialize!
|
19
|
+
Kozo.logger.debug "Initializing local state in #{path}"
|
20
|
+
|
21
|
+
return if File.exist?(path)
|
22
|
+
|
23
|
+
# Initialize empty local state
|
24
|
+
self.state = State.new
|
21
25
|
end
|
22
26
|
|
23
|
-
def
|
24
|
-
|
27
|
+
def data
|
28
|
+
Kozo.logger.debug "Reading local state in #{path}"
|
29
|
+
|
30
|
+
YAML
|
31
|
+
.safe_load(File.read(path), [Time, Date])
|
32
|
+
.deep_symbolize_keys
|
33
|
+
rescue Errno::ENOENT, Errno::ENOTDIR
|
34
|
+
raise StateError, "local state at #{path} not initialized"
|
35
|
+
rescue Psych::SyntaxError => e
|
36
|
+
raise StateError, "could not read state file: #{e.message}"
|
25
37
|
end
|
26
38
|
|
27
|
-
|
39
|
+
def data=(value)
|
40
|
+
Kozo.logger.debug "Writing local state in #{path}"
|
41
|
+
|
42
|
+
@data = value
|
43
|
+
|
44
|
+
# Write backup state file
|
45
|
+
FileUtils.mv(path, "#{path}.#{DateTime.current.to_i}.kzbackup") if backups
|
46
|
+
|
47
|
+
# Write local state file
|
48
|
+
File.write(path, value.deep_stringify_keys.to_yaml)
|
49
|
+
end
|
28
50
|
|
29
51
|
def file
|
30
|
-
@file ||=
|
52
|
+
@file ||= "kozo.kzstate"
|
53
|
+
end
|
54
|
+
|
55
|
+
def path
|
56
|
+
@path ||= File.join(directory, file)
|
31
57
|
end
|
32
58
|
end
|
33
59
|
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Kozo
|
4
|
+
module Backends
|
5
|
+
class Memory < Kozo::Backend
|
6
|
+
def initialize!
|
7
|
+
Kozo.logger.debug "Initializing local state in memory"
|
8
|
+
|
9
|
+
# Initialize empty local state
|
10
|
+
self.state = State.new
|
11
|
+
end
|
12
|
+
|
13
|
+
def data
|
14
|
+
Kozo.logger.debug "Reading local state in memory"
|
15
|
+
|
16
|
+
@data
|
17
|
+
end
|
18
|
+
|
19
|
+
def data=(value)
|
20
|
+
Kozo.logger.debug "Writing local state in memory"
|
21
|
+
|
22
|
+
@data = value
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
data/lib/kozo/cli.rb
CHANGED
@@ -1,19 +1,27 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "optparse"
|
4
|
+
require "English"
|
4
5
|
|
5
6
|
module Kozo
|
6
7
|
class CLI
|
7
8
|
attr_reader :parser, :args, :command_args
|
8
9
|
|
9
10
|
def initialize(args)
|
10
|
-
@parser = OptionParser.new("#{File.basename($PROGRAM_NAME)} [global options] command [command options]") do |o|
|
11
|
+
@parser = OptionParser.new("#{File.basename($PROGRAM_NAME)} [global options] command [subcommand] [command options]") do |o|
|
11
12
|
o.on("Global options:")
|
13
|
+
o.on("-d", "--directory=DIRECTORY", "Set working directory")
|
12
14
|
o.on("-v", "--verbose", "Turn on verbose logging")
|
13
15
|
o.on("-h", "--help", "Display this message") { usage }
|
14
16
|
o.separator("\n")
|
15
17
|
o.on("Commands:")
|
16
|
-
commands.each
|
18
|
+
commands.each do |(name, description, subcommands)|
|
19
|
+
o.on(" #{name.ljust(33)}#{description}")
|
20
|
+
|
21
|
+
subcommands.each do |(sb_name, sb_description)|
|
22
|
+
o.on(" #{sb_name.ljust(31)}#{sb_description}")
|
23
|
+
end
|
24
|
+
end
|
17
25
|
o.separator("\n")
|
18
26
|
end
|
19
27
|
|
@@ -37,28 +45,41 @@ module Kozo
|
|
37
45
|
def start
|
38
46
|
command = command_args.shift
|
39
47
|
|
40
|
-
|
48
|
+
raise UsageError, "no command specified" unless command
|
41
49
|
|
42
50
|
klass = "Kozo::Commands::#{command.camelize}".safe_constantize
|
43
51
|
|
44
|
-
|
52
|
+
raise UsageError, "unknown command: #{command}" unless klass
|
45
53
|
|
46
54
|
klass
|
47
|
-
.new(command_args)
|
55
|
+
.new(*command_args)
|
48
56
|
.start
|
57
|
+
rescue UsageError => e
|
58
|
+
# Don't print tail if no message was passed
|
59
|
+
return usage if e.message == e.class.name
|
60
|
+
|
61
|
+
usage(tail: "#{File.basename($PROGRAM_NAME)}: #{e.message}")
|
62
|
+
rescue Error => e
|
63
|
+
Kozo.logger.fatal e.message
|
49
64
|
end
|
50
65
|
|
51
66
|
private
|
52
67
|
|
53
68
|
def usage(code: 1, tail: nil)
|
54
|
-
|
55
|
-
|
69
|
+
Kozo.logger.info parser.to_s
|
70
|
+
Kozo.logger.info tail if tail
|
56
71
|
|
57
|
-
|
72
|
+
raise ExitError, code
|
58
73
|
end
|
59
74
|
|
60
75
|
def commands
|
61
|
-
|
76
|
+
Command.subclasses.sort_by(&:name).map do |k|
|
77
|
+
[
|
78
|
+
k.name.demodulize.underscore,
|
79
|
+
k.description,
|
80
|
+
k.descendants.sort_by(&:name).map { |s| [s.name.demodulize.underscore, s.description] },
|
81
|
+
]
|
82
|
+
end
|
62
83
|
end
|
63
84
|
end
|
64
85
|
end
|
data/lib/kozo/command.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Kozo
|
4
|
+
class Command
|
5
|
+
class_attribute :description
|
6
|
+
|
7
|
+
attr_reader :args
|
8
|
+
attr_writer :state, :configuration
|
9
|
+
|
10
|
+
def initialize(*args)
|
11
|
+
@args = args
|
12
|
+
end
|
13
|
+
|
14
|
+
def start
|
15
|
+
raise NotImplementedError
|
16
|
+
end
|
17
|
+
|
18
|
+
protected
|
19
|
+
|
20
|
+
def configuration
|
21
|
+
@configuration ||= Parser
|
22
|
+
.new(Kozo.options.directory)
|
23
|
+
.call
|
24
|
+
end
|
25
|
+
|
26
|
+
def state
|
27
|
+
@state ||= configuration
|
28
|
+
.backend
|
29
|
+
.state
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Kozo
|
4
|
+
module Commands
|
5
|
+
class Apply < Plan
|
6
|
+
self.description = "Execute a plan"
|
7
|
+
|
8
|
+
attr_reader :parser, :options
|
9
|
+
|
10
|
+
def initialize(*args)
|
11
|
+
super()
|
12
|
+
|
13
|
+
@options = {
|
14
|
+
yes: false,
|
15
|
+
}
|
16
|
+
|
17
|
+
@parser = OptionParser.new do |o|
|
18
|
+
o.on("-y", "--yes", "Answer yes to all prompts")
|
19
|
+
end.parse!(args, into: options)
|
20
|
+
end
|
21
|
+
|
22
|
+
def start
|
23
|
+
super
|
24
|
+
|
25
|
+
return if operations.empty?
|
26
|
+
|
27
|
+
unless options[:yes]
|
28
|
+
print "\nContinue executing these actions (yes/no)? ".bold
|
29
|
+
|
30
|
+
abort("\nApply cancelled") unless gets.chomp == "yes"
|
31
|
+
end
|
32
|
+
|
33
|
+
# Apply operations to in-memory state and remote infrastructure
|
34
|
+
operations
|
35
|
+
.each { |o| o.apply(state) }
|
36
|
+
|
37
|
+
# Write state
|
38
|
+
configuration
|
39
|
+
.backend
|
40
|
+
.state = state
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Kozo
|
4
|
+
module Commands
|
5
|
+
class Console < Kozo::Command
|
6
|
+
self.description = "Open an interactive Ruby console"
|
7
|
+
|
8
|
+
def start
|
9
|
+
# rubocop:disable Lint/Debugger
|
10
|
+
binding.irb
|
11
|
+
# rubocop:enable Lint/Debugger
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Kozo
|
4
|
+
module Commands
|
5
|
+
class Import < Kozo::Command
|
6
|
+
self.description = "Import existing resources into the state"
|
7
|
+
|
8
|
+
attr_reader :address, :id
|
9
|
+
|
10
|
+
def initialize(*args)
|
11
|
+
address = args.shift
|
12
|
+
|
13
|
+
raise UsageError, "address not specified" unless address
|
14
|
+
|
15
|
+
id = args.shift
|
16
|
+
|
17
|
+
raise UsageError, "id not specified" unless id
|
18
|
+
|
19
|
+
@address = address
|
20
|
+
@id = id
|
21
|
+
end
|
22
|
+
|
23
|
+
def start
|
24
|
+
# Find resource in configuration
|
25
|
+
resource = configuration
|
26
|
+
.resources
|
27
|
+
.find { |r| r.address == address }
|
28
|
+
|
29
|
+
raise StateError, "no such resource address: #{address}" unless resource
|
30
|
+
|
31
|
+
# Set ID and fetch attributes
|
32
|
+
resource
|
33
|
+
.tap { |r| r.id = id }
|
34
|
+
.refresh!
|
35
|
+
|
36
|
+
# Add or replace resource to in-memory state
|
37
|
+
state
|
38
|
+
.resources << resource
|
39
|
+
|
40
|
+
# Write state
|
41
|
+
configuration
|
42
|
+
.backend
|
43
|
+
.state = state
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Kozo
|
4
|
+
module Commands
|
5
|
+
class Init < Kozo::Command
|
6
|
+
self.description = "Prepare your working directory for other commands"
|
7
|
+
|
8
|
+
def start
|
9
|
+
configuration.backend.initialize!
|
10
|
+
|
11
|
+
Kozo.logger.info "Kozo initialized in #{configuration.directory}"
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
data/lib/kozo/commands/plan.rb
CHANGED
@@ -2,11 +2,37 @@
|
|
2
2
|
|
3
3
|
module Kozo
|
4
4
|
module Commands
|
5
|
-
class Plan <
|
6
|
-
self.description = "
|
5
|
+
class Plan < Kozo::Command
|
6
|
+
self.description = "Create and show an execution plan"
|
7
|
+
|
8
|
+
attr_reader :operations
|
9
|
+
|
10
|
+
def initialize(*_args)
|
11
|
+
@operations = []
|
12
|
+
end
|
7
13
|
|
8
14
|
def start
|
9
|
-
configuration.
|
15
|
+
@operations += configuration.changes.filter_map do |resource|
|
16
|
+
if resource.marked_for_creation?
|
17
|
+
Operations::Create.new(resource)
|
18
|
+
elsif resource.marked_for_deletion?
|
19
|
+
Operations::Destroy.new(resource)
|
20
|
+
elsif resource.changed?
|
21
|
+
Operations::Update.new(resource)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
Kozo.logger.info "Kozo analyzed the state and created the following execution plan. Actions are indicated by the following symbols:"
|
26
|
+
|
27
|
+
[:create, :update, :destroy]
|
28
|
+
.map { |c| "Kozo::Operations::#{c.to_s.camelize}".constantize }
|
29
|
+
.each { |o| Kozo.logger.info " #{o.display_symbol} #{o.name.demodulize.downcase}" }
|
30
|
+
|
31
|
+
return Kozo.logger.info "\nNo actions have to be performed." if operations.empty?
|
32
|
+
|
33
|
+
Kozo.logger.info "\nKozo will perform the following actions:"
|
34
|
+
|
35
|
+
operations.each { |o| Kozo.logger.info o.to_s }
|
10
36
|
end
|
11
37
|
end
|
12
38
|
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Kozo
|
4
|
+
module Commands
|
5
|
+
class Refresh < Kozo::Command
|
6
|
+
self.description = "Update the state to match remote infrastructure"
|
7
|
+
|
8
|
+
def start
|
9
|
+
# Refresh resources in-memory
|
10
|
+
state
|
11
|
+
.resources
|
12
|
+
.each(&:refresh!)
|
13
|
+
|
14
|
+
# Write state
|
15
|
+
configuration
|
16
|
+
.backend
|
17
|
+
.state = state
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Kozo
|
4
|
+
module Commands
|
5
|
+
class Show < Kozo::Command
|
6
|
+
self.description = "Show all resources in the state"
|
7
|
+
|
8
|
+
def start
|
9
|
+
state
|
10
|
+
.resources
|
11
|
+
.each { |r| Kozo.logger.info Operations::Show.new(r).to_s }
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Kozo
|
4
|
+
module Commands
|
5
|
+
class State < Kozo::Command
|
6
|
+
self.description = "Manage and manipulate state"
|
7
|
+
|
8
|
+
attr_reader :subcommand
|
9
|
+
|
10
|
+
def initialize(*args)
|
11
|
+
subcommand = args.shift
|
12
|
+
|
13
|
+
raise UsageError unless subcommand
|
14
|
+
|
15
|
+
klass = "Kozo::Commands::State::#{subcommand.camelize}".safe_constantize
|
16
|
+
|
17
|
+
raise UsageError, "unknown subcommand: state #{subcommand}" unless klass
|
18
|
+
|
19
|
+
@subcommand = klass.new(*args)
|
20
|
+
end
|
21
|
+
|
22
|
+
def start
|
23
|
+
subcommand
|
24
|
+
.start
|
25
|
+
end
|
26
|
+
|
27
|
+
class List < State
|
28
|
+
self.description = "List resources in the state"
|
29
|
+
|
30
|
+
def initialize(*_args); end
|
31
|
+
|
32
|
+
def start
|
33
|
+
state
|
34
|
+
.resources
|
35
|
+
.each { |r| Kozo.logger.info r.address }
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
class Show < State
|
40
|
+
self.description = "Show a resource in the state"
|
41
|
+
|
42
|
+
attr_reader :address
|
43
|
+
|
44
|
+
def initialize(*args)
|
45
|
+
address = args.shift
|
46
|
+
|
47
|
+
raise UsageError, "address not specified" unless address
|
48
|
+
|
49
|
+
@address = address
|
50
|
+
end
|
51
|
+
|
52
|
+
def start
|
53
|
+
resource = state
|
54
|
+
.resources
|
55
|
+
.find { |r| r.address == address }
|
56
|
+
|
57
|
+
raise StateError, "no such resource address: #{address}" unless resource
|
58
|
+
|
59
|
+
Kozo.logger.info Operations::Show.new(resource).to_s
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Kozo
|
4
|
+
module Commands
|
5
|
+
class Validate < Kozo::Command
|
6
|
+
self.description = "Check whether configuration is valid"
|
7
|
+
|
8
|
+
def start
|
9
|
+
Kozo.logger.info "The configuration in #{configuration.directory} is valid"
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Kozo
|
4
|
+
module Assignment
|
5
|
+
def initialize(attributes = {})
|
6
|
+
super()
|
7
|
+
|
8
|
+
assign_attributes(attributes) if attributes
|
9
|
+
end
|
10
|
+
|
11
|
+
def assign_attributes(attributes)
|
12
|
+
attributes.each do |k, v|
|
13
|
+
send(:"#{k}=", v)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|