ops_team 1.21.1 → 2.0.0.rc3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (61) hide show
  1. checksums.yaml +4 -4
  2. data/bin/ops +4 -40
  3. data/build/darwin_amd64/ops +0 -0
  4. data/build/darwin_arm64/ops +0 -0
  5. data/build/linux_amd64/ops +0 -0
  6. metadata +12 -224
  7. data/Gemfile +0 -30
  8. data/bin/benchmark +0 -185
  9. data/bin/print_config +0 -4
  10. data/bin/print_secrets +0 -4
  11. data/bin/tag +0 -16
  12. data/etc/ops.template.yml +0 -13
  13. data/etc/ruby.template.yml +0 -37
  14. data/etc/terraform.template.yml +0 -36
  15. data/lib/action.rb +0 -103
  16. data/lib/action_list.rb +0 -55
  17. data/lib/action_suggester.rb +0 -17
  18. data/lib/app_config.rb +0 -69
  19. data/lib/builtin.rb +0 -51
  20. data/lib/builtins/background.rb +0 -46
  21. data/lib/builtins/background_log.rb +0 -34
  22. data/lib/builtins/common/up_down.rb +0 -67
  23. data/lib/builtins/countdown.rb +0 -73
  24. data/lib/builtins/down.rb +0 -9
  25. data/lib/builtins/env.rb +0 -21
  26. data/lib/builtins/envdiff.rb +0 -127
  27. data/lib/builtins/exec.rb +0 -24
  28. data/lib/builtins/help.rb +0 -66
  29. data/lib/builtins/helpers/dependency_handler.rb +0 -27
  30. data/lib/builtins/helpers/enumerator.rb +0 -34
  31. data/lib/builtins/init.rb +0 -64
  32. data/lib/builtins/up.rb +0 -9
  33. data/lib/builtins/version.rb +0 -17
  34. data/lib/dependencies/apk.rb +0 -24
  35. data/lib/dependencies/apt.rb +0 -42
  36. data/lib/dependencies/brew.rb +0 -22
  37. data/lib/dependencies/cask.rb +0 -13
  38. data/lib/dependencies/custom.rb +0 -45
  39. data/lib/dependencies/dir.rb +0 -22
  40. data/lib/dependencies/docker.rb +0 -17
  41. data/lib/dependencies/gem.rb +0 -36
  42. data/lib/dependencies/helpers/apt_cache_policy.rb +0 -43
  43. data/lib/dependencies/pip.rb +0 -32
  44. data/lib/dependencies/snap.rb +0 -32
  45. data/lib/dependencies/sshkey.rb +0 -121
  46. data/lib/dependencies/versioned_dependency.rb +0 -25
  47. data/lib/dependency.rb +0 -69
  48. data/lib/environment.rb +0 -47
  49. data/lib/executor.rb +0 -20
  50. data/lib/forward.rb +0 -15
  51. data/lib/forwards.rb +0 -16
  52. data/lib/hook_handler.rb +0 -41
  53. data/lib/ops.rb +0 -129
  54. data/lib/options.rb +0 -22
  55. data/lib/output.rb +0 -71
  56. data/lib/profiler.rb +0 -47
  57. data/lib/runner.rb +0 -110
  58. data/lib/secrets.rb +0 -55
  59. data/lib/version.rb +0 -38
  60. data/loader.rb +0 -10
  61. data/ops_team.gemspec +0 -36
@@ -1,25 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Dependencies
4
- class VersionedDependency < Dependency
5
- VERSION_SEPARATOR = " "
6
-
7
- def dep_name
8
- name_components[0]
9
- end
10
-
11
- def dep_version
12
- name_components[1]
13
- end
14
-
15
- def versioned?
16
- !!dep_version
17
- end
18
-
19
- private
20
-
21
- def name_components
22
- name.split(VERSION_SEPARATOR, 2)
23
- end
24
- end
25
- end
data/lib/dependency.rb DELETED
@@ -1,69 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'open3'
4
- require 'English'
5
-
6
- class Dependency
7
- DESCRIPTION_TYPE_WIDTH = 8
8
-
9
- attr_reader :name
10
-
11
- def initialize(name)
12
- @name = name
13
- end
14
-
15
- def met?
16
- raise NotImplementedError
17
- end
18
-
19
- def meet
20
- raise NotImplementedError
21
- end
22
-
23
- def unmeet
24
- raise NotImplementedError
25
- end
26
-
27
- # should_meet? can be used to implement dependencies that should only be met on some platforms,
28
- # e.g. brew on Macs and apt on Linux
29
- # it can be used to base a decision on anything else that can be read from the environment at
30
- # runtime
31
- def should_meet?
32
- true
33
- end
34
-
35
- # if true, this type of resource must always have `meet` and `unmeet` called;
36
- # useful for resources that can't easily be checked to see if they're met
37
- def always_act?
38
- false
39
- end
40
-
41
- def type
42
- self.class.name.split('::').last
43
- end
44
-
45
- def success?
46
- @executor.nil? ? true : @executor.success?
47
- end
48
-
49
- def failure?
50
- !success?
51
- end
52
-
53
- def output
54
- @executor&.output
55
- end
56
-
57
- def exit_code
58
- @executor&.exit_code
59
- end
60
-
61
- private
62
-
63
- def execute(cmd)
64
- @executor = Executor.new(cmd)
65
- @executor.execute
66
-
67
- @executor.success?
68
- end
69
- end
data/lib/environment.rb DELETED
@@ -1,47 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class Environment
4
- class << self
5
- def environment
6
- return 'dev' if ENV['environment'].nil? || ENV['environment'].empty?
7
-
8
- ENV['environment']
9
- end
10
- end
11
-
12
- def initialize(env_hash, config_path)
13
- @env_hash = env_hash
14
- @config_path = config_path
15
- end
16
-
17
- def set_variables
18
- set_ops_variables
19
- set_environment_aliases
20
- set_configured_variables
21
- end
22
-
23
- private
24
-
25
- def set_ops_variables
26
- ENV["OPS_YML_DIR"] = File.dirname(@config_path)
27
- ENV["OPS_VERSION"] = Version.version.to_s
28
- ENV["OPS_SECRETS_FILE"] = Secrets.app_config_path
29
- ENV["OPS_CONFIG_FILE"] = AppConfig.app_config_path
30
- end
31
-
32
- def set_environment_aliases
33
- environment_aliases.each do |alias_name|
34
- ENV[alias_name] = Environment.environment
35
- end
36
- end
37
-
38
- def environment_aliases
39
- Options.get("environment_aliases") || ['environment']
40
- end
41
-
42
- def set_configured_variables
43
- @env_hash.each do |key, value|
44
- ENV[key] = `echo #{value}`.chomp
45
- end
46
- end
47
- end
data/lib/executor.rb DELETED
@@ -1,20 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class Executor
4
- attr_reader :output, :exit_code
5
-
6
- def initialize(command)
7
- @command = command
8
- end
9
-
10
- def execute
11
- @output, status = Open3.capture2e(@command)
12
- @exit_code = status.exitstatus
13
-
14
- success?
15
- end
16
-
17
- def success?
18
- @exit_code.nil? ? true : @exit_code.zero?
19
- end
20
- end
data/lib/forward.rb DELETED
@@ -1,15 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class Forward
4
- def initialize(dir, args)
5
- @dir = dir
6
- @args = args
7
- end
8
-
9
- def run
10
- Output.notice("Forwarding 'ops #{@args.join(" ")}' to '#{@dir}/'...")
11
-
12
- Dir.chdir(@dir)
13
- Ops.new(@args).run
14
- end
15
- end
data/lib/forwards.rb DELETED
@@ -1,16 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class Forwards
4
- def initialize(config, args = [])
5
- @config = config
6
- @args = args
7
- end
8
-
9
- def get(name)
10
- Forward.new(forwards[name], @args) if forwards[name]
11
- end
12
-
13
- def forwards
14
- @forwards ||= @config["forwards"] || {}
15
- end
16
- end
data/lib/hook_handler.rb DELETED
@@ -1,41 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class HookHandler
4
- class HookConfigError < StandardError; end
5
-
6
- class HookExecError < StandardError; end
7
-
8
- def initialize(config)
9
- @config = config
10
- end
11
-
12
- def do_hooks(name)
13
- raise HookConfigError, "'hooks.#{name}' must be a list" unless hooks(name).is_a?(Array)
14
-
15
- execute_hooks(name)
16
- end
17
-
18
- private
19
-
20
- def hooks(name)
21
- @config.dig("hooks", name) || []
22
- end
23
-
24
- def execute_hooks(name)
25
- hooks(name).each do |hook|
26
- Output.notice("Running #{name} hook: #{hook}")
27
- output, exit_code = execute_hook(hook)
28
-
29
- next if exit_code.zero?
30
-
31
- raise HookExecError, "#{name} hook '#{hook}' failed with exit code #{exit_code}:\n#{output}"
32
- end
33
- end
34
-
35
- def execute_hook(name)
36
- executor = Executor.new(name)
37
- executor.execute
38
-
39
- [executor.output, executor.exit_code]
40
- end
41
- end
data/lib/ops.rb DELETED
@@ -1,129 +0,0 @@
1
- #!/usr/bin/env ruby
2
- # frozen_string_literal: true
3
-
4
- Profiler.measure("ops:requires_external") do
5
- require 'yaml'
6
- require "rubygems"
7
- end
8
-
9
- Profiler.measure("ops:requires_internal") do
10
- end
11
-
12
- CONFIG_FILES = ["ops.yaml", "ops.yml"].freeze
13
-
14
- # executes commands based on local `ops.yml`
15
- class Ops
16
- INVALID_SYNTAX_EXIT_CODE = 64
17
- UNKNOWN_ACTION_EXIT_CODE = 65
18
- ERROR_LOADING_APP_CONFIG_EXIT_CODE = 66
19
- MIN_VERSION_NOT_MET_EXIT_CODE = 67
20
- ACTION_CONFIG_ERROR_EXIT_CODE = 68
21
- BUILTIN_SYNTAX_ERROR_EXIT_CODE = 69
22
- ACTION_NOT_ALLOWED_IN_ENV_EXIT_CODE = 70
23
-
24
- RECOMMEND_HELP_TEXT = "Run 'ops help' for a list of builtins and actions."
25
-
26
- class << self
27
- def project_name
28
- File.basename(::Dir.pwd)
29
- end
30
- end
31
-
32
- def initialize(argv, config_file: nil)
33
- @action_name = argv[0]
34
- @args = argv[1..-1]
35
- @config_file = config_file || CONFIG_FILES.find { |file| File.exist?(file) } || "ops.yml"
36
-
37
- Options.set(config["options"] || {})
38
- end
39
-
40
- # rubocop:disable Metrics/MethodLength
41
- # better to have all the rescues in one place
42
- def run
43
- # "return" is here to allow specs to stub "exit" without executing everything after it
44
- return exit(INVALID_SYNTAX_EXIT_CODE) unless syntax_valid?
45
- return exit(MIN_VERSION_NOT_MET_EXIT_CODE) unless min_version_met?
46
-
47
- Profiler.measure("runner:run") do
48
- runner.run
49
- end
50
- rescue Runner::UnknownActionError => e
51
- Output.error(e.to_s)
52
- Output.out(RECOMMEND_HELP_TEXT) unless print_did_you_mean
53
- exit(UNKNOWN_ACTION_EXIT_CODE)
54
- rescue Runner::ActionConfigError => e
55
- Output.error("Error(s) running action '#{@action_name}': #{e}")
56
- exit(ACTION_CONFIG_ERROR_EXIT_CODE)
57
- rescue Builtin::ArgumentError => e
58
- Output.error("Error running builtin '#{@action_name}': #{e}")
59
- exit(BUILTIN_SYNTAX_ERROR_EXIT_CODE)
60
- rescue AppConfig::ParsingError => e
61
- Output.error("Error parsing app config: #{e}")
62
- exit(ERROR_LOADING_APP_CONFIG_EXIT_CODE)
63
- rescue Runner::NotAllowedInEnvError => e
64
- Output.error("Error running action #{@action_name}: #{e}")
65
- exit(ACTION_NOT_ALLOWED_IN_ENV_EXIT_CODE)
66
- end
67
- # rubocop:enable Metrics/MethodLength
68
-
69
- private
70
-
71
- def syntax_valid?
72
- return true unless @action_name.nil?
73
-
74
- Output.error("Usage: ops <action>")
75
- Output.out(RECOMMEND_HELP_TEXT)
76
- false
77
- end
78
-
79
- def print_did_you_mean
80
- Output.out("Did you mean '#{runner.suggestions.join(", ")}'?") if runner.suggestions.any?
81
-
82
- runner.suggestions.any?
83
- end
84
-
85
- def min_version_met?
86
- return true unless min_version
87
-
88
- if Version.min_version_met?(min_version)
89
- true
90
- else
91
- Output.error("ops.yml specifies minimum version of #{min_version}, but ops version is #{Version.version}")
92
- false
93
- end
94
- end
95
-
96
- def min_version
97
- config["min_version"]
98
- end
99
-
100
- def runner
101
- @runner ||= Runner.new(@action_name, @args, config, config_file_absolute_path)
102
- end
103
-
104
- def config
105
- @config ||= if config_file_exists?
106
- parsed_config_contents
107
- else
108
- Output.warn("File '#{@config_file}' does not exist.") unless @action_name == "init"
109
- {}
110
- end
111
- end
112
-
113
- def parsed_config_contents
114
- YAML.load_file(@config_file)
115
- rescue StandardError => e
116
- Output.warn("Error parsing '#{@config_file}': #{e}")
117
- {}
118
- end
119
-
120
- def config_file_exists?
121
- File.exist?(@config_file)
122
- end
123
-
124
- def config_file_absolute_path
125
- File.expand_path(@config_file)
126
- end
127
- end
128
-
129
- Ops.new(ARGV).run if $PROGRAM_NAME == __FILE__
data/lib/options.rb DELETED
@@ -1,22 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class Options
4
- class << self
5
- def get(path)
6
- env_var = ENV[env_var(path)]
7
- return YAML.safe_load(env_var) unless env_var.nil?
8
-
9
- @options&.dig(*path.split('.'))
10
- end
11
-
12
- def set(options)
13
- @options = options
14
- end
15
-
16
- private
17
-
18
- def env_var(path)
19
- "OPS__#{path.upcase.gsub(".", "__")}"
20
- end
21
- end
22
- end
data/lib/output.rb DELETED
@@ -1,71 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'colorize'
4
-
5
- class Output
6
- @out = STDOUT
7
- @err = STDERR
8
-
9
- STATUS_WIDTH = "50"
10
-
11
- OKAY = "OK"
12
- SKIPPED = "SKIPPED"
13
- FAILED = "FAILED"
14
-
15
- # used to silence output, e.g. in testing
16
- class DummyOutput
17
- def print(*_); end
18
-
19
- def puts(*_); end
20
- end
21
-
22
- class << self
23
- def status(name)
24
- @out.print(format("%-#{STATUS_WIDTH}<name>s ", name: name))
25
- end
26
-
27
- def okay
28
- @out.puts(OKAY.green)
29
- end
30
-
31
- def skipped
32
- @out.puts(SKIPPED.yellow)
33
- end
34
-
35
- def failed
36
- @out.puts(FAILED.red)
37
- end
38
-
39
- def warn(msg)
40
- @err.puts(msg.yellow)
41
- end
42
-
43
- alias notice warn
44
-
45
- def error(msg)
46
- @err.puts(msg.red)
47
- end
48
-
49
- def out(msg)
50
- @out.puts(msg)
51
- end
52
-
53
- def print(msg)
54
- @out.print(msg)
55
- end
56
-
57
- def debug(msg)
58
- return unless ENV["OPS_DEBUG_OUTPUT"]
59
-
60
- @err.puts(msg.blue)
61
- end
62
-
63
- def silence
64
- @out = @err = dummy_output
65
- end
66
-
67
- def dummy_output
68
- @dummy_output ||= DummyOutput.new
69
- end
70
- end
71
- end
data/lib/profiler.rb DELETED
@@ -1,47 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class Profiler
4
- INDENT = " "
5
-
6
- @measurements = {}
7
-
8
- class << self
9
- attr_reader :measurements
10
-
11
- def measure(tag)
12
- start = time_now
13
- result = yield
14
- add_measurement(tag, time_now - start)
15
-
16
- result
17
- end
18
-
19
- def add_measurement(tag, seconds)
20
- return unless profiling?
21
-
22
- @measurements[tag] ||= []
23
-
24
- @measurements[tag] << seconds
25
- end
26
-
27
- def summary
28
- return unless profiling?
29
-
30
- @summary ||= measurements.reverse_each.each_with_object([]) do |(tag, values), output|
31
- output << "#{tag}:\n"
32
- values.sort.reverse.each do |value|
33
- value_str = format("%.3f", value * 1000)
34
- output << format("%<indent>s%9<value>sms\n", indent: INDENT, value: value_str)
35
- end
36
- end.join
37
- end
38
-
39
- def profiling?
40
- !ENV["OPS_PROFILE"].nil?
41
- end
42
-
43
- def time_now
44
- Process.clock_gettime(Process::CLOCK_MONOTONIC)
45
- end
46
- end
47
- end
data/lib/runner.rb DELETED
@@ -1,110 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Builtins
4
- end
5
-
6
- class Runner
7
- class UnknownActionError < StandardError; end
8
-
9
- class ActionConfigError < StandardError; end
10
-
11
- class NotAllowedInEnvError < StandardError; end
12
-
13
- def initialize(action_name, args, config, config_path)
14
- @action_name = action_name
15
- @args = args
16
- @config = config
17
- @config_path = config_path
18
- end
19
-
20
- def run
21
- return forward.run if forward
22
-
23
- do_before_all
24
-
25
- return builtin.run if builtin
26
-
27
- raise UnknownActionError, "Unknown action: #{@action_name}" unless action
28
- raise ActionConfigError, action.config_errors.join("; ") unless action.config_valid?
29
-
30
- do_before_action
31
-
32
- run_action
33
- end
34
-
35
- def suggestions
36
- @suggestions ||= ActionSuggester.new(action_list.names + action_list.aliases + builtin_names).check(@action_name)
37
- end
38
-
39
- private
40
-
41
- def do_before_all
42
- AppConfig.load
43
- Secrets.load if action&.load_secrets?
44
- environment.set_variables
45
- end
46
-
47
- def do_before_action
48
- return if ENV["OPS_RUNNING"] || action.skip_hooks?("before")
49
-
50
- # this prevents before hooks from running in ops executed by ops
51
- ENV["OPS_RUNNING"] = "1"
52
- hook_handler.do_hooks("before")
53
- end
54
-
55
- def hook_handler
56
- @hook_handler ||= HookHandler.new(@config)
57
- end
58
-
59
- def builtin
60
- Profiler.measure("runner:require_builtin") do
61
- @builtin ||= Builtin.class_for(name: @action_name)&.new(@args, @config)
62
- end
63
- rescue NameError
64
- # this means there isn't a builtin with that name in that module
65
- nil
66
- end
67
-
68
- def builtin_names
69
- Builtins.constants.select { |c| Builtins.const_get(c).is_a? Class }.map(&:downcase)
70
- end
71
-
72
- def forward
73
- @forward ||= Forwards.new(@config, @args).get(@action_name)
74
- end
75
-
76
- def run_action
77
- unless action.allowed_in_env?(Environment.environment)
78
- raise NotAllowedInEnvError, "Action not allowed in #{Environment.environment} environment."
79
- end
80
-
81
- unless action.execute_in_env?(Environment.environment)
82
- Output.warn("Skipping action '#{action.name}' in environment #{Environment.environment}.")
83
- return true
84
- end
85
-
86
- Output.notice("Running '#{action}' in environment '#{ENV['environment']}'...")
87
- action.run
88
- end
89
-
90
- def action
91
- return action_list.get(@action_name) if action_list.get(@action_name)
92
- return action_list.get_by_alias(@action_name) if action_list.get_by_alias(@action_name)
93
- end
94
-
95
- def action_list
96
- @action_list ||= begin
97
- Output.warn("'ops.yml' has no 'actions' defined.") if @config.any? && @config["actions"].nil?
98
-
99
- ActionList.new(@config["actions"], @args)
100
- end
101
- end
102
-
103
- def env_vars
104
- @config.dig("options", "environment") || {}
105
- end
106
-
107
- def environment
108
- @environment ||= Environment.new(env_vars, @config_path)
109
- end
110
- end
data/lib/secrets.rb DELETED
@@ -1,55 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'json'
4
- require 'open3'
5
-
6
- class Secrets < AppConfig
7
- class << self
8
- def default_filename
9
- config_path_for(Environment.environment)
10
- end
11
-
12
- def config_path_for(env)
13
- File.exist?(ejson_path_for(env)) ? ejson_path_for(env) : json_path_for(env)
14
- end
15
-
16
- def app_config_path
17
- expand_path(Options.get("secrets.path") || default_filename)
18
- end
19
-
20
- private
21
-
22
- def ejson_path_for(env)
23
- "config/#{env}/secrets.ejson"
24
- end
25
-
26
- def json_path_for(env)
27
- "config/#{env}/secrets.json"
28
- end
29
- end
30
-
31
- def initialize(filename = "")
32
- @filename = filename.empty? ? Secrets.default_filename : actual_filename_for(filename)
33
- end
34
-
35
- private
36
-
37
- def actual_filename_for(filename)
38
- File.exist?(filename) ? filename : filename.sub(".ejson", ".json")
39
- end
40
-
41
- def file_contents
42
- @file_contents ||= @filename.match(/\.ejson$/) ? ejson_contents : super
43
- end
44
-
45
- def ejson_contents
46
- @ejson_contents ||= begin
47
- out, err, _status = Open3.capture3("ejson decrypt #{@filename}")
48
-
49
- # TODO: err is only nil in testing, but I can't figure out why the stubbing isn't working
50
- raise ParsingError, "#{@filename}: #{err}" unless err.nil? || err.empty?
51
-
52
- out
53
- end
54
- end
55
- end
data/lib/version.rb DELETED
@@ -1,38 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "rubygems"
4
-
5
- class Version
6
- GEMSPEC_FILE = "#{__dir__}/../ops_team.gemspec"
7
-
8
- class << self
9
- # these class methods exist because I don't want to have to call `Version.new.version` elsewhere
10
- def version
11
- new.send(:version)
12
- end
13
-
14
- def min_version_met?(min_version)
15
- new.send(:min_version_met?, min_version)
16
- end
17
- end
18
-
19
- private
20
-
21
- # these instance methods exist so that I don't have to clear class variables between tests
22
- def version
23
- unless gemspec
24
- Output.error("Unable to load gemspec at '#{GEMSPEC_FILE}")
25
- return nil
26
- end
27
-
28
- gemspec.version
29
- end
30
-
31
- def min_version_met?(min_version)
32
- Gem::Version.new(version) >= Gem::Version.new(min_version)
33
- end
34
-
35
- def gemspec
36
- @gemspec ||= Gem::Specification.load(GEMSPEC_FILE)
37
- end
38
- end