kozo 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.
- checksums.yaml +7 -0
- data/.github/dependabot.yml +14 -0
- data/.github/workflows/ci.yml +81 -0
- data/.gitignore +69 -0
- data/.overcommit.yml +31 -0
- data/.rspec +2 -0
- data/.rubocop.yml +70 -0
- data/CHANGELOG.md +5 -0
- data/Gemfile +9 -0
- data/Gemfile.lock +126 -0
- data/LICENSE.md +20 -0
- data/README.md +27 -0
- data/Rakefile +11 -0
- data/bin/bundle +118 -0
- data/bin/console +7 -0
- data/bin/kozo +9 -0
- data/bin/rspec +28 -0
- data/bin/version +62 -0
- data/config/application.rb +6 -0
- data/config/dependencies.rb +30 -0
- data/config/inflections.rb +8 -0
- data/kozo.gemspec +45 -0
- data/lib/kozo.rb +48 -0
- data/lib/kozo/backends/base.rb +31 -0
- data/lib/kozo/backends/local.rb +34 -0
- data/lib/kozo/cli.rb +64 -0
- data/lib/kozo/commands/base.rb +21 -0
- data/lib/kozo/commands/plan.rb +13 -0
- data/lib/kozo/configuration.rb +25 -0
- data/lib/kozo/container.rb +35 -0
- data/lib/kozo/dsl.rb +52 -0
- data/lib/kozo/environment.rb +27 -0
- data/lib/kozo/logger.rb +30 -0
- data/lib/kozo/options.rb +31 -0
- data/lib/kozo/provider.rb +7 -0
- data/lib/kozo/providers/hcloud/provider.rb +17 -0
- data/lib/kozo/providers/hcloud/resource.rb +11 -0
- data/lib/kozo/providers/hcloud/resources/ssh_key.rb +15 -0
- data/lib/kozo/providers/null/provider.rb +15 -0
- data/lib/kozo/providers/null/resource.rb +11 -0
- data/lib/kozo/providers/null/resources/null.rb +13 -0
- data/lib/kozo/resource.rb +13 -0
- data/lib/kozo/version.rb +16 -0
- data/log/.keep +0 -0
- metadata +304 -0
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "json"
|
4
|
+
|
5
|
+
module Kozo
|
6
|
+
module Backends
|
7
|
+
class Local < Base
|
8
|
+
def state
|
9
|
+
Kozo.logger.debug "Reading local state in #{file}"
|
10
|
+
|
11
|
+
File.write(file, {}.to_json) unless File.exist?(file)
|
12
|
+
|
13
|
+
JSON.parse(File.read(file), symbolize_names: true)
|
14
|
+
rescue JSON::ParserError => e
|
15
|
+
Kozo.logger.fatal "Could not read state file: #{e.message}"
|
16
|
+
end
|
17
|
+
|
18
|
+
def state=(value)
|
19
|
+
Kozo.logger.debug "Writing local state in #{file}"
|
20
|
+
File.write(file, value.to_json)
|
21
|
+
end
|
22
|
+
|
23
|
+
def ==(other)
|
24
|
+
directory == other.directory
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def file
|
30
|
+
@file ||= File.join(directory, "kozo.kzstate")
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
data/lib/kozo/cli.rb
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "optparse"
|
4
|
+
|
5
|
+
module Kozo
|
6
|
+
class CLI
|
7
|
+
attr_reader :parser, :args, :command_args
|
8
|
+
|
9
|
+
def initialize(args)
|
10
|
+
@parser = OptionParser.new("#{File.basename($PROGRAM_NAME)} [global options] command [command options]") do |o|
|
11
|
+
o.on("Global options:")
|
12
|
+
o.on("-v", "--verbose", "Turn on verbose logging")
|
13
|
+
o.on("-h", "--help", "Display this message") { usage }
|
14
|
+
o.separator("\n")
|
15
|
+
o.on("Commands:")
|
16
|
+
commands.each { |(name, description)| o.on(" #{name}#{description.rjust(48)}") }
|
17
|
+
o.separator("\n")
|
18
|
+
end
|
19
|
+
|
20
|
+
@args = args
|
21
|
+
@command_args = []
|
22
|
+
|
23
|
+
parse!
|
24
|
+
end
|
25
|
+
|
26
|
+
def parse!
|
27
|
+
# Parse command line arguments (in order) and extract non-option arguments
|
28
|
+
# (unrecognized option values). Raise for invalid option arguments (unrecognized
|
29
|
+
# option keys). "--foo FOO --bar BAR" will result in "--foo" and "FOO" being parsed
|
30
|
+
# correctly, "--bar" and "BAR" will be extracted.
|
31
|
+
parser.order!(args, into: Kozo.options) { |value| command_args << value }
|
32
|
+
rescue OptionParser::InvalidOption => e
|
33
|
+
@command_args += e.args
|
34
|
+
retry
|
35
|
+
end
|
36
|
+
|
37
|
+
def start
|
38
|
+
command = command_args.shift
|
39
|
+
|
40
|
+
return usage unless command
|
41
|
+
|
42
|
+
klass = "Kozo::Commands::#{command.camelize}".safe_constantize
|
43
|
+
|
44
|
+
return usage(tail: "#{File.basename($PROGRAM_NAME)}: unknown command: #{command}") unless klass
|
45
|
+
|
46
|
+
klass
|
47
|
+
.new(command_args)
|
48
|
+
.start
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
def usage(code: 1, tail: nil)
|
54
|
+
puts parser.to_s
|
55
|
+
puts tail if tail
|
56
|
+
|
57
|
+
exit code
|
58
|
+
end
|
59
|
+
|
60
|
+
def commands
|
61
|
+
Commands::Base.descendants.map { |k| [k.name.demodulize.underscore, k.description] }
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Kozo
|
4
|
+
module Commands
|
5
|
+
class Base
|
6
|
+
class_attribute :description
|
7
|
+
|
8
|
+
def initialize(_args); end
|
9
|
+
|
10
|
+
def start
|
11
|
+
raise NotImplementedError
|
12
|
+
end
|
13
|
+
|
14
|
+
protected
|
15
|
+
|
16
|
+
def configuration
|
17
|
+
@configuration ||= Configuration.new(Kozo.options.directory)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Kozo
|
4
|
+
class Configuration
|
5
|
+
attr_writer :backend
|
6
|
+
attr_accessor :directory, :providers, :resources
|
7
|
+
|
8
|
+
def initialize(directory)
|
9
|
+
@directory = directory
|
10
|
+
@providers = {}
|
11
|
+
@resources = {}
|
12
|
+
end
|
13
|
+
|
14
|
+
def backend
|
15
|
+
@backend ||= Backends::Local.new(directory)
|
16
|
+
end
|
17
|
+
|
18
|
+
def parse!
|
19
|
+
dsl = DSL.new(self)
|
20
|
+
Dir[File.join(directory, "*.kz")]
|
21
|
+
.sort
|
22
|
+
.each { |file| dsl.instance_eval(File.read(file)) }
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Kozo
|
4
|
+
class Container
|
5
|
+
attr_reader :dependencies
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
@dependencies = {}
|
9
|
+
end
|
10
|
+
|
11
|
+
def register(key, force: false, &block)
|
12
|
+
raise DependencyAlreadyRegistered, key unless force || !dependencies[key]
|
13
|
+
|
14
|
+
dependencies[key] = block
|
15
|
+
end
|
16
|
+
|
17
|
+
def resolve(key, *args, quiet: false, &block)
|
18
|
+
return dependencies[key].call(*args, &block) if dependencies[key]
|
19
|
+
|
20
|
+
raise DependencyNotRegistered, key unless quiet
|
21
|
+
end
|
22
|
+
|
23
|
+
class DependencyAlreadyRegistered < StandardError
|
24
|
+
def initialize(message)
|
25
|
+
super("Dependency already registered: #{message}")
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
class DependencyNotRegistered < StandardError
|
30
|
+
def initialize(message)
|
31
|
+
super("Dependency not registered: #{message}")
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
data/lib/kozo/dsl.rb
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Kozo
|
4
|
+
class DSL
|
5
|
+
attr_reader :configuration
|
6
|
+
|
7
|
+
def initialize(configuration)
|
8
|
+
@configuration = configuration
|
9
|
+
end
|
10
|
+
|
11
|
+
def kozo(&_block)
|
12
|
+
yield
|
13
|
+
end
|
14
|
+
|
15
|
+
def backend(type)
|
16
|
+
backend = resolve(:backend, type)
|
17
|
+
|
18
|
+
yield backend if block_given?
|
19
|
+
|
20
|
+
configuration.backend = backend
|
21
|
+
end
|
22
|
+
|
23
|
+
def provider(type)
|
24
|
+
provider = resolve(:provider, type)
|
25
|
+
|
26
|
+
yield provider if block_given?
|
27
|
+
|
28
|
+
configuration.providers[provider.class.name] = provider
|
29
|
+
end
|
30
|
+
|
31
|
+
def resource(type, name)
|
32
|
+
resource = resolve(:resource, type, name)
|
33
|
+
resource.provider = configuration.providers[resource.class.provider]
|
34
|
+
|
35
|
+
Kozo.logger.fatal "Provider #{resource.class.provider}" unless resource.provider
|
36
|
+
|
37
|
+
yield resource if block_given?
|
38
|
+
|
39
|
+
configuration.resources[resource.class.name] = resource
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def resolve(resource, type, name = nil)
|
45
|
+
Kozo.logger.debug "Initializing #{resource} #{type} #{name}"
|
46
|
+
|
47
|
+
Kozo.container.resolve("#{resource}.#{type}")
|
48
|
+
rescue Container::DependencyNotRegistered
|
49
|
+
Kozo.logger.fatal "Unknown #{resource} type: #{type}"
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Kozo
|
4
|
+
class Environment < String
|
5
|
+
def initialize
|
6
|
+
super(ENV.fetch("KOZO_ENV", "development"))
|
7
|
+
|
8
|
+
return if %w(development test production).include?(self)
|
9
|
+
|
10
|
+
raise InvalidEnvironment, "Unknown environment: #{self}"
|
11
|
+
end
|
12
|
+
|
13
|
+
def development?
|
14
|
+
self == "development"
|
15
|
+
end
|
16
|
+
|
17
|
+
def test?
|
18
|
+
self == "test"
|
19
|
+
end
|
20
|
+
|
21
|
+
def production?
|
22
|
+
self == "production"
|
23
|
+
end
|
24
|
+
|
25
|
+
class InvalidEnvironment < StandardError; end
|
26
|
+
end
|
27
|
+
end
|
data/lib/kozo/logger.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "delegate"
|
4
|
+
require "logger"
|
5
|
+
|
6
|
+
module Kozo
|
7
|
+
class Logger < SimpleDelegator
|
8
|
+
def initialize
|
9
|
+
super(::Logger.new($stdout, level: level, formatter: formatter))
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def level
|
15
|
+
ENV.fetch("LOG_LEVEL", "info")
|
16
|
+
end
|
17
|
+
|
18
|
+
def formatter
|
19
|
+
level == "debug" ? ::Logger::Formatter.new : Formatter.new
|
20
|
+
end
|
21
|
+
|
22
|
+
class Formatter < ::Logger::Formatter
|
23
|
+
def call(severity, _time, _progname, msg)
|
24
|
+
abort("#{File.basename($PROGRAM_NAME)}: #{msg[0].downcase}#{msg[1..]}") if severity == "FATAL"
|
25
|
+
|
26
|
+
"#{msg}\n"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
data/lib/kozo/options.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Kozo
|
4
|
+
class Options
|
5
|
+
attr_writer :directory, :verbose
|
6
|
+
|
7
|
+
def directory
|
8
|
+
@directory ||= Dir.pwd
|
9
|
+
end
|
10
|
+
|
11
|
+
def directory?
|
12
|
+
directory.present?
|
13
|
+
end
|
14
|
+
|
15
|
+
def verbose
|
16
|
+
@verbose ||= false
|
17
|
+
end
|
18
|
+
|
19
|
+
def verbose?
|
20
|
+
verbose.present?
|
21
|
+
end
|
22
|
+
|
23
|
+
def [](key)
|
24
|
+
send(key)
|
25
|
+
end
|
26
|
+
|
27
|
+
def []=(key, value)
|
28
|
+
send(:"#{key}=", value)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|