cody 0.1.0 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.cody/buildspec.yml +8 -0
- data/.cody/project.rb +4 -0
- data/.cody/settings.yml +13 -0
- data/.gitignore +17 -10
- data/.gitmodules +9 -0
- data/.rspec +1 -1
- data/.ruby-version +1 -0
- data/CHANGELOG.md +59 -0
- data/Gemfile +3 -1
- data/Guardfile +19 -0
- data/LICENSE.txt +18 -17
- data/README.md +145 -18
- data/Rakefile +9 -2
- data/cody.gemspec +26 -12
- data/exe/cody +14 -0
- data/img/github-admin-settings-tab.png +0 -0
- data/lib/cody.rb +17 -1
- data/lib/cody/autoloader.rb +21 -0
- data/lib/cody/aws_services.rb +16 -0
- data/lib/cody/aws_services/helpers.rb +72 -0
- data/lib/cody/cli.rb +61 -0
- data/lib/cody/command.rb +82 -0
- data/lib/cody/completer.rb +159 -0
- data/lib/cody/completer/script.rb +6 -0
- data/lib/cody/completer/script.sh +10 -0
- data/lib/cody/core.rb +63 -0
- data/lib/cody/create.rb +12 -0
- data/lib/cody/default/settings.yml +3 -0
- data/lib/cody/delete.rb +27 -0
- data/lib/cody/deploy.rb +40 -0
- data/lib/cody/dsl/project.rb +119 -0
- data/lib/cody/dsl/project/ssm.rb +22 -0
- data/lib/cody/dsl/role.rb +50 -0
- data/lib/cody/dsl/schedule.rb +30 -0
- data/lib/cody/evaluate.rb +47 -0
- data/lib/cody/help.rb +9 -0
- data/lib/cody/help/completion.md +22 -0
- data/lib/cody/help/completion_script.md +3 -0
- data/lib/cody/help/deploy.md +32 -0
- data/lib/cody/help/init.md +71 -0
- data/lib/cody/help/start.md +12 -0
- data/lib/cody/init.rb +102 -0
- data/lib/cody/project.rb +72 -0
- data/lib/cody/role.rb +87 -0
- data/lib/cody/schedule.rb +102 -0
- data/lib/cody/sequence.rb +66 -0
- data/lib/cody/setting.rb +82 -0
- data/lib/cody/stack.rb +93 -0
- data/lib/cody/start.rb +69 -0
- data/lib/cody/update.rb +12 -0
- data/lib/cody/variables.rb +17 -0
- data/lib/cody/version.rb +2 -2
- data/lib/template/project/buildspec.yml +28 -0
- data/lib/template/project/project.rb.tt +29 -0
- data/lib/template/project/role.rb +2 -0
- data/lib/template/project/schedule.rb +3 -0
- data/lib/template/top/README.md +32 -0
- data/lib/template/top/settings.yml +9 -0
- data/lib/template/top/variables/base.rb +1 -0
- data/lib/template/top/variables/development.rb +1 -0
- data/lib/template/top/variables/production.rb +1 -0
- data/vendor/aws_data/CHANGELOG.md +7 -0
- data/vendor/aws_data/Gemfile +4 -0
- data/vendor/aws_data/LICENSE.txt +21 -0
- data/vendor/aws_data/README.md +42 -0
- data/vendor/aws_data/Rakefile +6 -0
- data/vendor/aws_data/aws_data.gemspec +30 -0
- data/{bin → vendor/aws_data/bin}/console +1 -1
- data/{bin → vendor/aws_data/bin}/setup +0 -0
- data/vendor/aws_data/lib/aws_data.rb +91 -0
- data/vendor/aws_data/lib/aws_data/version.rb +3 -0
- data/vendor/aws_data/spec/aws_data_spec.rb +5 -0
- data/vendor/aws_data/spec/spec_helper.rb +14 -0
- data/vendor/cfn-status/Gemfile +4 -0
- data/vendor/cfn-status/LICENSE.txt +21 -0
- data/vendor/cfn-status/README.md +56 -0
- data/vendor/cfn-status/Rakefile +6 -0
- data/vendor/cfn-status/bin/console +14 -0
- data/vendor/cfn-status/bin/setup +8 -0
- data/vendor/cfn-status/cfn-status.gemspec +30 -0
- data/vendor/cfn-status/lib/cfn/aws_service.rb +56 -0
- data/vendor/cfn-status/lib/cfn/status.rb +220 -0
- data/vendor/cfn-status/lib/cfn/status/version.rb +5 -0
- data/vendor/cfn-status/spec/cfn/status_spec.rb +81 -0
- data/vendor/cfn-status/spec/fixtures/cfn/stack-events-complete.json +1080 -0
- data/vendor/cfn-status/spec/fixtures/cfn/stack-events-in-progress.json +1080 -0
- data/vendor/cfn-status/spec/fixtures/cfn/stack-events-update-rollback-complete.json +1086 -0
- data/vendor/cfn-status/spec/spec_helper.rb +14 -0
- data/vendor/cfn_camelizer/CHANGELOG.md +10 -0
- data/vendor/cfn_camelizer/Gemfile +4 -0
- data/vendor/cfn_camelizer/LICENSE.txt +21 -0
- data/vendor/cfn_camelizer/README.md +40 -0
- data/vendor/cfn_camelizer/Rakefile +6 -0
- data/vendor/cfn_camelizer/bin/console +14 -0
- data/vendor/cfn_camelizer/bin/setup +8 -0
- data/vendor/cfn_camelizer/cfn_camelizer.gemspec +32 -0
- data/vendor/cfn_camelizer/lib/camelizer.yml +33 -0
- data/vendor/cfn_camelizer/lib/cfn_camelizer.rb +92 -0
- data/vendor/cfn_camelizer/lib/cfn_camelizer/version.rb +3 -0
- data/vendor/cfn_camelizer/spec/cfn_camelizer_spec.rb +79 -0
- data/vendor/cfn_camelizer/spec/spec_helper.rb +14 -0
- metadata +268 -21
- data/.travis.yml +0 -7
data/lib/cody/core.rb
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
require 'yaml'
|
3
|
+
require 'active_support/core_ext/string'
|
4
|
+
|
5
|
+
module Cody
|
6
|
+
module Core
|
7
|
+
extend Memoist
|
8
|
+
|
9
|
+
def root
|
10
|
+
path = ENV['CODY_ROOT'] || '.'
|
11
|
+
Pathname.new(path)
|
12
|
+
end
|
13
|
+
|
14
|
+
def env
|
15
|
+
# 2-way binding
|
16
|
+
cb_env = env_from_profile || 'development'
|
17
|
+
cb_env = ENV['CODY_ENV'] if ENV['CODY_ENV'] # highest precedence
|
18
|
+
ActiveSupport::StringInquirer.new(cb_env)
|
19
|
+
end
|
20
|
+
memoize :env
|
21
|
+
|
22
|
+
def env_extra
|
23
|
+
env_extra = ENV['CODY_ENV_EXTRA'] if ENV['CODY_ENV_EXTRA'] # highest precedence
|
24
|
+
return if env_extra&.empty?
|
25
|
+
env_extra
|
26
|
+
end
|
27
|
+
memoize :env_extra
|
28
|
+
|
29
|
+
# Overrides AWS_PROFILE based on the Cody.env if set in configs/settings.yml
|
30
|
+
# 2-way binding.
|
31
|
+
def set_aws_profile!
|
32
|
+
return if ENV['TEST']
|
33
|
+
return unless File.exist?("#{Cody.root}/.cody/settings.yml") # for rake docs
|
34
|
+
return unless settings # Only load if within Cody project and there's a settings.yml
|
35
|
+
|
36
|
+
data = settings || {}
|
37
|
+
if data[:aws_profile]
|
38
|
+
puts "Using AWS_PROFILE=#{data[:aws_profile]} from CODY_ENV=#{Cody.env} in config/settings.yml"
|
39
|
+
ENV['AWS_PROFILE'] = data[:aws_profile]
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def settings
|
44
|
+
Setting.new.data
|
45
|
+
end
|
46
|
+
memoize :settings
|
47
|
+
|
48
|
+
def check_codebuild_project!
|
49
|
+
check_path = "#{Cody.root}/.cody"
|
50
|
+
unless File.exist?(check_path)
|
51
|
+
puts "ERROR: No .cody folder found. Are you sure you are in a project with codebuild setup?".color(:red)
|
52
|
+
puts "Current directory: #{Dir.pwd}"
|
53
|
+
puts "If you want to set up codebuild for this project, please create a settings file via: cody init"
|
54
|
+
exit 1 unless ENV['TEST']
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
def env_from_profile
|
60
|
+
Cody::Setting.new.cb_env
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
data/lib/cody/create.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
module Cody
|
2
|
+
class Create < Stack
|
3
|
+
def perform
|
4
|
+
cfn.create_stack(
|
5
|
+
stack_name: @stack_name,
|
6
|
+
template_body: YAML.dump(@template),
|
7
|
+
capabilities: ["CAPABILITY_IAM"]
|
8
|
+
)
|
9
|
+
puts "Creating stack #{@stack_name}. Check CloudFormation console for status."
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
data/lib/cody/delete.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
module Cody
|
2
|
+
class Delete
|
3
|
+
include AwsServices
|
4
|
+
|
5
|
+
def initialize(options)
|
6
|
+
@options = options
|
7
|
+
@project_name = options[:project_name] || inferred_project_name
|
8
|
+
@stack_name = options[:stack_name] || inferred_stack_name(@project_name)
|
9
|
+
end
|
10
|
+
|
11
|
+
def run
|
12
|
+
message = "Deleted #{@stack_name} stack."
|
13
|
+
if @options[:noop]
|
14
|
+
puts "NOOP #{message}"
|
15
|
+
else
|
16
|
+
are_you_sure?(@stack_name, :delete)
|
17
|
+
|
18
|
+
if stack_exists?(@stack_name)
|
19
|
+
cfn.delete_stack(stack_name: @stack_name)
|
20
|
+
puts message
|
21
|
+
else
|
22
|
+
puts "#{@stack_name.inspect} stack does not exist".color(:red)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
data/lib/cody/deploy.rb
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
module Cody
|
2
|
+
class Deploy < Stack
|
3
|
+
def run
|
4
|
+
handle_rollback_completed!
|
5
|
+
if stack_exists?(@stack_name)
|
6
|
+
Update.new(@options).run
|
7
|
+
else
|
8
|
+
Create.new(@options).run
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def handle_rollback_completed!
|
13
|
+
@stack = find_stack(@stack_name)
|
14
|
+
if @stack && rollback_complete?(@stack)
|
15
|
+
puts "Existing stack in ROLLBACK_COMPLETE state. Deleting stack before continuing."
|
16
|
+
cfn.delete_stack(stack_name: @stack_name)
|
17
|
+
status.wait
|
18
|
+
status.reset
|
19
|
+
@stack = nil # at this point stack has been deleted
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def rollback_complete?(stack)
|
24
|
+
stack.stack_status == 'ROLLBACK_COMPLETE'
|
25
|
+
end
|
26
|
+
|
27
|
+
def find_stack(stack_name)
|
28
|
+
return if ENV['TEST']
|
29
|
+
resp = cfn.describe_stacks(stack_name: stack_name)
|
30
|
+
resp.stacks.first
|
31
|
+
rescue Aws::CloudFormation::Errors::ValidationError => e
|
32
|
+
# example: Stack with id demo-web does not exist
|
33
|
+
if e.message =~ /Stack with/ && e.message =~ /does not exist/
|
34
|
+
nil
|
35
|
+
else
|
36
|
+
raise
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,119 @@
|
|
1
|
+
module Cody::Dsl
|
2
|
+
module Project
|
3
|
+
include Ssm
|
4
|
+
|
5
|
+
PROPERTIES = %w[
|
6
|
+
artifacts
|
7
|
+
badge_enabled
|
8
|
+
cache
|
9
|
+
description
|
10
|
+
encryption_key
|
11
|
+
environment
|
12
|
+
logs_config
|
13
|
+
name
|
14
|
+
queued_timeout_in_minutes
|
15
|
+
secondary_artifacts
|
16
|
+
secondary_sources
|
17
|
+
service_role
|
18
|
+
source
|
19
|
+
tags
|
20
|
+
timeout_in_minutes
|
21
|
+
triggers
|
22
|
+
vpc_config
|
23
|
+
]
|
24
|
+
PROPERTIES.each do |prop|
|
25
|
+
define_method(prop) do |v|
|
26
|
+
@properties[prop.to_sym] = v
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# Convenience wrapper methods
|
31
|
+
def github_url(url)
|
32
|
+
@properties[:source][:location] = url
|
33
|
+
end
|
34
|
+
|
35
|
+
# So it looks like the auth resource property doesnt really get used.
|
36
|
+
# Instead an account level credential is worked. Refer to:
|
37
|
+
# https://github.com/tongueroo/cody/blob/master/readme/github_oauth.md
|
38
|
+
#
|
39
|
+
# Keeping this method around in case the CloudFormation method works one day,
|
40
|
+
# or end up figuring out to use it properly.
|
41
|
+
def github_token(token)
|
42
|
+
@properties[:source][:auth][:resource] = token
|
43
|
+
end
|
44
|
+
|
45
|
+
def github_source(options={})
|
46
|
+
source = {
|
47
|
+
type: "GITHUB",
|
48
|
+
location: options[:location],
|
49
|
+
git_clone_depth: 1,
|
50
|
+
git_submodules_config: { fetch_submodules: true },
|
51
|
+
build_spec: options[:buildspec] || ".cody/buildspec.yml", # options[:buildspec] accounts for type already
|
52
|
+
report_build_status: true,
|
53
|
+
}
|
54
|
+
|
55
|
+
if options[:oauth_token]
|
56
|
+
source[:auth] = {
|
57
|
+
type: "OAUTH",
|
58
|
+
resource: options[:oauth_token],
|
59
|
+
}
|
60
|
+
end
|
61
|
+
|
62
|
+
@properties[:source] = source
|
63
|
+
end
|
64
|
+
|
65
|
+
def linux_image(name)
|
66
|
+
linux_environment(image: name)
|
67
|
+
end
|
68
|
+
|
69
|
+
def linux_environment(options={})
|
70
|
+
image = options[:image] || "aws/codebuild/ruby:2.5.3-1.7.0"
|
71
|
+
env = {
|
72
|
+
compute_type: options[:compute_type] || "BUILD_GENERAL1_SMALL",
|
73
|
+
image_pull_credentials_type: "CODEBUILD", # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-codebuild-project-environment.html#cfn-codebuild-project-environment-imagepullcredentialstype
|
74
|
+
privileged_mode: true,
|
75
|
+
image: image,
|
76
|
+
type: "LINUX_CONTAINER"
|
77
|
+
}
|
78
|
+
# @mapped_env_vars is in memory
|
79
|
+
env[:environment_variables] = @mapped_env_vars if @mapped_env_vars
|
80
|
+
# options has highest precedence
|
81
|
+
env[:environment_variables] = options[:environment_variables] if options[:environment_variables]
|
82
|
+
@properties[:environment] = env
|
83
|
+
end
|
84
|
+
|
85
|
+
def environment_variables(vars)
|
86
|
+
# Storing @mapped_env_vars as instance variable for later usage in linux_environment
|
87
|
+
@mapped_env_vars = vars.map { |k,v|
|
88
|
+
k, v = k.to_s, v.to_s
|
89
|
+
if v =~ /^ssm:/
|
90
|
+
{ type: "PARAMETER_STORE", name: k, value: v.sub('ssm:','') }
|
91
|
+
else
|
92
|
+
{ type: "PLAINTEXT", name: k, value: v }
|
93
|
+
end
|
94
|
+
}
|
95
|
+
@properties[:environment] ||= {}
|
96
|
+
@properties[:environment][:environment_variables] = @mapped_env_vars
|
97
|
+
end
|
98
|
+
|
99
|
+
def local_cache(enable=true)
|
100
|
+
cache = if enable
|
101
|
+
{
|
102
|
+
type: "LOCAL",
|
103
|
+
modes: [
|
104
|
+
"LOCAL_DOCKER_LAYER_CACHE",
|
105
|
+
"LOCAL_SOURCE_CACHE",
|
106
|
+
"LOCAL_CUSTOM_CACHE"
|
107
|
+
]
|
108
|
+
}
|
109
|
+
else
|
110
|
+
{type: "NO_CACHE"}
|
111
|
+
end
|
112
|
+
@properties[:cache] = cache
|
113
|
+
end
|
114
|
+
|
115
|
+
def type
|
116
|
+
@options[:type]
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require "aws-sdk-ssm"
|
2
|
+
|
3
|
+
module Cody::Dsl::Project
|
4
|
+
module Ssm
|
5
|
+
# This method grabs the ssm parameter store value at "compile" time vs
|
6
|
+
# CloudFormation run time. In case we need it as part of the DSL compile phase.
|
7
|
+
def ssm(name)
|
8
|
+
resp = ssm_client.get_parameter(name: name)
|
9
|
+
if resp.parameter.type == "SecureString"
|
10
|
+
resp = ssm_client.get_parameter(name: name, with_decryption: true)
|
11
|
+
end
|
12
|
+
|
13
|
+
resp.parameter.value
|
14
|
+
rescue Aws::SSM::Errors::ParameterNotFound
|
15
|
+
puts "WARN: #{name} found on AWS SSM.".color(:yellow)
|
16
|
+
end
|
17
|
+
|
18
|
+
def ssm_client
|
19
|
+
@ssm_client ||= Aws::SSM::Client.new
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module Cody::Dsl
|
2
|
+
module Role
|
3
|
+
PROPERTIES = %w[
|
4
|
+
assume_role_policy_document
|
5
|
+
managed_policy_arns
|
6
|
+
max_session_duration
|
7
|
+
path
|
8
|
+
permissions_boundary
|
9
|
+
policies
|
10
|
+
role_name
|
11
|
+
]
|
12
|
+
PROPERTIES.each do |prop|
|
13
|
+
define_method(prop) do |v|
|
14
|
+
@properties[prop.to_sym] = v
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
# convenience wrapper methods
|
19
|
+
def iam_policy(*definitions)
|
20
|
+
@iam_statements = definitions.map { |definition| standardize_iam_policy(definition) }
|
21
|
+
end
|
22
|
+
|
23
|
+
# Returns standarized IAM statement
|
24
|
+
def standardize_iam_policy(definition)
|
25
|
+
case definition
|
26
|
+
when String
|
27
|
+
# Expands simple string from: logs => logs:*
|
28
|
+
definition = "#{definition}:*" unless definition.include?(':')
|
29
|
+
{
|
30
|
+
action: [definition],
|
31
|
+
effect: "Allow",
|
32
|
+
resource: "*",
|
33
|
+
}
|
34
|
+
when Hash
|
35
|
+
definition
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def managed_iam_policy(*definitions)
|
40
|
+
@managed_policy_arns = definitions.map { |definition| standardize_managed_iam_policy(definition) }
|
41
|
+
end
|
42
|
+
|
43
|
+
# AmazonEC2ReadOnlyAccess => arn:aws:iam::aws:policy/AmazonEC2ReadOnlyAccess
|
44
|
+
def standardize_managed_iam_policy(definition)
|
45
|
+
return definition if definition.include?('iam::aws:policy')
|
46
|
+
|
47
|
+
"arn:aws:iam::aws:policy/#{definition}"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Cody::Dsl
|
2
|
+
module Schedule
|
3
|
+
PROPERTIES = %w[
|
4
|
+
description
|
5
|
+
event_pattern
|
6
|
+
name
|
7
|
+
role_arn
|
8
|
+
schedule_expression
|
9
|
+
state
|
10
|
+
targets
|
11
|
+
]
|
12
|
+
PROPERTIES.each do |prop|
|
13
|
+
define_method(prop) do |v|
|
14
|
+
@properties[prop.to_sym] = v
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def rate(period)
|
19
|
+
@schedule_expression = "rate(#{period})"
|
20
|
+
end
|
21
|
+
|
22
|
+
def cron(expression)
|
23
|
+
@schedule_expression = "cron(#{expression})"
|
24
|
+
end
|
25
|
+
|
26
|
+
def rule_event(props={})
|
27
|
+
@rule_event_props = props
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module Cody
|
2
|
+
module Evaluate
|
3
|
+
def evaluate(path)
|
4
|
+
source_code = IO.read(path)
|
5
|
+
begin
|
6
|
+
instance_eval(source_code, path)
|
7
|
+
rescue Exception => e
|
8
|
+
if e.class == SystemExit # allow exit to happen normally
|
9
|
+
raise
|
10
|
+
else
|
11
|
+
task_definition_error(e)
|
12
|
+
puts "\nFull error:"
|
13
|
+
raise
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
# Prints out a user friendly task_definition error message
|
20
|
+
def task_definition_error(e)
|
21
|
+
error_info = e.backtrace.first
|
22
|
+
path, line_no, _ = error_info.split(':')
|
23
|
+
line_no = line_no.to_i
|
24
|
+
puts "Error evaluating #{path}:".color(:red)
|
25
|
+
puts e.message
|
26
|
+
puts "Here's the line in #{path} with the error:\n\n"
|
27
|
+
|
28
|
+
contents = IO.read(path)
|
29
|
+
content_lines = contents.split("\n")
|
30
|
+
context = 5 # lines of context
|
31
|
+
top, bottom = [line_no-context-1, 0].max, line_no+context-1
|
32
|
+
spacing = content_lines.size.to_s.size
|
33
|
+
content_lines[top..bottom].each_with_index do |line_content, index|
|
34
|
+
line_number = top+index+1
|
35
|
+
if line_number == line_no
|
36
|
+
printf("%#{spacing}d %s\n".color(:red), line_number, line_content)
|
37
|
+
else
|
38
|
+
printf("%#{spacing}d %s\n", line_number, line_content)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def lookup_codebuild_file(name)
|
44
|
+
[".cody", @options[:type], name].compact.join("/")
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|