ops_team 1.21.1 → 2.0.0.rc3

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 (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