kerbi 0.0.1

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 844a7ee42c68f643fd43d0a365d497061cf79b9d6176612d9af17789676a32c9
4
+ data.tar.gz: 0fa583609a29a99ab0019c46ab1a11f99f240477fce2ca5a8ce3c7017e1a3aa4
5
+ SHA512:
6
+ metadata.gz: 9a9d89cbdb45952d3baec555ef373c16b8ca4f59fb3bd88d31335f0eb40ca5537995085c6973c58f08481782202c4371af0db57ae022f75470e547d872833005
7
+ data.tar.gz: 42b22d628d97405b95c96b51fd7dee0c5b013682f7e482b5792ded5570d09cc97e0bd76d7cf08c922b87cc985d508a5e7af33df64341b9822c49b53f197d54a9
data/bin/kerbi ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env ruby
2
+ require "kerbi"
3
+ Kerbi::Cli::RootHandler.start(ARGV)
@@ -0,0 +1,6 @@
1
+ source 'https://rubygems.org'
2
+ git_source(:github) { |repo| "https://github.com/#{repo}.git" }
3
+
4
+ ruby '<%= data[:ruby_version] %>'
5
+
6
+ gem 'kerbi'
@@ -0,0 +1,9 @@
1
+ module <%= (module_name = data[:user_module_name]) %>
2
+ class RootMixer < Kerbi::Mixer
3
+ def mix
4
+ push dict(hello: values[:message])
5
+ end
6
+ end
7
+ end
8
+
9
+ Kerbi::Globals.mixers << <%= module_name %>::RootMixer
@@ -0,0 +1 @@
1
+ message: "default message"
data/lib/cli/base.rb ADDED
@@ -0,0 +1,83 @@
1
+ module Kerbi
2
+ module Cli
3
+
4
+ ##
5
+ # Superclass for all Thor CLI handlers.
6
+ class BaseHandler < Thor
7
+
8
+ protected
9
+
10
+ ##
11
+ # Convenience method for printing dicts as YAML or JSON,
12
+ # according to the CLI options.
13
+ # @param [Hash|Array<Hash>] dicts
14
+ def print_dicts(dicts)
15
+ if self.cli_opts.outputs_yaml?
16
+ printable_str = Kerbi::Utils::Cli.dicts_to_yaml(dicts)
17
+ elsif self.cli_opts.outputs_json?
18
+ printable_str = Kerbi::Utils::Cli.dicts_to_json(dicts)
19
+ else
20
+ raise "Unknown output format '#{cli_opts.output_format}'"
21
+ end
22
+ puts printable_str
23
+ end
24
+
25
+ ##
26
+ # Returns single merged dict containing all values given or
27
+ # pointed to by the CLI args, plus the default values.yaml file.
28
+ # This includes values from files, inline expressions, and the
29
+ # state ConfigMap.
30
+ # @return [Hash] symbol-keyed dict of all loaded values
31
+ def compile_values
32
+ utils = Kerbi::Utils::Values
33
+ file_values = utils.from_files(cli_opts.fname_exprs)
34
+ inline_values = utils.from_inlines(cli_opts.inline_val_exprs)
35
+ file_values.deep_merge(inline_values)
36
+ end
37
+
38
+ ##
39
+ # Returns a re-usable instance of the CLI-args
40
+ # wrapper Kerbi::CliOpts
41
+ # @return [Kerbi::CliOpts] re-usable instance
42
+ def cli_opts
43
+ @_options_obj ||= Kerbi::CliOpts.new(self.options)
44
+ end
45
+
46
+ ##
47
+ # Convenience class method for declaring a Thor subcommand
48
+ # metadata bundle, in accordance with the schema in
49
+ # Kerbi::Consts::OptionSchemas.
50
+ # @param [Hash] _schema a dict from Kerbi::Consts::OptionSchemas
51
+ # @param [Class<Thor>] handler_cls the handler
52
+ def self.thor_sub_meta(_schema, handler_cls)
53
+ schema = _schema.deep_dup
54
+ desc(schema[:name], schema[:desc])
55
+ subcommand schema[:name], handler_cls
56
+ end
57
+
58
+ ##
59
+ # Convenience class method for declaring a Thor command
60
+ # metadata bundle, in accordance with the schema in
61
+ # Kerbi::Consts::OptionSchemas.
62
+ # @param [Hash] _schema a dict from Kerbi::Consts::OptionSchemas
63
+ def self.thor_meta(_schema)
64
+ schema = _schema.deep_dup
65
+ desc(schema[:name], schema[:desc])
66
+ (schema[:options] || []).each do |opt_schema|
67
+ opt_key = opt_schema.delete(:key).to_sym
68
+ self.option opt_key, opt_schema
69
+ end
70
+ end
71
+
72
+ def self.exit_on_failure?
73
+ true
74
+ end
75
+
76
+ private
77
+
78
+ def utils
79
+ Kerbi::Utils
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,17 @@
1
+ module Kerbi
2
+ module Cli
3
+ class ProjectHandler < BaseHandler
4
+
5
+ thor_meta Kerbi::Consts::CommandSchemas::NEW_PROJECT
6
+ def new_project(project_name)
7
+ generator = Kerbi::CodeGen::ProjectGenerator.new(
8
+ project_name: project_name,
9
+ ruby_version: cli_opts.ruby_version,
10
+ verbose: true
11
+ )
12
+ success = generator.run
13
+ exit(success)
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,47 @@
1
+ module Kerbi
2
+
3
+ class Console
4
+ attr_reader :values
5
+ def initialize(values)
6
+ @values = values
7
+ end
8
+
9
+ def to_s
10
+ "kerbi"
11
+ end
12
+ end
13
+
14
+ module Cli
15
+
16
+ ##
17
+ # Top level CLI command handler with Thor.
18
+ class RootHandler < BaseHandler
19
+
20
+ cmd_schemas = Kerbi::Consts::CommandSchemas
21
+
22
+ thor_meta cmd_schemas::TEMPLATE
23
+ # @param [String] release_name helm-like Kubernetes release name
24
+ # @param [String] path root dir from which to search for kerbifile.rb
25
+ def template(release_name, path)
26
+ utils::Cli.load_kerbifile(path)
27
+ values = self.compile_values
28
+ mixer_classes = Kerbi::Globals.mixers
29
+ res_dicts = utils::Cli.run_mixers(mixer_classes, values, release_name)
30
+ print_dicts(res_dicts)
31
+ end
32
+
33
+ thor_meta cmd_schemas::CONSOLE
34
+ def console
35
+ utils::Cli.load_kerbifile(".")
36
+ values = self.compile_values
37
+ ARGV.clear
38
+ IRB.setup(eval("__FILE__"), argv: [])
39
+ workspace = IRB::WorkSpace.new(Console.new(values))
40
+ IRB::Irb.new(workspace).run(IRB.conf)
41
+ end
42
+
43
+ thor_sub_meta cmd_schemas::VALUES_SUPER, ValuesHandler
44
+ thor_sub_meta cmd_schemas::PROJECT_SUPER, ProjectHandler
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,11 @@
1
+ module Kerbi
2
+ module Cli
3
+ class ValuesHandler < BaseHandler
4
+ thor_meta Kerbi::Consts::CommandSchemas::SHOW_VALUES
5
+ def show
6
+ values = self.compile_values
7
+ print_dicts(values)
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,50 @@
1
+ module Kerbi
2
+
3
+ ##
4
+ # Convenience accessor struct for getting values from
5
+ # the CLI args.
6
+ class CliOpts
7
+
8
+ attr_reader :options
9
+
10
+ def initialize(options={})
11
+ @options = options.deep_dup
12
+ end
13
+
14
+ def output_format
15
+ value = options[consts::OUTPUT_FMT]
16
+ value || "yaml"
17
+ end
18
+
19
+ def outputs_yaml?
20
+ self.output_format == 'yaml'
21
+ end
22
+
23
+ def outputs_json?
24
+ self.output_format == 'json'
25
+ end
26
+
27
+ def ruby_version
28
+ options[consts::RUBY_VER]
29
+ end
30
+
31
+ def fname_exprs
32
+ options[consts::VALUE_FNAMES] || []
33
+ end
34
+
35
+ def inline_val_exprs
36
+ options[consts::INLINE_ASSIGNMENT] || []
37
+ end
38
+
39
+ def use_state?
40
+ options[consts::USE_STATE_VALUES].present?
41
+ end
42
+
43
+ private
44
+
45
+ def consts
46
+ Kerbi::Consts::OptionKeys
47
+ end
48
+
49
+ end
50
+ end
@@ -0,0 +1,104 @@
1
+ module Kerbi
2
+ module Consts
3
+
4
+ module OptionKeys
5
+ OUTPUT_FMT = "output"
6
+ INLINE_ASSIGNMENT = "inline-value"
7
+ VALUE_FNAMES = "values-file"
8
+ USE_STATE_VALUES = "use-state-values"
9
+ RUBY_VER = "ruby-version"
10
+ VERBOSE = "verbose"
11
+ end
12
+
13
+ module OptionSchemas
14
+ OUTPUT_FMT = {
15
+ key: OptionKeys::OUTPUT_FMT,
16
+ desc: "Specify YAML or JSON. Defaults to YAML",
17
+ enum: %w[yaml json]
18
+ }
19
+
20
+ USE_STATE_VALUES = {
21
+ key: OptionKeys::USE_STATE_VALUES,
22
+ desc: "If true, merges in values loaded from state ConfigMap",
23
+ type: "boolean"
24
+ }
25
+
26
+ INLINE_ASSIGNMENT = {
27
+ key: OptionKeys::INLINE_ASSIGNMENT,
28
+ aliases: "--set",
29
+ desc: "An inline variable assignment, e.g --set x.y=foo --set x.z=bar",
30
+ repeatable: true
31
+ }
32
+
33
+ VALUE_FNAMES = {
34
+ key: OptionKeys::VALUE_FNAMES,
35
+ aliases: "-f",
36
+ desc: "Name of a values file to be loaded.",
37
+ repeatable: true
38
+ }
39
+
40
+ RUBY_VER = {
41
+ key: OptionKeys::RUBY_VER,
42
+ desc: "Specify ruby version for Gemfile in a new project"
43
+ }
44
+
45
+ VERBOSE = {
46
+ key: OptionKeys::VERBOSE,
47
+ desc: "Run in verbose mode",
48
+ enum: %w[true false]
49
+ }
50
+ end
51
+
52
+ module CommandSchemas
53
+
54
+ VALUES_SUPER = {
55
+ name: "values",
56
+ desc: "Command group for values actions: show, get"
57
+ }
58
+
59
+ PROJECT_SUPER = {
60
+ name: "project",
61
+ desc: "Command group for project actions: new, info"
62
+ }
63
+
64
+ TEMPLATE = {
65
+ name: "template [KERBIFILE] [RELEASE_NAME]",
66
+ desc: "Runs mixers for RELEASE_NAME",
67
+ options: [
68
+ OptionSchemas::OUTPUT_FMT,
69
+ OptionSchemas::VALUE_FNAMES,
70
+ OptionSchemas::INLINE_ASSIGNMENT
71
+ ]
72
+ }
73
+
74
+ CONSOLE = {
75
+ name: "console",
76
+ desc: "Opens an IRB console so you can play with your mixers",
77
+ options: [
78
+ OptionSchemas::VALUE_FNAMES,
79
+ OptionSchemas::INLINE_ASSIGNMENT
80
+ ]
81
+ }
82
+
83
+ NEW_PROJECT = {
84
+ name: "new",
85
+ desc: "Create a new directory with boilerplate files",
86
+ options: [
87
+ OptionSchemas::RUBY_VER,
88
+ OptionSchemas::VERBOSE
89
+ ]
90
+ }
91
+
92
+ SHOW_VALUES = {
93
+ name: "show",
94
+ desc: "Print out loaded values as YAML",
95
+ options: [
96
+ OptionSchemas::OUTPUT_FMT,
97
+ OptionSchemas::VALUE_FNAMES,
98
+ OptionSchemas::INLINE_ASSIGNMENT
99
+ ]
100
+ }
101
+ end
102
+ end
103
+ end
104
+
@@ -0,0 +1,7 @@
1
+ module Kerbi
2
+ module Globals
3
+ def self.mixers
4
+ $_mixers ||= []
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,36 @@
1
+ module Kerbi
2
+ module Config
3
+ class Manager
4
+ attr_reader :bundle
5
+
6
+ def initialize
7
+ # TODO load me from filesystem
8
+ @bundle = {
9
+ tmp_helm_values_path: '/tmp/kerbi-helm-vals.yaml',
10
+ helm_exec: "helm"
11
+ }
12
+ end
13
+
14
+ def self.inst
15
+ # TODO create ~/.config/kerbi etc...
16
+ @_instance ||= self.new
17
+ end
18
+
19
+ def self.tmp_helm_values_path
20
+ inst.bundle[:tmp_helm_values_path]
21
+ end
22
+
23
+ def self.helm_exec
24
+ inst.bundle[:helm_exec]
25
+ end
26
+
27
+ def self.tmp_helm_values_path=(val)
28
+ inst.bundle[:tmp_helm_values_path] = val
29
+ end
30
+
31
+ def self.helm_exec=(val)
32
+ inst.bundle[:helm_exec] = val
33
+ end
34
+ end
35
+ end
36
+ end
data/lib/kerbi.rb ADDED
@@ -0,0 +1,43 @@
1
+ ##
2
+ # Loads all dependencies for all Kerbi code.
3
+ #
4
+ require 'erb'
5
+ require "irb"
6
+ require 'open3'
7
+ require "http"
8
+ require 'json'
9
+ require 'yaml'
10
+ require "thor"
11
+ require "base64"
12
+ require 'optparse'
13
+ require 'colorize'
14
+
15
+ require 'active_support/concern'
16
+ require 'active_support/inflector'
17
+ require 'active_support/core_ext/object/deep_dup'
18
+ require 'active_support/core_ext/hash/keys'
19
+ require 'active_support/core_ext/hash/deep_merge'
20
+ require 'active_support/core_ext/object/blank'
21
+ require 'active_support/core_ext/string/indent.rb'
22
+
23
+ require_relative './utils/misc'
24
+
25
+ require_relative './config/cli_schema'
26
+ require_relative './config/manager'
27
+ require_relative './config/globals'
28
+ require_relative './config/cli_opts'
29
+
30
+ require_relative './mixins/mixer'
31
+
32
+ require_relative './utils/mixing'
33
+ require_relative './utils/helm'
34
+ require_relative './utils/kubectl'
35
+ require_relative './utils/cli'
36
+ require_relative './utils/values'
37
+ require_relative './main/code_gen'
38
+
39
+ require_relative './cli/base'
40
+ require_relative './cli/values_handler'
41
+ require_relative './cli/project_handler'
42
+ require_relative './cli/root_handler'
43
+ require_relative './main/mixer'
@@ -0,0 +1,131 @@
1
+ module Kerbi
2
+ module CodeGen
3
+ class ProjectGenerator
4
+
5
+ ERB_EXT = ".erb"
6
+ BOILER_REL_PATH = "/../../boilerplate"
7
+
8
+ ##
9
+ # Serves as both the new project's dir name and the mixer's _module name
10
+ attr_accessor :project_name
11
+
12
+ ##
13
+ # Mostly for testing, uses this dir as root instead of Dir.pwd
14
+ attr_accessor :root_dir
15
+
16
+ ##
17
+ # Optional ruby version to use in the Gemfile
18
+ attr_accessor :ruby_version
19
+
20
+ ##
21
+ # Print as you go?
22
+ attr_accessor :verbose
23
+
24
+ ##
25
+ # Set accessors, no other side effects.
26
+ def initialize(params={})
27
+ self.project_name = params[:project_name]
28
+ self.root_dir = params[:root_dir]
29
+ self.ruby_version = params[:ruby_version]
30
+ self.verbose = params[:verbose]
31
+ end
32
+
33
+ ##
34
+ # Creates a new directory, writes new interpolated files to it.
35
+ def run
36
+ return false unless create_dir
37
+ self.class.boiler_file_abs_paths.each do |src_name, src_path|
38
+ process_file(src_name, src_path)
39
+ end
40
+ true
41
+ end
42
+
43
+ def create_dir
44
+ begin
45
+ Dir.mkdir(new_dir_path)
46
+ print_created('Created project at', new_dir_path)
47
+ true
48
+ rescue Errno::EEXIST
49
+ message = "Error dir already exists: #{new_dir_path}"
50
+ $stderr.puts message.colorize(:red)
51
+ end
52
+ end
53
+
54
+ ## Root function for processing a single file, e.g reading its
55
+ # original un-interpolated form, interpolating it, and finally
56
+ # writing the new content to destination directory.
57
+ # @param [String] src_file_name src file name w/o path e.g Gemfile.erb
58
+ # @param [String] src_file_path src file name including abs path
59
+ def process_file(src_file_name, src_file_path)
60
+ data = generate_data
61
+ new_file_content = self.class.interpolate_file(src_file_path, data)
62
+ new_file_path = mk_dest_path(src_file_name)
63
+ File.write(new_file_path, new_file_content)
64
+ print_file_processed(src_file_name)
65
+ end
66
+
67
+ def print_file_processed(src_file_name)
68
+ pretty_fname = "#{self.class.src_to_dest_file_name(src_file_name)}"
69
+ pretty_name = "#{project_name}/#{pretty_fname}"
70
+ self.print_created("Created file", pretty_name)
71
+ end
72
+
73
+ #noinspection RubyResolve
74
+ def print_created(bold_part, colored_part)
75
+ return unless verbose
76
+ puts "#{bold_part.bold} #{colored_part.colorize(:blue)}"
77
+ end
78
+
79
+ ##
80
+ # Returns a dict with all the key value assignments the ERBs
81
+ # need to do their work.
82
+ # @return [Hash{Symbol->String}] data bundle for interpolation
83
+ def generate_data
84
+ module_name = project_name.underscore.classify
85
+ version = ruby_version || RUBY_VERSION
86
+ {
87
+ user_module_name: module_name,
88
+ ruby_version: version
89
+ }
90
+ end
91
+
92
+ ##
93
+ # Generates absolute path of destination file based on filename
94
+ # of source file. E.g Gemfile.erb -> /home/foo/Gemfile
95
+ # @param [String] src_file_name src file name w/o path e.g Gemfile.erb
96
+ # @return [String] E.g Gemfile.erb -> /home/foo/Gemfile
97
+ def mk_dest_path(src_file_name)
98
+ new_fname = self.class.src_to_dest_file_name(src_file_name)
99
+ "#{new_dir_path}/#{new_fname}"
100
+ end
101
+
102
+ def self.src_to_dest_file_name(src_file_name)
103
+ src_file_name[0..-(ERB_EXT.length + 1)]
104
+ end
105
+
106
+ ## Returns the absolute path of the new project directory
107
+ # based on the constructor arguments
108
+ # @return [String] e.g /home/john/hello-kerbi
109
+ def new_dir_path
110
+ "#{root_dir || Dir.pwd}/#{project_name}"
111
+ end
112
+
113
+ def self.boiler_file_abs_paths
114
+ boiler_dir_path = "#{__dir__}#{BOILER_REL_PATH}"
115
+
116
+ is_boiler = ->(fname) { fname.end_with?(ERB_EXT) }
117
+ to_abs = ->(fname) { "#{boiler_dir_path}/#{fname}" }
118
+
119
+ boiler_fnames = Dir.entries(boiler_dir_path)
120
+ actual_fnames = boiler_fnames.select(&is_boiler)
121
+ Hash[actual_fnames.map { |f| [f, to_abs[f]] }]
122
+ end
123
+
124
+ def self.interpolate_file(src_path, data)
125
+ contents = File.read(src_path)
126
+ binding.local_variable_set(:data, data)
127
+ ERB.new(contents).result(binding)
128
+ end
129
+ end
130
+ end
131
+ end