From 0f903deced03274ab35805ce6ef6a24aae58619a Mon Sep 17 00:00:00 2001 From: Khash Sajadi Date: Fri, 12 Oct 2018 15:43:58 +0100 Subject: [PATCH] Hello World! --- .gitignore | 50 +++++++ .ruby-gemset | 1 + .ruby-version | 1 + .vscode/settings.json | 5 + Gemfile | 2 + Gemfile.lock | 50 +++++++ LICENSE | 201 +++++++++++++++++++++++++++ README.md | 9 ++ Rakefile | 2 + alterant.gemspec | 34 +++++ bin/alterant | 170 ++++++++++++++++++++++ bin/console | 14 ++ bin/setup | 8 ++ lib/alterant.rb | 6 + lib/alterant/alterant.rb | 59 ++++++++ lib/alterant/classes/classes.rb | 20 +++ lib/alterant/classes/containers.js | 16 +++ lib/alterant/classes/docker_image.js | 51 +++++++ lib/alterant/classes/json_reader.rb | 22 +++ lib/alterant/classes/ports.js | 16 +++ lib/alterant/classes/yaml_reader.rb | 22 +++ lib/alterant/error.rb | 5 + lib/alterant/helpers/jpath.rb | 12 ++ lib/alterant/loader.rb | 11 ++ lib/alterant/utils.rb | 11 ++ lib/alterant/version.rb | 5 + samples/add-service.js | 17 +++ samples/change-port.js | 3 + samples/change-tag.js | 5 + samples/config.yml | 19 +++ samples/multi-part.yml | 24 ++++ samples/namespace.yml | 4 + samples/ports.yml | 14 ++ samples/sidecar.js | 14 ++ samples/sidecar.json | 41 ++++++ samples/sidecar.yml | 21 +++ 36 files changed, 965 insertions(+) create mode 100644 .gitignore create mode 100644 .ruby-gemset create mode 100644 .ruby-version create mode 100644 .vscode/settings.json create mode 100644 Gemfile create mode 100644 Gemfile.lock create mode 100644 LICENSE create mode 100644 README.md create mode 100644 Rakefile create mode 100644 alterant.gemspec create mode 100755 bin/alterant create mode 100755 bin/console create mode 100755 bin/setup create mode 100644 lib/alterant.rb create mode 100644 lib/alterant/alterant.rb create mode 100644 lib/alterant/classes/classes.rb create mode 100644 lib/alterant/classes/containers.js create mode 100644 lib/alterant/classes/docker_image.js create mode 100644 lib/alterant/classes/json_reader.rb create mode 100644 lib/alterant/classes/ports.js create mode 100644 lib/alterant/classes/yaml_reader.rb create mode 100644 lib/alterant/error.rb create mode 100644 lib/alterant/helpers/jpath.rb create mode 100644 lib/alterant/loader.rb create mode 100644 lib/alterant/utils.rb create mode 100644 lib/alterant/version.rb create mode 100644 samples/add-service.js create mode 100644 samples/change-port.js create mode 100644 samples/change-tag.js create mode 100644 samples/config.yml create mode 100644 samples/multi-part.yml create mode 100644 samples/namespace.yml create mode 100644 samples/ports.yml create mode 100644 samples/sidecar.js create mode 100644 samples/sidecar.json create mode 100644 samples/sidecar.yml diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5e1422c --- /dev/null +++ b/.gitignore @@ -0,0 +1,50 @@ +*.gem +*.rbc +/.config +/coverage/ +/InstalledFiles +/pkg/ +/spec/reports/ +/spec/examples.txt +/test/tmp/ +/test/version_tmp/ +/tmp/ + +# Used by dotenv library to load environment variables. +# .env + +## Specific to RubyMotion: +.dat* +.repl_history +build/ +*.bridgesupport +build-iPhoneOS/ +build-iPhoneSimulator/ + +## Specific to RubyMotion (use of CocoaPods): +# +# We recommend against adding the Pods directory to your .gitignore. However +# you should judge for yourself, the pros and cons are mentioned at: +# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control +# +# vendor/Pods/ + +## Documentation cache and generated files: +/.yardoc/ +/_yardoc/ +/doc/ +/rdoc/ + +## Environment normalization: +/.bundle/ +/vendor/bundle +/lib/bundler/man/ + +# for a library or gem, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# Gemfile.lock +# .ruby-version +# .ruby-gemset + +# unless supporting rvm < 1.11.0 or doing something fancy, ignore this: +.rvmrc diff --git a/.ruby-gemset b/.ruby-gemset new file mode 100644 index 0000000..24f96d1 --- /dev/null +++ b/.ruby-gemset @@ -0,0 +1 @@ +alterant diff --git a/.ruby-version b/.ruby-version new file mode 100644 index 0000000..6bf7c6f --- /dev/null +++ b/.ruby-version @@ -0,0 +1 @@ +ruby-2.5.1 diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..ea04a5a --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "cSpell.words": [ + "fqin" + ] +} \ No newline at end of file diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000..851fabc --- /dev/null +++ b/Gemfile @@ -0,0 +1,2 @@ +source 'https://rubygems.org' +gemspec diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 0000000..f1d0559 --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,50 @@ +PATH + remote: . + specs: + alterant (0.0.1) + colorize (~> 0.8) + diffy (~> 3.2) + json (~> 1.4) + jsonpath (~> 0.9) + mini_racer (~> 0.2) + thor (~> 0.20) + +GEM + remote: https://rubygems.org/ + specs: + colorize (0.8.1) + diffy (3.2.1) + ffi (1.9.25) + json (1.8.6) + jsonpath (0.9.4) + multi_json + to_regexp (~> 0.2.1) + libv8 (6.7.288.46.1) + listen (3.1.5) + rb-fsevent (~> 0.9, >= 0.9.4) + rb-inotify (~> 0.9, >= 0.9.7) + ruby_dep (~> 1.2) + mini_racer (0.2.3) + libv8 (>= 6.3) + multi_json (1.13.1) + rake (10.5.0) + rb-fsevent (0.10.3) + rb-inotify (0.9.10) + ffi (>= 0.5.0, < 2) + rerun (0.13.0) + listen (~> 3.0) + ruby_dep (1.5.0) + thor (0.20.0) + to_regexp (0.2.1) + +PLATFORMS + ruby + +DEPENDENCIES + alterant! + bundler (~> 1.14) + rake (~> 10.0) + rerun (~> 0.13) + +BUNDLED WITH + 1.16.6 diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md new file mode 100644 index 0000000..48eff52 --- /dev/null +++ b/README.md @@ -0,0 +1,9 @@ +![Alterant Logo](https://s3.amazonaws.com/cdn.cloud66.com/images/alterant_logo.png) + +# Alterant + +Alterant is a tool to modify JSON and YAML configuration files. + +```bash +alterant modify --in config.yml --out output.yml --modifier script.js +``` diff --git a/Rakefile b/Rakefile new file mode 100644 index 0000000..43022f7 --- /dev/null +++ b/Rakefile @@ -0,0 +1,2 @@ +require "bundler/gem_tasks" +task :default => :spec diff --git a/alterant.gemspec b/alterant.gemspec new file mode 100644 index 0000000..89432bf --- /dev/null +++ b/alterant.gemspec @@ -0,0 +1,34 @@ + +# coding: utf-8 +lib = File.expand_path('../lib', __FILE__) +$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) + +require 'alterant/version' + +Gem::Specification.new do |spec| + spec.name = "alterant" + spec.version = Alterant::VERSION + spec.authors = ["Khash Sajadi"] + spec.email = ["khash@cloud66.com"] + + spec.summary = %q{Alterant gem and command line} + spec.description = %q{Alterant is a tool to alter configuration files} + spec.homepage = "https://github.com/cloud66/alterant" + spec.license = 'Nonstandard' + + spec.files = Dir.glob("{bin,lib}/**/*") + %w(README.md) + spec.bindir = "bin" + spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) } + spec.require_paths = ["lib"] + + spec.add_development_dependency "bundler", "~> 1.14" + spec.add_development_dependency "rake", "~> 10.0" + spec.add_development_dependency "rerun", "~> 0.13" + + spec.add_dependency 'jsonpath', '~>0.9' + spec.add_dependency 'json', '~> 1.4' + spec.add_dependency 'thor', '~> 0.20' + spec.add_dependency 'mini_racer', '~> 0.2' + spec.add_dependency 'colorize', '~> 0.8' + spec.add_dependency 'diffy', '~> 3.2' +end diff --git a/bin/alterant b/bin/alterant new file mode 100755 index 0000000..6a1dfe7 --- /dev/null +++ b/bin/alterant @@ -0,0 +1,170 @@ +#!/usr/bin/env ruby +require 'thor' +require 'yaml' +require 'colorize' +require_relative '../lib/alterant' + +module Alterant + class AlterantCLI < Thor + package_name "alterant" + + desc "version", "Show Alterant version" + def version + say "#{::Alterant::APP_NAME} #{::Alterant::VERSION}\n#{::Alterant::COPYRIGHT_MESSAGE}" + end + + desc "update", "Update Alterant to the latest version" + option :version, type: :string, desc: "Force a specific version" + def update + say "Updating Alterant..." + unless options[:version] + say `gem install alterant --no-ri --no-rdoc` + else + say `gem install alterant -v #{options[:version]} --no-ri --no-rdoc` + end + end + + desc "modify", "Runs the given script against a file" + option :modifier, type: :string, desc: "Alterant JS script" + option :in, type: :string, desc: "Input configuration file" + option :out, type: :string, desc: "Output configuration file" + option :output_format, type: :string, enum: ['yaml', 'json'], default: 'yaml', desc: "Output format" + option :diff, type: :boolean, default: false, desc: "Return the diff instead of the output itself" + option :debug, type: :boolean, default: false + option :input_format, type: :string, enum: ['yaml', 'json'], default: 'yaml', desc: "Input format if it's based on a stream and not a file" + option :overwrite, type: :boolean, default: false + def modify + $debug = options[:debug] || false + overwrite = options[:overwrite] + diff = options[:diff] + output_format = options[:output_format] + input_format = options[:input_format] + + in_file = options[:in] + if !in_file + STDERR.puts "No input file provided. Use --in option".red + exit(1) + end + if in_file != '-' + unless File.exists?(in_file) + STDERR.puts "Input file #{in_file} not found".red + exit(1) + end + # detect the type + input_ext = File.extname(in_file) + unless ['.yaml', '.yml', '.json'].include? input_ext + STDERR.puts "Only yaml and json files are supported for input".red + exit(1) + end + if ['.yaml', '.yml'].include?(input_ext) + input_format = 'yaml' + else + input_format = 'json' + end + + output_format = input_format if !output_format + end + + modifier_file = options[:modifier] + if !modifier_file + STDERR.puts "No script file provided. Use --modifier option".red + exit(1) + end + unless File.exists?(modifier_file) + STDERR.puts "Modifier file #{modifier_file} not found".red + exit(1) + end + + out_file = options[:out] + if out_file + if !overwrite && File.exists?(out_file) + STDERR.puts "Output file #{out_file} already exists. Use --overwrite flag to overwrite it".red + exit(1) + end + else + out_file = '-' # output to stdout + end + + + data = [] + if in_file == '-' + input_text = STDIN.read + else + input_text = File.read(in_file) + end + + if input_format == 'yaml' + if input_text.empty? + STDERR.puts "Empty input file".red + exit(2) + end + + input_text.split('---').each_with_index do |part, idx| + part_data = YAML.load(part) + data << part_data + end + + else + file_data = JSON.parse(input_text) + if file_data.is_a? Array + data = file_data + else + data = [file_data] + end + end + + run_context = {} + modifier = File.read(modifier_file) + if in_file != '-' + run_context[:basedir] = File.dirname(in_file) + end + + run_context[:js_preload] = ::Alterant::Classes.LoadClasses + alter = ::Alterant::Alterant.new(input: data, modifier: modifier, filename: modifier_file, options: run_context) + results = alter.execute(timeout: 500) + + if results.nil? + STDERR.puts "Aborting".red + exit(2) + end + + if output_format == 'yaml' || output_format == 'yml' + converted_as_text = results.map { |r| r.to_yaml }.join("\n") + input = data.map { |r| r.to_yaml }.join("\n") + elsif output_format == 'json' + converted_as_text = JSON.pretty_generate(results) + input = JSON.pretty_generate(data) + end + + if diff + output_as_text = Diffy::Diff.new(input, converted_as_text) + else + output_as_text = converted_as_text + end + + if out_file == "-" + puts output_as_text + else + File.open(out_file, 'w') do |file| + file.write(output_as_text) + end + end + return true + rescue ::Alterant::ParseError => exc + STDERR.puts "Syntax error: #{exc.message}".red + return false + rescue ::Alterant::RuntimeError => exc + STDERR.puts "Runtime error: #{exc.message}".red + return false + rescue => exc + if $debug + raise + else + STDERR.puts exc + end + return false + end + end + + AlterantCLI.start +end diff --git a/bin/console b/bin/console new file mode 100755 index 0000000..7f64c6b --- /dev/null +++ b/bin/console @@ -0,0 +1,14 @@ +#!/usr/bin/env ruby + +require "bundler/setup" +require "alterant" + +# You can add fixtures and/or initialization code here to make experimenting +# with your gem easier. You can also use a different console, if you like. + +# (If you use this, don't forget to add pry to your Gemfile!) +# require "pry" +# Pry.start + +require "irb" +IRB.start(__FILE__) diff --git a/bin/setup b/bin/setup new file mode 100755 index 0000000..dce67d8 --- /dev/null +++ b/bin/setup @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +set -euo pipefail +IFS=$'\n\t' +set -vx + +bundle install + +# Do any other automated setup that you need to do here diff --git a/lib/alterant.rb b/lib/alterant.rb new file mode 100644 index 0000000..9add4cf --- /dev/null +++ b/lib/alterant.rb @@ -0,0 +1,6 @@ +require_relative 'alterant/version' +require_relative 'alterant/utils' +require_relative 'alterant/loader' + +module Alterant +end diff --git a/lib/alterant/alterant.rb b/lib/alterant/alterant.rb new file mode 100644 index 0000000..d0cd4e3 --- /dev/null +++ b/lib/alterant/alterant.rb @@ -0,0 +1,59 @@ +module Alterant + class Alterant + attr_reader :basedir + + # input is a hash + # filename is the modifier filename use for the backtrace + # modifier is the script in string + def initialize(input:, modifier:, filename:, options: {}) + @modifier = modifier + @filename = filename + @input = input + @basedir = options[:basedir] + @js_preload = options[:js_preload] || [] + end + + # timeout is in ms + # returns a hash + def execute(timeout: 500, max_memory: 5000000) + jpath = ::Alterant::Helpers::Jpath.new + + result = [] + snapshot = MiniRacer::Snapshot.new("$$ = #{@input.to_json};\n" + @js_preload.join("\n")) # this is more efficient but we lose debug info (filename) of helper classes + + isolate = MiniRacer::Isolate.new(snapshot) + @input.each_with_index do |input, idx| + ctx = ::MiniRacer::Context.new(isolate: isolate, timeout: timeout, max_memory: max_memory) + ctx.eval("$ = #{input.to_json}") + ctx.eval("$['fetch'] = function(key) { return jpath.fetch(JSON.stringify($), key); }") + ctx.attach('jpath.fetch', proc{|x, y| jpath.fetch(x, y)}) + ctx.attach('console.log', proc{|x| STDERR.puts("DEBUG: #{x.inspect}") if $debug }) + ctx.attach('console.exception', proc{|x| raise ::Alterant::RuntimeError, x }) + ctx.attach('$$.push', proc{|x| result << x }) + ctx.attach('$.index', proc{ idx }) + ctx.attach('YamlReader', ::Alterant::Classes::YamlReader.new(self, ctx)) + ctx.attach('JsonReader', ::Alterant::Classes::JsonReader.new(self, ctx)) + + ctx.eval(@modifier, filename: @filename) + pre_convert = ctx.eval("JSON.stringify($)") + converted = JSON.parse(pre_convert) + result << converted + + ctx.dispose + isolate.idle_notification(100) + rescue ::MiniRacer::RuntimeError => exc + if $debug + raise + else + raise ::Alterant::ParseError, "part: #{idx} - #{exc.message}, #{exc.backtrace.first}" + end + rescue ::Alterant::AlterantError => exc + STDERR.puts exc.message.red + return nil + end + + return result + end + + end +end diff --git a/lib/alterant/classes/classes.rb b/lib/alterant/classes/classes.rb new file mode 100644 index 0000000..75ca659 --- /dev/null +++ b/lib/alterant/classes/classes.rb @@ -0,0 +1,20 @@ +module Alterant + module Classes + CLASSES = {} + + def self.LoadClasses + # ::Alterant::Classes.constants.map(&::Alterant::Classes.method(:const_get)).grep(Class) do |c| + # name = c.name.split('::').last + # ::Alterant::Classes::CLASSES[name] = c + # end + + # load all JS files in the classes dir and construct a long text + js_preload = [] + Dir["#{__dir__}/*.js"].each do |f| + js_preload << File.read(f) + end + + return js_preload + end + end +end diff --git a/lib/alterant/classes/containers.js b/lib/alterant/classes/containers.js new file mode 100644 index 0000000..c098415 --- /dev/null +++ b/lib/alterant/classes/containers.js @@ -0,0 +1,16 @@ +class Containers { + constructor(containers) { + this.containers = containers + } + + by_name(name) { + for (var c in this.containers) { + var item = this.containers[c] + if (item.name == name) { + return item + } + } + + return null + } +} diff --git a/lib/alterant/classes/docker_image.js b/lib/alterant/classes/docker_image.js new file mode 100644 index 0000000..16e5711 --- /dev/null +++ b/lib/alterant/classes/docker_image.js @@ -0,0 +1,51 @@ +class DockerImage { + constructor(source_image) { + var full_image = source_image.trim(); + var full_name = ''; + if (!full_image.includes('/')) { + full_name = "library/" + full_image; + } + if (!full_image.includes('/')) { + full_image = "library/" + full_image; + } + if (!full_image.includes(":")) { + full_image = full_image + ":latest"; + } + var proto = 'https://'; + if (/^https/.test(full_image)) { + proto = 'https://'; + full_image = full_image.replace(/https:\/\//, ''); + } + else if (/^http/.test(full_image)) { + full_image = full_image.replace(/http:\/\//, ''); + } + // trim / from front and back + full_image = full_image.replace(/^\//, '').replace(/\/$/, ''); + var registry + // figure out registry + if (/^library\//.test(full_image) || full_image.split('/').length < 3) { + // its docker io + registry = 'index.docker.io'; + } + else { + registry = full_image.replace(/\/.*/, ''); + } + // figure out image name + full_image = full_image.replace(/#{registry}(\/(v|V)(1|2)|)/i, '').replace(/^\//, '').replace(/\/$/, ''); + var image_parts = full_image.split(':'); + var image_name = image_parts[0]; + var image_tag = image_parts[1]; + // recombine for registry + var registry_url = proto + registry; + var fqin = registry_url + "/" + full_image; + this.fqin = fqin; + this.registry = registry; + this.registry_url = registry_url; + this.name = image_name; + this.tag = image_tag; + } + + address() { + return this.registry + this.name + ":" + this.tag + } +} diff --git a/lib/alterant/classes/json_reader.rb b/lib/alterant/classes/json_reader.rb new file mode 100644 index 0000000..22e6ba7 --- /dev/null +++ b/lib/alterant/classes/json_reader.rb @@ -0,0 +1,22 @@ +module Alterant + module Classes + class JsonReader + attr_reader :value + + def call(file) + if @alter.basedir.nil? + raise ::Alterant::RuntimeError, 'no basedir set' + end + + content = File.read(File.join(@alter.basedir, file)) + return ::JSON.load(content) + end + + def initialize(alter, context) + @context = context + @alter = alter + end + + end + end +end diff --git a/lib/alterant/classes/ports.js b/lib/alterant/classes/ports.js new file mode 100644 index 0000000..f38da06 --- /dev/null +++ b/lib/alterant/classes/ports.js @@ -0,0 +1,16 @@ +class Ports { + constructor(container) { + this.ports = container.ports + } + + containerPorts() { + var ports = this.ports; + var port_numbers = new Array(); + + for (var item in ports) { + port_numbers.push(ports[item]["containerPort"]); + } + + return port_numbers; + } +} diff --git a/lib/alterant/classes/yaml_reader.rb b/lib/alterant/classes/yaml_reader.rb new file mode 100644 index 0000000..c5d835a --- /dev/null +++ b/lib/alterant/classes/yaml_reader.rb @@ -0,0 +1,22 @@ +module Alterant + module Classes + class YamlReader + attr_reader :value + + def call(file) + if @alter.basedir.nil? + raise ::Alterant::RuntimeError, 'no basedir set' + end + + content = File.read(File.join(@alter.basedir, file)) + return ::YAML.safe_load(content) + end + + def initialize(alter, context) + @context = context + @alter = alter + end + + end + end +end diff --git a/lib/alterant/error.rb b/lib/alterant/error.rb new file mode 100644 index 0000000..74f1ef7 --- /dev/null +++ b/lib/alterant/error.rb @@ -0,0 +1,5 @@ +module Alterant + class AlterantError < StandardError; end + class ParseError < AlterantError; end + class RuntimeError < AlterantError; end + end diff --git a/lib/alterant/helpers/jpath.rb b/lib/alterant/helpers/jpath.rb new file mode 100644 index 0000000..dad13c3 --- /dev/null +++ b/lib/alterant/helpers/jpath.rb @@ -0,0 +1,12 @@ +module Alterant + module Helpers + class Jpath + require 'jsonpath' + + def fetch(context, key) + return ::JsonPath.on(context, key) + end + + end + end +end diff --git a/lib/alterant/loader.rb b/lib/alterant/loader.rb new file mode 100644 index 0000000..23ac3d5 --- /dev/null +++ b/lib/alterant/loader.rb @@ -0,0 +1,11 @@ +require 'mini_racer' +require 'yaml' +require 'json' +require 'diffy' + +Dir.glob File.join(__dir__, 'helpers', '**', '*.rb'), &method(:require) +Dir.glob File.join(__dir__, 'classes', '**', '*.rb'), &method(:require) + +# Load other Alterant classes. +Dir.glob File.join(__dir__, '**', '*.rb'), &method(:require) + diff --git a/lib/alterant/utils.rb b/lib/alterant/utils.rb new file mode 100644 index 0000000..564ff66 --- /dev/null +++ b/lib/alterant/utils.rb @@ -0,0 +1,11 @@ +class String + def underscore + word = self.dup + word.gsub!(/::/, '/') + word.gsub!(/([A-Z]+)([A-Z][a-z])/,'\1_\2') + word.gsub!(/([a-z\d])([A-Z])/,'\1_\2') + word.tr!("-", "_") + word.downcase! + word + end +end diff --git a/lib/alterant/version.rb b/lib/alterant/version.rb new file mode 100644 index 0000000..7f2abb6 --- /dev/null +++ b/lib/alterant/version.rb @@ -0,0 +1,5 @@ +module Alterant + VERSION = '0.0.1' + COPYRIGHT_MESSAGE = "(c) 2018 Cloud66 Inc." + APP_NAME = 'Alterant' +end diff --git a/samples/add-service.js b/samples/add-service.js new file mode 100644 index 0000000..4b8f537 --- /dev/null +++ b/samples/add-service.js @@ -0,0 +1,17 @@ +var namespace = $.metadata.name +deployment = { + apiVersion: "extensions/v1beta1", + kind: "Deployment", + metadata: [ + { namespace: namespace }, + { name: "web" } + ], + spec: + { template: + { spec: + { containers: [{ "image": "app_image:latest", "name": "my-pod" }] } + } + } + } + +$$.push(deployment) diff --git a/samples/change-port.js b/samples/change-port.js new file mode 100644 index 0000000..e76c6fd --- /dev/null +++ b/samples/change-port.js @@ -0,0 +1,3 @@ +var web_container = new Containers($.spec.template.spec.containers).by_name("web"); +var ports = [{ containerPort: 81 }, { containerPort: 444}] +web_container.ports = ports diff --git a/samples/change-tag.js b/samples/change-tag.js new file mode 100644 index 0000000..d9351ad --- /dev/null +++ b/samples/change-tag.js @@ -0,0 +1,5 @@ +var containers = new Containers($.spec.template.spec.containers) +var web_container = containers.by_name("web") +var containerImage = new DockerImage(web_container.image) +containerImage.tag = "1.2" +web_container.image = containerImage.address() diff --git a/samples/config.yml b/samples/config.yml new file mode 100644 index 0000000..35f36df --- /dev/null +++ b/samples/config.yml @@ -0,0 +1,19 @@ +apiVersion: extensions/v1beta1 +kind: Deployment +metadata: + namespace: foos + name: web +spec: + replicas: 1 + template: + metadata: + labels: + app: central + tier: web + spec: + containers: + - image: us.gcr.io/gentle-mule/kickass:latest + name: web + ports: + - containerPort: 3000 + command: ['bundle', 'exec', 'puma', '-b', 'tcp://0.0.0.0:3000'] diff --git a/samples/multi-part.yml b/samples/multi-part.yml new file mode 100644 index 0000000..62eae4d --- /dev/null +++ b/samples/multi-part.yml @@ -0,0 +1,24 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: foos +--- +apiVersion: extensions/v1beta1 +kind: Deployment +metadata: + namespace: foos + name: web +spec: + replicas: 1 + template: + metadata: + labels: + app: central + tier: web + spec: + containers: + - image: us.gcr.io/gentle-mule/kickass:latest + name: web + ports: + - containerPort: 3000 + command: ['bundle', 'exec', 'puma', '-b', 'tcp://0.0.0.0:3000'] diff --git a/samples/namespace.yml b/samples/namespace.yml new file mode 100644 index 0000000..d677997 --- /dev/null +++ b/samples/namespace.yml @@ -0,0 +1,4 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: foos diff --git a/samples/ports.yml b/samples/ports.yml new file mode 100644 index 0000000..4f2f58d --- /dev/null +++ b/samples/ports.yml @@ -0,0 +1,14 @@ +apiVersion: extensions/v1beta1 +kind: Deployment +metadata: + namespace: foo + name: foobar +spec: + template: + spec: + containers: + - image: myimage:latest + name: web + ports: + - containerPort: 80 + - containerPort: 443 diff --git a/samples/sidecar.js b/samples/sidecar.js new file mode 100644 index 0000000..c213cd6 --- /dev/null +++ b/samples/sidecar.js @@ -0,0 +1,14 @@ +// adds a CloudProxy sidecar to the deployment +if ($.kind == 'Deployment') { + var containers = $.spec.template.spec.containers + if (containers.length == 1) { + sidecar = JsonReader("sidecar.json") + + var sidecarImage = new DockerImage(sidecar.image) + var containerImage = new DockerImage(containers[0].image) + + sidecarImage.tag = containerImage.tag + sidecar.image = sidecarImage.address() + containers.push(sidecar) + } +} diff --git a/samples/sidecar.json b/samples/sidecar.json new file mode 100644 index 0000000..74a255f --- /dev/null +++ b/samples/sidecar.json @@ -0,0 +1,41 @@ +{ + "image": "gcr.io/cloudsql-docker/gce-proxy:1.11", + "name": "cloudsql-proxy", + "command": [ + "/cloud_sql_proxy", + "--dir=/cloudsql", + "-instances=$(INSTANCE_CONNECTION_NAME)=tcp:3306,$(RO_INSTANCE_CONNECTION_NAME)=tcp:3307", + "-credential_file=/secrets/cloudsql/credentials.json" + ], + "resources": { + "requests": { + "cpu": "20m" + } + }, + "envFrom": [ + { + "configMapRef": { + "name": "app-config" + } + } + ], + "volumeMounts": [ + { + "name": "cloudsql-instance-credentials", + "mountPath": "/secrets/cloudsql", + "readOnly": true + }, + { + "name": "ssl-certs", + "mountPath": "/etc/ssl/certs" + }, + { + "name": "cloudsql", + "mountPath": "/cloudsql" + }, + { + "name": "pod", + "mountPath": "/var/pod" + } + ] +} diff --git a/samples/sidecar.yml b/samples/sidecar.yml new file mode 100644 index 0000000..882420a --- /dev/null +++ b/samples/sidecar.yml @@ -0,0 +1,21 @@ +- image: gcr.io/cloudsql-docker/gce-proxy:1.11 + name: cloudsql-proxy + command: ["/cloud_sql_proxy", "--dir=/cloudsql", + "-instances=$(INSTANCE_CONNECTION_NAME)=tcp:3306,$(RO_INSTANCE_CONNECTION_NAME)=tcp:3307", + "-credential_file=/secrets/cloudsql/credentials.json"] + resources: + requests: + cpu: 20m + envFrom: + - configMapRef: + name: app-config + volumeMounts: + - name: cloudsql-instance-credentials + mountPath: /secrets/cloudsql + readOnly: true + - name: ssl-certs + mountPath: /etc/ssl/certs + - name: cloudsql + mountPath: /cloudsql + - name: pod + mountPath: /var/pod