env_lint 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 7074acbb267701af05f8e2f5307fb5d8f563a1dc
4
+ data.tar.gz: 1f07d11602f9e8230159f83a0719b3cfdb503047
5
+ SHA512:
6
+ metadata.gz: f1895359dbf17fca012f7ed52ff250ba0ea60b0edfd493691aabbf7b424f4d209d4b8d07c747a9b91e856f6e30f8b478c6fc07462f1592e6ba0a5c720226ae32
7
+ data.tar.gz: 564fa51150c32279cfcdc4fc965dd0e6b72c1c90d75b9c8ec33fe516394cb8c26fce7034c39d86446c24830275d624689ca4b6caf9ed4c9253df15b2add49573
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.travis.yml ADDED
@@ -0,0 +1,6 @@
1
+ rvm:
2
+ - 1.9.3
3
+ - 2.0.0
4
+ - 2.1.0
5
+
6
+ script: "bundle exec rspec"
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in env_lint.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Tim Fischbach
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,104 @@
1
+ # Env Lint
2
+
3
+ <img src="https://travis-ci.org/tf/env_lint.png" data-bindattr-466="466" title="Build Status Images">
4
+
5
+ Check environment variables accoring to a `.env.example` file.
6
+
7
+ * Avoid spelling errors in variable names in your code or on the
8
+ command line
9
+ * Ensure all relevant environment variables are described in the
10
+ `.env.example` file.
11
+ * Ensure all required environment variables are configured before
12
+ deploying a new version of an app
13
+ * Ease setting up a new development machine
14
+
15
+ # Status
16
+
17
+ Only tested with [recap](https://github.com/tomafro/recap) capistrano
18
+ tasks.
19
+
20
+ ## Installation
21
+
22
+ Add this line to your application's Gemfile:
23
+
24
+ gem 'env_lint'
25
+
26
+ ## Usage
27
+
28
+ Define a `.env.example` file:
29
+
30
+ # Explain each variable in comments like this one
31
+ APP_NAME=my_app
32
+
33
+ # Comments are also recognized if they span multiple
34
+ # lines
35
+ FEATURE=true
36
+
37
+ # Optional variables
38
+ # OPTIONAL_VAR="set me if you like"
39
+
40
+ ### Rake Task
41
+
42
+ Require it in your `Rakefile`:
43
+
44
+ require 'env_lint/tasks'
45
+
46
+ Now you can check your environment:
47
+
48
+ $ rake env:lint
49
+ => Complains if non optional variables are missing
50
+
51
+ If special steps are needed
52
+
53
+ ### Capistrano Task
54
+
55
+ Require it in your `Capfile`:
56
+
57
+ require 'env_lint/capistrano'
58
+
59
+ Now you can check your servers:
60
+
61
+ $ cap env:lint
62
+ => Complains if non optional variables are missing
63
+
64
+ You might want to lint the environment automatically before each
65
+ deploy.
66
+
67
+ before 'deploy', 'env:lint'
68
+
69
+ Lint variable names before setting them:
70
+
71
+ before 'env:set', 'env:lint_args'
72
+
73
+ $ cap env:set APP_NAME=myapp
74
+ => Complains if APP_NAME is defined
75
+
76
+ ### Lint at Runtime
77
+
78
+ Access ENV through a `LintedEnv`:
79
+
80
+ require 'env_lint'
81
+
82
+ class MyApp
83
+ LINTED_ENV = EnvLint::LintedEnv.from_file('.env.example')
84
+
85
+ def self.env
86
+ LINTED_ENV
87
+ end
88
+ end
89
+
90
+ Accessing env variables:
91
+
92
+ # Ensures APP_NAME is defined in .env.example
93
+ MyApp.env.fetch(:app_name, 'App name')
94
+
95
+ # Ensures APP_NAME is non optional in .env.example
96
+ MyApp.env.fetch(:app_name)
97
+
98
+ ## Contributing
99
+
100
+ 1. Fork it
101
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
102
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
103
+ 4. Push to the branch (`git push origin my-new-feature`)
104
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
data/env_lint.gemspec ADDED
@@ -0,0 +1,26 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'env_lint/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'env_lint'
8
+ spec.version = EnvLint::VERSION
9
+ spec.authors = ['Tim Fischbach']
10
+ spec.email = ['mail@timfischbach.de']
11
+ spec.summary = 'Lint the environment according by .env.example'
12
+ spec.homepage = ''
13
+ spec.license = 'MIT'
14
+
15
+ spec.files = `git ls-files`.split($/)
16
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
17
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
18
+ spec.require_paths = ['lib']
19
+
20
+ spec.add_runtime_dependency 'formatador'
21
+ spec.add_runtime_dependency 'capistrano', '~> 2.9'
22
+
23
+ spec.add_development_dependency 'rspec', '3.0.0.beta1'
24
+ spec.add_development_dependency 'bundler', '~> 1.3'
25
+ spec.add_development_dependency 'rake'
26
+ end
@@ -0,0 +1,51 @@
1
+ require 'env_lint'
2
+ require 'capistrano'
3
+
4
+ module EnvLint
5
+ module Capistrano
6
+ def self.load_into(config, formatter)
7
+ config.load do
8
+ set(:env_definition_file) { '.env.example' }
9
+ set(:env_probe_command) { "su - #{application_user} -c 'export'" }
10
+
11
+ namespace :env do
12
+ desc 'Check that every non optional ENV variable is defined.'
13
+ task :lint do
14
+ begin
15
+ EnvLint.verify_export_output(env_definition_file, capture(env_probe_command, via: :sudo))
16
+ formatter.ok('env looks ok')
17
+ rescue EnvLint::MissingVariables => e
18
+ formatter.missing_variables(e.dot_env_file, e.missing_variables)
19
+ abort
20
+ rescue EnvLint::Error => e
21
+ formatter.error(e.message)
22
+ abort
23
+ end
24
+ end
25
+
26
+ desc 'Lint args passed to command.'
27
+ task :lint_args do
28
+ begin
29
+ EnvLint.verify_args(env_definition_file, env_args)
30
+ rescue EnvLint::UnknownVariables => e
31
+ formatter.unknown_variables(e.dot_env_file, e.unknown_variables)
32
+ abort
33
+ rescue EnvLint::Error => e
34
+ formatter.error(e.message)
35
+ abort
36
+ end
37
+ end
38
+
39
+ def env_args
40
+ ARGV[1..-1]
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
47
+
48
+ if Capistrano::Configuration.instance
49
+ EnvLint::Capistrano.load_into(Capistrano::Configuration.instance(:must_exist),
50
+ Formatter.new)
51
+ end
@@ -0,0 +1,48 @@
1
+ module EnvLint
2
+ class DotEnvFile
3
+ attr_reader :name, :variables
4
+
5
+ def initialize(name, variables)
6
+ @name = name
7
+ @variables = variables
8
+
9
+ @variables_by_name = variables.each_with_object({}) do |variable, hash|
10
+ hash[variable.name] = variable
11
+ end
12
+ end
13
+
14
+ def find_variable(name)
15
+ @variables_by_name[name]
16
+ end
17
+
18
+ def verify_no_missing(variable_names)
19
+ find_missing(variable_names).tap do |missing_variables|
20
+ raise(MissingVariables.new(self, missing_variables)) if missing_variables.any?
21
+ end
22
+ end
23
+
24
+ def verify_no_unknown(variable_names)
25
+ find_unknown(variable_names).tap do |unknown_names|
26
+ raise(UnknownVariables.new(self, unknown_names)) if unknown_names.any?
27
+ end
28
+ end
29
+
30
+ def self.from_file(file_name)
31
+ new(file_name, DotEnvParser.new.parse(File.read(file_name)))
32
+ end
33
+
34
+ private
35
+
36
+ def find_missing(variable_names)
37
+ @variables.find_all do |variable|
38
+ !variable.optional? && !variable_names.include?(variable.name)
39
+ end
40
+ end
41
+
42
+ def find_unknown(variable_names)
43
+ variable_names.find_all do |name|
44
+ !@variables_by_name.key?(name)
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,39 @@
1
+ module EnvLint
2
+ class DotEnvParser
3
+ def parse(text)
4
+ comment_lines = []
5
+
6
+ variables = text.lines.each_with_object([]) do |line, result|
7
+ if match = line.strip.match(ASSIGNMENT)
8
+ optional, name, value = match.captures
9
+ value ||= ''
10
+ value = value.strip.sub(/\A(['"])(.*)\1\z/, '\2')
11
+
12
+ result << Variable.new(name, value, !!optional, comment_lines * "\n")
13
+ comment_lines = []
14
+ elsif match = line.strip.match(COMMENT)
15
+ comment_lines << match.captures.first
16
+ elsif line.strip.empty?
17
+ comment_lines = []
18
+ else
19
+ raise(UnrecognizedDotEnvLine.new(line))
20
+ end
21
+ end
22
+
23
+ variables
24
+ end
25
+
26
+ COMMENT = /\A#\s*(.*)\z/
27
+
28
+ ASSIGNMENT = /
29
+ \A
30
+ (\#\s*)? # optional variable marker
31
+ ([\w\.]+) # key
32
+ = # separator
33
+ ( # optional value begin
34
+ [^#\n]+ # unquoted value
35
+ )? # value end
36
+ \z
37
+ /x
38
+ end
39
+ end
@@ -0,0 +1,11 @@
1
+ module EnvLint
2
+ class EnvKeyParser
3
+ def parse_args(args)
4
+ args.map { |arg| arg.split('=').first if arg.include?('=') }.compact
5
+ end
6
+
7
+ def parse_export_output(text)
8
+ parse_args(text.split(/[\n\r]/).map { |line| line.gsub('declare -x', '').strip })
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,62 @@
1
+ module EnvLint
2
+ class Error < StandardError
3
+ end
4
+
5
+ class UnrecognizedDotEnvLine < Error
6
+ attr_reader :line
7
+
8
+ def initialize(line)
9
+ super("Unrecognized line in dot env file: '#{line}'")
10
+ @line = line
11
+ end
12
+ end
13
+
14
+ class VariableError < StandardError
15
+ attr_reader :variable_name
16
+
17
+ def initialize(variable_name, message)
18
+ super(message)
19
+ @variable_name = variable_name
20
+ end
21
+ end
22
+
23
+ class UnknownVariable < VariableError
24
+ def initialize(variable_name)
25
+ super(variable_name, "Unknown variable #{variable_name}.")
26
+ end
27
+ end
28
+
29
+ class DefaultValueRequiredForOptionalVariable < VariableError
30
+ def initialize(variable_name)
31
+ super(variable_name, "Non optional variable #{variable_name} used without default value.")
32
+ end
33
+ end
34
+
35
+ class MissingVariable < VariableError
36
+ def initialize(variable_name)
37
+ super(variable_name, "Missing variable #{variable_name}. Check your .env file")
38
+ end
39
+ end
40
+
41
+ class MissingVariables < Error
42
+ attr_reader :dot_env_file, :missing_variables
43
+
44
+ def initialize(dot_env_file, missing_variables)
45
+ @dot_env_file = dot_env_file
46
+ @missing_variables = missing_variables
47
+
48
+ super("Missing variables #{missing_variables * ', '}.")
49
+ end
50
+ end
51
+
52
+ class UnknownVariables < Error
53
+ attr_reader :dot_env_file, :unknown_variables
54
+
55
+ def initialize(dot_env_file, unknown_variables)
56
+ @dot_env_file = dot_env_file
57
+ @unknown_variables = unknown_variables
58
+
59
+ super("Unknown variables #{unknown_variables * ', '}.")
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,49 @@
1
+ require 'formatador'
2
+
3
+ module EnvLint
4
+ class Formatter
5
+ def initialize(out = Formatador)
6
+ @out = out
7
+ end
8
+
9
+ def missing_variables(dot_env_file, variables)
10
+ error("Missing env variables:\n")
11
+
12
+ variables.each do |variable|
13
+ @out.display_line(" [yellow]#{variable.name}[/] - #{variable.comment}")
14
+ end
15
+
16
+ new_line
17
+ info("Either set the variable or make it optional in the #{dot_env_file.name} file.")
18
+ end
19
+
20
+ def unknown_variables(dot_env_file, variable_names)
21
+ error("Unknown env variables:\n")
22
+
23
+ variable_names.each do |name|
24
+ @out.display_line(" [yellow]#{name}[/]")
25
+ end
26
+
27
+ new_line
28
+ info("Only variables descibred in #{dot_env_file.name} can be used.")
29
+ end
30
+
31
+ def error(message)
32
+ @out.display_line("* [red]#{message}[/]")
33
+ end
34
+
35
+ def ok(message)
36
+ @out.display_line("* [green]#{message}[/]")
37
+ end
38
+
39
+ private
40
+
41
+ def info(message)
42
+ @out.display_line(" #{message}")
43
+ end
44
+
45
+ def new_line
46
+ @out.display_line('')
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,29 @@
1
+ module EnvLint
2
+ class LintedEnv
3
+ def initialize(env, dot_env_file)
4
+ @env = env
5
+ @dot_env_file = dot_env_file
6
+ end
7
+
8
+ def fetch(name, *args, &block)
9
+ name = name.to_s.upcase if name.is_a?(Symbol)
10
+ variable = @dot_env_file.find_variable(name)
11
+
12
+ raise(UnknownVariable.new(name)) unless variable
13
+
14
+ if variable.optional? && args.empty? && !block_given?
15
+ raise(DefaultValueRequiredForOptionalVariable.new(name))
16
+ end
17
+
18
+ block ||= lambda do |i|
19
+ args.any? ? args.first : raise(MissingVariable.new(name))
20
+ end
21
+
22
+ @env.fetch(name, &block)
23
+ end
24
+
25
+ def self.from_file(file_name)
26
+ new(ENV, DotEnvFile.from_file(file_name))
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,21 @@
1
+ namespace :env do
2
+ desc 'Ensure every non optional ENV variable is defined.'
3
+ task :lint => :load do
4
+ begin
5
+ EnvLint.verify_hash(env_definition_file, ENV)
6
+ EnvLint.formatter.ok('env looks ok')
7
+ rescue EnvLint::MissingVariables => e
8
+ EnvLint.formatter.missing_variables(e.dot_env_file, e.missing_variables)
9
+ abort
10
+ rescue EnvLint::Error => e
11
+ EnvLint.formatter.error(e.message)
12
+ end
13
+ end
14
+
15
+ task :load do
16
+ end
17
+
18
+ def env_definition_file
19
+ ENV['DEFINITION'] || '.env.example'
20
+ end
21
+ end
@@ -0,0 +1,5 @@
1
+ module EnvLint
2
+ class Variable < Struct.new(:name, :value, :optional, :comment)
3
+ alias_method :optional?, :optional
4
+ end
5
+ end
@@ -0,0 +1,3 @@
1
+ module EnvLint
2
+ VERSION = "0.0.1"
3
+ end
data/lib/env_lint.rb ADDED
@@ -0,0 +1,24 @@
1
+ require 'env_lint/dot_env_file'
2
+ require 'env_lint/dot_env_parser'
3
+ require 'env_lint/formatter'
4
+ require 'env_lint/env_key_parser'
5
+ require 'env_lint/errors'
6
+ require 'env_lint/linted_env'
7
+ require 'env_lint/variable'
8
+ require 'env_lint/version'
9
+
10
+ module EnvLint
11
+ def self.verify_hash(env_definition_file, env)
12
+ DotEnvFile.from_file(env_definition_file).verify_no_missing(env.keys)
13
+ end
14
+
15
+ def self.verify_export_output(env_definition_file, export_output)
16
+ DotEnvFile.from_file(env_definition_file)
17
+ .verify_no_missing(EnvKeyParser.new.parse_export_output(export_output))
18
+ end
19
+
20
+ def self.verify_args(env_definition_file, args)
21
+ DotEnvFile.from_file(env_definition_file)
22
+ .verify_no_unknown(EnvKeyParser.new.parse_args(args))
23
+ end
24
+ end
@@ -0,0 +1,94 @@
1
+ require 'spec_helper'
2
+ require 'env_lint/capistrano'
3
+
4
+ module EnvLint
5
+ describe Capistrano do
6
+ let :config do
7
+ ::Capistrano::Configuration.new
8
+ end
9
+
10
+ let :namespace do
11
+ config.env
12
+ end
13
+
14
+ let :formatter do
15
+ instance_double(Formatter, ok: true, missing_variables: true, unknown_variables: true, error: true)
16
+ end
17
+
18
+ before do
19
+ config.set(:application_user, 'myapp')
20
+
21
+ EnvLint::Capistrano.load_into(config, formatter)
22
+ end
23
+
24
+ describe 'env:lint' do
25
+ it 'calls verify_export_output with result of capute' do
26
+ config.set(:env_definition_file, '.env.example')
27
+
28
+ allow(namespace).to receive(:capture).and_return('declare -x APP=1')
29
+ expect(EnvLint).to receive(:verify_export_output).with('.env.example', 'declare -x APP=1')
30
+
31
+ config.find_and_execute_task('env:lint')
32
+ end
33
+
34
+ it 'passes missing variables to formatter and aborts' do
35
+ dot_env_file = {}
36
+ variables = ['APP']
37
+
38
+ allow(namespace).to receive(:capture).and_return('declare -x APP=1')
39
+ allow(EnvLint).to receive(:verify_export_output).and_raise(MissingVariables.new(dot_env_file, variables))
40
+ expect(namespace).to receive(:abort)
41
+ expect(formatter).to receive(:missing_variables).with(dot_env_file, variables)
42
+
43
+ config.find_and_execute_task('env:lint')
44
+ end
45
+
46
+ it 'passes error message to formatter and aborts' do
47
+ dot_env_file = {}
48
+ variables = ['APP']
49
+
50
+ allow(namespace).to receive(:capture).and_return('declare -x APP=1')
51
+ allow(EnvLint).to receive(:verify_export_output).and_raise(Error.new('message'))
52
+ expect(namespace).to receive(:abort)
53
+ expect(formatter).to receive(:error).with('message')
54
+
55
+ config.find_and_execute_task('env:lint')
56
+ end
57
+ end
58
+
59
+ describe 'env:lint_args' do
60
+ it 'calls verify_args with result of capute' do
61
+ config.set(:env_definition_file, '.env.example')
62
+
63
+ allow(namespace).to receive(:env_args).and_return(['APP=1'])
64
+ expect(EnvLint).to receive(:verify_args).with('.env.example', ['APP=1'])
65
+
66
+ config.find_and_execute_task('env:lint_args')
67
+ end
68
+
69
+ it 'passes missing variables to formatter and aborts' do
70
+ dot_env_file = {}
71
+ variables = ['APP']
72
+
73
+ allow(namespace).to receive(:env_args).and_return(['APP=1'])
74
+ allow(EnvLint).to receive(:verify_args).and_raise(UnknownVariables.new(dot_env_file, variables))
75
+ expect(namespace).to receive(:abort)
76
+ expect(formatter).to receive(:unknown_variables).with(dot_env_file, variables)
77
+
78
+ config.find_and_execute_task('env:lint_args')
79
+ end
80
+
81
+ it 'passes error message to formatter and aborts' do
82
+ dot_env_file = {}
83
+ variables = ['APP']
84
+
85
+ allow(namespace).to receive(:env_args).and_return(['APP=1'])
86
+ allow(EnvLint).to receive(:verify_args).and_raise(Error.new('message'))
87
+ expect(namespace).to receive(:abort)
88
+ expect(formatter).to receive(:error).with('message')
89
+
90
+ config.find_and_execute_task('env:lint_args')
91
+ end
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,73 @@
1
+ require 'spec_helper'
2
+
3
+ module EnvLint
4
+ describe DotEnvFile do
5
+ describe '#verify_no_missing' do
6
+ it 'passes if all non optional variables are present in the env' do
7
+ dot_env_file = DotEnvFile.new('.env.example', [Variable.new('APP')])
8
+
9
+ expect {
10
+ dot_env_file.verify_no_missing(['APP'])
11
+ }.not_to raise_error
12
+ end
13
+
14
+ it 'raises an exception listing undefined non optional variables' do
15
+ dot_env_file = DotEnvFile.new('.env.example', [Variable.new('APP'), Variable.new('URL')])
16
+
17
+ expect {
18
+ dot_env_file.verify_no_missing(['APP'])
19
+ }.to raise_error(MissingVariables)
20
+ end
21
+
22
+ it 'lists undefined non optional variables in exception' do
23
+ dot_env_file = DotEnvFile.new('.env.example', [Variable.new('APP'), Variable.new('URL'), Variable.new('OTHER')])
24
+
25
+ begin
26
+ dot_env_file.verify_no_missing(['APP'])
27
+ rescue MissingVariables => e
28
+ expect(e.missing_variables.size).to eq(2)
29
+ expect(e.missing_variables.first.name).to eq('URL')
30
+ expect(e.missing_variables.last.name).to eq('OTHER')
31
+ end
32
+ end
33
+
34
+ it 'passes if optional variable is not defined' do
35
+ dot_env_file = DotEnvFile.new('.env.example', [Variable.new('APP', '', true)])
36
+
37
+ expect {
38
+ dot_env_file.verify_no_missing([''])
39
+ }.not_to raise_error
40
+ end
41
+ end
42
+
43
+ describe '#verfy_no_unknown' do
44
+ it 'passes if all variables of the env are known' do
45
+ dot_env_file = DotEnvFile.new('.env.example', [Variable.new('APP')])
46
+
47
+ expect {
48
+ dot_env_file.verify_no_unknown(['APP'])
49
+ }.not_to raise_error
50
+ end
51
+
52
+ it 'raises an exception if a variable is unknown' do
53
+ dot_env_file = DotEnvFile.new('.env.example', [Variable.new('APP')])
54
+
55
+ expect {
56
+ dot_env_file.verify_no_unknown(['APP', 'UNKNOWN', 'OTHER'])
57
+ }.to raise_error(UnknownVariables)
58
+ end
59
+
60
+ it 'lists unknown variables in exception' do
61
+ dot_env_file = DotEnvFile.new('.env.example', [Variable.new('APP')])
62
+
63
+ begin
64
+ dot_env_file.verify_no_unknown(['APP', 'UNKNOWN', 'OTHER'])
65
+ rescue UnknownVariables => e
66
+ expect(e.unknown_variables.size).to eq(2)
67
+ expect(e.unknown_variables.first).to eq('UNKNOWN')
68
+ expect(e.unknown_variables.last).to eq('OTHER')
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,105 @@
1
+ require 'spec_helper'
2
+
3
+ module EnvLint
4
+ describe DotEnvParser do
5
+ describe '#parse' do
6
+ it 'recognizes variables' do
7
+ variables = DotEnvParser.new.parse(<<-END)
8
+ APP=myapp
9
+ END
10
+
11
+ expect(variables.first.name).to eq('APP')
12
+ expect(variables.first.value).to eq('myapp')
13
+ end
14
+
15
+ it 'recognizes double quoted values' do
16
+ variables = DotEnvParser.new.parse(<<-END)
17
+ APP="my app"
18
+ END
19
+
20
+ expect(variables.first.value).to eq('my app')
21
+ end
22
+
23
+ it 'recognizes single quoted values' do
24
+ variables = DotEnvParser.new.parse(<<-END)
25
+ APP='my app'
26
+ END
27
+
28
+ expect(variables.first.value).to eq('my app')
29
+ end
30
+
31
+ it 'handles empty assignments' do
32
+ variables = DotEnvParser.new.parse(<<-END)
33
+ APP=
34
+ END
35
+
36
+ expect(variables.first.value).to eq('')
37
+ end
38
+
39
+ it 'ignores blank linkes' do
40
+ variables = DotEnvParser.new.parse(<<-END)
41
+ APP1=myapp
42
+
43
+ APP2=myapp
44
+ END
45
+
46
+ expect(variables.count).to eq(2)
47
+ expect(variables.first.name).to eq('APP1')
48
+ expect(variables.last.name).to eq('APP2')
49
+ end
50
+
51
+ it 'recognizes optional variables' do
52
+ variables = DotEnvParser.new.parse(<<-END)
53
+ # APP=myapp
54
+ #OTHER=myapp
55
+ APP=myapp
56
+ END
57
+
58
+ expect(variables[0]).to be_optional
59
+ expect(variables[1]).to be_optional
60
+ expect(variables[2]).not_to be_optional
61
+ end
62
+
63
+ it 'recognizes comments' do
64
+ variables = DotEnvParser.new.parse(<<-END)
65
+ # The name of the app
66
+ APP=myapp
67
+ END
68
+
69
+ expect(variables.first.comment).to eq('The name of the app')
70
+ end
71
+
72
+ it 'recognizes multi line comments' do
73
+ variables = DotEnvParser.new.parse(<<-END)
74
+ # The name of the app
75
+ # and here the text goes on
76
+ APP=myapp
77
+ END
78
+
79
+ expect(variables.first.comment).to eq("The name of the app\nand here the text goes on")
80
+ end
81
+
82
+ it 'starts new comment at blank line' do
83
+ variables = DotEnvParser.new.parse(<<-END)
84
+ # This is some header which is not related
85
+ # to the following variable
86
+
87
+ # This is the comment
88
+ APP=myapp
89
+ END
90
+
91
+ expect(variables.first.comment).to eq('This is the comment')
92
+ end
93
+
94
+ it 'raises exception for unrecognized line' do
95
+ text = <<-END
96
+ what is this?
97
+ END
98
+
99
+ expect {
100
+ DotEnvParser.new.parse(text)
101
+ }.to raise_error(UnrecognizedDotEnvLine)
102
+ end
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,36 @@
1
+ require 'spec_helper'
2
+
3
+ module EnvLint
4
+ describe EnvKeyParser do
5
+ describe '.parse_args' do
6
+ it 'recognizes assignments' do
7
+ parser = EnvKeyParser.new
8
+
9
+ keys = parser.parse_args(['APP=myname', 'NAME=test'])
10
+
11
+ expect(keys).to eq(['APP', 'NAME'])
12
+ end
13
+
14
+ it 'ignores other args' do
15
+ parser = EnvKeyParser.new
16
+
17
+ keys = parser.parse_args(['env:set', 'APP=myname'])
18
+
19
+ expect(keys).to eq(['APP'])
20
+ end
21
+ end
22
+
23
+ describe '.parse_export_output' do
24
+ it 'recognizes assignments' do
25
+ parser = EnvKeyParser.new
26
+
27
+ keys = parser.parse_export_output(<<-END)
28
+ declare -x APP="myapp"
29
+ declare -x NAME="test"
30
+ END
31
+
32
+ expect(keys).to eq(['APP', 'NAME'])
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,85 @@
1
+ require 'spec_helper'
2
+
3
+ module EnvLint
4
+ describe LintedEnv do
5
+ describe '#fetch' do
6
+ it 'returns value from env for known variable' do
7
+ env = {'APP' => 'myapp'}
8
+ dot_env_file = DotEnvFile.new('.env.example', [Variable.new('APP')])
9
+ linted_env = LintedEnv.new(env, dot_env_file)
10
+
11
+ expect(linted_env.fetch('APP')).to eq('myapp')
12
+ end
13
+
14
+ it 'translates symbols to uppercase strings' do
15
+ env = {'APP_NAME' => 'myapp'}
16
+ dot_env_file = DotEnvFile.new('.env.example', [Variable.new('APP_NAME')])
17
+ linted_env = LintedEnv.new(env, dot_env_file)
18
+
19
+ expect(linted_env.fetch(:app_name)).to eq('myapp')
20
+ end
21
+
22
+ it 'raises exception for unknown variable' do
23
+ env = {'APP' => 'myapp'}
24
+ dot_env_file = DotEnvFile.new('.env.example', [])
25
+ linted_env = LintedEnv.new(env, dot_env_file)
26
+
27
+ expect {
28
+ linted_env.fetch('APP')
29
+ }.to raise_error(UnknownVariable)
30
+ end
31
+
32
+ it 'raises exception for undefined variable' do
33
+ env = {}
34
+ dot_env_file = DotEnvFile.new('.env.example', [Variable.new('APP')])
35
+ linted_env = LintedEnv.new(env, dot_env_file)
36
+
37
+ expect {
38
+ linted_env.fetch('APP')
39
+ }.to raise_error(MissingVariable)
40
+ end
41
+
42
+ it 'returns default value for undefined variable' do
43
+ env = {}
44
+ dot_env_file = DotEnvFile.new('.env.example', [Variable.new('APP')])
45
+ linted_env = LintedEnv.new(env, dot_env_file)
46
+
47
+ expect(linted_env.fetch('APP', 'default')).to eq('default')
48
+ end
49
+
50
+ it 'returns default from block for undefined variable' do
51
+ env = {}
52
+ dot_env_file = DotEnvFile.new('.env.example', [Variable.new('APP')])
53
+ linted_env = LintedEnv.new(env, dot_env_file)
54
+
55
+ expect(linted_env.fetch('APP') { 'default' }).to eq('default')
56
+ end
57
+
58
+ it 'raises exception if optional variable is used without default' do
59
+ env = {'APP' => 'myapp'}
60
+ dot_env_file = DotEnvFile.new('.env.example', [Variable.new('APP', '', true)])
61
+ linted_env = LintedEnv.new(env, dot_env_file)
62
+
63
+ expect {
64
+ linted_env.fetch('APP')
65
+ }.to raise_error(DefaultValueRequiredForOptionalVariable)
66
+ end
67
+
68
+ it 'allows to use optional variable with default value' do
69
+ env = {'APP' => 'myapp'}
70
+ dot_env_file = DotEnvFile.new('.env.example', [Variable.new('APP', '', true)])
71
+ linted_env = LintedEnv.new(env, dot_env_file)
72
+
73
+ expect(linted_env.fetch('APP', 'default')).to eq('myapp')
74
+ end
75
+
76
+ it 'allows to use optional variable with block' do
77
+ env = {'APP' => 'myapp'}
78
+ dot_env_file = DotEnvFile.new('.env.example', [Variable.new('APP', '', true)])
79
+ linted_env = LintedEnv.new(env, dot_env_file)
80
+
81
+ expect(linted_env.fetch('APP') { 'block' }).to eq('myapp')
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,109 @@
1
+ require 'spec_helper'
2
+ require 'fileutils'
3
+
4
+ describe EnvLint do
5
+ SANDBOX_DIR = File.join(File.dirname(__FILE__), 'tmp')
6
+
7
+ around do |example|
8
+ clean_up_sandbox
9
+ in_sandbox { example.run }
10
+ end
11
+
12
+ describe '.verify_hash' do
13
+ it 'passes when hash is complete' do
14
+ File.write('.env.example', <<-END)
15
+ APP=myapp
16
+ END
17
+
18
+ expect {
19
+ EnvLint.verify_hash('.env.example', {'APP' => 'some name'})
20
+ }.not_to raise_error
21
+ end
22
+
23
+ it 'fails when hash is missing non optional variable' do
24
+ File.write('.env.example', <<-END)
25
+ APP=myapp
26
+ END
27
+
28
+ expect {
29
+ EnvLint.verify_hash('.env.example', {'OTHER' => 'other'})
30
+ }.to raise_error(EnvLint::MissingVariables)
31
+ end
32
+ end
33
+
34
+ describe '.verify_args' do
35
+ it 'passes when all args are known' do
36
+ File.write('.env.example', <<-END)
37
+ APP=myapp
38
+ OTHER=other
39
+ END
40
+
41
+ expect {
42
+ EnvLint.verify_args('.env.example', ['APP=name'])
43
+ }.not_to raise_error
44
+ end
45
+
46
+ it 'fails when there are unknown args' do
47
+ File.write('.env.example', <<-END)
48
+ APP=myapp
49
+ END
50
+
51
+ expect {
52
+ EnvLint.verify_args('.env.example', ['OTHER=name'])
53
+ }.to raise_error(EnvLint::UnknownVariables)
54
+ end
55
+ end
56
+
57
+ describe '.verify_export_output' do
58
+ it 'passes when the env is complete' do
59
+ File.write('.env.example', <<-END)
60
+ APP=myapp
61
+ END
62
+ export_output = <<-END
63
+ declare -x APP="myapp"
64
+ declare -x OTHER="other"
65
+ END
66
+
67
+ expect {
68
+ EnvLint.verify_export_output('.env.example', export_output)
69
+ }.not_to raise_error
70
+ end
71
+
72
+ it 'fails when the env is missing non optional variable' do
73
+ File.write('.env.example', <<-END)
74
+ APP=myapp
75
+ END
76
+ export_output = <<-END
77
+ declare -x OTHER="other"
78
+ END
79
+
80
+ expect {
81
+ EnvLint.verify_export_output('.env.example', export_output)
82
+ }.to raise_error(EnvLint::MissingVariables)
83
+ end
84
+ end
85
+
86
+ describe EnvLint::LintedEnv do
87
+ it 'allows to read defined variables from ENV' do
88
+ File.write('.env.example', <<-END)
89
+ APP=myapp
90
+ END
91
+ allow(ENV).to receive(:fetch).with('APP').and_return('value')
92
+
93
+ linted_env = EnvLint::LintedEnv.from_file('.env.example')
94
+
95
+ expect(linted_env.fetch(:app)).to eq('value')
96
+ end
97
+ end
98
+
99
+ private
100
+
101
+ def clean_up_sandbox
102
+ FileUtils.rm_rf(SANDBOX_DIR)
103
+ FileUtils.mkdir_p(SANDBOX_DIR)
104
+ end
105
+
106
+ def in_sandbox(&block)
107
+ Dir.chdir(SANDBOX_DIR, &block)
108
+ end
109
+ end
@@ -0,0 +1,5 @@
1
+ require 'rspec'
2
+
3
+ require 'env_lint'
4
+
5
+ Dir[File.join(File.dirname(__FILE__), 'support/**/*.rb')].each { |f| require f }
metadata ADDED
@@ -0,0 +1,146 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: env_lint
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Tim Fischbach
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-02-19 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: formatador
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '>='
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '>='
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: capistrano
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: '2.9'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ~>
39
+ - !ruby/object:Gem::Version
40
+ version: '2.9'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '='
46
+ - !ruby/object:Gem::Version
47
+ version: 3.0.0.beta1
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '='
53
+ - !ruby/object:Gem::Version
54
+ version: 3.0.0.beta1
55
+ - !ruby/object:Gem::Dependency
56
+ name: bundler
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: '1.3'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ~>
67
+ - !ruby/object:Gem::Version
68
+ version: '1.3'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rake
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - '>='
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - '>='
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ description:
84
+ email:
85
+ - mail@timfischbach.de
86
+ executables: []
87
+ extensions: []
88
+ extra_rdoc_files: []
89
+ files:
90
+ - .gitignore
91
+ - .travis.yml
92
+ - Gemfile
93
+ - LICENSE.txt
94
+ - README.md
95
+ - Rakefile
96
+ - env_lint.gemspec
97
+ - lib/env_lint.rb
98
+ - lib/env_lint/capistrano.rb
99
+ - lib/env_lint/dot_env_file.rb
100
+ - lib/env_lint/dot_env_parser.rb
101
+ - lib/env_lint/env_key_parser.rb
102
+ - lib/env_lint/errors.rb
103
+ - lib/env_lint/formatter.rb
104
+ - lib/env_lint/linted_env.rb
105
+ - lib/env_lint/tasks.rb
106
+ - lib/env_lint/variable.rb
107
+ - lib/env_lint/version.rb
108
+ - spec/env_lint/capistrano_spec.rb
109
+ - spec/env_lint/dot_env_file_spec.rb
110
+ - spec/env_lint/dot_env_parser_spec.rb
111
+ - spec/env_lint/env_key_parser_spec.rb
112
+ - spec/env_lint/linted_env_spec.rb
113
+ - spec/integration_spec.rb
114
+ - spec/spec_helper.rb
115
+ homepage: ''
116
+ licenses:
117
+ - MIT
118
+ metadata: {}
119
+ post_install_message:
120
+ rdoc_options: []
121
+ require_paths:
122
+ - lib
123
+ required_ruby_version: !ruby/object:Gem::Requirement
124
+ requirements:
125
+ - - '>='
126
+ - !ruby/object:Gem::Version
127
+ version: '0'
128
+ required_rubygems_version: !ruby/object:Gem::Requirement
129
+ requirements:
130
+ - - '>='
131
+ - !ruby/object:Gem::Version
132
+ version: '0'
133
+ requirements: []
134
+ rubyforge_project:
135
+ rubygems_version: 2.2.2
136
+ signing_key:
137
+ specification_version: 4
138
+ summary: Lint the environment according by .env.example
139
+ test_files:
140
+ - spec/env_lint/capistrano_spec.rb
141
+ - spec/env_lint/dot_env_file_spec.rb
142
+ - spec/env_lint/dot_env_parser_spec.rb
143
+ - spec/env_lint/env_key_parser_spec.rb
144
+ - spec/env_lint/linted_env_spec.rb
145
+ - spec/integration_spec.rb
146
+ - spec/spec_helper.rb