Skip to content
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
2 changes: 1 addition & 1 deletion Gemfile.lock
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
PATH
remote: .
specs:
envirobly (1.9.0)
envirobly (1.10.0)
activesupport (~> 8.0)
aws-sdk-s3 (~> 1.182)
concurrent-ruby (~> 1.3)
Expand Down
6 changes: 3 additions & 3 deletions lib/envirobly/cli/main.rb
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,14 @@ def set_default_region
Envirobly::Defaults::Region.new(shell:).require_value
end

desc "validate", "Validates config"
def validate
desc "validate", "Validates config (for given environ)"
def validate(environ_name = nil)
Envirobly::AccessToken.new(shell:).require!

config = Envirobly::Config.new
api = Envirobly::Api.new

params = { validation: { configs: config.configs } }
params = { validation: { config: config.merge(environ_name).to_yaml } }
api.validate_shape params

say "Config is valid #{green_check}"
Expand Down
70 changes: 53 additions & 17 deletions lib/envirobly/config.rb
Original file line number Diff line number Diff line change
@@ -1,26 +1,62 @@
# frozen_string_literal: true

class Envirobly::Config
DIR = ".envirobly"
BASE = "deploy.yml"
OVERRIDES_PATTERN = /deploy\.([a-z0-9\-_]+)\.yml/i
module Envirobly
class Config
DIR = ".envirobly"
BASE = "deploy.yml"
OVERRIDES_PATTERN = /deploy\.([a-z0-9\-_]+)\.yml/i

def initialize(dir = DIR)
@dir = Pathname.new dir
end
attr_reader :errors

def configs
Dir.entries(@dir).map do |file|
path = File.join(@dir, file)
def initialize(dir = DIR)
@dir = Pathname.new dir
@errors = []
end

next unless File.file?(path) && config_file?(file)
def configs
Dir.entries(@dir).map do |file|
path = File.join(@dir, file)

[ "#{DIR}/#{file}", ERB.new(File.read(path)).result ]
end.compact.to_h
end
next unless File.file?(path) && config_file?(file)

[ "#{DIR}/#{file}", File.read(path) ]
end.compact.to_h
end

def merge(environ_name = nil)
path = Pathname.new(DIR).join(BASE).to_s
yaml = configs.fetch(path)
result = parse yaml, path

private
def config_file?(file)
file == BASE || file.match?(OVERRIDES_PATTERN)
if environ_name.present?
override_path = Pathname.new(DIR).join("deploy.#{environ_name}.yml").to_s

if configs.key?(override_path)
other_yaml = configs.fetch(override_path)
override = parse other_yaml, override_path
result = result.deep_merge(override) if override.is_a?(Hash)
end
end

@errors.empty? ? result : nil
end

private
def config_file?(file)
file == BASE || file.match?(OVERRIDES_PATTERN)
end

def parse(content, path)
begin
yaml = ERB.new(content).result
rescue Exception => e
@errors << { message: e.message, path: }
return
end

YAML.safe_load yaml, aliases: true, permitted_classes: [ Secret ]
rescue Psych::Exception => e
@errors << { message: e.message, path: }
end
end
end
27 changes: 24 additions & 3 deletions lib/envirobly/deployment.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ module Envirobly
class Deployment
include Colorize

attr_reader :params
attr_reader :params, :shell

def initialize(environ_name:, commit:, account_id:, project_name:, project_id:, region:, shell:)
@commit = commit
Expand Down Expand Up @@ -53,19 +53,40 @@ def initialize(environ_name:, commit:, account_id:, project_name:, project_id:,
commit_time: @commit.time,
commit_message: @commit.message,
object_tree_checksum: @commit.object_tree_checksum,
configs: @config.configs
config: @config.merge(@environ_name).to_yaml
}
}
end

def perform(dry_run:)
if dry_run
shell.say "This is a dry run, nothing will be deployed.", :green
end

# TODO: Replace with shell
puts [ "Deploying commit", yellow(@commit.short_ref), faint("→"), green(@environ_name) ].join(" ")
puts
# TODO: Multiline indent
puts " #{@commit.message}"
puts

if dry_run
puts YAML.dump(@params)
puts green("Config:")
puts @params[:deployment][:config]

shell.say
shell.say "Targeting:", :green

targets_and_values = [
[ "Account ID", @params[:account_id].to_s ],
[ "Project ID", @params[:project_id].to_s ],
[ "Region", @params[:region] ],
[ "Project Name", @params[:project_name] ],
[ "Environ Name", @params[:deployment][:environ_name] ]
]

shell.print_table targets_and_values, borders: true

return
end

Expand Down
29 changes: 29 additions & 0 deletions lib/envirobly/secret.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# frozen_string_literal: true

module Envirobly
class Secret
attr_reader :plain

def initialize(value)
@plain = value
end

def init_with(coder)
@plain = coder.scalar
end

def encode_with(coder)
coder.scalar = @plain
end

def to_s
"[SECRET]"
end

def ==(other)
other.is_a?(Secret) && other.plain == plain
end
end
end

YAML.add_tag("!secret", Envirobly::Secret)
2 changes: 1 addition & 1 deletion lib/envirobly/version.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# frozen_string_literal: true

module Envirobly
VERSION = "1.9.0"
VERSION = "1.10.0"
end
67 changes: 67 additions & 0 deletions test/envirobly/config_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,71 @@ class Envirobly::ConfigTest < ActiveSupport::TestCase
}
assert_equal expected, config.configs
end

test "merge without override" do
config = Envirobly::Config.new("test/fixtures/configs")
expected = {
"x" => {
"shared_env" => {
"SECRET" => Envirobly::Secret.new("mySecret")
}
},
"services" => {
"app" => {
"public" => true,
"env" => {
"SECRET" => Envirobly::Secret.new("mySecret"),
"APP_ENV" => "production"
}
}
}
}

assert_equal expected, config.merge
assert_empty config.errors

assert_equal expected, config.merge("xyz"), "This override doens't exist"
assert_empty config.errors
end

test "merge with environ override" do
config = Envirobly::Config.new("test/fixtures/configs")
expected = {
"x" => {
"shared_env" => {
"SECRET" => Envirobly::Secret.new("mySecret")
}
},
"services" => {
"app" => {
"public" => false,
"instance_type" => "t4g.micro",
"env" => {
"SECRET" => Envirobly::Secret.new("mySecret"),
"APP_ENV" => "staging",
"STAGING_VAR" => "abcd"
}
}
}
}
assert_equal expected, config.merge("staging")
assert_empty config.errors
end

test "invalid YAML" do
config = Envirobly::Config.new("test/fixtures/invalid_configs")
assert_nil config.merge
assert_equal 1, config.errors.size
assert_equal "(<unknown>): did not find expected node content while parsing a flow node at line 2 column 3", config.errors.first[:message]
assert_equal ".envirobly/deploy.yml", config.errors.first[:path]
end

test "invalid ERB" do
config = Envirobly::Config.new("test/fixtures/invalid_configs")
assert_nil config.merge("erb-error")
assert_equal 2, config.errors.size
assert_equal ".envirobly/deploy.yml", config.errors.first[:path]
assert_equal ".envirobly/deploy.erb-error.yml", config.errors.second[:path]
assert_equal "uncaught throw \"ERB error\"", config.errors.second[:message]
end
end
23 changes: 23 additions & 0 deletions test/envirobly/secret_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# frozen_string_literal: true

require "test_helper"

module Envirobly
class SecretTest < ActiveSupport::TestCase
test "equality" do
secret1 = Secret.new("a")
secret2 = Secret.new("a")
assert_equal secret1, secret2

secret2 = Secret.new("b")
assert_not_equal secret1, secret2
end

test "dump" do
yaml = {
secret: Secret.new("hello")
}.to_yaml
assert_equal "---\n:secret: !secret hello\n", yaml
end
end
end
4 changes: 4 additions & 0 deletions test/fixtures/configs/deploy.staging.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
services:
app:
public: <%= "false" if true %>
instance_type: t4g.micro
env:
APP_ENV: staging
STAGING_VAR: abcd
9 changes: 7 additions & 2 deletions test/fixtures/configs/deploy.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
project: https://envirobly.com/projects/1
x:
shared_env: &shared_env
SECRET: !secret mySecret

services:
app:
public: true
public: <%= "true" %>
env:
<<: *shared_env
APP_ENV: production
Empty file.
1 change: 1 addition & 0 deletions test/fixtures/configs/ignored.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
This file is ignored by Config class.
Empty file.
Empty file.
3 changes: 3 additions & 0 deletions test/fixtures/configs/nirvana/deploy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
services:
app:
public: false
1 change: 1 addition & 0 deletions test/fixtures/invalid_configs/deploy.erb-error.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
services: <% throw "ERB error" %>
2 changes: 2 additions & 0 deletions test/fixtures/invalid_configs/deploy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
invalid_yaml: {
-