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.
@@ -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
@@ -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,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kozo
4
+ module Commands
5
+ class Plan < Base
6
+ self.description = "Show execution plan"
7
+
8
+ def start
9
+ configuration.parse!
10
+ end
11
+ end
12
+ end
13
+ 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
@@ -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
@@ -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
@@ -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
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kozo
4
+ class Provider
5
+ class_attribute :name
6
+ end
7
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kozo
4
+ module Providers
5
+ module HCloud
6
+ class Provider < Kozo::Provider
7
+ attr_accessor :key
8
+
9
+ self.name = "hcloud"
10
+
11
+ def ==(other)
12
+ key == other.key
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kozo
4
+ module Providers
5
+ module HCloud
6
+ class Resource < Kozo::Resource
7
+ self.provider = "hcloud"
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kozo
4
+ module Providers
5
+ module HCloud
6
+ module Resources
7
+ class SSHKey < Resource
8
+ attr_accessor :name, :public_key
9
+
10
+ self.name = "hcloud_ssh_key"
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kozo
4
+ module Providers
5
+ module Null
6
+ class Provider < Kozo::Provider
7
+ self.name = "null"
8
+
9
+ def ==(_other)
10
+ true
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kozo
4
+ module Providers
5
+ module Null
6
+ class Resource < Kozo::Resource
7
+ self.provider = "null"
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kozo
4
+ module Providers
5
+ module Null
6
+ module Resources
7
+ class Null < Resource
8
+ self.name = "null"
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end