Skip to content

Commit 137094e

Browse files
Alan YeoPair
authored andcommitted
Add support for Cloud Foundry
Signed-off-by: Gamaliel Amaudruz <gamaudruz@pivotal.io>
1 parent 5e67861 commit 137094e

File tree

16 files changed

+348
-21
lines changed

16 files changed

+348
-21
lines changed

README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,16 @@ filesystem gets recreated from the git sources on each instance refresh. To use
333333

334334
To upload your local values to Heroku you could ran `bundle exec rake config:heroku`.
335335

336+
### Working with Cloud Foundry
337+
338+
Cloud Foundry integration will generate a manifest adding to your CF manifest.yml the defined ENV variables under the `env` section of specified app in the yaml file.
339+
You must specify the app name and optionally the name of your CF manifest file:
340+
341+
bundle exec rake config:cf[app_name, cf_manifest.yml]
342+
343+
The result of this command will have the manifest file name suffixed with the environment you ran the task in. You can then push your app with the generated manifest.
344+
345+
336346
### Fine-tuning
337347

338348
You can customize how environment variables are processed:
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
require 'bundler'
2+
require 'yaml'
3+
require_relative '../../../lib/config/integrations/helpers/cf_manifest_merger'
4+
5+
module Config
6+
module Integrations
7+
class CloudFoundry < Struct.new(:app_name, :file_path)
8+
9+
def invoke
10+
manifest_path = file_path || 'manifest.yml'
11+
file_name, _ext = manifest_path.split('.yml')
12+
13+
manifest_hash = YAML.load(IO.read(File.join(::Rails.root, manifest_path)))
14+
15+
puts "Generating manifest... (base cf manifest: #{manifest_path})"
16+
17+
merged_hash = Config::CFManifestMerger.new(app_name, manifest_hash).add_to_env
18+
19+
target_manifest_path = File.join(::Rails.root, "#{file_name}-#{::Rails.env}.yml")
20+
IO.write(target_manifest_path, merged_hash.to_yaml)
21+
22+
puts "File #{target_manifest_path} generated."
23+
end
24+
25+
end
26+
end
27+
end
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
require_relative 'helpers'
2+
3+
module Config
4+
class CFManifestMerger
5+
include Integrations::Helpers
6+
7+
def initialize(app_name, manifest_hash)
8+
@app_name = app_name
9+
@manifest_hash = manifest_hash
10+
raise ArgumentError.new("Manifest path & app name must be specified") unless @app_name && @manifest_hash
11+
end
12+
13+
def add_to_env
14+
15+
settings_hash = Config.const_get(Config.const_name).to_hash.stringify_keys
16+
17+
prefix_keys_with_const_name_hash = to_dotted_hash(settings_hash, namespace: Config.const_name)
18+
19+
app_hash = @manifest_hash['applications'].detect { |hash| hash['name'] == @app_name }
20+
21+
raise ArgumentError, "Application '#{@app_name}' is not specified in your manifest" if app_hash.nil?
22+
23+
check_conflicting_keys(app_hash['env'], settings_hash)
24+
25+
app_hash['env'].merge!(prefix_keys_with_const_name_hash)
26+
27+
@manifest_hash
28+
end
29+
30+
private
31+
32+
def check_conflicting_keys(env_hash, settings_hash)
33+
conflicting_keys = env_hash.keys & settings_hash.keys
34+
raise ArgumentError.new("Conflicting keys: #{conflicting_keys.join(', ')}") if conflicting_keys.any?
35+
end
36+
37+
end
38+
end
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
module Config::Integrations::Helpers
2+
3+
def to_dotted_hash(source, target: {}, namespace: nil)
4+
raise ArgumentError, "target must be a hash (given: #{target.class.name})" unless target.kind_of? Hash
5+
prefix = "#{namespace}." if namespace
6+
case source
7+
when Hash
8+
source.each do |key, value|
9+
to_dotted_hash(value, target: target, namespace: "#{prefix}#{key}")
10+
end
11+
when Array
12+
source.each_with_index do |value, index|
13+
to_dotted_hash(value, target: target, namespace: "#{prefix}#{index}")
14+
end
15+
else
16+
target[namespace] = source
17+
end
18+
target
19+
end
20+
21+
end

lib/config/integrations/heroku.rb

Lines changed: 7 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
require 'bundler'
2+
require_relative 'helpers/helpers'
23

34
module Config
45
module Integrations
56
class Heroku < Struct.new(:app)
7+
include Integrations::Helpers
8+
69
def invoke
710
puts 'Setting vars...'
811
heroku_command = "config:set #{vars}"
@@ -14,13 +17,13 @@ def invoke
1417
def vars
1518
# Load only local options to Heroku
1619
Config.load_and_set_settings(
17-
Rails.root.join("config", "settings.local.yml").to_s,
18-
Rails.root.join("config", "settings", "#{environment}.local.yml").to_s,
19-
Rails.root.join("config", "environments", "#{environment}.local.yml").to_s
20+
::Rails.root.join("config", "settings.local.yml").to_s,
21+
::Rails.root.join("config", "settings", "#{environment}.local.yml").to_s,
22+
::Rails.root.join("config", "environments", "#{environment}.local.yml").to_s
2023
)
2124

2225
out = ''
23-
dotted_hash = to_dotted_hash Kernel.const_get(Config.const_name).to_hash, {}, Config.const_name
26+
dotted_hash = to_dotted_hash Kernel.const_get(Config.const_name).to_hash, namespace: Config.const_name
2427
dotted_hash.each {|key, value| out += " #{key}=#{value} "}
2528
out
2629
end
@@ -38,22 +41,6 @@ def `(command)
3841
Bundler.with_clean_env { super }
3942
end
4043

41-
def to_dotted_hash(source, target = {}, namespace = nil)
42-
prefix = "#{namespace}." if namespace
43-
case source
44-
when Hash
45-
source.each do |key, value|
46-
to_dotted_hash(value, target, "#{prefix}#{key}")
47-
end
48-
when Array
49-
source.each_with_index do |value, index|
50-
to_dotted_hash(value, target, "#{prefix}#{index}")
51-
end
52-
else
53-
target[namespace] = source
54-
end
55-
target
56-
end
5744
end
5845
end
5946
end

lib/config/integrations/rails/railtie.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ def preload
1515

1616
# Load rake tasks (eg. Heroku)
1717
rake_tasks do
18-
Dir[File.join(File.dirname(__FILE__), '../tasks/*.rake')].each { |f| load f }
18+
Dir[File.join(File.dirname(__FILE__), '../../tasks/*.rake')].each { |f| load f }
1919
end
2020

2121
config.before_configuration { preload }
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
require 'config/integrations/cloud_foundry'
2+
3+
namespace 'config' do
4+
5+
desc 'Create a cf manifest with the env variables defined by config under current environment'
6+
task :'cf', [:app_name, :file_path] => :environment do |_, args|
7+
Config::Integrations::CloudFoundry.new(args[:app_name], args[:file_path]).invoke
8+
end
9+
10+
end

lib/config/tasks/heroku.rake

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
require 'config/integrations/heroku'
22

33
namespace 'config' do
4+
5+
desc 'Upload to Heroku all env variables defined by config under current environment'
46
task :heroku, [:app] => :environment do |_, args|
57
Config::Integrations::Heroku.new(args[:app]).invoke
68
end
9+
710
end

spec/fixtures/cf/cf_conflict.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
DEFAULT_HOST: host
2+
DEFAULT_PORT: port

spec/fixtures/cf/cf_manifest.yml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
applications:
2+
- name: some-cf-app
3+
instances: 1
4+
env:
5+
DEFAULT_HOST: host
6+
DEFAULT_PORT: port
7+
FOO: BAR
8+
9+
- name: app_name
10+
env:
11+
DEFAULT_HOST: host

0 commit comments

Comments
 (0)