From 47e4dfa1350845bdb24055286797f5645bb79972 Mon Sep 17 00:00:00 2001 From: Ly Minh Phuong Date: Thu, 15 Jun 2017 10:28:25 +0700 Subject: [PATCH] #321 #314 #302 should implement project specific config support feature #326 --- Vagrantfile | 65 ++++++++++++--- lib/utility.rb | 132 ++++++++++++++++++------------- spec/fixture/config_overide.json | 12 +++ spec/fixture/org_project.json | 49 ++++++++++++ spec/fixture/project1.json | 63 +++++++++++++++ spec/fixture/project2.json | 61 ++++++++++++++ spec/utility_spec.rb | 129 ++++++++++++++++++++++++++++++ vagrant_config.json | 11 +++ 8 files changed, 459 insertions(+), 63 deletions(-) create mode 100644 spec/fixture/config_overide.json create mode 100644 spec/fixture/org_project.json create mode 100644 spec/fixture/project1.json create mode 100644 spec/fixture/project2.json diff --git a/Vagrantfile b/Vagrantfile index a5c3ee9d..406fd7ea 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -4,28 +4,75 @@ require 'json' load File.dirname(__FILE__) + '/lib/utility.rb' load File.dirname(__FILE__) + '/lib/provisioner.rb' + # Load default setting file = File.read(File.dirname(__FILE__) + '/vagrant_config.json') data_hash = JSON.parse(file) override_hash = nil # Check and override if exist any match JSON object from vagrant_config_override.json -if File.exist? (File.dirname(__FILE__) + '/vagrant_config_override.json') - override_file = File.read(File.dirname(__FILE__) + '/vagrant_config_override.json') - begin +begin + if File.exist?(File.dirname(__FILE__) + '/vagrant_config_override.json') + parsing_file = File.dirname(__FILE__) + '/vagrant_config_override.json' + override_file = File.read(parsing_file) override_hash = JSON.parse(override_file) + data_hash = overrides(data_hash, override_hash) - rescue Exception => msg - puts red(msg) - puts red('from vagrant_config_override.json') - ans = prompt yellow("some errors have occured and 'vagrant_config_override.json' file will not be used, do you want to continue? [y/N]: ") - if ans.downcase != 'y' - exit! + end + + if File.exist?(File.dirname(__FILE__) + '/workspace/dev-setup/vagrant_config_default.json') + parsing_file = File.dirname(__FILE__) + '/workspace/dev-setup/vagrant_config_default.json' + org_config_file = File.read(parsing_file) + org_config_hash = JSON.parse(org_config_file) + + overide_config_file_path = parsing_file.gsub(/default\.json$/, "overide.json") + if File.exist?(overide_config_file_path) + override_config_file = File.read(overide_config_file_path) + parsing_file = overide_config_file_path + overide_config_hash = JSON.parse(override_config_file) + org_config_hash = overrides(org_config_hash, overide_config_hash) + end + + if !org_config_hash.nil? + overrides(data_hash, org_config_hash) + end + end + + + if data_hash['vagrant'] && data_hash['vagrant']['config_paths'] + data_hash['vagrant']['config_paths'].map do |default_config_file_path| + overide_config_file_path = default_config_file_path.gsub(/default\.json$/, "overide.json") + if File.exist?(default_config_file_path) + default_config_file = File.read(default_config_file_path) + parsing_file = default_config_file_path + project_config_hash = JSON.parse(default_config_file) + else + puts red('Error read file ' + default_config_file_path + '. Please check if that file exist. Exiting.' ) + exit! + end + + if File.exist?(overide_config_file_path) + override_config_file = File.read(overide_config_file_path) + parsing_file = overide_config_file_path + overide_config_hash = JSON.parse(override_config_file) + project_config_hash = overrides(project_config_hash, overide_config_hash) + end + if !project_config_hash.nil? + overrides(data_hash, project_config_hash) + end end end +rescue Exception => msg + puts red(msg) + puts red('from ' + parsing_file) + ans = prompt yellow("some errors have occured and '" + parsing_file + "' file will not be used, do you want to continue? [y/N]: ") + if ans.downcase != 'y' + exit! + end end + Vagrant.configure("2") do |config| vm_hash = data_hash["vm"] diff --git a/lib/utility.rb b/lib/utility.rb index 49ac3028..c5b4a9e1 100644 --- a/lib/utility.rb +++ b/lib/utility.rb @@ -1,73 +1,97 @@ # Utility functions def overrides(obj1, obj2) obj2.each do |key, value| + replaced_key = key.to_s.sub(/_u?[ra]_/, '') + + if !obj1.has_key?(replaced_key) + if value.class.name == 'Hash' + obj1[key] = {} + elsif value.class.name == 'Array' + obj1[replaced_key] = [] + end + end + # replace - replaced_key = key.sub(/_r_/, '') - if key.start_with?('_r_') and obj1.has_key?(replaced_key) and value.class.name == 'Array' - obj1[replaced_key] = [] - key = replaced_key + if value.class.name == 'Array' + if (key.start_with?('_r_') || key.start_with?('_a_') || key.start_with?('_ua_')) && obj1.has_key?(key) + obj1[replaced_key] = obj1[key].clone + obj1.delete(key) + end + + if (key.start_with?('_r_') || key.start_with?('_a_') || key.start_with?('_ua_')) && !obj1.has_key?(replaced_key) + obj1[replaced_key] = [] + key = replaced_key + else + if key.start_with?('_r_') + obj1[replaced_key] = [] + key = replaced_key + elsif key.start_with?('_a_') + value = obj1[replaced_key].concat(obj2[key]) + key = replaced_key + elsif key.start_with?('_ua_') + value = (obj1[replaced_key].concat(obj2[key])).uniq + key = replaced_key + end + end end - if obj1.has_key?(key) - if value.class.name == 'Hash' - obj1[key] = overrides(obj1[key], obj2[key]) - elsif value.class.name == 'Array' - obj1_value = obj1[key].clone - if value[0].class.name != 'Hash' - obj1_value = value - else - value.map! do |val| - if val.class.name == 'Hash' - id_existing = false - obj1[key].each do |val1| - if val1['_id'] == val['_id'] - id_existing = true - break - end + if value.class.name == 'Hash' + obj1[key] = overrides(obj1[key], obj2[key]) + elsif value.class.name == 'Array' + obj1_value = obj1[key].clone + if value[0].class.name != 'Hash' + obj1_value = value + else + value.map! do |val| + if val.class.name == 'Hash' + id_existing = false + obj1[key].each do |val1| + if val1['_id'] == val['_id'] + id_existing = true + break end - if id_existing == false - if !val['_op'].nil? and val['_op'] != 'a' - # warnings - puts yellow("_op = #{val['_op']} is invalid for non-existing id: #{val}") - end - val['_op'] = 'a' - elsif val['_op'].nil? - val['_op'] = 'o' + end + if id_existing == false + if !val['_op'].nil? and val['_op'] != 'a' + # warnings + puts yellow("_op = #{val['_op']} is invalid for non-existing id: #{val}") end + val['_op'] = 'a' + elsif val['_op'].nil? + val['_op'] = 'o' + end - if val['_op'] == 'a' - if val['_idx'].nil? - obj1_value.push(val) - else - obj1_value.insert(val['_idx'], val) - end - elsif val['_op'] == 'o' - obj1_value.map! do |val2| - if val2['_id'] == val['_id'] - val2 = overrides(val2, val) - end - val2 + if val['_op'] == 'a' + if val['_idx'].nil? + obj1_value.push(overrides({}, val)) + else + obj1_value.insert(val['_idx'], overrides({}, val)) + end + elsif val['_op'] == 'o' + obj1_value.map! do |val2| + if val2['_id'] == val['_id'] + val2 = overrides(val2, val) end - elsif val['_op'] == 'r' - obj1_value.map! do |val3| - if val3['_id'] == val['_id'] - val3 = val - end - val3 + val2 + end + elsif val['_op'] == 'r' + obj1_value.map! do |val3| + if val3['_id'] == val['_id'] + val3 = overrides({}, val) end - elsif val['_op'] == 'd' - obj1_value.delete_if {|val4| val4['_id'] == val['_id'] } + val3 end - else - obj1_value = value + elsif val['_op'] == 'd' + obj1_value.delete_if {|val4| val4['_id'] == val['_id'] } end - val + else + obj1_value = value end + val end - obj1[key] = obj1_value - else - obj1[key] = value end + obj1[key] = obj1_value + else # merge key here obj1[key] = value diff --git a/spec/fixture/config_overide.json b/spec/fixture/config_overide.json new file mode 100644 index 00000000..880cd61f --- /dev/null +++ b/spec/fixture/config_overide.json @@ -0,0 +1,12 @@ +{ + "vm":{ + "synced_folders":[{ //see: http://docs.vagrantup.com/v2/synced-folders/index.html + "_id": "0", + "_op": "d" + }, { + "_id": "1", + "_op": "d" + } + ] + } +} \ No newline at end of file diff --git a/spec/fixture/org_project.json b/spec/fixture/org_project.json new file mode 100644 index 00000000..d491c736 --- /dev/null +++ b/spec/fixture/org_project.json @@ -0,0 +1,49 @@ +{ + "vm": { + "networks": [{ + "_id": "0", + "bridge": [ + // If it asks for your network interface, add it the right network interface here + // to choose it by default, you don't have to choose again when $ vagrant reload + // note that if you choose the inactive network interface, problems could happen + // then uncomment the line below and $ vagrant reload to select the right one + //"en0: Wi-Fi (AirPort)" + ] + }] + }, + "vagrant": { + "config_paths": [ + "workspace/angular-hello-world/vagrant_config.default.json", + "workspace/nextjs-hello-world/vagrant_config.default.json" + ] + }, + "vb": { + "memory": 2048, // need to adjust for the right optimal MEM + "cpus": 2 + }, + "provisioners": [{ + "_id": "0", + "_a_cookbooks_path": [ + "workspace/dev-setup/chef/main-cookbooks" + ], + "_ua_run_list": [ + "vim", + "teracy-dev", + "acme" + ], + "json": { + "teracy-dev": { + "aliases": [ { + "name": "http", // `$ http` to make http requests, how to use: https://github.com/teracyhq/httpie-jwt-auth + "command": "docker container run -it --rm --net=host teracy/httpie-jwt-auth:latest-alpine", + "action": "add" + }], + "proxy": { + "container": { + "enabled": true + } + } + } + } + }] +} diff --git a/spec/fixture/project1.json b/spec/fixture/project1.json new file mode 100644 index 00000000..63833358 --- /dev/null +++ b/spec/fixture/project1.json @@ -0,0 +1,63 @@ +{ + "vm": { + "synced_folders":[{ + "_id": "100", + "type": "rsync", + "host": "./workspace/angular-hello-world", + "guest": "/home/vagrant/workspace/angular-hello-world", + "rsync__exclude": [".git", ".idea/", "node_modules/", "bower_components/", ".npm/"] + }, { + "_id": "100", + "type": "virtual_box", + "host": "workspace/angular-hello-world/node_modules", + "guest": "/home/vagrant/workspace/angular-hello-world/node_modules", + "mount_options": [ + "dmode=775", + "fmode=755" + ] + }, { + // enable this to sync /dist back and forth to the host automatically + "_id": "101", + "type": "virtual_box", + "host": "workspace/angular-hello-world/dist", + "guest": "/home/vagrant/workspace/angular-hello-world/dist", + "mount_options": [ + "dmode=775", + "fmode=755" + ] + }] + }, + + "provisioners": [{ + "_id": "0", + "_a_cookbooks_path": [ + "workspace/angular-hello-world/dev-setup/chef/main-cookbooks" + ], + "_ua_run_list": [ + "angular-hello-world" + ], + "json": { + "teracy-dev": { + "aliases": [ { + "_id": "100", + "name": "ahw", // `$ ahw` to cd right into the angular-hello-world project + "command": "cd ~/workspace/angular-hello-world", + "action": "add" + }] + }, + "acme": { + "angular-hello-world": { + "project_guest_path": "/home/vagrant/workspace/angular-hello-world" + } + } + } + }], + "plugins": [{ + "_id": "2", + "options": { + "_ua_aliases": [ + "dev.ahw.acme.dev", "review.ahw.acme.dev", "ahw.acme.dev" + ] + } + }] +} diff --git a/spec/fixture/project2.json b/spec/fixture/project2.json new file mode 100644 index 00000000..83a235bf --- /dev/null +++ b/spec/fixture/project2.json @@ -0,0 +1,61 @@ +{ + "vm": { + "synced_folders":[{ + "_id": "200", + "type": "rsync", + "host": "./workspace/nextjs-hello-world", + "guest": "/home/vagrant/workspace/nextjs-hello-world", + "rsync__exclude": [".git", ".idea/", "node_modules/", "bower_components/", ".npm/"] + }, { + "id": "201", + "type": "virtual_box", + "host": "workspace/nextjs-hello-world/node_modules", + "guest": "/home/vagrant/workspace/nextjs-hello-world/node_modules", + "mount_options": [ + "dmode=775", + "fmode=755" + ] + }, { // enable this to sync /.next back and forth to the host automatically + "id": "202", + "type": "virtual_box", + "host": "workspace/nextjs-hello-world/.next", + "guest": "/home/vagrant/workspace/nextjs-hello-world/.next", + "mount_options": [ + "dmode=775", + "fmode=755" + ] + }] + }, + "provisioners": [{ + "_id": "0", + "_a_cookbooks_path": [ + "workspace/nextjs-hello-world/dev-setup/chef/main-cookbooks" + ], + "_ua_run_list": [ + "nextjs-hello-world" + ], + "json": { + "teracy-dev": { + "aliases": [ { + "_id": "200", + "name": "nhw", // `$ nhw` to cd right into the nextjs-hello-world project + "command": "cd ~/workspace/nextjs-hello-world", + "action": "add" + }] + }, + "acme": { + "nextjs-hello-world": { + "project_guest_path": "/home/vagrant/workspace/nextjs-hello-world" + } + } + } + }], + "plugins": [{ + "_id": "2", + "options": { + "_ua_aliases": [ + "dev.nextjs.acme.dev", "review.nextjs.acme.dev", "nextjs.acme.dev" + ] + } + }] +} diff --git a/spec/utility_spec.rb b/spec/utility_spec.rb index 5c52185d..8b983f88 100644 --- a/spec/utility_spec.rb +++ b/spec/utility_spec.rb @@ -287,5 +287,134 @@ expect(new_provisioners[0]['run_list']).to eql(obj2['provisioners'][0]['run_list']) end end + context "given an object then override it with another object containing an array" do + it "all array name in new and old array must be normalized" do + obj1 = {} + obj2 = { + "provisioners" => [{ + "_id" => "0", + "_a_run_list" => [] + }] + } + new_provisioners = overrides(obj1, obj2)['provisioners'] + expect(new_provisioners[0]['run_list']).to eql(obj2['provisioners'][0]['_a_run_list']) + end + it "all array name in new and old array must be normalized and value must be appended" do + obj1 = {} + obj2 = { + "provisioners" => [{ + "_id" => "0", + "_a_run_list" => ["vagrant", "vim", "teracy"] + }] + } + new_provisioners = overrides(obj1, obj2)['provisioners'] + expect(new_provisioners[0]['run_list']).to eql(obj2['provisioners'][0]['_a_run_list']) + end + it "all array name in new and old array must be normalized and value must be appended" do + obj1 = { + "provisioners" => [{ + "_id" => "0", + "_a_run_list" => ["vagrant", "vim", "helloworld"] + }] + } + obj2 = { + "provisioners" => [{ + "_id" => "0", + "_a_run_list" => ["vagrant", "vim", "teracy"] + }] + } + new_provisioners = overrides(obj1, obj2)['provisioners'] + expect(new_provisioners[0]['run_list']).to eql(["vagrant", "vim", "helloworld", "vagrant", "vim", "teracy"]) + end + end + + context "given two objects then unique override it with another object contain array" do + it "all array name in new and old array must be normalized and valued must be merged" do + obj1 = { + "provisioners" => [{ + "_id" => "0", + "_ua_run_list" => ["vim", "teracy", "widget", "testsuite"] + }] + } + + obj2 = { + "provisioners" => [{ + "_id" => "0", + "_ua_run_list" => ["vagrant", "vim", "teracy"] + }] + } + obj1_origin = obj1.clone + + new_provisioners = overrides(obj1, obj2)['provisioners'] + expect(new_provisioners[0]['run_list']).to eql(["vim", "teracy", "widget", "testsuite", "vagrant"]) + end + it "all array name in new and old array must be normalized and value must be merged" do + obj1 = { + "provisioners" => [{ + "_id" => "0", + "run_list" => ["vim", "teracy", "widget", "testsuite"] + }] + } + + obj2 = { + "provisioners" => [{ + "_id" => "0", + "_ua_run_list" => ["vagrant", "vim", "teracy"] + }] + } + obj1_origin = obj1.clone + + new_provisioners = overrides(obj1, obj2)['provisioners'] + expect(new_provisioners[0]['run_list']).to eql(["vim", "teracy", "widget", "testsuite", "vagrant"]) + end + it "all array name in new and old array must be normalize" do + obj1 = {} + obj2 = { + "provisioners" => [{ + "_id" => "0", + "_a_run_list" => ["hi", "there"] + }] + } + new_provisioners = overrides(obj1, obj2)['provisioners'] + + expect(new_provisioners[0]['run_list']).to eql(obj2['provisioners'][0]['_a_run_list']) + end + end + + context "Giving many config file follow project base config requirement" do + it "after override the config must satisfy the requirement" do + teracy_default_config = JSON.parse(File.read(File.dirname(__FILE__) + '/fixture/config.json')) + teracy_override_config = JSON.parse(File.read(File.dirname(__FILE__) + '/fixture/config_overide.json')) + project_org_config = JSON.parse(File.read(File.dirname(__FILE__) + '/fixture/org_project.json')) + project1_config = JSON.parse(File.read(File.dirname(__FILE__) + '/fixture/project1.json')) + project2_config = JSON.parse(File.read(File.dirname(__FILE__) + '/fixture/project2.json')) + + origin_teracy_default_config = JSON.parse(File.read(File.dirname(__FILE__) + '/fixture/config.json')) + origin_teracy_override_config = JSON.parse(File.read(File.dirname(__FILE__) + '/fixture/config_overide.json')) + origin_project_org_config = JSON.parse(File.read(File.dirname(__FILE__) + '/fixture/org_project.json')) + origin_project1_config = JSON.parse(File.read(File.dirname(__FILE__) + '/fixture/project1.json')) + origin_project2_config = JSON.parse(File.read(File.dirname(__FILE__) + '/fixture/project2.json')) + + final_config = overrides(teracy_default_config, teracy_override_config) + final_config = overrides(final_config, project_org_config) + final_config = overrides(final_config, project1_config) + final_config = overrides(final_config, project2_config) + + expect(final_config['vm']['synced_folders'].length).to eql(origin_teracy_default_config['vm']['synced_folders'].length - 2 + + origin_project1_config['vm']['synced_folders'].length + + origin_project2_config['vm']['synced_folders'].length) + + expect(final_config['vm']['synced_folders'][6]['guest']).to eql(origin_project2_config['vm']['synced_folders'][2]['guest']) + expect(final_config['vm']['synced_folders'][6]['host']).to eql(origin_project2_config['vm']['synced_folders'][2]['host']) + expect(final_config['vm']['synced_folders'][6]['id']).to eql(origin_project2_config['vm']['synced_folders'][2]['id']) + + expect(final_config['vm']['synced_folders'][6]['guest']).to eql(origin_project2_config['vm']['synced_folders'][2]['guest']) + expect(final_config['vm']['synced_folders'][6]['host']).to eql(origin_project2_config['vm']['synced_folders'][2]['host']) + expect(final_config['vm']['synced_folders'][6]['id']).to eql(origin_project2_config['vm']['synced_folders'][2]['id']) + + expect(final_config['plugins'][2]['options']['aliases'].length).to eql(origin_project1_config['plugins'][0]['options']['_ua_aliases'].length + + origin_project2_config['plugins'][0]['options']['_ua_aliases'].length) + end + end end end diff --git a/vagrant_config.json b/vagrant_config.json index d1d180c7..dffc6e74 100644 --- a/vagrant_config.json +++ b/vagrant_config.json @@ -139,9 +139,20 @@ "action": "add" // one of add, remove. Default: add. // See more: https://github.com/customink-webops/magic_shell/blob/master/resources/alias.rb }, { + "_id": "1", "name": "ctop", // `$ ctop` for top-like interface for container metrics, see: https://github.com/bcicen/ctop "command": "docker run -ti --name ctop --rm -v /var/run/docker.sock:/var/run/docker.sock quay.io/vektorlab/ctop:latest", "action": "add" + },{ + "_id": "2", + "name": "http", // `$ http` to make http requests, how to use: https://github.com/teracyhq/httpie-jwt-auth + "command": "docker container run -it --rm --net=host teracy/httpie-jwt-auth:latest-alpine", + "action": "add" + }, { + "_id": "3", + "name": "https", // shortcut for making https requests + "command": "http --default-scheme=https", + "action": "add" }], "env_vars": [{ "_id": "0",