Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ability to allow and deny envs, regions, and stacks #194

Merged
merged 1 commit into from
Jan 23, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 12 additions & 2 deletions lib/terraspace/app.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,26 @@ def initialize

def defaults
config = ActiveSupport::OrderedOptions.new

config.all = ActiveSupport::OrderedOptions.new
config.all.concurrency = 5
config.all.exit_on_fail = ActiveSupport::OrderedOptions.new
config.all.exit_on_fail.down = true
config.all.exit_on_fail.up = true
config.all.ignore_stacks = nil
config.all.include_stacks = nil

config.allow = ActiveSupport::OrderedOptions.new
config.allow.envs = nil
config.allow.regions = nil
config.allow.stacks = nil
config.deny = ActiveSupport::OrderedOptions.new
config.deny.envs = nil
config.deny.regions = nil
config.deny.stacks = nil

config.all.exclude_stacks = nil
config.all.include_stacks = nil
config.all.consider_allow_deny_stacks = true

config.auto_create_backend = true
config.build = ActiveSupport::OrderedOptions.new
config.build.cache_dir = ":CACHE_ROOT/:REGION/:ENV/:BUILD_DIR"
Expand Down
58 changes: 58 additions & 0 deletions lib/terraspace/app/callable_option.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# Class represents a terraspace option that is possibly callable. Examples:
#
# config.allow.envs
# config.allow.regions
# config.deny.envs
# config.deny.regions
# config.all.include_stacks
# config.all.exclude_stacks
#
# Abstraction is definitely obtuse. Using it to get rid of duplication.
#
class Terraspace::App
class CallableOption
include Terraspace::Util::Logging

def initialize(options={})
@options = options
# Example:
# config_name: config.allow.envs
# config_value: ["dev"]
# args: [@stack_name] # passed to object.call
@config_name = options[:config_name]
@config_value = options[:config_value]
@passed_args = options[:passed_args]
end

# Returns either an Array or nil
def object
case @config_value
when nil
return nil
when Array
return @config_value
when -> (c) { c.respond_to?(:public_instance_methods) && c.public_instance_methods.include?(:call) }
object= @config_value.new
when -> (c) { c.respond_to?(:call) }
object = @config_value
else
raise "Invalid option for #{@config_name}"
end

if object
result = @passed_args.empty? ? object.call : object.call(*@passed_args)
unless result.is_a?(Array) || result.is_a?(NilClass)
message = "ERROR: The #{@config_name} needs to return an Array or nil"
logger.info message.color(:yellow)
logger.info <<~EOL
The #{@config_name} when assigned a class, object, or proc must implement
the call method and return an Array or nil.
The current return value is a #{result.class}
EOL
raise message
end
end
result
end
end
end
12 changes: 12 additions & 0 deletions lib/terraspace/app/callable_option/concern.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
class Terraspace::App::CallableOption
module Concern
def callable_option(options={})
callable_option = Terraspace::App::CallableOption.new(
config_name: options[:config_name],
config_value: options[:config_value],
passed_args: options[:passed_args],
)
callable_option.object
end
end
end
36 changes: 3 additions & 33 deletions lib/terraspace/builder/allow.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,39 +5,9 @@ def initialize(mod)
end

def check!
messages = []
unless env_allowed?
messages << "This env is not allowed to be used: TS_ENV=#{Terraspace.env}"
messages << "Allowed envs: #{config.allow.envs.join(', ')}"
end
unless region_allowed?
messages << "This region is not allowed to be used: Detected current region=#{current_region}"
messages << "Allowed regions: #{config.allow.regions.join(', ')}"
end
unless messages.empty?
puts "ERROR: The configs do not allow this.".color(:red)
puts messages
exit 1
end
end

def env_allowed?
return true unless config.allow.envs
config.allow.envs.include?(Terraspace.env)
end

def region_allowed?
return true unless config.allow.regions
config.allow.regions.include?(current_region)
end

def current_region
expander = Terraspace::Compiler::Expander.autodetect(@mod).expander
expander.region
end

def config
Terraspace.config
Env.new(@mod).check!
Stack.new(@mod).check!
Region.new(@mod).check!
end
end
end
59 changes: 59 additions & 0 deletions lib/terraspace/builder/allow/base.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
class Terraspace::Builder::Allow
class Base
include Terraspace::App::CallableOption::Concern

def initialize(mod)
@mod = mod # Only Region subclass uses @mod but keeping interface same for Env for simplicity
@stack_name = mod.name
end

def check!
messages = []
unless allowed?
messages << message # message is interface method
end
unless messages.empty?
puts "ERROR: The configs do not allow this.".color(:red)
puts messages
exit 1
end
end

def allowed?
if allows.nil? && denys.nil?
true
elsif denys.nil?
allows.include?(check_value)
elsif allows.nil?
!denys.include?(check_value)
else
allows.include?(check_value) && !denys.include?(check_value)
end
end

def allows
callable_option(
config_name: "config.allow.#{config_name}",
config_value: config.dig(:allow, config_name),
passed_args: [@stack_name],
)
end

def denys
callable_option(
config_name: "config.deny.#{config_name}",
config_value: config.dig(:deny, config_name),
passed_args: [@stack_name],
)
end

private
def config
Terraspace.config
end

def config_name
self.class.to_s.split('::').last.underscore.pluralize.to_sym # ActiveSuport::HashWithIndifferentAccess#dig requires symbol
end
end
end
17 changes: 17 additions & 0 deletions lib/terraspace/builder/allow/env.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
class Terraspace::Builder::Allow
class Env < Base
# interface method
def message
messages = []
messages << "This env is not allowed to be used: TS_ENV=#{Terraspace.env}"
messages << "Allow envs: #{allows.join(', ')}" if allows
messages << "Deny envs: #{denys.join(', ')}" if denys
messages.join("\n")
end

# interface method
def check_value
Terraspace.env
end
end
end
31 changes: 31 additions & 0 deletions lib/terraspace/builder/allow/region.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
class Terraspace::Builder::Allow
class Region < Base
# interface method
def message
messages = []
word = config_name.to_s # IE: regions or locations
messages << "This #{word.singularize} is not allowed to be used: Detected current #{word.singularize}=#{current_region}"
messages << "Allow #{word}: #{allows.join(', ')}" if allows
messages << "Deny #{word}: #{denys.join(', ')}" if denys
messages.join("\n")
end

# interface method
def check_value
current_region
end

def current_region
expander = Terraspace::Compiler::Expander.autodetect(@mod).expander
expander.region
end

def config_name
if config.allow.locations || config.deny.locations
:locations # ActiveSuport::HashWithIndifferentAccess#dig requires symbol
else
super # :regions
end
end
end
end
17 changes: 17 additions & 0 deletions lib/terraspace/builder/allow/stack.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
class Terraspace::Builder::Allow
class Stack < Base
# interface method
def message
messages = []
messages << "This stack is not allowed to be used for TS_ENV=#{Terraspace.env}"
messages << "Allow stacks: #{allows.join(', ')}" if allows
messages << "Deny stacks: #{denys.join(', ')}" if denys
messages.join("\n")
end

# interface method
def check_value
@mod.name
end
end
end
65 changes: 30 additions & 35 deletions lib/terraspace/compiler/select.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
module Terraspace::Compiler
class Select
include Terraspace::App::CallableOption::Concern
include Terraspace::Util::Logging

def initialize(path)
Expand All @@ -22,48 +23,42 @@ def selected?
end

def include_stacks
include_option(:include_stacks)
if config.all.include_stacks
config_name = "config.all.include_stacks"
config_value = config.dig(:all, :include_stacks)
elsif config.all.consider_allow_deny_stacks
config_name = "config.allow.stacks"
config_value = config.dig(:allow, :stacks)
else
return
end
callable_option(
config_name: config_name,
config_value: config_value,
passed_args: [@stack_name],
)
end

def exclude_stacks
include_option(:exclude_stacks)
end

def include_option(name)
option = all[name] # IE: include_stacks or exclude_stacks
option ||= all[:ignore_stacks] if name == :exclude_stacks
case option
when nil
return nil
when Array
return option
when -> (c) { c.respond_to?(:public_instance_methods) && c.public_instance_methods.include?(:call) }
object= option.new
when -> (c) { c.respond_to?(:call) }
object = option
if config.all.exclude_stacks
config_name = "config.all.exclude_stacks"
config_value = config.dig(:all, :exclude_stacks)
elsif config.all.consider_allow_deny_stacks
config_name = "config.deny.stacks"
config_value = config.dig(:deny, :stacks)
else
raise "Invalid option for config.all.#{name}"
end

if object
result = object.call(@stack_name)
unless result.is_a?(Array) || result.is_a?(NilClass)
message = "ERROR: The config.all.#{name} needs to return an Array or nil"
logger.info message.color(:yellow)
logger.info <<~EOL
The config.all.#{name} when assigned a class, object, or proc must implement
the call method and return an Array or nil.
The current return value is a #{result.class}
EOL
raise message
end
return
end
result
callable_option(
config_name: config_name,
config_value: config_value,
passed_args: [@stack_name],
)
end

private
def all
Terraspace.config.all
def config
Terraspace.config
end

def extract_stack_name(path)
Expand All @@ -72,7 +67,7 @@ def extract_stack_name(path)

@@ignore_stacks_deprecation_warning = nil
def ignore_stacks_deprecation_warning
return unless all.ignore_stacks
return unless config.all.ignore_stacks
return if @@ignore_stacks_deprecation_warning
puts <<~EOL.color(:yellow)
DEPRECATED: config.all.ignore_stacks
Expand Down