auster 0.2.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. checksums.yaml +7 -0
  2. data/.editorconfig +13 -0
  3. data/.gitignore +9 -0
  4. data/.rspec +2 -0
  5. data/.rubocop.yml +41 -0
  6. data/.travis.yml +5 -0
  7. data/CODE_OF_CONDUCT.md +74 -0
  8. data/Gemfile +3 -0
  9. data/LICENSE.txt +21 -0
  10. data/README.md +152 -0
  11. data/Rakefile +6 -0
  12. data/auster.gemspec +37 -0
  13. data/bin/auster +8 -0
  14. data/bin/console +8 -0
  15. data/example-repo/.auster.yaml +0 -0
  16. data/example-repo/cfer-helpers/.keep +0 -0
  17. data/example-repo/config/.keep +0 -0
  18. data/example-repo/config/schema.yaml +10 -0
  19. data/example-repo/config/us-west-2/dev-ed1.yaml +7 -0
  20. data/example-repo/config/validator.rb +5 -0
  21. data/example-repo/steps/.keep +0 -0
  22. data/example-repo/steps/00.bootstrap/.keep +0 -0
  23. data/example-repo/steps/00.bootstrap/cfer/defs/s3.rb +3 -0
  24. data/example-repo/steps/00.bootstrap/cfer/outputs.rb +1 -0
  25. data/example-repo/steps/00.bootstrap/cfer/parameters.rb +0 -0
  26. data/example-repo/steps/00.bootstrap/cfer/require.rb +0 -0
  27. data/example-repo/steps/00.bootstrap/on-create.d/00-debug.rb +4 -0
  28. data/example-repo/steps/00.bootstrap/on-destroy.d/00-debug.rb +4 -0
  29. data/example-repo/steps/00.bootstrap/post-converge.d/00-debug.rb +5 -0
  30. data/example-repo/steps/00.bootstrap/pre-converge.d/00-debug.rb +4 -0
  31. data/example-repo/steps/01.dependent/.keep +0 -0
  32. data/example-repo/steps/01.dependent/cfer/defs/s3.rb +3 -0
  33. data/example-repo/steps/01.dependent/cfer/outputs.rb +1 -0
  34. data/example-repo/steps/01.dependent/cfer/parameters.rb +0 -0
  35. data/example-repo/steps/01.dependent/cfer/require.rb +0 -0
  36. data/example-repo/steps/01.dependent/on-create.d/00-debug.rb +4 -0
  37. data/example-repo/steps/01.dependent/on-destroy.d/00-debug.rb +4 -0
  38. data/example-repo/steps/01.dependent/post-converge.d/00-debug.rb +7 -0
  39. data/example-repo/steps/01.dependent/pre-converge.d/00-debug.rb +5 -0
  40. data/lib/cfer/auster/cfer_evaluator.rb +125 -0
  41. data/lib/cfer/auster/cfer_helpers.rb +71 -0
  42. data/lib/cfer/auster/cli/_shared.rb +49 -0
  43. data/lib/cfer/auster/cli/destroy.rb +34 -0
  44. data/lib/cfer/auster/cli/generate/repo.rb +27 -0
  45. data/lib/cfer/auster/cli/generate/step.rb +27 -0
  46. data/lib/cfer/auster/cli/generate.rb +35 -0
  47. data/lib/cfer/auster/cli/json.rb +44 -0
  48. data/lib/cfer/auster/cli/nuke.rb +62 -0
  49. data/lib/cfer/auster/cli/run.rb +34 -0
  50. data/lib/cfer/auster/cli.rb +60 -0
  51. data/lib/cfer/auster/config.rb +70 -0
  52. data/lib/cfer/auster/logging.rb +32 -0
  53. data/lib/cfer/auster/param_validator.rb +22 -0
  54. data/lib/cfer/auster/repo.rb +158 -0
  55. data/lib/cfer/auster/script_executor.rb +57 -0
  56. data/lib/cfer/auster/step.rb +181 -0
  57. data/lib/cfer/auster/version.rb +5 -0
  58. data/lib/cfer/auster.rb +22 -0
  59. metadata +270 -0
@@ -0,0 +1,125 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "cfer"
4
+
5
+ module Cfer
6
+ module Core
7
+ class Resource
8
+ # TODO: we need a better way to inject general helpers.
9
+
10
+ def cfize(text, capture_regexp: nil)
11
+ Cfer::Auster::CferHelpers.cfize(text, capture_regexp: capture_regexp)
12
+ end
13
+ end
14
+ end
15
+
16
+ module Auster
17
+ class CloudFormationError < RuntimeError; end
18
+
19
+ class CferEvaluator
20
+ include Cfer::Auster::Logging::Mixin
21
+
22
+ def initialize(path:, stack_name:, parameters:, metadata:, client_options: {}, stack_options: {})
23
+ raise "path must be a String" unless path.is_a?(String)
24
+ raise "path must be a directory" unless File.directory?(path)
25
+
26
+ raise "stack_name must be a String" unless stack_name.is_a?(String)
27
+
28
+ raise "parameters must be a Hash" unless parameters.is_a?(Hash)
29
+
30
+ parameters[:AusterOptions] ||= {}
31
+
32
+ @stack_name = stack_name
33
+ @stack_options = stack_options
34
+ @cfer_client = Cfer::Cfn::Client.new(client_options.merge(stack_name: stack_name, region: parameters[:AWSRegion]))
35
+ @cfer_stack = Cfer::Core::Stack.new(
36
+ stack_options.merge(
37
+ client: @cfer_client, include_base: path, parameters: parameters,
38
+ force_s3: !!parameters[:AusterOptions][:S3Path],
39
+ s3_path: parameters[:AusterOptions][:S3Path]
40
+ )
41
+ )
42
+
43
+ require_rb = File.join(path, "require.rb")
44
+ parameters_rb = File.join(path, "parameters.rb")
45
+ outputs_rb = File.join(path, "outputs.rb")
46
+
47
+ global_helpers_dir = File.join(path, "../../../cfer-helpers")
48
+ global_helpers = Dir["#{global_helpers_dir}/**/*.rb"].reject { |f| File.basename(f).start_with?("_") }
49
+ helpers_dir = File.join(path, "helpers")
50
+ helpers = Dir["#{helpers_dir}/**/*.rb"].reject { |f| File.basename(f).start_with?("_") }
51
+ defs_dir = File.join(path, "defs")
52
+
53
+ @cfer_stack.extend Cfer::Auster::CferHelpers
54
+ @cfer_stack.build_from_block do
55
+ self[:Metadata][:Auster] = metadata
56
+
57
+ global_helpers.each do |helper_file|
58
+ eval_file helper_file
59
+ end
60
+
61
+ helpers.each do |helper_file|
62
+ eval_file helper_file
63
+ end
64
+
65
+ [require_rb, parameters_rb, outputs_rb].each do |file|
66
+ f = file.gsub(path, ".")
67
+ include_template f if File.file?(file)
68
+ end
69
+
70
+ Dir["#{defs_dir}/**/*.rb"].each do |def_file|
71
+ f = def_file.gsub(path, ".")
72
+ include_template f
73
+ end
74
+ end
75
+ end
76
+
77
+ def converge!(block: true)
78
+ # because it isn't actually redundant...
79
+ # rubocop:disable Style/RedundantBegin
80
+ begin
81
+ @cfer_stack.converge!(@stack_options)
82
+ tail! if block
83
+ rescue Aws::CloudFormation::Errors::ValidationError => err
84
+ if err.message == "No updates are to be performed."
85
+ logger.info "CloudFormation has no updates to perform."
86
+ else
87
+ logger.error "Error (#{err.class.name}) in converge: #{err.message}"
88
+ raise err
89
+ end
90
+ end
91
+ end
92
+
93
+ def destroy!(block: true)
94
+ @cfer_client.delete_stack(stack_name: @stack_name)
95
+ tail! if block
96
+ end
97
+
98
+ def tail!(throw_if_failed: true)
99
+ tail_start = DateTime.now
100
+ has_shown_bar = false
101
+ has_failed = false
102
+
103
+ @cfer_client.tail(number: 0, follow: true) do |event|
104
+ has_failed = true if event.timestamp >= tail_start && event.resource_status.include?("FAILED")
105
+ if event.timestamp >= tail_start && !has_shown_bar
106
+ logger.info "CFN >> ----- CURRENT RUN START -----"
107
+ has_failed = false
108
+ has_shown_bar = true
109
+ end
110
+ logger.info "CFN >> %-30s %-40s %-20s %s" % [
111
+ event.resource_status, event.resource_type,
112
+ event.logical_resource_id, event.resource_status_reason
113
+ ]
114
+ end
115
+
116
+ raise "Operation failed. Please check the log." if has_failed && throw_if_failed
117
+ nil
118
+ end
119
+
120
+ def generate_json
121
+ JSON.pretty_generate(@cfer_stack)
122
+ end
123
+ end
124
+ end
125
+ end
@@ -0,0 +1,71 @@
1
+ module Cfer
2
+ module Auster
3
+ module CferHelpers
4
+ CFIZER_DEFAULT_CAPTURE_REGEXP = /C\{(?<directive>.*?)\}/
5
+
6
+ def eval_file(filename)
7
+ instance_eval IO.read(filename), filename
8
+ end
9
+
10
+ def import(name)
11
+ { "Fn::ImportValue" => _exported_name(name) }
12
+ end
13
+
14
+ def export(name, value)
15
+ output name, value, Export: { Name: _exported_name(name) }
16
+ end
17
+
18
+ def _exported_name(name)
19
+ "#{parameters[:PlanID]}--#{name}"
20
+ end
21
+
22
+ def cfize(text, capture_regexp: nil)
23
+ CferHelpers.cfize(text, capture_regexp: capture_regexp)
24
+ end
25
+
26
+ def self.cfize(text, capture_regexp: nil)
27
+ raise "'text' must be a string." unless text.is_a?(String)
28
+
29
+ capture_regexp ||= CFIZER_DEFAULT_CAPTURE_REGEXP
30
+
31
+ raise "'capture_regexp' must be a Regexp." unless capture_regexp.is_a?(Regexp)
32
+ raise "'capture_regexp' must include a 'contents' named 'directive'." \
33
+ unless capture_regexp.named_captures.key?("directive")
34
+
35
+ working = []
36
+ until working[-2] == "" && working[-1] == "" do
37
+ if working.empty?
38
+ working = text.partition(capture_regexp)
39
+ else
40
+ working[-1] = working[-1].partition(capture_regexp)
41
+ working = working.flatten
42
+ end
43
+ end
44
+
45
+ cfizer = Cfizer.new
46
+ Cfizer::Fn.join("", working.map do |token|
47
+ match = capture_regexp.match(token)
48
+ if match.nil?
49
+ token
50
+ else
51
+ cfizer.cfize(match["directive"])
52
+ end
53
+ end.reject { |t| t == ""})
54
+ end
55
+
56
+ class Cfizer
57
+ begin
58
+ include Cfer::Core::Functions
59
+ rescue NameError => _
60
+ # we need to fall back to the old Cfer setup
61
+ include Cfer::Core
62
+ include Cfer::Cfn
63
+ end
64
+
65
+ def cfize(directive)
66
+ instance_eval directive
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+ require "cri"
3
+ require "logger"
4
+
5
+ module Cfer
6
+ module Auster
7
+ module CLI
8
+ def self.base_options(cmd)
9
+ cmd.instance_eval do
10
+ flag :v, :verbose, "sets logging to DEBUG" do |_, _|
11
+ Cfer::Auster::Logging.logger.level = Logger::DEBUG
12
+ end
13
+ end
14
+ end
15
+
16
+ def self.standard_options(cmd)
17
+ cmd.instance_eval do
18
+ CLI.base_options(cmd)
19
+
20
+ flag :h, :help, "show help for this command" do |_, cmd|
21
+ puts cmd.help
22
+ Kernel.exit 0
23
+ end
24
+
25
+ option :l, :"log-level",
26
+ "Configures the verbosity of the Auster and Cfer loggers. (default: info)",
27
+ argument: :required
28
+
29
+ option :p, :"plan-path",
30
+ "The path to the Auster plan repo that should be used (otherwise searches from pwd)",
31
+ argument: :required
32
+ end
33
+ end
34
+
35
+ def self.repo_from_options(opts, &block)
36
+ require "cfer/auster/repo"
37
+
38
+ repo =
39
+ if opts[:"plan-path"]
40
+ Cfer::Auster::Repo.new(opts[:"plan-path"])
41
+ else
42
+ Cfer::Auster::Repo.discover_from_cwd
43
+ end
44
+
45
+ block.call(repo)
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+ require "cri"
3
+
4
+ require "cfer/auster/cli/_shared"
5
+
6
+ module Cfer
7
+ module Auster
8
+ module CLI
9
+ def self.destroy
10
+ Cri::Command.define do
11
+ name "destroy"
12
+ usage "destroy aws-region/config-set count-or-tag"
13
+ description "Destroys this Auster step in your AWS account."
14
+
15
+ CLI.standard_options(self)
16
+
17
+ run do |opts, args, cmd|
18
+ if args.length < 2
19
+ puts cmd.help
20
+ exit 1
21
+ else
22
+ CLI.repo_from_options(opts) do |repo|
23
+ config_set = repo.config_set(args[0])
24
+ step = repo.step_by_count_or_tag(args[1])
25
+
26
+ step.destroy(config_set)
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+ require "cri"
3
+
4
+ module Cfer
5
+ module Auster
6
+ module CLI
7
+ def self.generate_repo
8
+ Cri::Command.define do
9
+ name "repo"
10
+ usage "repo OUTPUT_PATH"
11
+ description "Generates a new Auster plan repo."
12
+
13
+ CLI.base_options(self)
14
+
15
+ flag :h, :help, "show help for this command" do |_, cmd|
16
+ puts cmd.help
17
+ Kernel.exit 0
18
+ end
19
+
20
+ run do |_, _, cmd|
21
+ raise "TODO: implement"
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+ require "cri"
3
+
4
+ module Cfer
5
+ module Auster
6
+ module CLI
7
+ def self.generate_step
8
+ Cri::Command.define do
9
+ name "step"
10
+ usage "step ##"
11
+ description "Generates a step in the current Auster repo."
12
+
13
+ CLI.base_options(self)
14
+
15
+ flag :h, :help, "show help for this command" do |_, cmd|
16
+ puts cmd.help
17
+ Kernel.exit 0
18
+ end
19
+
20
+ run do |_, _, cmd|
21
+ raise "TODO: implement"
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+ require "cri"
3
+
4
+ require "cfer/auster/cli/generate/repo"
5
+ require "cfer/auster/cli/generate/step"
6
+
7
+ module Cfer
8
+ module Auster
9
+ module CLI
10
+ def self.generate
11
+ ret = Cri::Command.define do
12
+ name "generate"
13
+ description "Encapsulates generators for Auster."
14
+
15
+ CLI.base_options(self)
16
+
17
+ flag :h, :help, "show help for this command" do |_, cmd|
18
+ puts cmd.help
19
+ Kernel.exit 0
20
+ end
21
+
22
+ run do |_, _, cmd|
23
+ puts cmd.help
24
+ Kernel.exit 0
25
+ end
26
+ end
27
+
28
+ ret.add_command(CLI.generate_repo)
29
+ ret.add_command(CLI.generate_step)
30
+
31
+ ret
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+ require "cri"
3
+
4
+ require "cfer/auster/cli/_shared"
5
+
6
+ module Cfer
7
+ module Auster
8
+ module CLI
9
+ def self.json
10
+ Cri::Command.define do
11
+ name "json"
12
+ usage "json aws-region/config-set count-or-tag"
13
+ description "Generates the CloudFormation JSON for this step."
14
+
15
+ CLI.standard_options(self)
16
+
17
+ option :o, :"output-file",
18
+ "Saves the JSON output to a file (otherwise prints to stdout)",
19
+ argument: :required
20
+
21
+ run do |opts, args, cmd|
22
+ if args.length < 2
23
+ puts cmd.help
24
+ exit 1
25
+ else
26
+ CLI.repo_from_options(opts) do |repo|
27
+ config_set = repo.config_set(args[0])
28
+ step = repo.step_by_count_or_tag(args[1])
29
+
30
+ ret = step.json(config_set)
31
+
32
+ if opts[:"output-file"]
33
+ IO.write(opts[:"output-file"], ret)
34
+ else
35
+ puts ret
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+ require "cri"
3
+
4
+ require "cfer/auster/cli/_shared"
5
+
6
+ module Cfer
7
+ module Auster
8
+ module CLI
9
+ def self.nuke
10
+ Cri::Command.define do
11
+ extend Cfer::Auster::Logging::Mixin
12
+
13
+ name "nuke"
14
+ usage "nuke aws-region/config-set"
15
+ description "Destroys ALL AWS RESOURCES related to this config set."
16
+
17
+ CLI.standard_options(self)
18
+
19
+ flag nil, :force, "bypasses confirmation - use with care!"
20
+
21
+ run do |opts, args, cmd|
22
+ if args.length < 1
23
+ puts cmd.help
24
+ exit 1
25
+ else
26
+ CLI.repo_from_options(opts) do |repo|
27
+ config_set = repo.config_set(args[0])
28
+
29
+ accepted = !!opts[:force]
30
+
31
+ if !accepted && $stdin.tty?
32
+ $stderr.write "\n\n"
33
+ $stderr.write "!!! YOU ARE ABOUT TO DO SOMETHING VERY DRASTIC! !!!\n"
34
+ $stderr.write "You are requesting to destroy ALL STEPS of the config set '#{config_set.full_name}'.\n"
35
+ $stderr.write "If you are certain you wish to do this, please type CONFIRM: "
36
+
37
+ input = $stdin.readline.chomp
38
+
39
+ if input != "CONFIRM"
40
+ $stderr.write "\n\nInvalid input. Aborting nuke.\n\n"
41
+ Kernel.exit 1
42
+ end
43
+
44
+ accepted = true
45
+ end
46
+
47
+ unless accepted
48
+ logger.error "You must pass interactive confirmation or use the --force parameter to nuke."
49
+ Kernel.exit 1
50
+ end
51
+
52
+ repo.nuke(config_set)
53
+
54
+ logger.warn "I really, really hope you meant to do that."
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+ require "cri"
3
+
4
+ require "cfer/auster/cli/_shared"
5
+
6
+ module Cfer
7
+ module Auster
8
+ module CLI
9
+ def self.run
10
+ Cri::Command.define do
11
+ name "run"
12
+ usage "run aws-region/config-set count-or-tag"
13
+ description "Runs this Auster step against your AWS infrastructure."
14
+
15
+ CLI.standard_options(self)
16
+
17
+ run do |opts, args, cmd|
18
+ if args.length < 2
19
+ puts cmd.help
20
+ exit 1
21
+ else
22
+ CLI.repo_from_options(opts) do |repo|
23
+ config_set = repo.config_set(args[0])
24
+ step = repo.step_by_count_or_tag(args[1])
25
+
26
+ step.run(config_set)
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+ require "cri"
3
+ require "semantic"
4
+ require "json"
5
+
6
+ require "cfer/auster"
7
+ require "cfer/auster/cli/_shared"
8
+ require "cfer/auster/cli/generate"
9
+ require "cfer/auster/cli/json"
10
+ require "cfer/auster/cli/run"
11
+ require "cfer/auster/cli/destroy"
12
+ require "cfer/auster/cli/nuke"
13
+
14
+ module Cfer
15
+ module Auster
16
+ module CLI
17
+ def self.root
18
+ ret = Cri::Command.define do
19
+ name "auster"
20
+ description "The best way to manage CloudFormation. Ever. (We think.)"
21
+
22
+ CLI.base_options(self)
23
+
24
+ flag :h, :help, "show help for this command" do |_, cmd|
25
+ puts cmd.help
26
+ Kernel.exit 0
27
+ end
28
+
29
+ flag nil, :version, "show version information for this command" do |_, _|
30
+ puts Cfer::Auster::VERSION
31
+ Kernel.exit 0
32
+ end
33
+ flag nil, :"version-json", "show version information for this command in JSON" do |_, _|
34
+ puts JSON.pretty_generate(
35
+ Semantic::Version.new(Cfer::Auster::VERSION).to_h.reject { |_, v| v.nil? }
36
+ )
37
+ Kernel.exit 0
38
+ end
39
+
40
+ run do |_, _, cmd|
41
+ puts cmd.help
42
+ Kernel.exit 0
43
+ end
44
+ end
45
+
46
+ ret.add_command(CLI.generate)
47
+ ret.add_command(CLI.json)
48
+ ret.add_command(CLI.run)
49
+ ret.add_command(CLI.destroy)
50
+ ret.add_command(CLI.nuke)
51
+
52
+ ret
53
+ end
54
+
55
+ def self.execute(args)
56
+ CLI.root.run(args)
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+ require "kwalify"
3
+
4
+ module Cfer
5
+ module Auster
6
+ class Config
7
+ include Cfer::Auster::Logging::Mixin
8
+
9
+ attr_reader :name
10
+ attr_reader :aws_region
11
+ attr_reader :data
12
+
13
+ def initialize(name:, aws_region:, data:)
14
+ raise "name must be a String" unless name.is_a?(String)
15
+ raise "aws_region must be a String" unless aws_region.is_a?(String)
16
+ raise "data must be a Hash" unless data.is_a?(Hash)
17
+
18
+ @name = name.dup.freeze
19
+ @aws_region = aws_region.dup.freeze
20
+ @data = data.deep_symbolize_keys
21
+
22
+ @data[:PlanID] = @name
23
+ @data[:AWSRegion] = @aws_region
24
+
25
+ IceNine.deep_freeze(@data)
26
+ end
27
+
28
+ def full_name
29
+ "#{aws_region}/#{name}"
30
+ end
31
+
32
+ def env_vars_for_shell
33
+ {
34
+ "PLAN_ID" => name,
35
+ "AWS_REGION" => aws_region,
36
+ "AWS_DEFAULT_REGION" => aws_region
37
+ }
38
+ end
39
+
40
+ class << self
41
+ include Cfer::Auster::Logging::Mixin
42
+
43
+ def from_file(name:, aws_region:, data_file:, schema_file:)
44
+ logger.debug "Loading config set from #{data_file}"
45
+ schema = schema_file.nil? ? nil : Kwalify::Yaml.load_file(schema_file)
46
+ validator = schema.nil? ? nil : Kwalify::Validator.new(schema)
47
+
48
+ parser = Kwalify::Yaml::Parser.new(validator)
49
+
50
+ data = parser.parse_file(data_file)
51
+ errors = parser.errors()
52
+
53
+ if errors && !errors.empty?
54
+ # TODO: make a better error to raise that can encapsulate these validation failures.
55
+ msg = "Schema validation failed for #{data_file}."
56
+
57
+ logger.error "Schema validation failed for #{data_file}."
58
+ errors.each do |e|
59
+ logger.error "#{e.linenum}:#{e.column} [#{e.path}] #{e.message}"
60
+ end
61
+
62
+ raise msg
63
+ end
64
+
65
+ Config.new(name: name, aws_region: aws_region, data: data)
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "logger"
4
+
5
+ module Cfer
6
+ module Auster
7
+ module Logging
8
+ def self.logger
9
+ @logger
10
+ end
11
+
12
+ def self.logdev
13
+ @logdev
14
+ end
15
+
16
+ # rubocop:disable Style/AccessorMethodName
17
+ def self.set_logdev(logdev)
18
+ @logdev = logdev
19
+ @logger = Logger.new(@logdev)
20
+ end
21
+
22
+ set_logdev($stderr)
23
+ @logger.level = Logger::INFO
24
+
25
+ module Mixin
26
+ def logger
27
+ Cfer::Auster::Logging.logger
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cfer
4
+ module Auster
5
+ class ParamValidator
6
+ def initialize(&validator)
7
+ raise "validator must be a Proc." unless validator.is_a?(Proc)
8
+ raise "validator must be arity 2." unless validator.arity == 2
9
+
10
+ @validator = validator
11
+ end
12
+
13
+ def validate(parameters)
14
+ raise "parameters must be a Hash." unless parameters.is_a?(Hash)
15
+
16
+ errors = []
17
+ @validator.call(parameters, errors)
18
+ errors
19
+ end
20
+ end
21
+ end
22
+ end