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