cpl 0.4.1 → 0.5.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 +4 -4
- data/.overcommit.yml +10 -0
- data/Gemfile.lock +10 -3
- data/README.md +6 -0
- data/cpl.gemspec +1 -0
- data/docs/commands.md +51 -3
- data/googlee2da545df05d92f9.html +1 -0
- data/lib/command/base.rb +65 -7
- data/lib/command/build_image.rb +6 -5
- data/lib/command/cleanup_old_images.rb +8 -7
- data/lib/command/cleanup_stale_apps.rb +11 -9
- data/lib/command/config.rb +30 -15
- data/lib/command/copy_image_from_upstream.rb +110 -0
- data/lib/command/delete.rb +10 -12
- data/lib/command/deploy_image.rb +6 -5
- data/lib/command/env.rb +2 -2
- data/lib/command/exists.rb +4 -4
- data/lib/command/info.rb +233 -0
- data/lib/command/latest_image.rb +2 -2
- data/lib/command/logs.rb +4 -4
- data/lib/command/no_command.rb +3 -3
- data/lib/command/open.rb +4 -4
- data/lib/command/promote_app_from_upstream.rb +58 -0
- data/lib/command/ps.rb +10 -13
- data/lib/command/ps_restart.rb +9 -6
- data/lib/command/ps_start.rb +7 -6
- data/lib/command/ps_stop.rb +7 -6
- data/lib/command/run.rb +5 -5
- data/lib/command/run_detached.rb +7 -5
- data/lib/command/setup.rb +71 -13
- data/lib/command/test.rb +2 -2
- data/lib/command/version.rb +2 -2
- data/lib/core/config.rb +26 -19
- data/lib/core/controlplane.rb +77 -11
- data/lib/core/controlplane_api.rb +12 -0
- data/lib/core/controlplane_api_cli.rb +1 -1
- data/lib/core/controlplane_api_direct.rb +2 -2
- data/lib/core/shell.rb +25 -3
- data/lib/cpl/version.rb +1 -1
- data/lib/cpl.rb +19 -10
- data/lib/deprecated_commands.json +6 -0
- data/script/add_command +37 -0
- data/script/generate_commands_docs +5 -5
- data/script/rename_command +43 -0
- metadata +24 -2
    
        data/lib/command/setup.rb
    CHANGED
    
    | @@ -9,7 +9,7 @@ module Command | |
| 9 9 | 
             
                  app_option(required: true)
         | 
| 10 10 | 
             
                ].freeze
         | 
| 11 11 | 
             
                DESCRIPTION = "Applies application-specific configs from templates"
         | 
| 12 | 
            -
                LONG_DESCRIPTION = <<~ | 
| 12 | 
            +
                LONG_DESCRIPTION = <<~DESC
         | 
| 13 13 | 
             
                  - Applies application-specific configs from templates (e.g., for every review-app)
         | 
| 14 14 | 
             
                  - Publishes (creates or updates) those at Control Plane infrastructure
         | 
| 15 15 | 
             
                  - Picks templates from the `.controlplane/templates` directory
         | 
| @@ -23,8 +23,8 @@ module Command | |
| 23 23 | 
             
                  APP_ORG      - organization
         | 
| 24 24 | 
             
                  APP_IMAGE    - will use latest app image
         | 
| 25 25 | 
             
                  ```
         | 
| 26 | 
            -
                 | 
| 27 | 
            -
                EXAMPLES = <<~ | 
| 26 | 
            +
                DESC
         | 
| 27 | 
            +
                EXAMPLES = <<~EX
         | 
| 28 28 | 
             
                  ```sh
         | 
| 29 29 | 
             
                  # Applies single template.
         | 
| 30 30 | 
             
                  cpl setup redis -a $APP_NAME
         | 
| @@ -32,31 +32,89 @@ module Command | |
| 32 32 | 
             
                  # Applies several templates (practically creating full app).
         | 
| 33 33 | 
             
                  cpl setup gvc postgres redis rails -a $APP_NAME
         | 
| 34 34 | 
             
                  ```
         | 
| 35 | 
            -
                 | 
| 35 | 
            +
                EX
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                def call # rubocop:disable Metrics/MethodLength
         | 
| 38 | 
            +
                  @app_status = :existing
         | 
| 39 | 
            +
                  @created_workloads = []
         | 
| 40 | 
            +
                  @failed_workloads = []
         | 
| 36 41 |  | 
| 37 | 
            -
                def call
         | 
| 38 42 | 
             
                  config.args.each do |template|
         | 
| 39 43 | 
             
                    filename = "#{config.app_cpln_dir}/templates/#{template}.yml"
         | 
| 40 | 
            -
             | 
| 41 | 
            -
                     | 
| 42 | 
            -
             | 
| 44 | 
            +
             | 
| 45 | 
            +
                    step("Applying template '#{template}'", abort_on_error: false) do
         | 
| 46 | 
            +
                      unless File.exist?(filename)
         | 
| 47 | 
            +
                        report_failure(template)
         | 
| 48 | 
            +
             | 
| 49 | 
            +
                        raise "Can't find template '#{template}' at '#{filename}', please create it."
         | 
| 50 | 
            +
                      end
         | 
| 51 | 
            +
             | 
| 52 | 
            +
                      apply_template(filename)
         | 
| 53 | 
            +
                      if $CHILD_STATUS.success?
         | 
| 54 | 
            +
                        report_success(template)
         | 
| 55 | 
            +
                      else
         | 
| 56 | 
            +
                        report_failure(template)
         | 
| 57 | 
            +
                      end
         | 
| 58 | 
            +
             | 
| 59 | 
            +
                      $CHILD_STATUS.success?
         | 
| 60 | 
            +
                    end
         | 
| 43 61 | 
             
                  end
         | 
| 62 | 
            +
             | 
| 63 | 
            +
                  print_app_status
         | 
| 64 | 
            +
                  print_created_workloads
         | 
| 65 | 
            +
                  print_failed_workloads
         | 
| 44 66 | 
             
                end
         | 
| 45 67 |  | 
| 46 68 | 
             
                private
         | 
| 47 69 |  | 
| 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 | 
            -
             | 
| 52 70 | 
             
                def apply_template(filename)
         | 
| 53 71 | 
             
                  data = File.read(filename)
         | 
| 54 72 | 
             
                             .gsub("APP_GVC", config.app)
         | 
| 55 73 | 
             
                             .gsub("APP_LOCATION", config[:default_location])
         | 
| 56 | 
            -
                             .gsub("APP_ORG", config | 
| 74 | 
            +
                             .gsub("APP_ORG", config.org)
         | 
| 57 75 | 
             
                             .gsub("APP_IMAGE", latest_image)
         | 
| 58 76 |  | 
| 59 77 | 
             
                  cp.apply(YAML.safe_load(data))
         | 
| 60 78 | 
             
                end
         | 
| 79 | 
            +
             | 
| 80 | 
            +
                def report_success(template)
         | 
| 81 | 
            +
                  if template == "gvc"
         | 
| 82 | 
            +
                    @app_status = :success
         | 
| 83 | 
            +
                  else
         | 
| 84 | 
            +
                    @created_workloads.push(template)
         | 
| 85 | 
            +
                  end
         | 
| 86 | 
            +
                end
         | 
| 87 | 
            +
             | 
| 88 | 
            +
                def report_failure(template)
         | 
| 89 | 
            +
                  if template == "gvc"
         | 
| 90 | 
            +
                    @app_status = :failure
         | 
| 91 | 
            +
                  else
         | 
| 92 | 
            +
                    @failed_workloads.push(template)
         | 
| 93 | 
            +
                  end
         | 
| 94 | 
            +
                end
         | 
| 95 | 
            +
             | 
| 96 | 
            +
                def print_app_status
         | 
| 97 | 
            +
                  return if @app_status == :existing
         | 
| 98 | 
            +
             | 
| 99 | 
            +
                  if @app_status == :success
         | 
| 100 | 
            +
                    progress.puts("\n#{Shell.color("Created app '#{config.app}'.", :green)}")
         | 
| 101 | 
            +
                  else
         | 
| 102 | 
            +
                    progress.puts("\n#{Shell.color("Failed to create app '#{config.app}'.", :red)}")
         | 
| 103 | 
            +
                  end
         | 
| 104 | 
            +
                end
         | 
| 105 | 
            +
             | 
| 106 | 
            +
                def print_created_workloads
         | 
| 107 | 
            +
                  return unless @created_workloads.any?
         | 
| 108 | 
            +
             | 
| 109 | 
            +
                  workloads = @created_workloads.map { |template| "  - #{template}" }.join("\n")
         | 
| 110 | 
            +
                  progress.puts("\n#{Shell.color('Created workloads:', :green)}\n#{workloads}")
         | 
| 111 | 
            +
                end
         | 
| 112 | 
            +
             | 
| 113 | 
            +
                def print_failed_workloads
         | 
| 114 | 
            +
                  return unless @failed_workloads.any?
         | 
| 115 | 
            +
             | 
| 116 | 
            +
                  workloads = @failed_workloads.map { |template| "  - #{template}" }.join("\n")
         | 
| 117 | 
            +
                  progress.puts("\n#{Shell.color('Failed to create workloads:', :red)}\n#{workloads}")
         | 
| 118 | 
            +
                end
         | 
| 61 119 | 
             
              end
         | 
| 62 120 | 
             
            end
         | 
    
        data/lib/command/test.rb
    CHANGED
    
    
    
        data/lib/command/version.rb
    CHANGED
    
    | @@ -4,10 +4,10 @@ module Command | |
| 4 4 | 
             
              class Version < Base
         | 
| 5 5 | 
             
                NAME = "version"
         | 
| 6 6 | 
             
                DESCRIPTION = "Displays the current version of the CLI"
         | 
| 7 | 
            -
                LONG_DESCRIPTION = <<~ | 
| 7 | 
            +
                LONG_DESCRIPTION = <<~DESC
         | 
| 8 8 | 
             
                  - Displays the current version of the CLI
         | 
| 9 9 | 
             
                  - Can also be done with `cpl --version` or `cpl -v`
         | 
| 10 | 
            -
                 | 
| 10 | 
            +
                DESC
         | 
| 11 11 |  | 
| 12 12 | 
             
                def call
         | 
| 13 13 | 
             
                  puts Cpl::VERSION
         | 
    
        data/lib/core/config.rb
    CHANGED
    
    | @@ -2,7 +2,7 @@ | |
| 2 2 |  | 
| 3 3 | 
             
            class Config
         | 
| 4 4 | 
             
              attr_reader :config, :current,
         | 
| 5 | 
            -
                          :app, :app_dir,
         | 
| 5 | 
            +
                          :org, :app, :apps, :app_dir,
         | 
| 6 6 | 
             
                          # command line options
         | 
| 7 7 | 
             
                          :args, :options
         | 
| 8 8 |  | 
| @@ -11,9 +11,13 @@ class Config | |
| 11 11 | 
             
              def initialize(args, options)
         | 
| 12 12 | 
             
                @args = args
         | 
| 13 13 | 
             
                @options = options
         | 
| 14 | 
            +
                @org = options[:org]
         | 
| 14 15 | 
             
                @app = options[:app]
         | 
| 15 16 |  | 
| 16 17 | 
             
                load_app_config
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                @apps = config[:apps]
         | 
| 20 | 
            +
             | 
| 17 21 | 
             
                pick_current_config if app
         | 
| 18 22 | 
             
                warn_deprecated_options if current
         | 
| 19 23 | 
             
              end
         | 
| @@ -27,7 +31,7 @@ class Config | |
| 27 31 | 
             
                elsif old_key && current.key?(old_key)
         | 
| 28 32 | 
             
                  current.fetch(old_key)
         | 
| 29 33 | 
             
                else
         | 
| 30 | 
            -
                   | 
| 34 | 
            +
                  raise "Can't find option '#{key}' for app '#{app}' in 'controlplane.yml'."
         | 
| 31 35 | 
             
                end
         | 
| 32 36 | 
             
              end
         | 
| 33 37 |  | 
| @@ -42,34 +46,33 @@ class Config | |
| 42 46 | 
             
              private
         | 
| 43 47 |  | 
| 44 48 | 
             
              def ensure_current_config!
         | 
| 45 | 
            -
                 | 
| 49 | 
            +
                raise "Can't find current config, please specify an app." unless current
         | 
| 46 50 | 
             
              end
         | 
| 47 51 |  | 
| 48 52 | 
             
              def ensure_current_config_app!(app)
         | 
| 49 | 
            -
                 | 
| 53 | 
            +
                raise "Can't find app '#{app}' in 'controlplane.yml'." unless current
         | 
| 50 54 | 
             
              end
         | 
| 51 55 |  | 
| 52 56 | 
             
              def ensure_config!
         | 
| 53 | 
            -
                 | 
| 57 | 
            +
                raise "'controlplane.yml' is empty." unless config
         | 
| 54 58 | 
             
              end
         | 
| 55 59 |  | 
| 56 60 | 
             
              def ensure_config_apps!
         | 
| 57 | 
            -
                 | 
| 61 | 
            +
                raise "Can't find key 'apps' in 'controlplane.yml'." unless config[:apps]
         | 
| 58 62 | 
             
              end
         | 
| 59 63 |  | 
| 60 64 | 
             
              def ensure_config_app!(app, options)
         | 
| 61 | 
            -
                 | 
| 65 | 
            +
                raise "App '#{app}' is empty in 'controlplane.yml'." unless options
         | 
| 62 66 | 
             
              end
         | 
| 63 67 |  | 
| 64 68 | 
             
              def pick_current_config
         | 
| 65 | 
            -
                ensure_config!
         | 
| 66 | 
            -
                ensure_config_apps!
         | 
| 67 69 | 
             
                config[:apps].each do |c_app, c_data|
         | 
| 68 70 | 
             
                  ensure_config_app!(c_app, c_data)
         | 
| 69 | 
            -
                   | 
| 70 | 
            -
             | 
| 71 | 
            -
             | 
| 72 | 
            -
                   | 
| 71 | 
            +
                  next unless c_app.to_s == app || (c_data[:match_if_app_name_starts_with] && app.start_with?(c_app.to_s))
         | 
| 72 | 
            +
             | 
| 73 | 
            +
                  @current = c_data
         | 
| 74 | 
            +
                  @org = self[:cpln_org]
         | 
| 75 | 
            +
                  break
         | 
| 73 76 | 
             
                end
         | 
| 74 77 | 
             
                ensure_current_config_app!(app)
         | 
| 75 78 | 
             
              end
         | 
| @@ -78,6 +81,8 @@ class Config | |
| 78 81 | 
             
                config_file = find_app_config_file
         | 
| 79 82 | 
             
                @config = YAML.safe_load_file(config_file, symbolize_names: true, aliases: true)
         | 
| 80 83 | 
             
                @app_dir = Pathname.new(config_file).parent.parent.to_s
         | 
| 84 | 
            +
                ensure_config!
         | 
| 85 | 
            +
                ensure_config_apps!
         | 
| 81 86 | 
             
              end
         | 
| 82 87 |  | 
| 83 88 | 
             
              def find_app_config_file
         | 
| @@ -90,7 +95,7 @@ class Config | |
| 90 95 | 
             
                  path = path.parent
         | 
| 91 96 |  | 
| 92 97 | 
             
                  if path.root?
         | 
| 93 | 
            -
                     | 
| 98 | 
            +
                    raise "Can't find project config file at 'project_folder/#{CONFIG_FILE_LOCATIION}', please create it."
         | 
| 94 99 | 
             
                  end
         | 
| 95 100 | 
             
                end
         | 
| 96 101 | 
             
              end
         | 
| @@ -104,11 +109,13 @@ class Config | |
| 104 109 | 
             
              end
         | 
| 105 110 |  | 
| 106 111 | 
             
              def warn_deprecated_options
         | 
| 107 | 
            -
                old_option_keys. | 
| 108 | 
            -
             | 
| 109 | 
            -
             | 
| 110 | 
            -
             | 
| 111 | 
            -
                   | 
| 112 | 
            +
                deprecated_option_keys = old_option_keys.filter { |_new_key, old_key| current.key?(old_key) }
         | 
| 113 | 
            +
                return if deprecated_option_keys.empty?
         | 
| 114 | 
            +
             | 
| 115 | 
            +
                deprecated_option_keys.each do |new_key, old_key|
         | 
| 116 | 
            +
                  Shell.warn_deprecated("Option '#{old_key}' is deprecated, " \
         | 
| 117 | 
            +
                                        "please use '#{new_key}' instead (in 'controlplane.yml').")
         | 
| 112 118 | 
             
                end
         | 
| 119 | 
            +
                $stderr.puts
         | 
| 113 120 | 
             
              end
         | 
| 114 121 | 
             
            end
         | 
    
        data/lib/core/controlplane.rb
    CHANGED
    
    | @@ -7,7 +7,30 @@ class Controlplane # rubocop:disable Metrics/ClassLength | |
| 7 7 | 
             
                @config = config
         | 
| 8 8 | 
             
                @api = ControlplaneApi.new
         | 
| 9 9 | 
             
                @gvc = config.app
         | 
| 10 | 
            -
                @org = config | 
| 10 | 
            +
                @org = config.org
         | 
| 11 | 
            +
              end
         | 
| 12 | 
            +
             | 
| 13 | 
            +
              # profile
         | 
| 14 | 
            +
             | 
| 15 | 
            +
              def profile_switch(profile)
         | 
| 16 | 
            +
                ENV["CPLN_PROFILE"] = profile
         | 
| 17 | 
            +
              end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
              def profile_exists?(profile)
         | 
| 20 | 
            +
                cmd = "cpln profile get #{profile} -o yaml"
         | 
| 21 | 
            +
                perform_yaml(cmd).length.positive?
         | 
| 22 | 
            +
              end
         | 
| 23 | 
            +
             | 
| 24 | 
            +
              def profile_create(profile, token)
         | 
| 25 | 
            +
                cmd = "cpln profile create #{profile} --token #{token}"
         | 
| 26 | 
            +
                cmd += " > /dev/null" if Shell.tmp_stderr
         | 
| 27 | 
            +
                perform!(cmd)
         | 
| 28 | 
            +
              end
         | 
| 29 | 
            +
             | 
| 30 | 
            +
              def profile_delete(profile)
         | 
| 31 | 
            +
                cmd = "cpln profile delete #{profile}"
         | 
| 32 | 
            +
                cmd += " > /dev/null" if Shell.tmp_stderr
         | 
| 33 | 
            +
                perform!(cmd)
         | 
| 11 34 | 
             
              end
         | 
| 12 35 |  | 
| 13 36 | 
             
              # image
         | 
| @@ -18,8 +41,8 @@ class Controlplane # rubocop:disable Metrics/ClassLength | |
| 18 41 | 
             
                perform!(cmd)
         | 
| 19 42 | 
             
              end
         | 
| 20 43 |  | 
| 21 | 
            -
              def image_query(app_name = config.app)
         | 
| 22 | 
            -
                cmd = "cpln image query --org #{ | 
| 44 | 
            +
              def image_query(app_name = config.app, org_name = config.org)
         | 
| 45 | 
            +
                cmd = "cpln image query --org #{org_name} -o yaml --max -1 --prop repository=#{app_name}"
         | 
| 23 46 | 
             
                perform_yaml(cmd)
         | 
| 24 47 | 
             
              end
         | 
| 25 48 |  | 
| @@ -27,8 +50,36 @@ class Controlplane # rubocop:disable Metrics/ClassLength | |
| 27 50 | 
             
                api.image_delete(org: org, image: image)
         | 
| 28 51 | 
             
              end
         | 
| 29 52 |  | 
| 53 | 
            +
              def image_login(org_name = config.org)
         | 
| 54 | 
            +
                cmd = "cpln image docker-login --org #{org_name}"
         | 
| 55 | 
            +
                cmd += " > /dev/null 2>&1" if Shell.tmp_stderr
         | 
| 56 | 
            +
                perform!(cmd)
         | 
| 57 | 
            +
              end
         | 
| 58 | 
            +
             | 
| 59 | 
            +
              def image_pull(image)
         | 
| 60 | 
            +
                cmd = "docker pull #{image}"
         | 
| 61 | 
            +
                cmd += " > /dev/null" if Shell.tmp_stderr
         | 
| 62 | 
            +
                perform!(cmd)
         | 
| 63 | 
            +
              end
         | 
| 64 | 
            +
             | 
| 65 | 
            +
              def image_tag(old_tag, new_tag)
         | 
| 66 | 
            +
                cmd = "docker tag #{old_tag} #{new_tag}"
         | 
| 67 | 
            +
                cmd += " > /dev/null" if Shell.tmp_stderr
         | 
| 68 | 
            +
                perform!(cmd)
         | 
| 69 | 
            +
              end
         | 
| 70 | 
            +
             | 
| 71 | 
            +
              def image_push(image)
         | 
| 72 | 
            +
                cmd = "docker push #{image}"
         | 
| 73 | 
            +
                cmd += " > /dev/null" if Shell.tmp_stderr
         | 
| 74 | 
            +
                perform!(cmd)
         | 
| 75 | 
            +
              end
         | 
| 76 | 
            +
             | 
| 30 77 | 
             
              # gvc
         | 
| 31 78 |  | 
| 79 | 
            +
              def fetch_gvcs
         | 
| 80 | 
            +
                api.gvc_list(org: org)
         | 
| 81 | 
            +
              end
         | 
| 82 | 
            +
             | 
| 32 83 | 
             
              def gvc_query(app_name = config.app)
         | 
| 33 84 | 
             
                # When `match_if_app_name_starts_with` is `true`, we query for any gvc containing the name,
         | 
| 34 85 | 
             
                # otherwise we query for a gvc with the exact name.
         | 
| @@ -38,15 +89,15 @@ class Controlplane # rubocop:disable Metrics/ClassLength | |
| 38 89 | 
             
                perform_yaml(cmd)
         | 
| 39 90 | 
             
              end
         | 
| 40 91 |  | 
| 41 | 
            -
              def fetch_gvc(a_gvc = gvc)
         | 
| 42 | 
            -
                api.gvc_get(gvc: a_gvc, org:  | 
| 92 | 
            +
              def fetch_gvc(a_gvc = gvc, a_org = org)
         | 
| 93 | 
            +
                api.gvc_get(gvc: a_gvc, org: a_org)
         | 
| 43 94 | 
             
              end
         | 
| 44 95 |  | 
| 45 96 | 
             
              def fetch_gvc!(a_gvc = gvc)
         | 
| 46 97 | 
             
                gvc_data = fetch_gvc(a_gvc)
         | 
| 47 98 | 
             
                return gvc_data if gvc_data
         | 
| 48 99 |  | 
| 49 | 
            -
                 | 
| 100 | 
            +
                raise "Can't find GVC '#{gvc}', please create it with 'cpl setup gvc -a #{config.app}'."
         | 
| 50 101 | 
             
              end
         | 
| 51 102 |  | 
| 52 103 | 
             
              def gvc_delete(a_gvc = gvc)
         | 
| @@ -55,6 +106,14 @@ class Controlplane # rubocop:disable Metrics/ClassLength | |
| 55 106 |  | 
| 56 107 | 
             
              # workload
         | 
| 57 108 |  | 
| 109 | 
            +
              def fetch_workloads(a_gvc = gvc)
         | 
| 110 | 
            +
                api.workload_list(gvc: a_gvc, org: org)
         | 
| 111 | 
            +
              end
         | 
| 112 | 
            +
             | 
| 113 | 
            +
              def fetch_workloads_by_org(a_org = org)
         | 
| 114 | 
            +
                api.workload_list_by_org(org: a_org)
         | 
| 115 | 
            +
              end
         | 
| 116 | 
            +
             | 
| 58 117 | 
             
              def fetch_workload(workload)
         | 
| 59 118 | 
             
                api.workload_get(workload: workload, gvc: gvc, org: org)
         | 
| 60 119 | 
             
              end
         | 
| @@ -63,7 +122,7 @@ class Controlplane # rubocop:disable Metrics/ClassLength | |
| 63 122 | 
             
                workload_data = fetch_workload(workload)
         | 
| 64 123 | 
             
                return workload_data if workload_data
         | 
| 65 124 |  | 
| 66 | 
            -
                 | 
| 125 | 
            +
                raise "Can't find workload '#{workload}', please create it with 'cpl setup #{workload} -a #{config.app}'."
         | 
| 67 126 | 
             
              end
         | 
| 68 127 |  | 
| 69 128 | 
             
              def workload_get_replicas(workload, location:)
         | 
| @@ -73,7 +132,8 @@ class Controlplane # rubocop:disable Metrics/ClassLength | |
| 73 132 |  | 
| 74 133 | 
             
              def workload_set_image_ref(workload, container:, image:)
         | 
| 75 134 | 
             
                cmd = "cpln workload update #{workload} #{gvc_org}"
         | 
| 76 | 
            -
                cmd += " --set spec.containers.#{container}.image=/org/#{config | 
| 135 | 
            +
                cmd += " --set spec.containers.#{container}.image=/org/#{config.org}/image/#{image}"
         | 
| 136 | 
            +
                cmd += " > /dev/null" if Shell.tmp_stderr
         | 
| 77 137 | 
             
                perform!(cmd)
         | 
| 78 138 | 
             
              end
         | 
| 79 139 |  | 
| @@ -85,6 +145,7 @@ class Controlplane # rubocop:disable Metrics/ClassLength | |
| 85 145 |  | 
| 86 146 | 
             
              def workload_force_redeployment(workload)
         | 
| 87 147 | 
             
                cmd = "cpln workload force-redeployment #{workload} #{gvc_org}"
         | 
| 148 | 
            +
                cmd += " > /dev/null" if Shell.tmp_stderr
         | 
| 88 149 | 
             
                perform!(cmd)
         | 
| 89 150 | 
             
              end
         | 
| 90 151 |  | 
| @@ -126,12 +187,17 @@ class Controlplane # rubocop:disable Metrics/ClassLength | |
| 126 187 |  | 
| 127 188 | 
             
              # apply
         | 
| 128 189 |  | 
| 129 | 
            -
              def apply(data)
         | 
| 190 | 
            +
              def apply(data) # rubocop:disable Metrics/MethodLength
         | 
| 130 191 | 
             
                Tempfile.create do |f|
         | 
| 131 192 | 
             
                  f.write(data.to_yaml)
         | 
| 132 193 | 
             
                  f.rewind
         | 
| 133 194 | 
             
                  cmd = "cpln apply #{gvc_org} --file #{f.path} > /dev/null"
         | 
| 134 | 
            -
                   | 
| 195 | 
            +
                  if Shell.tmp_stderr
         | 
| 196 | 
            +
                    cmd += " 2> #{Shell.tmp_stderr.path}"
         | 
| 197 | 
            +
                    perform(cmd)
         | 
| 198 | 
            +
                  else
         | 
| 199 | 
            +
                    perform!(cmd)
         | 
| 200 | 
            +
                  end
         | 
| 135 201 | 
             
                end
         | 
| 136 202 | 
             
              end
         | 
| 137 203 |  | 
| @@ -147,7 +213,7 @@ class Controlplane # rubocop:disable Metrics/ClassLength | |
| 147 213 |  | 
| 148 214 | 
             
              def perform_yaml(cmd)
         | 
| 149 215 | 
             
                result = `#{cmd}`
         | 
| 150 | 
            -
                 | 
| 216 | 
            +
                $CHILD_STATUS.success? ? YAML.safe_load(result) : exit(false)
         | 
| 151 217 | 
             
              end
         | 
| 152 218 |  | 
| 153 219 | 
             
              def gvc_org
         | 
| @@ -1,6 +1,10 @@ | |
| 1 1 | 
             
            # frozen_string_literal: true
         | 
| 2 2 |  | 
| 3 3 | 
             
            class ControlplaneApi
         | 
| 4 | 
            +
              def gvc_list(org:)
         | 
| 5 | 
            +
                api_json("/org/#{org}/gvc", method: :get)
         | 
| 6 | 
            +
              end
         | 
| 7 | 
            +
             | 
| 4 8 | 
             
              def gvc_get(org:, gvc:)
         | 
| 5 9 | 
             
                api_json("/org/#{org}/gvc/#{gvc}", method: :get)
         | 
| 6 10 | 
             
              end
         | 
| @@ -29,6 +33,14 @@ class ControlplaneApi | |
| 29 33 | 
             
                api_json_direct("/logs/org/#{org}/loki/api/v1/query_range?#{params}", method: :get, host: :logs)
         | 
| 30 34 | 
             
              end
         | 
| 31 35 |  | 
| 36 | 
            +
              def workload_list(org:, gvc:)
         | 
| 37 | 
            +
                api_json("/org/#{org}/gvc/#{gvc}/workload", method: :get)
         | 
| 38 | 
            +
              end
         | 
| 39 | 
            +
             | 
| 40 | 
            +
              def workload_list_by_org(org:)
         | 
| 41 | 
            +
                api_json("/org/#{org}/workload", method: :get)
         | 
| 42 | 
            +
              end
         | 
| 43 | 
            +
             | 
| 32 44 | 
             
              def workload_get(org:, gvc:, workload:)
         | 
| 33 45 | 
             
                api_json("/org/#{org}/gvc/#{gvc}/workload/#{workload}", method: :get)
         | 
| 34 46 | 
             
              end
         | 
| @@ -37,7 +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 | 
            -
                 | 
| 41 | 
            -
             | 
| 40 | 
            +
                raise "Unknown API token format. " \
         | 
| 41 | 
            +
                      "Please re-run 'cpln profile login' or set the correct CPLN_TOKEN env variable."
         | 
| 42 42 | 
             
              end
         | 
| 43 43 | 
             
            end
         | 
    
        data/lib/core/shell.rb
    CHANGED
    
    | @@ -1,6 +1,10 @@ | |
| 1 1 | 
             
            # frozen_string_literal: true
         | 
| 2 2 |  | 
| 3 3 | 
             
            class Shell
         | 
| 4 | 
            +
              class << self
         | 
| 5 | 
            +
                attr_reader :tmp_stderr
         | 
| 6 | 
            +
              end
         | 
| 7 | 
            +
             | 
| 4 8 | 
             
              def self.shell
         | 
| 5 9 | 
             
                @shell ||= Thor::Shell::Color.new
         | 
| 6 10 | 
             
              end
         | 
| @@ -9,6 +13,24 @@ class Shell | |
| 9 13 | 
             
                @stderr ||= $stderr
         | 
| 10 14 | 
             
              end
         | 
| 11 15 |  | 
| 16 | 
            +
              def self.use_tmp_stderr
         | 
| 17 | 
            +
                @tmp_stderr = Tempfile.create
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                yield
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                @tmp_stderr.close
         | 
| 22 | 
            +
                @tmp_stderr = nil
         | 
| 23 | 
            +
              end
         | 
| 24 | 
            +
             | 
| 25 | 
            +
              def self.write_to_tmp_stderr(message)
         | 
| 26 | 
            +
                tmp_stderr.write(message)
         | 
| 27 | 
            +
              end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
              def self.read_from_tmp_stderr
         | 
| 30 | 
            +
                tmp_stderr.rewind
         | 
| 31 | 
            +
                tmp_stderr.read.strip
         | 
| 32 | 
            +
              end
         | 
| 33 | 
            +
             | 
| 12 34 | 
             
              def self.color(message, color_key)
         | 
| 13 35 | 
             
                shell.set_color(message, color_key)
         | 
| 14 36 | 
             
              end
         | 
| @@ -18,14 +40,14 @@ class Shell | |
| 18 40 | 
             
              end
         | 
| 19 41 |  | 
| 20 42 | 
             
              def self.warn(message)
         | 
| 21 | 
            -
                stderr.puts(color("WARNING: #{message} | 
| 43 | 
            +
                stderr.puts(color("WARNING: #{message}", :yellow))
         | 
| 22 44 | 
             
              end
         | 
| 23 45 |  | 
| 24 46 | 
             
              def self.warn_deprecated(message)
         | 
| 25 | 
            -
                stderr.puts(color("DEPRECATED: #{message} | 
| 47 | 
            +
                stderr.puts(color("DEPRECATED: #{message}", :yellow))
         | 
| 26 48 | 
             
              end
         | 
| 27 49 |  | 
| 28 50 | 
             
              def self.abort(message)
         | 
| 29 | 
            -
                Kernel.abort(color("ERROR: #{message} | 
| 51 | 
            +
                Kernel.abort(color("ERROR: #{message}", :red))
         | 
| 30 52 | 
             
              end
         | 
| 31 53 | 
             
            end
         | 
    
        data/lib/cpl/version.rb
    CHANGED
    
    
    
        data/lib/cpl.rb
    CHANGED
    
    | @@ -70,12 +70,17 @@ module Cpl | |
| 70 70 | 
             
                end
         | 
| 71 71 |  | 
| 72 72 | 
             
                def self.deprecated_commands
         | 
| 73 | 
            -
                   | 
| 74 | 
            -
                     | 
| 75 | 
            -
                     | 
| 76 | 
            -
                     | 
| 77 | 
            -
                     | 
| 78 | 
            -
             | 
| 73 | 
            +
                  @deprecated_commands ||= begin
         | 
| 74 | 
            +
                    deprecated_commands_file_path = "#{__dir__}/deprecated_commands.json"
         | 
| 75 | 
            +
                    deprecated_commands_data = File.binread(deprecated_commands_file_path)
         | 
| 76 | 
            +
                    deprecated_commands = JSON.parse(deprecated_commands_data)
         | 
| 77 | 
            +
                    deprecated_commands.to_h do |old_command_name, new_command_name|
         | 
| 78 | 
            +
                      file_name = new_command_name.gsub(/[^A-Za-z]/, "_")
         | 
| 79 | 
            +
                      class_name = file_name.split("_").map(&:capitalize).join
         | 
| 80 | 
            +
             | 
| 81 | 
            +
                      [old_command_name, Object.const_get("::Command::#{class_name}")]
         | 
| 82 | 
            +
                    end
         | 
| 83 | 
            +
                  end
         | 
| 79 84 | 
             
                end
         | 
| 80 85 |  | 
| 81 86 | 
             
                def self.all_base_commands
         | 
| @@ -113,6 +118,7 @@ module Cpl | |
| 113 118 | 
             
                    if deprecated
         | 
| 114 119 | 
             
                      ::Shell.warn_deprecated("Command '#{command_key}' is deprecated, " \
         | 
| 115 120 | 
             
                                              "please use '#{name}' instead.")
         | 
| 121 | 
            +
                      $stderr.puts
         | 
| 116 122 | 
             
                    end
         | 
| 117 123 |  | 
| 118 124 | 
             
                    args = if provided_args.length.positive?
         | 
| @@ -123,13 +129,16 @@ module Cpl | |
| 123 129 |  | 
| 124 130 | 
             
                    raise_args_error.call(args, nil) if (args.empty? && requires_args) || (!args.empty? && !requires_args)
         | 
| 125 131 |  | 
| 126 | 
            -
                     | 
| 132 | 
            +
                    begin
         | 
| 133 | 
            +
                      config = Config.new(args, options)
         | 
| 127 134 |  | 
| 128 | 
            -
             | 
| 135 | 
            +
                      command_class.new(config).call
         | 
| 136 | 
            +
                    rescue RuntimeError => e
         | 
| 137 | 
            +
                      ::Shell.abort(e.message)
         | 
| 138 | 
            +
                    end
         | 
| 129 139 | 
             
                  end
         | 
| 130 140 | 
             
                rescue StandardError => e
         | 
| 131 | 
            -
                   | 
| 132 | 
            -
                  logger.puts("Unable to load command: #{e.message}")
         | 
| 141 | 
            +
                  ::Shell.abort("Unable to load command: #{e.message}")
         | 
| 133 142 | 
             
                end
         | 
| 134 143 | 
             
              end
         | 
| 135 144 | 
             
            end
         | 
    
        data/script/add_command
    ADDED
    
    | @@ -0,0 +1,37 @@ | |
| 1 | 
            +
            #!/usr/bin/env ruby
         | 
| 2 | 
            +
            # frozen_string_literal: true
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            command_name = ARGV[0]&.downcase
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            abort("ERROR: Must provide command name.") unless command_name
         | 
| 7 | 
            +
             | 
| 8 | 
            +
            file_name = command_name.gsub(/[^A-Za-z]/, "_")
         | 
| 9 | 
            +
            file_path = "#{__dir__}/../lib/command/#{file_name}.rb"
         | 
| 10 | 
            +
             | 
| 11 | 
            +
            abort("ERROR: Command '#{command_name}' already exists.") if File.exist?(file_path)
         | 
| 12 | 
            +
             | 
| 13 | 
            +
            class_name = file_name.split("_").map(&:capitalize).join
         | 
| 14 | 
            +
             | 
| 15 | 
            +
            file_data =
         | 
| 16 | 
            +
              <<~DATA
         | 
| 17 | 
            +
                # frozen_string_literal: true
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                module Command
         | 
| 20 | 
            +
                  class #{class_name} < Base
         | 
| 21 | 
            +
                    # See `base.rb` for other constants to add here
         | 
| 22 | 
            +
                    NAME = "#{command_name}"
         | 
| 23 | 
            +
                    OPTIONS = [
         | 
| 24 | 
            +
                      # Add options here
         | 
| 25 | 
            +
                    ].freeze
         | 
| 26 | 
            +
                    DESCRIPTION = "Add description here"
         | 
| 27 | 
            +
                    LONG_DESCRIPTION = <<~DESC
         | 
| 28 | 
            +
                      - Add long description here
         | 
| 29 | 
            +
                    DESC
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                    def call
         | 
| 32 | 
            +
                      # Add command logic here
         | 
| 33 | 
            +
                    end
         | 
| 34 | 
            +
                  end
         | 
| 35 | 
            +
                end
         | 
| 36 | 
            +
              DATA
         | 
| 37 | 
            +
            File.binwrite(file_path, file_data)
         | 
| @@ -41,9 +41,9 @@ end | |
| 41 41 |  | 
| 42 42 | 
             
            commands_str = commands_str_arr.join("\n\n")
         | 
| 43 43 |  | 
| 44 | 
            -
             | 
| 45 | 
            -
             | 
| 46 | 
            -
              <<~ | 
| 44 | 
            +
            file_path = "#{__dir__}/../docs/commands.md"
         | 
| 45 | 
            +
            file_data =
         | 
| 46 | 
            +
              <<~DATA
         | 
| 47 47 | 
             
                <!-- NOTE: This file is automatically generated by running `script/generate_commands_docs`. Do NOT edit it manually. -->
         | 
| 48 48 |  | 
| 49 49 | 
             
                ### Common Options
         | 
| @@ -58,5 +58,5 @@ commands_data = | |
| 58 58 | 
             
                ### Commands
         | 
| 59 59 |  | 
| 60 60 | 
             
                #{commands_str}
         | 
| 61 | 
            -
               | 
| 62 | 
            -
            File.binwrite( | 
| 61 | 
            +
              DATA
         | 
| 62 | 
            +
            File.binwrite(file_path, file_data)
         |