From c8ac26d723db5ce77098379a5e3a8bdd48304434 Mon Sep 17 00:00:00 2001 From: Alexandre de Oliveira Date: Fri, 16 Dec 2016 15:38:45 -0200 Subject: [PATCH 1/3] Adds Configuration class to get ENV var parameters --- .gitignore | 1 + README.md | 26 ++++++-- lib/doc_orchestra/configuration.rb | 41 ++++++++++++ spec/doc_orchestra/configuration_spec.rb | 81 ++++++++++++++++++++++++ 4 files changed, 145 insertions(+), 4 deletions(-) create mode 100644 lib/doc_orchestra/configuration.rb create mode 100644 spec/doc_orchestra/configuration_spec.rb diff --git a/.gitignore b/.gitignore index 0cb6eeb..8082097 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +*.gem /.bundle/ /.yardoc /Gemfile.lock diff --git a/README.md b/README.md index 0061c73..d34cc2b 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,23 @@ **Important:** this gem is not production ready. -This is the code that orchestrates Zipmark's automatically generated -documentation and its publication. +## The problem + +You have systems that change and their documentation gets +outdated because they are maintaned manually. + +**The solution** + +DocOrchestra attempts to help mitigate that problem with several +functionalities: + +* automate document generation (e.g [rspec_api_documentation](https://github.com/zipmark/rspec_api_documentation) ) +* automate linting of the document files (e.g [linter-api-blueprint](https://github.com/zdne/linter-api-blueprint) +* automate testing the documentation against real APIs (e.g [dredd](https://github.com/apiaryio/dredd)) +* automate concatenating multiple documentation files into one (e.g microservirces) +* automate generating [API Elements](http://api-elements.readthedocs.io/en/latest/) +* automate publishing the documentation to the cloud (e.g S3) + ## Installation @@ -19,9 +34,12 @@ And then execute: ## Usage -Pending +Running this tool requires specifying a few options via ENV variables, like +the following: -## Development +``` +DOC_PATH=./docs/main.apib DOC_STORAGE=s3 doc_orchestra sync +``` ## Contributing diff --git a/lib/doc_orchestra/configuration.rb b/lib/doc_orchestra/configuration.rb new file mode 100644 index 0000000..ab7869d --- /dev/null +++ b/lib/doc_orchestra/configuration.rb @@ -0,0 +1,41 @@ +module DocOrchestra + class Configuration + class UnknownStrategy < StandardError; end + + KNOWN_DOC_FORMATS = ["apib"] + KNOWN_DOC_STORAGES = ["s3"] + + def initialize(env) + @env = env + end + + def strategy + { + doc_format: env_value(name: :doc_format, whitelist: KNOWN_DOC_FORMATS), + doc_storage: env_value(name: :doc_storage, whitelist: KNOWN_DOC_STORAGES), + doc_path: env_value(name: :doc_path), + } + end + + private + + def env_value(name:, whitelist: nil) + env_var_name = name.to_s.upcase + whitelisted_value(env_var_name, whitelist) || @env[env_var_name] + end + + def whitelisted_value(env_var_name, whitelist = nil) + if whitelist + if whitelist.include?(@env[env_var_name]) + @env[env_var_name].to_sym + else + raise UnknownStrategy, "ENV var '#{env_var_name}' has unknown value, '#{@env[env_var_name]}'. See #{help_url} for details." + end + end + end + + def help_url + "https://github.com/zipmark/doc_orchestra" + end + end +end diff --git a/spec/doc_orchestra/configuration_spec.rb b/spec/doc_orchestra/configuration_spec.rb new file mode 100644 index 0000000..a3035df --- /dev/null +++ b/spec/doc_orchestra/configuration_spec.rb @@ -0,0 +1,81 @@ +require "doc_orchestra/configuration" + +describe DocOrchestra::Configuration do + let(:doc_format) { "apib" } + let(:doc_storage) { "s3" } + + let(:env) do + { + "DOC_FORMAT" => doc_format, + "DOC_STORAGE" => doc_storage + } + end + + subject(:config) { described_class.new(env) } + + describe "#strategy" do + context 'doc_format' do + subject { config.strategy[:doc_format] } + + context 'apib' do + let(:doc_format) { "apib" } + + it "saves format strategy" do + expect(subject).to eq :apib + end + end + + context 'unknown format' do + let(:doc_format) { "something_else" } + + it "raises" do + expect { subject }.to raise_error DocOrchestra::Configuration::UnknownStrategy + end + end + end + + context 'doc_storage' do + subject { config.strategy[:doc_storage] } + + context 's3' do + let(:doc_storage) { "s3" } + + it "saves storage strategy" do + expect(subject).to eq :s3 + end + end + + context 'unknown storage' do + let(:doc_storage) { "something_else" } + + it "raises" do + expect { subject }.to raise_error DocOrchestra::Configuration::UnknownStrategy + end + end + end + + context 'doc_path' do + subject { config.strategy[:doc_path] } + + before do + env.merge!("DOC_PATH" => doc_path) + end + + context 'a file path is specified' do + let(:doc_path) { "./some_path" } + + it "saves the doc path" do + expect(subject).to eq "./some_path" + end + end + + context 'when no doc path specified' do + let(:doc_path) { nil } + + it "returns nil" do + expect(subject).to eq nil + end + end + end + end +end From 761992433d477be3735a0457b598af60a09ce39a Mon Sep 17 00:00:00 2001 From: Alexandre de Oliveira Date: Fri, 16 Dec 2016 18:17:45 -0200 Subject: [PATCH 2/3] Ensures bucket exists on S3 --- README.md | 2 + bin/doc_orchestra | 5 + doc_orchestra.gemspec | 2 + docs/getting_started.md | 24 +++++ lib/doc_orchestra.rb | 11 +++ lib/doc_orchestra/configuration.rb | 47 ++++----- lib/doc_orchestra/log.rb | 7 ++ lib/doc_orchestra/strategies/format/apib.rb | 19 ++++ lib/doc_orchestra/strategies/storage.rb | 7 ++ lib/doc_orchestra/strategies/storages/s3.rb | 47 +++++++++ lib/doc_orchestra/strategy.rb | 18 ++++ spec/doc_orchestra/configuration_spec.rb | 102 ++++++++++---------- spec/fixtures/documents/main.apib | 1 + spec/spec_helper.rb | 2 + 14 files changed, 218 insertions(+), 76 deletions(-) create mode 100755 bin/doc_orchestra create mode 100644 docs/getting_started.md create mode 100644 lib/doc_orchestra/log.rb create mode 100644 lib/doc_orchestra/strategies/format/apib.rb create mode 100644 lib/doc_orchestra/strategies/storage.rb create mode 100644 lib/doc_orchestra/strategies/storages/s3.rb create mode 100644 lib/doc_orchestra/strategy.rb create mode 100644 spec/fixtures/documents/main.apib diff --git a/README.md b/README.md index d34cc2b..6858d1a 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,8 @@ the following: DOC_PATH=./docs/main.apib DOC_STORAGE=s3 doc_orchestra sync ``` +See [Getting Started](/docs/getting_started.md) for details. + ## Contributing Bug reports and pull requests are welcome on GitHub at https://github.com/zipmark/doc_orchestra. diff --git a/bin/doc_orchestra b/bin/doc_orchestra new file mode 100755 index 0000000..4e9a07b --- /dev/null +++ b/bin/doc_orchestra @@ -0,0 +1,5 @@ +#!/usr/bin/env ruby +require "bundler/setup" +require "doc_orchestra" + +DocOrchestra.run diff --git a/doc_orchestra.gemspec b/doc_orchestra.gemspec index c956cdf..316ae17 100644 --- a/doc_orchestra.gemspec +++ b/doc_orchestra.gemspec @@ -21,6 +21,8 @@ Gem::Specification.new do |spec| spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) } spec.require_paths = ["lib"] + spec.add_dependency "aws-sdk", "~> 2.6" + spec.add_development_dependency "bundler", "~> 1.13" spec.add_development_dependency "rake", "~> 10.0" spec.add_development_dependency "rspec", "~> 3.5" diff --git a/docs/getting_started.md b/docs/getting_started.md new file mode 100644 index 0000000..f566bfb --- /dev/null +++ b/docs/getting_started.md @@ -0,0 +1,24 @@ +# Getting Started + +Each one of these steps will depend on the orchestration strategies chosen. +These are the strategies that can be defined: + +* `DOC_FORMAT`: depending on this value, specific linting and testing tools + will be used. For instance, in a project with API Blueprint, + `DOC_FORMAT=apib` will define usage of `dredd` as testing tool. +* `DOC_STORAGE`: where will + will be used. For instance, in a project with API Blueprint, + `DOC_FORMAT=apib` will define usage of `dredd` as testing tool. + + +### Options + +These are the options that can be defined. For details on how + +* `DOC_FORMAT` (required, string): defines the format of the documentation. + Accepted values are: `apib` (APIBlueprint). +* `DOC_STORAGE` (required, string): defines where the documentation will be stored. + Accepted values are: `s3` (APIBlueprint). +* `DOC_ELEMENTS` (int): `1` to convert docs to + [API Elements](http://api-elements.readthedocs.io/en/latest/). Defaults to + `0`. diff --git a/lib/doc_orchestra.rb b/lib/doc_orchestra.rb index 5eec888..662ae03 100644 --- a/lib/doc_orchestra.rb +++ b/lib/doc_orchestra.rb @@ -1,4 +1,15 @@ require "doc_orchestra/version" +require "doc_orchestra/configuration" +require "doc_orchestra/log" +require "doc_orchestra/strategy" +require "doc_orchestra/strategies/storage" +require "doc_orchestra/strategies/storages/s3" module DocOrchestra + def self.run(env = ENV) + @config = Configuration.new(env) + + @strategy = Strategy.new(@config) + @strategy.orchestrate + end end diff --git a/lib/doc_orchestra/configuration.rb b/lib/doc_orchestra/configuration.rb index ab7869d..c4355d9 100644 --- a/lib/doc_orchestra/configuration.rb +++ b/lib/doc_orchestra/configuration.rb @@ -1,41 +1,42 @@ module DocOrchestra class Configuration - class UnknownStrategy < StandardError; end - - KNOWN_DOC_FORMATS = ["apib"] - KNOWN_DOC_STORAGES = ["s3"] + class UnexpectedConfiguration < StandardError; end + class MissingConfiguration < StandardError; end def initialize(env) @env = env + @dictionary = {} end - def strategy - { - doc_format: env_value(name: :doc_format, whitelist: KNOWN_DOC_FORMATS), - doc_storage: env_value(name: :doc_storage, whitelist: KNOWN_DOC_STORAGES), - doc_path: env_value(name: :doc_path), - } + def [](key) + if @env[key] != "" + @env[key] + end end - private - - def env_value(name:, whitelist: nil) - env_var_name = name.to_s.upcase - whitelisted_value(env_var_name, whitelist) || @env[env_var_name] + def required_keys(*keys) + keys.each do |key| + if blank_value?(@env[key]) + raise MissingConfiguration, "The following environment variables are missing: #{key}" + end + end end - def whitelisted_value(env_var_name, whitelist = nil) - if whitelist - if whitelist.include?(@env[env_var_name]) - @env[env_var_name].to_sym - else - raise UnknownStrategy, "ENV var '#{env_var_name}' has unknown value, '#{@env[env_var_name]}'. See #{help_url} for details." - end + def required_values(key, *values) + required_keys(key) + unless values.include?(self[key]) + raise UnexpectedConfiguration, "Environment variable #{key} accepted values are: #{values.join(", ")} (#{self[key]} provided). #{help_url}" end end + private + + def blank_value?(str) + str.nil? || str == "" + end + def help_url - "https://github.com/zipmark/doc_orchestra" + "See https://github.com/zipmark/doc_orchestra for details." end end end diff --git a/lib/doc_orchestra/log.rb b/lib/doc_orchestra/log.rb new file mode 100644 index 0000000..ca422c9 --- /dev/null +++ b/lib/doc_orchestra/log.rb @@ -0,0 +1,7 @@ +module DocOrchestra + class Log + def self.out(str) + puts str + end + end +end diff --git a/lib/doc_orchestra/strategies/format/apib.rb b/lib/doc_orchestra/strategies/format/apib.rb new file mode 100644 index 0000000..f1c1880 --- /dev/null +++ b/lib/doc_orchestra/strategies/format/apib.rb @@ -0,0 +1,19 @@ +module DocOrchestra + module Strategies + module FileFormat + class Apib + def initialize(options) + @options = options + end + + def method + + end + + private + + attr_reader :options + end + end + end +end diff --git a/lib/doc_orchestra/strategies/storage.rb b/lib/doc_orchestra/strategies/storage.rb new file mode 100644 index 0000000..89e02dc --- /dev/null +++ b/lib/doc_orchestra/strategies/storage.rb @@ -0,0 +1,7 @@ +module DocOrchestra + module Strategies + class Storage + + end + end +end diff --git a/lib/doc_orchestra/strategies/storages/s3.rb b/lib/doc_orchestra/strategies/storages/s3.rb new file mode 100644 index 0000000..9ff6f4b --- /dev/null +++ b/lib/doc_orchestra/strategies/storages/s3.rb @@ -0,0 +1,47 @@ +require 'aws-sdk' + +module DocOrchestra + module Strategies + module Storages + class S3 + REQUIRED_CONFIG = [ + "STORAGE_KEY", "STORAGE_SECRET", "STORAGE_BUCKET" + ] + + def initialize(config:, region: 'us-west-2') + config.required_keys(*REQUIRED_CONFIG) + @key = config["STORAGE_KEY"] + @secret = config["STORAGE_SECRET"] + @bucket = config["STORAGE_BUCKET"] + @region = config["STORAGE_REGION"] || region + end + + def upload + setup_credentials + create_bucket + end + + private + + def setup_credentials + Aws.config.update( + region: @region, + credentials: credentials + ) + end + + def credentials + Aws::Credentials.new(@key, @secret) + end + + def create_bucket + s3 = Aws::S3::Resource.new + s3.bucket(@bucket).create + Log.out("Creating bucket '#{@bucket}'... done.") + rescue Aws::S3::Errors::BucketAlreadyExists, Aws::S3::Errors::BucketAlreadyOwnedByYou + Log.out("Bucket '#{@bucket}' already exists.") + end + end + end + end +end diff --git a/lib/doc_orchestra/strategy.rb b/lib/doc_orchestra/strategy.rb new file mode 100644 index 0000000..86c9dde --- /dev/null +++ b/lib/doc_orchestra/strategy.rb @@ -0,0 +1,18 @@ +module DocOrchestra + class Strategy + def initialize(config) + config.required_values("DOC_STORAGE", "s3") + @config = config + end + + def orchestrate + storage.upload + end + + private + + def storage + Strategies::Storages::S3.new(config: @config) + end + end +end diff --git a/spec/doc_orchestra/configuration_spec.rb b/spec/doc_orchestra/configuration_spec.rb index a3035df..10ec7ee 100644 --- a/spec/doc_orchestra/configuration_spec.rb +++ b/spec/doc_orchestra/configuration_spec.rb @@ -1,80 +1,76 @@ -require "doc_orchestra/configuration" +require "spec_helper" describe DocOrchestra::Configuration do - let(:doc_format) { "apib" } - let(:doc_storage) { "s3" } - let(:env) do { - "DOC_FORMAT" => doc_format, - "DOC_STORAGE" => doc_storage + "DOC_STORAGE" => "s3", + "CUSTOM_KEY" => "custom value", + "BLANK_STRING" => "", } end subject(:config) { described_class.new(env) } - describe "#strategy" do - context 'doc_format' do - subject { config.strategy[:doc_format] } - - context 'apib' do - let(:doc_format) { "apib" } - - it "saves format strategy" do - expect(subject).to eq :apib - end + describe "#required_keys" do + context 'when no key is missing' do + it 'does not raise errors' do + expect { + config.required_keys("CUSTOM_KEY", "DOC_STORAGE") + }.to_not raise_error end + end - context 'unknown format' do - let(:doc_format) { "something_else" } - - it "raises" do - expect { subject }.to raise_error DocOrchestra::Configuration::UnknownStrategy - end + context 'when one key is not defined' do + it 'raises an error' do + expect { + config.required_keys("MISSING_KEY", "DOC_STORAGE") + }.to raise_error described_class::MissingConfiguration end end + end - context 'doc_storage' do - subject { config.strategy[:doc_storage] } - - context 's3' do - let(:doc_storage) { "s3" } - - it "saves storage strategy" do - expect(subject).to eq :s3 - end + describe '#[]' do + context 'value exists' do + it 'returns an env var value' do + expect(config["CUSTOM_KEY"]).to eq "custom value" end + end - context 'unknown storage' do - let(:doc_storage) { "something_else" } - - it "raises" do - expect { subject }.to raise_error DocOrchestra::Configuration::UnknownStrategy - end + context 'value is a blank string' do + it 'returns nil' do + expect(config["BLANK_STRING"]).to eq nil end end - context 'doc_path' do - subject { config.strategy[:doc_path] } - - before do - env.merge!("DOC_PATH" => doc_path) + context 'value does not exist' do + it 'returns nil' do + expect(config["NO_KEY"]).to eq nil end + end + end - context 'a file path is specified' do - let(:doc_path) { "./some_path" } - - it "saves the doc path" do - expect(subject).to eq "./some_path" - end + describe '#required_values' do + context 'a value is incorrect' do + it 'raises' do + expect { + config.required_values("DOC_STORAGE", "s4", "s5") + }.to raise_error described_class::UnexpectedConfiguration end + end - context 'when no doc path specified' do - let(:doc_path) { nil } + context 'the key does not exist' do + it 'raises' do + expect { + config.required_values("NO_KEY", "s3") + }.to raise_error described_class::MissingConfiguration + end + end - it "returns nil" do - expect(subject).to eq nil - end + context 'a value is incorrect' do + it 'raises' do + expect { + config.required_values("DOC_STORAGE", "s3") + }.to_not raise_error end end end diff --git a/spec/fixtures/documents/main.apib b/spec/fixtures/documents/main.apib new file mode 100644 index 0000000..e3398df --- /dev/null +++ b/spec/fixtures/documents/main.apib @@ -0,0 +1 @@ +# My documentation diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 3dbe855..f808696 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,2 +1,4 @@ $LOAD_PATH.unshift File.expand_path("../../lib", __FILE__) require "doc_orchestra" +require 'pry' +require 'awesome_print' From 79edb7149e15ac59fc12e02629b1b7f9f78e44ca Mon Sep 17 00:00:00 2001 From: Alexandre de Oliveira Date: Mon, 19 Dec 2016 15:01:19 -0200 Subject: [PATCH 3/3] fixes readme about roadmap --- README.md | 3 +-- docs/getting_started.md | 2 ++ spec/doc_orchestra/strategies/storages/s3_spec.rb | 14 ++++++++++++++ 3 files changed, 17 insertions(+), 2 deletions(-) create mode 100644 spec/doc_orchestra/strategies/storages/s3_spec.rb diff --git a/README.md b/README.md index 6858d1a..3819879 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ outdated because they are maintaned manually. **The solution** DocOrchestra attempts to help mitigate that problem with several -functionalities: +functionalities. This is the roadmap * automate document generation (e.g [rspec_api_documentation](https://github.com/zipmark/rspec_api_documentation) ) * automate linting of the document files (e.g [linter-api-blueprint](https://github.com/zdne/linter-api-blueprint) @@ -19,7 +19,6 @@ functionalities: * automate generating [API Elements](http://api-elements.readthedocs.io/en/latest/) * automate publishing the documentation to the cloud (e.g S3) - ## Installation Add this line to your application's Gemfile under development: diff --git a/docs/getting_started.md b/docs/getting_started.md index f566bfb..f3be9e3 100644 --- a/docs/getting_started.md +++ b/docs/getting_started.md @@ -1,5 +1,7 @@ # Getting Started +[WIP] + Each one of these steps will depend on the orchestration strategies chosen. These are the strategies that can be defined: diff --git a/spec/doc_orchestra/strategies/storages/s3_spec.rb b/spec/doc_orchestra/strategies/storages/s3_spec.rb new file mode 100644 index 0000000..e94a4c5 --- /dev/null +++ b/spec/doc_orchestra/strategies/storages/s3_spec.rb @@ -0,0 +1,14 @@ +require "doc_orchestra/strategies/storages/s3" + +describe DocOrchestra::Strategies::Storages::S3 do + let(:item) { double } + + subject { described_class.new(item) } + + describe "#upload" do + it "returns true" do + subject.method + + end + end +end