ops_team 0.13.3 → 0.15.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: ea3bc79f136cc22e2710bba983cb23d14ed332d7f9a4e14e1bf94b53f17e5a1b
4
- data.tar.gz: 404cc0d081d9d1bbe4082fd4e24532322f5a1324d8063a6e6eaaea615a7a0321
3
+ metadata.gz: d3beee0325741d140b711ee3f939306452b176b9be9f7d9094c1e578ed780373
4
+ data.tar.gz: 33aef85445dd2494967e246c1b4ebfe7433d76b9b09512756cb2a086321438cd
5
5
  SHA512:
6
- metadata.gz: 1df25521312cd853043220089fed8b33b0bd3bbaa192ac3720e17528a14615ecd74ac3a17d69d3c87209f571149ef8d6d9adc4ce877cad9ef8fd0bf0b36fe0ef
7
- data.tar.gz: 261a40eac02451b42d6c331bb2281647f687c04e2aafba7523d42a70bcc37e486904cf58993869452641f50d1ce07c2ea417054013c726b28cc75c6b6f6ee7f6
6
+ metadata.gz: b0d69fca7b243d34d4c14cf53d8b10942b212cd53fd897c136079d9883e939670f3b74c3383df547d6c2f9a7c1d25f0ce4ba11e3337e566cdab010a6ebe81ef9
7
+ data.tar.gz: 96e25352e3b8b337a53632e560e1f494c38d2fc09b0b314c4a4e6e07aec92324b52051ac0c5235a4ab8d394247683890230dd5b29fe86b8ec26102af0482c559
@@ -36,6 +36,20 @@ class Action
36
36
  @config["skip_#{name}_hooks"]
37
37
  end
38
38
 
39
+ def config_valid?
40
+ config_errors.empty?
41
+ end
42
+
43
+ def config_errors
44
+ @config_errors ||= begin
45
+ errors = []
46
+
47
+ errors << "No 'command' specified in 'action'." unless @config['command']
48
+
49
+ errors
50
+ end
51
+ end
52
+
39
53
  private
40
54
 
41
55
  def load_secrets?
@@ -8,6 +8,14 @@ class AppConfig
8
8
  new(app_config_path).load
9
9
  end
10
10
 
11
+ def default_filename
12
+ config_path_for(Environment.environment)
13
+ end
14
+
15
+ def config_path_for(env)
16
+ "config/#{env}/config.json"
17
+ end
18
+
11
19
  private
12
20
 
13
21
  def app_config_path
@@ -20,7 +28,7 @@ class AppConfig
20
28
  end
21
29
 
22
30
  def initialize(filename = "")
23
- @filename = filename.empty? ? default_filename : filename
31
+ @filename = filename.empty? ? AppConfig.default_filename : filename
24
32
  end
25
33
 
26
34
  def load
@@ -31,10 +39,6 @@ class AppConfig
31
39
 
32
40
  private
33
41
 
34
- def default_filename
35
- "config/#{environment}/config.json"
36
- end
37
-
38
42
  def config
39
43
  @config ||= file_contents ? YAML.safe_load(file_contents) : {}
40
44
  rescue YAML::SyntaxError => e
@@ -48,8 +52,4 @@ class AppConfig
48
52
  nil
49
53
  end
50
54
  end
51
-
52
- def environment
53
- ENV['environment']
54
- end
55
55
  end
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Builtin
4
+ class ArgumentError < StandardError; end
5
+
4
6
  attr_reader :args, :config
5
7
 
6
8
  class << self
@@ -0,0 +1,117 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'builtin'
4
+
5
+ module Builtins
6
+ class EnvDiff < Builtin
7
+ class << self
8
+ def description
9
+ "compares keys present in config and secrets between different environments"
10
+ end
11
+ end
12
+
13
+ def run
14
+ check_args
15
+
16
+ if source_only_keys.empty? && dest_only_keys.empty?
17
+ Output.out("Environments '#{source_env}' and '#{dest_env}' define the same #{source_keys.length} key(s).")
18
+ return
19
+ end
20
+
21
+ output_key_summary(source_only_keys, source_env, dest_env) if source_only_keys.any?
22
+ output_key_summary(dest_only_keys, dest_env, source_env) if dest_only_keys.any?
23
+ end
24
+
25
+ private
26
+
27
+ def output_key_summary(keys, in_env, not_in_env)
28
+ Output.warn("Environment '#{in_env}' defines keys that '#{not_in_env}' does not:\n")
29
+ keys.each do |key|
30
+ Output.warn(" - #{key}")
31
+ end
32
+ Output.out("")
33
+ end
34
+
35
+ def source_only_keys
36
+ @source_only_keys ||= source_keys - dest_keys
37
+ end
38
+
39
+ def dest_only_keys
40
+ @dest_only_keys ||= dest_keys - source_keys
41
+ end
42
+
43
+ def source_keys
44
+ @source_keys ||= keys_for(source_env)
45
+ end
46
+
47
+ def dest_keys
48
+ @dest_keys ||= keys_for(dest_env)
49
+ end
50
+
51
+ def keys_for(env)
52
+ config_keys_for(env) | secrets_keys_for(env)
53
+ end
54
+
55
+ def config_keys_for(env)
56
+ (config_for(env)["environment"]&.keys || []).map do |key|
57
+ "[CONFIG] #{key}"
58
+ end
59
+ end
60
+
61
+ def secrets_keys_for(env)
62
+ (secrets_for(env)["environment"]&.keys || []).map do |key|
63
+ "[SECRET] #{key}"
64
+ end
65
+ end
66
+
67
+ def config_for(env)
68
+ YAML.load_file(config_path_for(env))
69
+ end
70
+
71
+ def secrets_for(env)
72
+ YAML.load_file(secrets_path_for(env))
73
+ end
74
+
75
+ def check_args
76
+ raise Builtin::ArgumentError, "Usage: ops envdiff <env_one> <env_two>" unless args.length == 2
77
+
78
+ check_environment(source_env)
79
+ check_environment(dest_env)
80
+ end
81
+
82
+ def source_env
83
+ args[0]
84
+ end
85
+
86
+ def dest_env
87
+ args[1]
88
+ end
89
+
90
+ def check_environment(name)
91
+ raise_missing_file_error(config_path_for(name)) unless config_file_exists?(name)
92
+ raise_missing_file_error(secrets_path_for(name)) unless secrets_file_exists?(name)
93
+ end
94
+
95
+ def raise_missing_file_error(path)
96
+ raise Builtin::ArgumentError, "File '#{path}' does not exist."
97
+ end
98
+
99
+ def config_file_exists?(env)
100
+ File.exist?(config_path_for(env))
101
+ end
102
+
103
+ def secrets_file_exists?(env)
104
+ File.exist?(secrets_path_for(env))
105
+ end
106
+
107
+ def config_path_for(env)
108
+ AppConfig.config_path_for(env)
109
+ end
110
+
111
+ def secrets_path_for(env)
112
+ Secrets.config_path_for(env)
113
+ end
114
+ end
115
+
116
+ Envdiff = EnvDiff
117
+ end
@@ -20,7 +20,7 @@ module Dependencies
20
20
  end
21
21
 
22
22
  def should_meet?
23
- `uname`.chomp == "Linux" && system("which apk")
23
+ `uname`.chomp == "Linux" && system("which apk", out: File::NULL, err: File::NULL)
24
24
  end
25
25
  end
26
26
  end
@@ -1,20 +1,24 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'dependency'
3
+ require 'dependencies/versioned_dependency'
4
4
  require 'dependencies/helpers/apt_cache_policy'
5
5
 
6
6
  module Dependencies
7
- class Apt < Dependency
8
- PACKAGE_NAME_SEPARATOR = "/"
9
-
7
+ class Apt < VersionedDependency
10
8
  def met?
11
- return apt_cache_policy.installed_version == package_version if package_version
12
-
13
- apt_cache_policy.installed?
9
+ if versioned?
10
+ apt_cache_policy.installed_version == dep_version
11
+ else
12
+ apt_cache_policy.installed?
13
+ end
14
14
  end
15
15
 
16
16
  def meet
17
- execute("#{sudo_string}apt-get install -y #{name}")
17
+ if versioned?
18
+ execute("#{sudo_string}apt-get install -y #{dep_name}=#{dep_version}")
19
+ else
20
+ execute("#{sudo_string}apt-get install -y #{name}")
21
+ end
18
22
  end
19
23
 
20
24
  def unmeet
@@ -23,25 +27,13 @@ module Dependencies
23
27
  end
24
28
 
25
29
  def should_meet?
26
- `uname`.chomp == "Linux" && system("which apt-get")
30
+ `uname`.chomp == "Linux" && system("which apt-get", out: File::NULL, err: File::NULL)
27
31
  end
28
32
 
29
33
  private
30
34
 
31
- def package_name
32
- name_components[0]
33
- end
34
-
35
- def package_version
36
- name_components[1]
37
- end
38
-
39
- def name_components
40
- name.split(PACKAGE_NAME_SEPARATOR, 2)
41
- end
42
-
43
35
  def apt_cache_policy
44
- @apt_cache_policy ||= Dependencies::Helpers::AptCachePolicy.new(package_name)
36
+ @apt_cache_policy ||= Dependencies::Helpers::AptCachePolicy.new(dep_name)
45
37
  end
46
38
 
47
39
  def sudo_string
@@ -4,18 +4,20 @@ require 'dependency'
4
4
  require 'options'
5
5
 
6
6
  module Dependencies
7
- class Gem < Dependency
7
+ class Gem < VersionedDependency
8
8
  def met?
9
- execute("gem list -i '^#{name}$'")
9
+ if versioned?
10
+ execute("gem list -i '^#{dep_name}$' -v '#{dep_version}'") if versioned?
11
+ else
12
+ execute("gem list -i '^#{name}$'")
13
+ end
10
14
  end
11
15
 
12
16
  def meet
13
- if Options.get("gem.use_sudo")
14
- execute("sudo gem install #{name}")
15
- elsif Options.get("gem.user_install")
16
- execute("gem install --user-install #{name}")
17
+ if versioned?
18
+ execute("#{sudo_string}gem install #{user_install_string}'#{dep_name}' -v '#{dep_version}'")
17
19
  else
18
- execute("gem install #{name}")
20
+ execute("#{sudo_string}gem install #{user_install_string}'#{name}'")
19
21
  end
20
22
  end
21
23
 
@@ -23,5 +25,15 @@ module Dependencies
23
25
  # do nothing; we don't want to uninstall packages and reinstall them every time
24
26
  true
25
27
  end
28
+
29
+ private
30
+
31
+ def sudo_string
32
+ Options.get("gem.use_sudo") ? "sudo " : ""
33
+ end
34
+
35
+ def user_install_string
36
+ Options.get("gem.user_install") ? "--user-install " : ""
37
+ end
26
38
  end
27
39
  end
@@ -0,0 +1,25 @@
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
@@ -3,6 +3,14 @@
3
3
  require 'version'
4
4
 
5
5
  class Environment
6
+ class << self
7
+ def environment
8
+ return 'dev' if ENV['environment'].nil? || ENV['environment'].empty?
9
+
10
+ ENV['environment']
11
+ end
12
+ end
13
+
6
14
  def initialize(env_hash)
7
15
  @env_hash = env_hash
8
16
  end
@@ -13,12 +21,6 @@ class Environment
13
21
  set_configured_variables
14
22
  end
15
23
 
16
- def environment
17
- return 'dev' if ENV['environment'].nil? || ENV['environment'].empty?
18
-
19
- ENV['environment']
20
- end
21
-
22
24
  private
23
25
 
24
26
  def set_ops_variables
@@ -28,7 +30,7 @@ class Environment
28
30
 
29
31
  def set_environment_aliases
30
32
  environment_aliases.each do |alias_name|
31
- ENV[alias_name] = environment
33
+ ENV[alias_name] = Environment.environment
32
34
  end
33
35
  end
34
36
 
data/lib/ops.rb CHANGED
@@ -19,6 +19,7 @@ require_rel "builtins"
19
19
  # executes commands based on local `ops.yml`
20
20
  class Ops
21
21
  class UnknownActionError < StandardError; end
22
+ class ActionConfigError < StandardError; end
22
23
 
23
24
  CONFIG_FILE = "ops.yml"
24
25
 
@@ -26,6 +27,8 @@ class Ops
26
27
  UNKNOWN_ACTION_EXIT_CODE = 65
27
28
  ERROR_LOADING_APP_CONFIG_EXIT_CODE = 66
28
29
  MIN_VERSION_NOT_MET_EXIT_CODE = 67
30
+ ACTION_CONFIG_ERROR_EXIT_CODE = 68
31
+ BUILTIN_SYNTAX_ERROR_EXIT_CODE = 69
29
32
 
30
33
  RECOMMEND_HELP_TEXT = "Run 'ops help' for a list of builtins and actions."
31
34
 
@@ -52,6 +55,9 @@ class Ops
52
55
  Output.error(e.to_s)
53
56
  Output.out(RECOMMEND_HELP_TEXT) unless print_did_you_mean
54
57
  exit(UNKNOWN_ACTION_EXIT_CODE)
58
+ rescue ActionConfigError => e
59
+ Output.error("Error(s) running action '#{@action_name}': #{e}")
60
+ exit(ACTION_CONFIG_ERROR_EXIT_CODE)
55
61
  end
56
62
 
57
63
  private
@@ -98,9 +104,14 @@ class Ops
98
104
 
99
105
  return builtin.run if builtin
100
106
 
107
+ raise ActionConfigError, action.config_errors.join("; ") unless action.config_valid?
108
+
101
109
  do_before_action
102
110
  Output.notice("Running '#{action}' from #{CONFIG_FILE} in environment '#{ENV['environment']}'...")
103
111
  action.run
112
+ rescue Builtin::ArgumentError => e
113
+ Output.error("Error running builtin '#{@action_name}': #{e}")
114
+ exit(BUILTIN_SYNTAX_ERROR_EXIT_CODE)
104
115
  rescue AppConfig::ParsingError => e
105
116
  Output.error("Error parsing app config: #{e}")
106
117
  exit(ERROR_LOADING_APP_CONFIG_EXIT_CODE)
@@ -40,7 +40,7 @@ class Output
40
40
  @err.puts(msg.yellow)
41
41
  end
42
42
 
43
- alias_method :notice, :warn
43
+ alias notice warn
44
44
 
45
45
  def error(msg)
46
46
  @err.puts(msg.red)
@@ -9,31 +9,35 @@ require 'options'
9
9
 
10
10
  class Secrets < AppConfig
11
11
  class << self
12
+ def default_filename
13
+ config_path_for(Environment.environment)
14
+ end
15
+
16
+ def config_path_for(env)
17
+ File.exist?(ejson_path_for(env)) ? ejson_path_for(env) : json_path_for(env)
18
+ end
19
+
12
20
  private
13
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
+
14
30
  def app_config_path
15
31
  expand_path(Options.get("secrets.path"))
16
32
  end
17
33
  end
18
34
 
19
35
  def initialize(filename = "")
20
- @filename = filename.empty? ? default_filename : actual_filename_for(filename)
36
+ @filename = filename.empty? ? Secrets.default_filename : actual_filename_for(filename)
21
37
  end
22
38
 
23
39
  private
24
40
 
25
- def default_filename
26
- File.exist?(default_ejson_filename) ? default_ejson_filename : default_json_filename
27
- end
28
-
29
- def default_ejson_filename
30
- "config/#{environment}/secrets.ejson"
31
- end
32
-
33
- def default_json_filename
34
- "config/#{environment}/secrets.json"
35
- end
36
-
37
41
  def actual_filename_for(filename)
38
42
  File.exist?(filename) ? filename : filename.sub(".ejson", ".json")
39
43
  end
@@ -2,7 +2,7 @@
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = 'ops_team'
5
- s.version = '0.13.3'
5
+ s.version = '0.15.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.13.3
4
+ version: 0.15.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - nickthecook@gmail.com
@@ -154,6 +154,7 @@ files:
154
154
  - lib/builtins/background_log.rb
155
155
  - lib/builtins/down.rb
156
156
  - lib/builtins/env.rb
157
+ - lib/builtins/envdiff.rb
157
158
  - lib/builtins/exec.rb
158
159
  - lib/builtins/help.rb
159
160
  - lib/builtins/helpers/dependency_handler.rb
@@ -170,6 +171,7 @@ files:
170
171
  - lib/dependencies/gem.rb
171
172
  - lib/dependencies/helpers/apt_cache_policy.rb
172
173
  - lib/dependencies/sshkey.rb
174
+ - lib/dependencies/versioned_dependency.rb
173
175
  - lib/dependency.rb
174
176
  - lib/environment.rb
175
177
  - lib/executor.rb