ops_team 0.11.0.pre2 → 0.13.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0a5f98b47649c277f4c0b29228d9d9ced79c985301bde206ec9a9a1cdad1990c
4
- data.tar.gz: 6a5f36ef9a54b6a31dc745922888996f2420959a1a414f95318e6374fa162cc0
3
+ metadata.gz: 9656333af7b4f103645f9f27af758b4cc11e749c0e9770474ec249af8db85983
4
+ data.tar.gz: d1a69bbc44b1626bbcd754fa8484547f2bfa3beede615d8ebcd951e00432008a
5
5
  SHA512:
6
- metadata.gz: a0869b884b7c8bda06c1ae664c3a056610776326ca7a0f87adfa64c9f45f4d4deb53d5e64000ffcdd0528865153da7b0a4155a92d20c4939cd38a8224ac3aa7f
7
- data.tar.gz: 444234f4870cc90c6d56233763a69973de5f6f78c92eada37324bb5da354d09ae4493a49b04f15bf09f5d471f4669595f558e9de52e7f75ee287f9b1a05964fd
6
+ metadata.gz: 73e177e194c2f3e7415612bd23aea779502182929fbb5264c00a1516a52869056bb045e75ff5aaca4c546b92f03481f5346eb24958f4a6ccdd247d0d3cdc06dc
7
+ data.tar.gz: 53c224dd9162d269c0154c842ec36d4e748dd59b401efb1a959199332e3ee99489a1de4cf88d3e2d2bff7c04a37906c687d542ac2bc4852d7fa38a0f4e0ad9c6
data/bin/ops CHANGED
@@ -5,4 +5,6 @@ require_relative "../loader"
5
5
 
6
6
  require 'ops'
7
7
 
8
+ require 'pry-byebug' if ENV['environment'] == 'dev' || ENV['environment'].nil?
9
+
8
10
  Ops.new(ARGV).run
@@ -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,15 @@
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
+ def spellchecker
13
+ @spellchecker ||= DidYouMean::SpellChecker.new(dictionary: @dictionary)
14
+ end
15
+ 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.pre2'
5
+ s.version = '0.13.0'
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.pre2
4
+ version: 0.13.0
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
@@ -193,9 +196,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
193
196
  version: '2.5'
194
197
  required_rubygems_version: !ruby/object:Gem::Requirement
195
198
  requirements:
196
- - - ">"
199
+ - - ">="
197
200
  - !ruby/object:Gem::Version
198
- version: 1.3.1
201
+ version: '0'
199
202
  requirements: []
200
203
  rubygems_version: 3.0.3
201
204
  signing_key: