ops_team 0.11.0 → 0.13.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 952f4e8174c60f623b0768fc6a35eb62524c1f574ea8d887c07023d423365d8e
4
- data.tar.gz: 9e8421e67c77535683fd7aae10d90c8a671ea3e58c37656a975f303092c8df06
3
+ metadata.gz: 2290c83f4f7765f62b2efe59ed8e662970039057b7d3d948b68e4f0f0eb46a7c
4
+ data.tar.gz: 9b9948ff23bf8799ca294c9a637638980e9b679bb862657085712a97bde35fc1
5
5
  SHA512:
6
- metadata.gz: 8e3c9bb627b2f8570cca8a3dd60f8e176138dcf01b9417889bcf50e2a24cc25759f5cf0492784bbc28e779fe6d201f570e0992fcb1a8261cab9d09f2f4d54e40
7
- data.tar.gz: 4c89388eb2bf7669cb7d4ddc588424911f56d04a5f6c72921e9fb2729caa0c95aa7cd2efac3b7040842d8976c367364eb881e0e4e672583b3c923d97b291d297
6
+ metadata.gz: 7fa27cf9f164af9e91a2d27820e811779a0781cb6b2f8849ef37e6ddd6042d4188422560343559ee072701351dc60f51cc48ce74c82bc1aa9201d233e37ef09d
7
+ data.tar.gz: 6af6633e1552b93fa07c3f6443bd292e0695fe9595dfda661e588fd9cc84df8176b2f5b1dbc7613d47bde7b5e1ed30a1b9551bbfd9963c7cc5b70bcb4b54c460
data/Gemfile CHANGED
@@ -6,8 +6,12 @@ git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }
6
6
 
7
7
  gem "bcrypt_pbkdf"
8
8
  gem "colorize"
9
+ gem "e2mmap"
9
10
  gem "ed25519"
10
11
  gem "ejson"
12
+ gem "etc"
13
+ gem "io-console"
14
+ gem "json"
11
15
  gem "net-ssh"
12
16
  gem "require_all"
13
17
 
@@ -17,6 +21,7 @@ group :test do
17
21
  end
18
22
 
19
23
  group :development do
24
+ gem "irb"
20
25
  gem "pry"
21
26
  gem "pry-byebug"
22
27
  gem "rerun"
@@ -32,6 +32,10 @@ class Action
32
32
  @config["description"]
33
33
  end
34
34
 
35
+ def skip_hooks?(name)
36
+ @config["skip_#{name}_hooks"]
37
+ end
38
+
35
39
  private
36
40
 
37
41
  def load_secrets?
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ActionList
4
+ class UnknownActionError < StandardError; end
5
+
6
+ def initialize(actions_list, args)
7
+ @actions_list = actions_list
8
+ @args = args
9
+
10
+ process_action_list
11
+ end
12
+
13
+ def get(name)
14
+ @actions[name]
15
+ end
16
+
17
+ def get_by_alias(name)
18
+ @aliases[name]
19
+ end
20
+
21
+ def names
22
+ @actions.keys
23
+ end
24
+
25
+ def aliases
26
+ @aliases.keys
27
+ end
28
+
29
+ private
30
+
31
+ def process_action_list
32
+ @actions = {}
33
+ @aliases = {}
34
+
35
+ @actions_list.each do |name, config|
36
+ action = Action.new(config, @args)
37
+
38
+ @actions[name] = action
39
+ @aliases[action.alias] = action
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ActionSuggester
4
+ def initialize(dictionary)
5
+ @dictionary = dictionary
6
+ end
7
+
8
+ def check(word)
9
+ spellchecker.correct(word)
10
+ end
11
+
12
+ private
13
+
14
+ def spellchecker
15
+ @spellchecker ||= DidYouMean::SpellChecker.new(dictionary: @dictionary)
16
+ end
17
+ end
@@ -1,13 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "rubygems"
4
-
5
3
  require "output"
4
+ require "version"
6
5
 
7
6
  module Builtins
8
7
  class Version < Builtin
9
- GEMSPEC_FILE = "#{__dir__}/../../ops_team.gemspec"
10
-
11
8
  class << self
12
9
  def description
13
10
  "prints the version of ops that is running"
@@ -15,18 +12,7 @@ module Builtins
15
12
  end
16
13
 
17
14
  def run
18
- unless gemspec
19
- Output.error("Unable to load gemspec at '#{GEMSPEC_FILE}")
20
- return false
21
- end
22
-
23
- Output.out(gemspec.version)
24
- end
25
-
26
- private
27
-
28
- def gemspec
29
- @gemspec ||= Gem::Specification.load(GEMSPEC_FILE)
15
+ Output.out(::Version.version)
30
16
  end
31
17
  end
32
18
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'version'
4
+
3
5
  class Environment
4
6
  def initialize(env_hash)
5
7
  @env_hash = env_hash
@@ -21,6 +23,7 @@ class Environment
21
23
 
22
24
  def set_ops_variables
23
25
  ENV["OPS_YML_DIR"] = Dir.pwd
26
+ ENV["OPS_VERSION"] = Version.version.to_s
24
27
  end
25
28
 
26
29
  def set_environment_aliases
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'output'
4
+
3
5
  class HookHandler
4
6
  class HookConfigError < StandardError; end
5
7
  class HookExecError < StandardError; end
@@ -22,6 +24,7 @@ class HookHandler
22
24
 
23
25
  def execute_hooks(name)
24
26
  hooks(name).each do |hook|
27
+ Output.notice("Running #{name} hook: #{hook}")
25
28
  output, exit_code = Executor.execute(hook)
26
29
 
27
30
  next if exit_code.zero?
data/lib/ops.rb CHANGED
@@ -3,12 +3,17 @@
3
3
 
4
4
  require 'yaml'
5
5
  require 'require_all'
6
+ require "rubygems"
6
7
 
7
8
  require 'hook_handler'
8
9
  require 'action'
9
10
  require 'output'
10
11
  require 'options'
11
12
  require 'environment'
13
+ require 'version'
14
+ require 'action_list'
15
+ require 'action_suggester'
16
+
12
17
  require_rel "builtins"
13
18
 
14
19
  # executes commands based on local `ops.yml`
@@ -20,6 +25,7 @@ class Ops
20
25
  INVALID_SYNTAX_EXIT_CODE = 64
21
26
  UNKNOWN_ACTION_EXIT_CODE = 65
22
27
  ERROR_LOADING_APP_CONFIG_EXIT_CODE = 66
28
+ MIN_VERSION_NOT_MET_EXIT_CODE = 67
23
29
 
24
30
  class << self
25
31
  def project_name
@@ -35,11 +41,14 @@ class Ops
35
41
  end
36
42
 
37
43
  def run
44
+ # "return" is here to allow specs to stub "exit" without executing everything after it
38
45
  return exit(INVALID_SYNTAX_EXIT_CODE) unless syntax_valid?
46
+ return exit(MIN_VERSION_NOT_MET_EXIT_CODE) unless min_version_met?
39
47
 
40
48
  run_action
41
49
  rescue UnknownActionError => e
42
- Output.error("Error: #{e}")
50
+ Output.error(e.to_s)
51
+ print_did_you_mean
43
52
  exit(UNKNOWN_ACTION_EXIT_CODE)
44
53
  end
45
54
 
@@ -54,11 +63,37 @@ class Ops
54
63
  end
55
64
  end
56
65
 
66
+ def print_did_you_mean
67
+ suggestions = did_you_mean.check(@action_name)
68
+
69
+ Output.out("Did you mean '#{suggestions.join(", ")}'?") if suggestions.any?
70
+ end
71
+
72
+ def did_you_mean
73
+ ActionSuggester.new(action_list.names + action_list.aliases + builtin_names)
74
+ end
75
+
76
+ def min_version_met?
77
+ return true unless min_version
78
+
79
+ if Version.min_version_met?(min_version)
80
+ true
81
+ else
82
+ Output.error("ops.yml specifies minimum version of #{min_version}, but ops version is #{Version.version}")
83
+ false
84
+ end
85
+ end
86
+
87
+ def min_version
88
+ config["min_version"]
89
+ end
90
+
57
91
  def run_action
58
- do_before_run_action
92
+ do_before_all
59
93
 
60
94
  return builtin.run if builtin
61
95
 
96
+ do_before_action
62
97
  Output.notice("Running '#{action}' from #{CONFIG_FILE} in environment '#{ENV['environment']}'...")
63
98
  action.run
64
99
  rescue AppConfig::ParsingError => e
@@ -66,10 +101,16 @@ class Ops
66
101
  exit(ERROR_LOADING_APP_CONFIG_EXIT_CODE)
67
102
  end
68
103
 
69
- def do_before_run_action
104
+ def do_before_all
70
105
  environment.set_variables
71
106
  AppConfig.load
72
- hook_handler.do_hooks("before")
107
+ end
108
+
109
+ def do_before_action
110
+ hook_handler.do_hooks("before") unless ENV["OPS_RUNNING"] || action.skip_hooks?("before")
111
+
112
+ # this prevents before hooks from running in ops executed by ops
113
+ ENV["OPS_RUNNING"] = "1"
73
114
  end
74
115
 
75
116
  def hook_handler
@@ -83,24 +124,22 @@ class Ops
83
124
  nil
84
125
  end
85
126
 
127
+ def builtin_names
128
+ Builtins.constants.select { |c| Builtins.const_get(c).is_a? Class }.map(&:downcase)
129
+ end
130
+
86
131
  def action
87
- return actions[@action_name] if actions[@action_name]
88
- return aliases[@action_name] if aliases[@action_name]
132
+ return action_list.get(@action_name) if action_list.get(@action_name)
133
+ return action_list.get_by_alias(@action_name) if action_list.get_by_alias(@action_name)
89
134
 
90
135
  raise UnknownActionError, "Unknown action: #{@action_name}"
91
136
  end
92
137
 
93
- def actions
94
- @actions ||= begin
95
- if config["actions"]
96
- config["actions"]&.transform_values do |config|
97
- Action.new(config, @args)
98
- end
99
- else
100
- # only print this error if ops.yml had something in it
101
- Output.warn("'ops.yml' has no 'actions' defined.") if config.any?
102
- {}
103
- end
138
+ def action_list
139
+ @action_list ||= begin
140
+ Output.warn("'ops.yml' has no 'actions' defined.") if config.any? && config["actions"].nil?
141
+
142
+ ActionList.new(config["actions"], @args)
104
143
  end
105
144
  end
106
145
 
@@ -114,14 +153,6 @@ class Ops
114
153
  end
115
154
  end
116
155
 
117
- def aliases
118
- @aliases ||= begin
119
- actions.each_with_object({}) do |(_name, action), alias_hash|
120
- alias_hash[action.alias] = action if action.alias
121
- end
122
- end
123
- end
124
-
125
156
  def env_vars
126
157
  @config.dig("options", "environment") || {}
127
158
  end
@@ -129,10 +160,6 @@ class Ops
129
160
  def environment
130
161
  @environment ||= Environment.new(env_vars)
131
162
  end
132
-
133
- def app_config
134
- @app_config ||= AppConfig.new
135
- end
136
163
  end
137
164
 
138
165
  Ops.new(ARGV).run if $PROGRAM_NAME == __FILE__
@@ -0,0 +1,38 @@
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
@@ -2,7 +2,7 @@
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = 'ops_team'
5
- s.version = '0.11.0'
5
+ s.version = '0.13.1'
6
6
  s.authors = [
7
7
  'nickthecook@gmail.com'
8
8
  ]
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ops_team
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.11.0
4
+ version: 0.13.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - nickthecook@gmail.com
@@ -146,6 +146,8 @@ files:
146
146
  - etc/ruby.template.yml
147
147
  - etc/terraform.template.yml
148
148
  - lib/action.rb
149
+ - lib/action_list.rb
150
+ - lib/action_suggester.rb
149
151
  - lib/app_config.rb
150
152
  - lib/builtin.rb
151
153
  - lib/builtins/background.rb
@@ -176,6 +178,7 @@ files:
176
178
  - lib/options.rb
177
179
  - lib/output.rb
178
180
  - lib/secrets.rb
181
+ - lib/version.rb
179
182
  - loader.rb
180
183
  - ops_team.gemspec
181
184
  homepage: https://github.com/nickthecook/ops