ops_team 0.13.3 → 0.15.0

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