cpl 0.1.0 → 0.3.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.
@@ -4,7 +4,8 @@ module Command
4
4
  class Delete < Base
5
5
  NAME = "delete"
6
6
  OPTIONS = [
7
- app_option(required: true)
7
+ app_option(required: true),
8
+ skip_confirm_option
8
9
  ].freeze
9
10
  DESCRIPTION = "Deletes the whole app (GVC with all workloads and all images)"
10
11
  LONG_DESCRIPTION = <<~HEREDOC
@@ -13,10 +14,7 @@ module Command
13
14
  HEREDOC
14
15
 
15
16
  def call
16
- progress.puts "Type 'delete' to delete #{config.app} and images"
17
- progress.print "> "
18
-
19
- return progress.puts "Not confirmed" unless $stdin.gets.chomp == "delete"
17
+ return unless confirm_delete
20
18
 
21
19
  delete_gvc
22
20
  delete_images
@@ -24,6 +22,16 @@ module Command
24
22
 
25
23
  private
26
24
 
25
+ def confirm_delete
26
+ return true if config.options[:yes]
27
+
28
+ confirmed = Shell.confirm("Are you sure you want to delete '#{config.app}'?")
29
+ return false unless confirmed
30
+
31
+ progress.puts
32
+ true
33
+ end
34
+
27
35
  def delete_gvc
28
36
  progress.puts "- Deleting gvc:"
29
37
 
@@ -1,21 +1,21 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Command
4
- class PromoteImage < Base
5
- NAME = "promote-image"
4
+ class DeployImage < Base
5
+ NAME = "deploy-image"
6
6
  OPTIONS = [
7
7
  app_option(required: true)
8
8
  ].freeze
9
- DESCRIPTION = "Promotes the latest image to app workloads"
9
+ DESCRIPTION = "Deploys the latest image to app workloads"
10
10
  LONG_DESCRIPTION = <<~HEREDOC
11
- - Promotes the latest image to app workloads
11
+ - Deploys the latest image to app workloads
12
12
  HEREDOC
13
13
 
14
14
  def call
15
15
  image = latest_image
16
16
 
17
17
  config[:app_workloads].each do |workload|
18
- cp.workload_get(workload).dig("spec", "containers").each do |container|
18
+ cp.workload_get_and_ensure(workload).dig("spec", "containers").each do |container|
19
19
  next unless container["image"].match?(%r{^/org/#{config[:cpln_org]}/image/#{config.app}:})
20
20
 
21
21
  cp.workload_set_image_ref(workload, container: container["name"], image: image)
data/lib/command/env.rb CHANGED
@@ -12,7 +12,7 @@ module Command
12
12
  HEREDOC
13
13
 
14
14
  def call
15
- cp.gvc_get.dig("spec", "env").map do |prop|
15
+ cp.gvc_get_and_ensure.dig("spec", "env").map do |prop|
16
16
  # NOTE: atm no special chars handling, consider adding if needed
17
17
  puts "#{prop['name']}=#{prop['value']}"
18
18
  end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Command
4
+ class NoCommand < Base
5
+ NAME = "no-command"
6
+ OPTIONS = [version_option].freeze
7
+ DESCRIPTION = "Called when no command was specified"
8
+ LONG_DESCRIPTION = <<~HEREDOC
9
+ - Called when no command was specified
10
+ HEREDOC
11
+ HIDE = true
12
+
13
+ def call
14
+ return unless config.options[:version]
15
+
16
+ Version.new(config).call
17
+ end
18
+ end
19
+ end
data/lib/command/open.rb CHANGED
@@ -23,7 +23,7 @@ module Command
23
23
 
24
24
  def call
25
25
  workload = config.options[:workload] || config[:one_off_workload]
26
- data = cp.workload_get(workload)
26
+ data = cp.workload_get_and_ensure(workload)
27
27
  url = data["status"]["endpoint"]
28
28
  opener = `which xdg-open open`.split("\n").grep_v("not found").first
29
29
 
data/lib/command/run.rb CHANGED
@@ -59,7 +59,7 @@ module Command
59
59
  progress.puts "- Cloning workload '#{workload}' on '#{config.options[:app]}' to '#{one_off}'"
60
60
 
61
61
  # Create a base copy of workload props
62
- spec = cp.workload_get(workload).fetch("spec")
62
+ spec = cp.workload_get_and_ensure(workload).fetch("spec")
63
63
  container = spec["containers"].detect { _1["name"] == workload } || spec["containers"].first
64
64
 
65
65
  # remove other containers if any
@@ -57,7 +57,7 @@ module Command
57
57
  progress.puts "- Cloning workload '#{workload}' on '#{config.options[:app]}' to '#{one_off}'"
58
58
 
59
59
  # Get base specs of workload
60
- spec = cp.workload_get(workload).fetch("spec")
60
+ spec = cp.workload_get_and_ensure(workload).fetch("spec")
61
61
  container = spec["containers"].detect { _1["name"] == workload } || spec["containers"].first
62
62
 
63
63
  # remove other containers if any
data/lib/command/setup.rb CHANGED
@@ -37,8 +37,7 @@ module Command
37
37
  def call
38
38
  config.args.each do |template|
39
39
  filename = "#{config.app_cpln_dir}/templates/#{template}.yml"
40
- abort("ERROR: Can't find template for '#{template}' at #{filename}") unless File.exist?(filename)
41
-
40
+ ensure_template(template, filename)
42
41
  apply_template(filename)
43
42
  progress.puts(template)
44
43
  end
@@ -46,6 +45,10 @@ module Command
46
45
 
47
46
  private
48
47
 
48
+ def ensure_template(template, filename)
49
+ Shell.abort("Can't find template '#{template}' at '#{filename}', please create it.") unless File.exist?(filename)
50
+ end
51
+
49
52
  def apply_template(filename)
50
53
  data = File.read(filename)
51
54
  .gsub("APP_GVC", config.app)
data/lib/command/test.rb CHANGED
@@ -3,7 +3,6 @@
3
3
  # Be sure to have run: gem install debug
4
4
  require "debug"
5
5
 
6
- # rubocop:disable Lint/Debugger
7
6
  module Command
8
7
  class Test < Base
9
8
  NAME = "test"
@@ -23,4 +22,3 @@ module Command
23
22
  end
24
23
  end
25
24
  end
26
- # rubocop:enable Lint/Debugger
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Command
4
+ class Version < Base
5
+ NAME = "version"
6
+ DESCRIPTION = "Displays the current version of the CLI"
7
+ LONG_DESCRIPTION = <<~HEREDOC
8
+ - Displays the current version of the CLI
9
+ - Can also be done with `cpl --version` or `cpl -v`
10
+ HEREDOC
11
+
12
+ def call
13
+ puts Cpl::VERSION
14
+ end
15
+ end
16
+ end
data/lib/core/config.rb CHANGED
@@ -15,26 +15,19 @@ class Config
15
15
 
16
16
  load_app_config
17
17
  pick_current_config if app
18
+ warn_deprecated_options if current
18
19
  end
19
20
 
20
- def [](key) # rubocop:disable Metrics/MethodLength, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
21
- abort("ERROR: should specify app") unless app
22
-
23
- logger = $stderr
21
+ def [](key)
22
+ ensure_current_config
24
23
 
24
+ old_key = old_option_keys[key]
25
25
  if current.key?(key)
26
26
  current.fetch(key)
27
- elsif key == :cpln_org && current.key?(:org)
28
- logger.puts("DEPRECATED: option 'org' is deprecated, use 'cpln_org' instead\n")
29
- current.fetch(:org)
30
- elsif key == :default_location && current.key?(:location)
31
- logger.puts("DEPRECATED: option 'location' is deprecated, use 'default_location' instead\n")
32
- current.fetch(:location)
33
- elsif key == :match_if_app_name_starts_with && current.key?(:prefix)
34
- logger.puts("DEPRECATED: option 'prefix' is deprecated, use 'match_if_app_name_starts_with' instead\n")
35
- current.fetch(:prefix)
27
+ elsif old_key && current.key?(old_key)
28
+ current.fetch(old_key)
36
29
  else
37
- abort("ERROR: should specify #{key} in controlplane.yml")
30
+ Shell.abort("Can't find option '#{key}' for app '#{app}' in 'controlplane.yml'.")
38
31
  end
39
32
  end
40
33
 
@@ -48,13 +41,37 @@ class Config
48
41
 
49
42
  private
50
43
 
44
+ def ensure_current_config
45
+ Shell.abort("Can't find current config, please specify an app.") unless current
46
+ end
47
+
48
+ def ensure_current_config_app(app)
49
+ Shell.abort("Can't find app '#{app}' in 'controlplane.yml'.") unless current
50
+ end
51
+
52
+ def ensure_config
53
+ Shell.abort("'controlplane.yml' is empty.") unless config
54
+ end
55
+
56
+ def ensure_config_apps
57
+ Shell.abort("Can't find key 'apps' in 'controlplane.yml'.") unless config[:apps]
58
+ end
59
+
60
+ def ensure_config_app(app, options)
61
+ Shell.abort("App '#{app}' is empty in 'controlplane.yml'.") unless options
62
+ end
63
+
51
64
  def pick_current_config
65
+ ensure_config
66
+ ensure_config_apps
52
67
  config[:apps].each do |c_app, c_data|
68
+ ensure_config_app(c_app, c_data)
53
69
  if c_app.to_s == app || (c_data[:match_if_app_name_starts_with] && app.start_with?(c_app.to_s))
54
70
  @current = c_data
55
71
  break
56
72
  end
57
73
  end
74
+ ensure_current_config_app(app)
58
75
  end
59
76
 
60
77
  def load_app_config
@@ -73,8 +90,24 @@ class Config
73
90
  path = path.parent
74
91
 
75
92
  if path.root?
76
- puts "ERROR: Can't find project config file, should be 'project_folder/#{CONFIG_FILE_LOCATIION}'"
77
- exit(-1)
93
+ Shell.abort("Can't find project config file at 'project_folder/#{CONFIG_FILE_LOCATIION}', please create it.")
94
+ end
95
+ end
96
+ end
97
+
98
+ def old_option_keys
99
+ {
100
+ cpln_org: :org,
101
+ default_location: :location,
102
+ match_if_app_name_starts_with: :prefix
103
+ }
104
+ end
105
+
106
+ def warn_deprecated_options
107
+ old_option_keys.each do |new_key, old_key|
108
+ if current.key?(old_key)
109
+ Shell.warn_deprecated("Option '#{old_key}' is deprecated, " \
110
+ "please use '#{new_key}' instead (in 'controlplane.yml').")
78
111
  end
79
112
  end
80
113
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- class Controlplane
3
+ class Controlplane # rubocop:disable Metrics/ClassLength
4
4
  attr_reader :config, :api, :gvc, :org
5
5
 
6
6
  def initialize(config)
@@ -18,8 +18,8 @@ class Controlplane
18
18
  perform(cmd)
19
19
  end
20
20
 
21
- def image_query
22
- cmd = "cpln image query --org #{org} -o yaml --max -1 --prop repository=#{config.app}"
21
+ def image_query(app_name = config.app)
22
+ cmd = "cpln image query --org #{org} -o yaml --max -1 --prop repository=#{app_name}"
23
23
  perform_yaml(cmd)
24
24
  end
25
25
 
@@ -29,10 +29,26 @@ class Controlplane
29
29
 
30
30
  # gvc
31
31
 
32
+ def gvc_query(app_name = config.app)
33
+ # When `match_if_app_name_starts_with` is `true`, we query for any gvc containing the name,
34
+ # otherwise we query for a gvc with the exact name.
35
+ op = config.current[:match_if_app_name_starts_with] ? "~" : "="
36
+
37
+ cmd = "cpln gvc query --org #{org} -o yaml --prop name#{op}#{app_name}"
38
+ perform_yaml(cmd)
39
+ end
40
+
32
41
  def gvc_get(a_gvc = gvc)
33
42
  api.gvc_get(gvc: a_gvc, org: org)
34
43
  end
35
44
 
45
+ def gvc_get_and_ensure(a_gvc = gvc)
46
+ gvc_data = gvc_get(a_gvc)
47
+ return gvc_data if gvc_data
48
+
49
+ Shell.abort("Can't find GVC '#{gvc}', please create it with 'cpl setup gvc -a #{config.app}'.")
50
+ end
51
+
36
52
  def gvc_delete(a_gvc = gvc)
37
53
  api.gvc_delete(gvc: a_gvc, org: org)
38
54
  end
@@ -43,6 +59,13 @@ class Controlplane
43
59
  api.workload_get(workload: workload, gvc: gvc, org: org)
44
60
  end
45
61
 
62
+ def workload_get_and_ensure(workload)
63
+ workload_data = workload_get(workload)
64
+ return workload_data if workload_data
65
+
66
+ Shell.abort("Can't find workload '#{workload}', please create it with 'cpl setup #{workload} -a #{config.app}'.")
67
+ end
68
+
46
69
  def workload_get_replicas(workload, location:)
47
70
  cmd = "cpln workload get-replicas #{workload} #{gvc_org} --location #{location} -o yaml"
48
71
  perform_yaml(cmd)
@@ -55,7 +78,7 @@ class Controlplane
55
78
  end
56
79
 
57
80
  def workload_set_suspend(workload, value)
58
- data = workload_get(workload)
81
+ data = workload_get_and_ensure(workload)
59
82
  data["spec"]["defaultOptions"]["suspend"] = value
60
83
  apply(data)
61
84
  end
@@ -37,6 +37,7 @@ class ControlplaneApiDirect
37
37
  @@api_token = ENV.fetch("CPLN_TOKEN", `cpln profile token`.chomp) # rubocop:disable Style/ClassVars
38
38
  return @@api_token if @@api_token.match?(API_TOKEN_REGEX)
39
39
 
40
- abort("ERROR: Unknown API token format. Please re-run 'cpln profile login' or set correct CPLN_TOKEN env variable")
40
+ Shell.abort("Unknown API token format. " \
41
+ "Please re-run 'cpln profile login' or set the correct CPLN_TOKEN env variable.")
41
42
  end
42
43
  end
data/lib/core/shell.rb ADDED
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Shell
4
+ def self.shell
5
+ @shell ||= Thor::Shell::Color.new
6
+ end
7
+
8
+ def self.stderr
9
+ @stderr ||= $stderr
10
+ end
11
+
12
+ def self.color(message, color_key)
13
+ shell.set_color(message, color_key)
14
+ end
15
+
16
+ def self.confirm(message)
17
+ shell.yes?("#{message} (y/n)")
18
+ end
19
+
20
+ def self.warn(message)
21
+ stderr.puts(color("WARNING: #{message}\n", :yellow))
22
+ end
23
+
24
+ def self.warn_deprecated(message)
25
+ stderr.puts(color("DEPRECATED: #{message}\n", :yellow))
26
+ end
27
+
28
+ def self.abort(message)
29
+ Kernel.abort(color("ERROR: #{message}\n", :red))
30
+ end
31
+ end
data/lib/cpl/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Cpl
4
- VERSION = "0.1.0"
4
+ VERSION = "0.3.0"
5
5
  end
data/lib/cpl.rb CHANGED
@@ -37,6 +37,7 @@ module Cpl
37
37
 
38
38
  class Cli < Thor
39
39
  package_name "cpl"
40
+ default_task :no_command
40
41
 
41
42
  def self.start(*args)
42
43
  fix_help_option
@@ -71,7 +72,8 @@ module Cpl
71
72
  def self.deprecated_commands
72
73
  {
73
74
  build: ::Command::BuildImage,
74
- promote: ::Command::PromoteImage,
75
+ promote: ::Command::DeployImage,
76
+ promote_image: ::Command::DeployImage,
75
77
  runner: ::Command::RunDetached
76
78
  }
77
79
  end
@@ -109,8 +111,8 @@ module Cpl
109
111
 
110
112
  define_method(name_for_method) do |*provided_args| # rubocop:disable Metrics/MethodLength
111
113
  if deprecated
112
- logger = $stderr
113
- logger.puts("DEPRECATED: command '#{command_key}' is deprecated, use '#{name}' instead\n")
114
+ ::Shell.warn_deprecated("Command '#{command_key}' is deprecated, " \
115
+ "please use '#{name}' instead.")
114
116
  end
115
117
 
116
118
  args = if provided_args.length.positive?
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "English"
4
+
5
+ desc("Releases the gem package using the given version.
6
+
7
+ IMPORTANT: the gem version must be in valid rubygem format (no dashes).
8
+ This task depends on the gem-release ruby gem.
9
+
10
+ 1st argument: The new version in rubygem format (no dashes). Pass no argument to
11
+ automatically perform a patch version bump.
12
+ 2nd argument: Perform a dry run by passing 'true' as a second argument.
13
+
14
+ Example: `rake create_release[2.1.0,false]`")
15
+
16
+ task :create_release, %i[gem_version dry_run] do |_t, args|
17
+ args_hash = args.to_hash
18
+
19
+ is_dry_run = Release.object_to_boolean(args_hash[:dry_run])
20
+ gem_version = args_hash.fetch(:gem_version, "").strip
21
+ gem_root = Release.gem_root
22
+
23
+ Release.update_the_local_project
24
+ Release.ensure_there_is_nothing_to_commit
25
+ Release.sh_in_dir(gem_root,
26
+ "gem bump --no-commit #{gem_version == '' ? '' : %(--version #{gem_version})}")
27
+ Release.sh_in_dir(gem_root, "bundle install")
28
+ Release.sh_in_dir(gem_root, "git commit -am 'Bump version to #{gem_version}'")
29
+
30
+ # See https://github.com/svenfuchs/gem-release
31
+ Release.release_the_new_gem_version unless is_dry_run
32
+ end
33
+
34
+ module Release
35
+ extend FileUtils
36
+ class << self
37
+ def gem_root
38
+ File.expand_path("..", __dir__)
39
+ end
40
+
41
+ # Executes a string or an array of strings in a shell in the given directory in an unbundled environment
42
+ def sh_in_dir(dir, *shell_commands)
43
+ shell_commands.flatten.each { |shell_command| sh %(cd #{dir} && #{shell_command.strip}) }
44
+ end
45
+
46
+ def ensure_there_is_nothing_to_commit
47
+ status = `git status --porcelain`
48
+
49
+ return if $CHILD_STATUS.success? && status == ""
50
+
51
+ error = if $CHILD_STATUS.success?
52
+ "You have uncommitted code. Please commit or stash your changes before continuing"
53
+ else
54
+ "You do not have Git installed. Please install Git, and commit your changes before continuing"
55
+ end
56
+ raise(error)
57
+ end
58
+
59
+ def object_to_boolean(value)
60
+ [true, "true", "yes", 1, "1", "t"].include?(value.instance_of?(String) ? value.downcase : value)
61
+ end
62
+
63
+ def update_the_local_project
64
+ puts "Pulling latest commits from remote repository"
65
+
66
+ sh_in_dir(gem_root, "git pull --rebase")
67
+ raise "Failed in pulling latest changes from default remore repository." unless $CHILD_STATUS.success?
68
+ rescue Errno::ENOENT
69
+ raise "Ensure you have Git and Bundler installed before continuing."
70
+ end
71
+
72
+ def release_the_new_gem_version
73
+ puts "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ"
74
+ puts "Use the OTP for RubyGems!"
75
+ puts "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ"
76
+
77
+ sh_in_dir(gem_root, "gem release")
78
+ end
79
+ end
80
+ end
@@ -6,7 +6,9 @@ require_relative "../lib/cpl"
6
6
  commands_str_arr = []
7
7
 
8
8
  commands = Command::Base.all_commands
9
- commands.each do |_command_key, command_class|
9
+ commands.keys.sort.each do |command_key|
10
+ command_class = commands[command_key]
11
+
10
12
  next if command_class::HIDE
11
13
 
12
14
  name = command_class::NAME