minfra-cli 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.
Files changed (60) hide show
  1. checksums.yaml +7 -0
  2. data/.dockerignore +12 -0
  3. data/.gitignore +16 -0
  4. data/.rspec +2 -0
  5. data/CHANGELOG.md +2 -0
  6. data/Dockerfile +12 -0
  7. data/bin/build +20 -0
  8. data/bin/console +16 -0
  9. data/bin/container_exec +9 -0
  10. data/bin/run_tests +74 -0
  11. data/bin/setup.sh +22 -0
  12. data/exe/minfra +6 -0
  13. data/lib/deep_merge.rb +149 -0
  14. data/lib/hash.rb +28 -0
  15. data/lib/minfra/cli/ask.rb +43 -0
  16. data/lib/minfra/cli/command.rb +35 -0
  17. data/lib/minfra/cli/commands/dev.rb +54 -0
  18. data/lib/minfra/cli/commands/kube.rb +279 -0
  19. data/lib/minfra/cli/commands/project/branch.rb +17 -0
  20. data/lib/minfra/cli/commands/project/tag.rb +40 -0
  21. data/lib/minfra/cli/commands/project.rb +113 -0
  22. data/lib/minfra/cli/commands/setup.rb +49 -0
  23. data/lib/minfra/cli/commands/stack/app_template.rb +65 -0
  24. data/lib/minfra/cli/commands/stack/client_template.rb +36 -0
  25. data/lib/minfra/cli/commands/stack/kube_stack_template.rb +94 -0
  26. data/lib/minfra/cli/commands/stack.rb +120 -0
  27. data/lib/minfra/cli/commands/tag.rb +86 -0
  28. data/lib/minfra/cli/common.rb +41 -0
  29. data/lib/minfra/cli/config.rb +111 -0
  30. data/lib/minfra/cli/document.rb +19 -0
  31. data/lib/minfra/cli/hook.rb +65 -0
  32. data/lib/minfra/cli/logging.rb +26 -0
  33. data/lib/minfra/cli/main_command.rb +32 -0
  34. data/lib/minfra/cli/plugins.rb +34 -0
  35. data/lib/minfra/cli/runner.rb +59 -0
  36. data/lib/minfra/cli/templater.rb +63 -0
  37. data/lib/minfra/cli/version.rb +5 -0
  38. data/lib/minfra/cli.rb +80 -0
  39. data/lib/orchparty/ast.rb +53 -0
  40. data/lib/orchparty/cli.rb +69 -0
  41. data/lib/orchparty/context.rb +22 -0
  42. data/lib/orchparty/dsl_parser.rb +229 -0
  43. data/lib/orchparty/dsl_parser_kubernetes.rb +361 -0
  44. data/lib/orchparty/kubernetes_application.rb +305 -0
  45. data/lib/orchparty/plugin.rb +24 -0
  46. data/lib/orchparty/plugins/env.rb +41 -0
  47. data/lib/orchparty/transformations/all.rb +18 -0
  48. data/lib/orchparty/transformations/mixin.rb +73 -0
  49. data/lib/orchparty/transformations/remove_internal.rb +16 -0
  50. data/lib/orchparty/transformations/sort.rb +10 -0
  51. data/lib/orchparty/transformations/variable.rb +56 -0
  52. data/lib/orchparty/transformations.rb +24 -0
  53. data/lib/orchparty/version.rb +3 -0
  54. data/lib/orchparty.rb +59 -0
  55. data/minfra-cli.gemspec +40 -0
  56. data/project.json +7 -0
  57. data/templates/kind.yaml.erb +33 -0
  58. data/templates/kube_config.yaml.erb +7 -0
  59. data/templates/minfra_config.json.erb +26 -0
  60. metadata +196 -0
@@ -0,0 +1,120 @@
1
+ require 'fileutils'
2
+ require_relative 'stack/app_template'
3
+ require_relative 'stack/client_template'
4
+ require_relative 'stack/kube_stack_template'
5
+
6
+ module Minfra
7
+ module Cli
8
+ class Stack < Command
9
+
10
+ desc "describe","get information about a stack"
11
+ option :environment, aliases: ['-e']
12
+ def describe
13
+ pp @minfra_config.describe(options["environment"])
14
+ end
15
+
16
+ desc "dashboard <stack_name>", "openening a dashboard for a stack"
17
+ option :environment, aliases: ['-e']
18
+ option :deployment, aliases: ['-d']
19
+ option :cluster, aliases: ['-c']
20
+ def dashboard(stack_name='all')
21
+ kube.dashboard(stack_name, options[:environment], options[:deployment], options[:cluster])
22
+ end
23
+
24
+ desc "deploy <stack_name> '<message> (optional)'", "deploy a complete stack"
25
+ option :environment, aliases: ['-e']
26
+ # option :deployment, aliases: ['-d']
27
+ option :cluster, aliases: ['-c']
28
+ option :dev, type: :boolean # currently, about to be changed
29
+ option :explain, type: :boolean
30
+ option :install, type: :boolean
31
+ option :test, type: :boolean
32
+ option :opts
33
+ def deploy(stack_name, message='')
34
+ kube.deploy(stack_name, message)
35
+ end
36
+
37
+ desc "rollback <extraargs>", "rollback a deployment"
38
+ option :environment, aliases: ['-e']
39
+ option :deployment, aliases: ['-d']
40
+ option :cluster, aliases: ['-c']
41
+ def rollback(*args)
42
+ STDERR.puts "needs implementation"
43
+ exit 1
44
+ #kube.rollback(stack_name, options[:environment], options[:deployment], options[:cluster], args)
45
+ end
46
+
47
+ desc "destroy", "remove the whole stack"
48
+ option :environment, aliases: ['-e']
49
+ option :cluster, aliases: ['-c']
50
+ def destroy(stack_name)
51
+ kube.destroy(stack_name)
52
+ end
53
+
54
+ desc "list", "list all stacks in an environment"
55
+ option :environment, aliases: ['-e']
56
+ option :cluster, aliases: ['-c']
57
+ def list
58
+ kube.list
59
+ end
60
+
61
+ desc "app", "show the app a stack provides"
62
+ option :environment, aliases: ['-e']
63
+ def app(stack_name)
64
+ cluster=nil
65
+ deployment=nil
66
+ template = Minfra::Cli::StackM::AppTemplate.new(stack_name, minfra_config)
67
+ template.read
68
+ puts "Template: #{template.app_path}\n#{template.to_s}"
69
+ apps = AppResource.all(filter: {identifier: template.app.identifier} ).data
70
+ if apps.empty?
71
+ puts "Auth: app uninstalled"
72
+ atts = {
73
+ identifier: template.app.identifier,
74
+ name: template.app.name,
75
+ short_name: template.app.short_name,
76
+ description: template.app.description,
77
+ native: template.app.native,
78
+ start_url: template.app.start_url,
79
+ public: template.app.public
80
+ }
81
+ app_res=AppResource.build( {data: {attributes: atts, type: 'apps'}} )
82
+ app_res.save
83
+ app = app_res.data
84
+ else
85
+ app = apps.first
86
+ end
87
+ puts "Auth: app installed #{app.id}"
88
+
89
+ clients = OauthClientResource.all(filter: {app_id: app.id}).data
90
+ if clients.empty?
91
+ puts "Auth: client not registered"
92
+ atts= {redirect_uris: template.client.redirect_uris.map { |r| "#{app.start_url}#{r}" },
93
+ native: template.client.native, ppid: template.client.ppid, name: template.client.name, app_id: app.id}
94
+ client_res=OauthClientResource.build( {data: {attributes: atts, type: 'oauth_clients'}} )
95
+ client_res.save
96
+ client=client_res.data
97
+ else
98
+ client = clients.first
99
+ end
100
+ puts "Auth: client registered #{client.id}"
101
+
102
+ client_template=Minfra::Cli::StackM::ClientTemplate.new(stack_name, client.name, minfra_config)
103
+ unless client_template.exist?
104
+ File.write(client_template.path.to_s, { name: client.name, identifier: client.identifier, secret: client.secret }.to_json)
105
+ end
106
+
107
+
108
+ # TODO: create app configuration
109
+ # TODO: create provider
110
+ end
111
+
112
+ private
113
+ def kube
114
+ Kube.new(options, @minfra_config)
115
+ end
116
+ end
117
+ end
118
+ end
119
+
120
+ Minfra::Cli.register("stack", "dealing wit stacks", Minfra::Cli::Stack)
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Minfra
4
+ module Cli
5
+ class Tag
6
+ include Minfra::Cli::Logging
7
+
8
+ def initialize
9
+ @now = Time.now.utc
10
+ @format = '%Y_%m_%dT%H_%M_%SZ'
11
+ @tags_folder = '.tags'
12
+ end
13
+
14
+ def tag_current_commit_for_deploy(message, format)
15
+ @format = format if format
16
+
17
+ info 'Creating tag.'
18
+ debug "Using .tags folder..."
19
+ write_tag_folder_file(message)
20
+ run_cmd(cmd_add_tag_info("#{@tags_folder}/#{tag_name}"), :system)
21
+ run_cmd(cmd_create_tag_commit, :system)
22
+ run_cmd(cmd_tag_commit(message), :system)
23
+ info 'Pushing tag to remote.'
24
+ run_cmd(cmd_push, :system)
25
+ run_cmd(cmd_push_tag, :system)
26
+ end
27
+
28
+ def ensure_commit_is_pushed
29
+ info 'Checking that the current commit is present on the remote.'
30
+ output = run_cmd(cmd_ensure_commit_is_pushed)
31
+
32
+ if output.empty?
33
+ exit_error "The current commit is not present on the remote.\n" \
34
+ 'Please push your changes to origin and try again.'
35
+ end
36
+ end
37
+
38
+ def cmd_ensure_commit_is_pushed
39
+ 'git branch -r --contains $(git rev-list --max-count=1 HEAD)'
40
+ end
41
+
42
+ def cmd_add_tag_info(file)
43
+ "git add #{file}"
44
+ end
45
+
46
+ def cmd_create_tag_commit
47
+ "git commit -m '#{tag_name}'"
48
+ end
49
+
50
+ def cmd_tag_commit(message)
51
+ "git tag -a #{tag_name} -m '#{message}'"
52
+ end
53
+
54
+ def cmd_push
55
+ "git push"
56
+ end
57
+
58
+ def cmd_push_tag
59
+ "git push origin #{tag_name}"
60
+ end
61
+
62
+ def git_current_branch
63
+ `git rev-parse --abbrev-ref HEAD`.strip
64
+ end
65
+
66
+ #TBD: this should be more flexible
67
+ def tag_name
68
+ "#{git_current_branch}-REL-#{@now.strftime(@format)}"
69
+ end
70
+
71
+ def write_tag_folder_file(message)
72
+ File.write("#{@tags_folder}/#{tag_name}", "#{message}\n")
73
+ end
74
+
75
+ def write_tag_file(_message)
76
+ File.write(@tags_file.to_s, "#{tag_name}\n")
77
+ end
78
+
79
+ def run_cmd(cmd, _how = :system)
80
+ Runner.run(cmd)
81
+ end
82
+ end
83
+ end
84
+ end
85
+
86
+ #Minfra::Cli.register("tag", "creating tags", Minfra::Cli::Tag)
@@ -0,0 +1,41 @@
1
+ require 'pathname'
2
+
3
+ module Minfra
4
+ module Cli
5
+ module Common
6
+
7
+ def exit_error(msg)
8
+ STDERR.puts("ERROR: #{msg}" )
9
+ exit 1
10
+ end
11
+
12
+ def run_cmd(cmd, type = :non_system, silence: false)
13
+ puts cmd unless silence
14
+ case type
15
+ when :exec
16
+ Kernel.exec(cmd)
17
+ when :bash
18
+ res = system(%{bash -c "#{Array.new(cmd).join(' && ')}"})
19
+ exit_error("failed!") unless res
20
+ nil # no output!
21
+ when :system
22
+ res = system(cmd)
23
+ exit_error("failed!") unless res
24
+ nil # no output!
25
+ else
26
+ `#{cmd}` # with output!
27
+ end
28
+ end
29
+
30
+ def parse_cmd(cmd, silence: false)
31
+ reply = JSON.parse(run_cmd(cmd, silence: silence))
32
+ rescue JSON::ParserError, TypeError
33
+ error "ERROR: #{$ERROR_INFO.message}"
34
+ error reply
35
+ error "command was: #{cmd}"
36
+ exit 1
37
+ end
38
+
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,111 @@
1
+ require 'pathname'
2
+ require 'hashie/mash'
3
+ require 'json'
4
+ require_relative 'templater'
5
+ module Minfra
6
+ module Cli
7
+ # responsible the read the config file(s) and add a small abstraction layer on top of it
8
+ class Config
9
+ include Logging
10
+
11
+ class ConfigNotFoundError < StandardError
12
+ end
13
+ class EnvironmentNotFoundError < StandardError
14
+ end
15
+
16
+ attr_reader :base_path
17
+ attr_reader :config_path
18
+ attr_reader :stacks_path
19
+ attr_reader :me_path
20
+ attr_reader :kube_path
21
+ attr_reader :kube_config_path
22
+ attr_reader :kind_config_path
23
+
24
+ attr_reader :orch_env
25
+ attr_reader :orch_env_config
26
+ attr_reader :config
27
+ attr_reader :project
28
+
29
+ def self.load(orch_env, base_path_str = nil)
30
+ new(base_path_str).load(orch_env)
31
+ end
32
+
33
+ def initialize(base_path_str=nil)
34
+ init!(base_path_str)
35
+ end
36
+
37
+ def init!(base_path_str=nil)
38
+ debug( "Config: initializing" )
39
+ @base_path = Pathname.new(base_path_str || ENV["MINFRA_PATH"]).expand_path
40
+ @me_path = @base_path.join('me')
41
+ @project_config_path=@base_path.join("config","project.json")
42
+ @config_path = @me_path.join('config.json')
43
+ @stacks_path = @base_path.join('stacks')
44
+ @kube_path=@me_path.join('kube')
45
+ @kube_config_path=@kube_path.join('config')
46
+ @kind_config_path=@me_path.join("kind.yaml.erb")
47
+ @project_minfrarc_path = @base_path.join("config",'minfrarc.rb')
48
+ require @project_minfrarc_path if @project_minfrarc_path.exist?
49
+ @me_minfrarc_path = @me_path.join('minfrarc.rb')
50
+ require @me_minfrarc_path if @me_minfrarc_path.exist?
51
+ if config_path.exist?
52
+ @config = Hashie::Mash.new(JSON.parse(Minfra::Cli::Templater.render(File.read(config_path),{})))
53
+ else
54
+ warn("personal minfra configuration file '#{config_path}' not found, you might have to run 'minfra setup dev'")
55
+ @config = Hashie::Mash.new({})
56
+ end
57
+ @project = Hashie::Mash.new(JSON.parse(Minfra::Cli::Templater.render(File.read(@project_config_path),{})))
58
+ end
59
+
60
+ def load(orch_env)
61
+ debug( "loading config env: #{orch_env} #{@orch_env}" )
62
+ return self if defined?(@orch_env)
63
+ @orch_env = orch_env
64
+ @orch_env_config = @config.environments[@orch_env] || raise(EnvironmentNotFoundError.new("Configuration for orchestration environment '#{@orch_env}' not found. Available orechstration environments: #{@config.environments.keys.inspect}"))
65
+ @project= @project.
66
+ deep_merge(@project.environments[@orch_env]).
67
+ deep_merge(@config).
68
+ deep_merge(@orch_env_config)
69
+ @orch_env_config['env']=@orch_env
70
+ self
71
+ end
72
+
73
+ def name
74
+ @project.name
75
+ end
76
+
77
+ def describe(environment)
78
+ {
79
+ env: {
80
+ minfra_name: ENV["MINFRA_NAME"],
81
+ minfra_path: ENV["MINFRA_PATH"],
82
+ },
83
+ base_path: base_path.to_s,
84
+ me_path: me_path.to_s,
85
+ kube_path: kube_path.to_s,
86
+ config_path: config_path.to_s,
87
+ config: @config.to_h,
88
+ env_config: @orch_env_config.to_h,
89
+ project: @project
90
+ }
91
+ end
92
+ def dev?
93
+ @orch_env=='dev'
94
+ end
95
+
96
+ def email
97
+ @config.identity.email
98
+ end
99
+
100
+ def api_key
101
+ @project.account_api_key
102
+ end
103
+
104
+ def endpoint(name)
105
+ Hashie::Mash.new({"api_key": api_key}).deep_merge(@project.endpoints[name])
106
+ rescue
107
+ raise("endpoint #{name} is undefinded please add <env>:endpoints:#{name} to you #{config_path} file ")
108
+ end
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,19 @@
1
+ module Minfra
2
+ module Cli
3
+ class Document
4
+ def self.document(config, message)
5
+ new(config).document(message)
6
+ end
7
+
8
+ def initialize(config)
9
+ @config = config
10
+ end
11
+
12
+ def document(message)
13
+ return true if @config.dev?
14
+ puts "TBD: calling documentation hooks"
15
+ true
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,65 @@
1
+ # no support for around hooks yet
2
+
3
+ module Minfra
4
+ module Cli
5
+ module Hook
6
+ class Hook
7
+ def initialize(type, name, block)
8
+ @type=type
9
+ @name=name
10
+ @block=block
11
+ end
12
+
13
+ def match?(type, name)
14
+ @type == type && @name == name
15
+ end
16
+
17
+ def exec (obj)
18
+ obj.instance_eval(&@block)
19
+ end
20
+
21
+ end
22
+
23
+ class Hooker
24
+ def initialize(klass)
25
+ @klass=klass
26
+ @hooks=[]
27
+ end
28
+
29
+ def register_before(name, block)
30
+ @hooks << Hook.new(:before, name, block)
31
+ end
32
+
33
+ def register_after(name, block)
34
+ @hooks << Hook.new(:after, name, block)
35
+ end
36
+
37
+ def call(obj, name, &block)
38
+ @hooks.select do |h| h.match?(:before, name) end.each do |h| h.exec(obj) end
39
+ obj.instance_eval(&block)
40
+ @hooks.select do |h| h.match?(:after, name) end.each do |h| h.exec(obj) end
41
+ end
42
+ end
43
+
44
+ def self.included(klass)
45
+ klass.extend(ClassMethods)
46
+ end
47
+
48
+ module ClassMethods
49
+ def hooks
50
+ @hooker||=Hooker.new(self)
51
+ end
52
+ def after_hook(name,&block)
53
+ hooks.register_after(name, block)
54
+ end
55
+ def before_hook(name, &block)
56
+ hooks.register_before(name, block)
57
+ end
58
+ end
59
+
60
+ def with_hook(hook_name, &block)
61
+ self.class.hooks.call(self, hook_name, &block)
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,26 @@
1
+ module Minfra
2
+ module Cli
3
+ module Logging
4
+ def error(str)
5
+ STDERR.puts "Error: #{str}"
6
+ end
7
+
8
+ def exit_error(str)
9
+ error str
10
+ exit 1
11
+ end
12
+
13
+ def info(str)
14
+ STDOUT.puts str
15
+ end
16
+
17
+ def debug(str)
18
+ STDOUT.puts "Debug: #{str}"
19
+ end
20
+
21
+ def deprecated(comment)
22
+ puts "DEPRECATED: #{comment}"
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,32 @@
1
+ module Minfra
2
+ module Cli
3
+ class Main < Command
4
+
5
+ desc 'kube', 'kubectl wrapper and other features'
6
+ long_desc '
7
+ '
8
+ option :environment, required: false, aliases: ['-e']
9
+ option :cluster
10
+ def kube(*args)
11
+ kube.kubectl_command(args)
12
+ end
13
+
14
+ # tbd: move this to project
15
+ desc 'tag', 'tag current commit for deployment - triggers CI'
16
+ option :message, default: 'release', aliases: ['-m']
17
+ option :format, required: false, aliases: ['-f']
18
+ def tag
19
+ Tag.new.tag_current_commit_for_deploy(options[:message], options[:format])
20
+ end
21
+
22
+ desc 'version', 'prints version of the cli'
23
+ def version
24
+ puts Minfra::Cli::VERSION
25
+ end
26
+
27
+ def self.exit_on_failure?
28
+ true
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,34 @@
1
+ module Minfra
2
+ module Cli
3
+ class Plugins
4
+ def self.load
5
+ [Pathname.new(ENV["MINFRA_PATH"]).join("config","minfra_plugins.json"),
6
+ Pathname.new(ENV["MINFRA_PATH"]).join("me","minfra_plugins.json")].each do |file|
7
+
8
+ next unless File.exist?(file)
9
+
10
+ plugins=JSON.parse(File.read(file))
11
+ plugins["plugins"].each do |spec|
12
+ opts=spec["opts"] || {}
13
+ opts.merge(require: false)
14
+ if opts["path"]
15
+ begin
16
+ $LOAD_PATH.unshift opts["path"]+"/lib"
17
+ require spec["name"]
18
+ rescue Gem::Requirement::BadRequirementError
19
+ STDERR.puts("Can't load plugin: #{spec["name"]}")
20
+ end
21
+ else
22
+ begin
23
+ Gem::Specification.find_by_name(spec["name"])
24
+ gem spec["name"], spec["version"]
25
+ rescue Gem::MissingSpecError
26
+ STDERR.puts("Can't load plugin: #{spec["name"]}, #{spec["version"]}; run 'minfra plugin setup'")
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,59 @@
1
+ require 'open3'
2
+
3
+ module Minfra
4
+ module Cli
5
+ class Runner
6
+ class Result
7
+ attr_reader :stdout, :stderr
8
+ def initialize(stdout,stderr, status)
9
+ @stderr=stderr
10
+ @stdout=stdout
11
+ @status=status
12
+ end
13
+
14
+ def success?
15
+ @status.success?
16
+ end
17
+
18
+ def error?
19
+ !success?
20
+ end
21
+
22
+ def to_s
23
+ @stdout.to_s
24
+ end
25
+ end
26
+
27
+ include Logging
28
+ def self.run(cmd, **args)
29
+ new(cmd, **args).run
30
+ end
31
+
32
+ attr_reader :exit_on_error
33
+ def initialize(cmd, exit_on_error: true)
34
+ @cmd=cmd
35
+ @exit_on_error = exit_on_error
36
+ end
37
+
38
+ def run
39
+ debug(@cmd)
40
+ res=nil
41
+ begin
42
+ res=Result.new(*Open3.capture3(@cmd))
43
+ rescue
44
+ end
45
+ if res&.error?
46
+ STDERR.puts "command failed: #{@cmd}"
47
+ STDERR.puts res.stdout
48
+ STDERR.puts res.stderr
49
+ end
50
+ if exit_on_error && res&.error?
51
+ STDERR.puts "exiting on error"
52
+ exit 1
53
+ end
54
+ res
55
+ end
56
+
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,63 @@
1
+ require 'erb'
2
+
3
+ module Minfra
4
+ module Cli
5
+ class Templater # not threadsafe!
6
+ def self.read(path, params: {}, fallback: nil)
7
+ p=Pathname.new(path)
8
+ if p.exist?
9
+ content=File.read(path)
10
+ else
11
+ if fallback
12
+ content=fallback
13
+ else
14
+ raise "file #{path} not found"
15
+ end
16
+ end
17
+ render(template, params)
18
+ end
19
+
20
+ def self.render(template, params)
21
+ new(template).render(params)
22
+ end
23
+
24
+ def initialize(template)
25
+ @erb = ERB.new(template)
26
+ @check_mode=false
27
+ @check_missing=[]
28
+ end
29
+
30
+ def missing?
31
+ !check_missing.empty?
32
+ end
33
+
34
+ def check_missing(&block)
35
+ begin
36
+ @check_mode = true
37
+ @check_block = block
38
+ @check_missing = []
39
+ @erb.result(binding)
40
+ ensure
41
+ @check_block = nil
42
+ @check_mode = false
43
+ end
44
+ @check_missing
45
+ end
46
+
47
+ def render(params)
48
+ @erb.result_with_hash(params)
49
+ end
50
+
51
+ def method_missing(name)
52
+ if @check_mode
53
+ if @check_block
54
+ @check_block.call(name)
55
+ end
56
+ @check_missing << name
57
+ else
58
+ super
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,5 @@
1
+ module Minfra
2
+ module Cli
3
+ VERSION = '0.1.0'.freeze
4
+ end
5
+ end