Skip to content

Commit

Permalink
feat: only use CPLN_ORG and CPLN_APP env vars if allowed
Browse files Browse the repository at this point in the history
  • Loading branch information
rafaelgomesxyz committed Nov 17, 2023
1 parent d8263b4 commit e56e33f
Show file tree
Hide file tree
Showing 5 changed files with 93 additions and 19 deletions.
13 changes: 11 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -159,10 +159,19 @@ Here's a complete example of all supported config keys explained for the `contro
```yaml
# Keys beginning with "cpln_" correspond to your settings in Control Plane.

# Global settings that apply to `cpl` usage.
# You can opt out of allowing the use of CPLN_ORG and CPLN_APP env vars
# to avoid any accidents with the wrong org / app.
allow_org_override_by_env: true
allow_app_override_by_env: true

aliases:
common: &common
# Organization name for staging (customize to your needs).
# Production apps will use a different organization, specified below, for security.
# Organization for staging and QA apps is typically set as an alias.
# Production apps will use a different organization, specified in `apps`, for security.
# Change this value to your organization name
# or set the CPLN_ORG env var and it will override this for all `cpl` commands
# (provided that `allow_org_override_by_env` is set to `true`).
cpln_org: my-org-staging

# Example apps use only one location. Control Plane offers the ability to use multiple locations.
Expand Down
6 changes: 6 additions & 0 deletions examples/controlplane.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Keys beginning with "cpln_" correspond to your settings in Control Plane.

# Global settings that apply to `cpl` usage.
# You can opt out of allowing the use of CPLN_ORG and CPLN_APP env vars
# to avoid any accidents with the wrong org / app.
allow_org_override_by_env: true
allow_app_override_by_env: true

aliases:
common: &common
# Organization name for staging (customize to your needs).
Expand Down
10 changes: 4 additions & 6 deletions lib/command/base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -44,30 +44,28 @@ def self.common_options
[org_option, verbose_option]
end

def self.org_option(required: false) # rubocop:disable Metrics/MethodLength
def self.org_option(required: false)
{
name: :org,
params: {
aliases: ["-o"],
banner: "ORG_NAME",
desc: "Organization name",
type: :string,
required: required,
default: ENV.fetch("CPLN_ORG", nil)
required: required
}
}
end

def self.app_option(required: false) # rubocop:disable Metrics/MethodLength
def self.app_option(required: false)
{
name: :app,
params: {
aliases: ["-a"],
banner: "APP_NAME",
desc: "Application name",
type: :string,
required: required,
default: ENV.fetch("CPLN_APP", nil)
required: required
}
}
end
Expand Down
74 changes: 65 additions & 9 deletions lib/core/config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,27 @@

class Config # rubocop:disable Metrics/ClassLength
attr_reader :config, :current,
:org, :org_comes_from_env, :app, :apps, :app_dir,
:org, :org_comes_from_env, :app, :apps, :app_dir, :required_options,
# command line options
:args, :options

CONFIG_FILE_LOCATIION = ".controlplane/controlplane.yml"

def initialize(args, options)
def initialize(args, options, required_options)
@args = args
@options = options
@org = options[:org]&.strip
@org_comes_from_env = true if ENV.fetch("CPLN_ORG", nil)
@app = options[:app]&.strip
@required_options = required_options

load_app_config

configure_org
configure_app

load_apps

ensure_org! if required_options.include?(:org)
ensure_required_options!

Shell.verbose_mode(options[:verbose])
end

Expand Down Expand Up @@ -54,9 +59,9 @@ def ensure_current_config_app!(app_name)
def ensure_current_config_org!(app_name)
return if @org

raise "Can't find option 'cpln_org' for app '#{app_name}' in 'controlplane.yml', " \
"and CPLN_ORG env var is not set. " \
"The org can also be provided through --org."
raise "Can't find option 'cpln_org' for app '#{app_name}' in 'controlplane.yml'. " \
"The org can also be provided either through --org " \
"or the CPLN_ORG env var ('allow_org_override_by_env' must be set to true in 'controlplane.yml')."
end

def ensure_config!
Expand All @@ -71,6 +76,57 @@ def ensure_config_app!(app_name, app_options)
raise "App '#{app_name}' is empty in 'controlplane.yml'." unless app_options
end

def ensure_org!
return if @org

raise "No org provided. The org can be provided either through --org " \
"or the CPLN_ORG env var ('allow_org_override_by_env' must be set to true in 'controlplane.yml')."
end

def ensure_app!
return if @app

raise "No app provided. The app can be provided either through --app " \
"or the CPLN_APP env var ('allow_app_override_by_env' must be set to true in 'controlplane.yml')."
end

def ensure_required_options!
missing_str = required_options
.reject { |option_name| %i[org app].include?(option_name) || options.key?(option_name) }
.map { |option_name| "--#{option_name}" }
.join(", ")

raise "Required options missing: #{missing_str}" unless missing_str.empty?
end

def non_empty_str_or_nil(str)
return str if str.nil?

str.empty? ? nil : str
end

def configure_org
if config[:allow_org_override_by_env]
org_from_env = non_empty_str_or_nil(ENV.fetch("CPLN_ORG", nil)&.strip)
if org_from_env
@org = org_from_env
@org_comes_from_env = true
end
end

return if @org

@org = non_empty_str_or_nil(options[:org]&.strip)
end

def configure_app
@app = non_empty_str_or_nil(ENV.fetch("CPLN_APP", nil)&.strip) if config[:allow_app_override_by_env]
return if @app

@app = non_empty_str_or_nil(options[:app]&.strip)
ensure_app! if required_options.include?(:app)
end

def app_matches_current?(app_name, app_options)
app && (app_name.to_s == app || (app_options[:match_if_app_name_starts_with] && app.start_with?(app_name.to_s)))
end
Expand All @@ -81,7 +137,7 @@ def pick_current_config(app_name, app_options)

return if @org

@org = current.fetch(:cpln_org)&.strip if current.key?(:cpln_org)
@org = non_empty_str_or_nil(current.fetch(:cpln_org)&.strip) if current.key?(:cpln_org)
ensure_current_config_org!(app_name)
end

Expand Down
9 changes: 7 additions & 2 deletions lib/cpl.rb
Original file line number Diff line number Diff line change
Expand Up @@ -157,8 +157,13 @@ def self.all_base_commands
desc(usage, description, hide: hide)
long_desc(long_description)

required_options = []
command_options.each do |option|
method_option(option[:name], **option[:params])
# We'll handle required options manually in `Config`
required_options.push(option[:name]) if option[:params][:required]

params = option[:params].reject { |key, _| key == :required }
method_option(option[:name], **params)
end

define_method(name_for_method) do |*provided_args| # rubocop:disable Metrics/MethodLength
Expand All @@ -177,7 +182,7 @@ def self.all_base_commands
raise_args_error.call(args, nil) if (args.empty? && requires_args) || (!args.empty? && !requires_args)

begin
config = Config.new(args, options)
config = Config.new(args, options, required_options)

show_info_header(config) if with_info_header

Expand Down

0 comments on commit e56e33f

Please sign in to comment.