cody 0.1.0 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (104) hide show
  1. checksums.yaml +4 -4
  2. data/.cody/buildspec.yml +8 -0
  3. data/.cody/project.rb +4 -0
  4. data/.cody/settings.yml +13 -0
  5. data/.gitignore +17 -10
  6. data/.gitmodules +9 -0
  7. data/.rspec +1 -1
  8. data/.ruby-version +1 -0
  9. data/CHANGELOG.md +59 -0
  10. data/Gemfile +3 -1
  11. data/Guardfile +19 -0
  12. data/LICENSE.txt +18 -17
  13. data/README.md +145 -18
  14. data/Rakefile +9 -2
  15. data/cody.gemspec +26 -12
  16. data/exe/cody +14 -0
  17. data/img/github-admin-settings-tab.png +0 -0
  18. data/lib/cody.rb +17 -1
  19. data/lib/cody/autoloader.rb +21 -0
  20. data/lib/cody/aws_services.rb +16 -0
  21. data/lib/cody/aws_services/helpers.rb +72 -0
  22. data/lib/cody/cli.rb +61 -0
  23. data/lib/cody/command.rb +82 -0
  24. data/lib/cody/completer.rb +159 -0
  25. data/lib/cody/completer/script.rb +6 -0
  26. data/lib/cody/completer/script.sh +10 -0
  27. data/lib/cody/core.rb +63 -0
  28. data/lib/cody/create.rb +12 -0
  29. data/lib/cody/default/settings.yml +3 -0
  30. data/lib/cody/delete.rb +27 -0
  31. data/lib/cody/deploy.rb +40 -0
  32. data/lib/cody/dsl/project.rb +119 -0
  33. data/lib/cody/dsl/project/ssm.rb +22 -0
  34. data/lib/cody/dsl/role.rb +50 -0
  35. data/lib/cody/dsl/schedule.rb +30 -0
  36. data/lib/cody/evaluate.rb +47 -0
  37. data/lib/cody/help.rb +9 -0
  38. data/lib/cody/help/completion.md +22 -0
  39. data/lib/cody/help/completion_script.md +3 -0
  40. data/lib/cody/help/deploy.md +32 -0
  41. data/lib/cody/help/init.md +71 -0
  42. data/lib/cody/help/start.md +12 -0
  43. data/lib/cody/init.rb +102 -0
  44. data/lib/cody/project.rb +72 -0
  45. data/lib/cody/role.rb +87 -0
  46. data/lib/cody/schedule.rb +102 -0
  47. data/lib/cody/sequence.rb +66 -0
  48. data/lib/cody/setting.rb +82 -0
  49. data/lib/cody/stack.rb +93 -0
  50. data/lib/cody/start.rb +69 -0
  51. data/lib/cody/update.rb +12 -0
  52. data/lib/cody/variables.rb +17 -0
  53. data/lib/cody/version.rb +2 -2
  54. data/lib/template/project/buildspec.yml +28 -0
  55. data/lib/template/project/project.rb.tt +29 -0
  56. data/lib/template/project/role.rb +2 -0
  57. data/lib/template/project/schedule.rb +3 -0
  58. data/lib/template/top/README.md +32 -0
  59. data/lib/template/top/settings.yml +9 -0
  60. data/lib/template/top/variables/base.rb +1 -0
  61. data/lib/template/top/variables/development.rb +1 -0
  62. data/lib/template/top/variables/production.rb +1 -0
  63. data/vendor/aws_data/CHANGELOG.md +7 -0
  64. data/vendor/aws_data/Gemfile +4 -0
  65. data/vendor/aws_data/LICENSE.txt +21 -0
  66. data/vendor/aws_data/README.md +42 -0
  67. data/vendor/aws_data/Rakefile +6 -0
  68. data/vendor/aws_data/aws_data.gemspec +30 -0
  69. data/{bin → vendor/aws_data/bin}/console +1 -1
  70. data/{bin → vendor/aws_data/bin}/setup +0 -0
  71. data/vendor/aws_data/lib/aws_data.rb +91 -0
  72. data/vendor/aws_data/lib/aws_data/version.rb +3 -0
  73. data/vendor/aws_data/spec/aws_data_spec.rb +5 -0
  74. data/vendor/aws_data/spec/spec_helper.rb +14 -0
  75. data/vendor/cfn-status/Gemfile +4 -0
  76. data/vendor/cfn-status/LICENSE.txt +21 -0
  77. data/vendor/cfn-status/README.md +56 -0
  78. data/vendor/cfn-status/Rakefile +6 -0
  79. data/vendor/cfn-status/bin/console +14 -0
  80. data/vendor/cfn-status/bin/setup +8 -0
  81. data/vendor/cfn-status/cfn-status.gemspec +30 -0
  82. data/vendor/cfn-status/lib/cfn/aws_service.rb +56 -0
  83. data/vendor/cfn-status/lib/cfn/status.rb +220 -0
  84. data/vendor/cfn-status/lib/cfn/status/version.rb +5 -0
  85. data/vendor/cfn-status/spec/cfn/status_spec.rb +81 -0
  86. data/vendor/cfn-status/spec/fixtures/cfn/stack-events-complete.json +1080 -0
  87. data/vendor/cfn-status/spec/fixtures/cfn/stack-events-in-progress.json +1080 -0
  88. data/vendor/cfn-status/spec/fixtures/cfn/stack-events-update-rollback-complete.json +1086 -0
  89. data/vendor/cfn-status/spec/spec_helper.rb +14 -0
  90. data/vendor/cfn_camelizer/CHANGELOG.md +10 -0
  91. data/vendor/cfn_camelizer/Gemfile +4 -0
  92. data/vendor/cfn_camelizer/LICENSE.txt +21 -0
  93. data/vendor/cfn_camelizer/README.md +40 -0
  94. data/vendor/cfn_camelizer/Rakefile +6 -0
  95. data/vendor/cfn_camelizer/bin/console +14 -0
  96. data/vendor/cfn_camelizer/bin/setup +8 -0
  97. data/vendor/cfn_camelizer/cfn_camelizer.gemspec +32 -0
  98. data/vendor/cfn_camelizer/lib/camelizer.yml +33 -0
  99. data/vendor/cfn_camelizer/lib/cfn_camelizer.rb +92 -0
  100. data/vendor/cfn_camelizer/lib/cfn_camelizer/version.rb +3 -0
  101. data/vendor/cfn_camelizer/spec/cfn_camelizer_spec.rb +79 -0
  102. data/vendor/cfn_camelizer/spec/spec_helper.rb +14 -0
  103. metadata +268 -21
  104. data/.travis.yml +0 -7
@@ -0,0 +1,6 @@
1
+ class Cody::Completer::Script
2
+ def self.generate
3
+ bash_script = File.expand_path("script.sh", File.dirname(__FILE__))
4
+ puts "source #{bash_script}"
5
+ end
6
+ end
@@ -0,0 +1,10 @@
1
+ _cody() {
2
+ COMPREPLY=()
3
+ local word="${COMP_WORDS[COMP_CWORD]}"
4
+ local words=("${COMP_WORDS[@]}")
5
+ unset words[0]
6
+ local completion=$(cody completion ${words[@]})
7
+ COMPREPLY=( $(compgen -W "$completion" -- "$word") )
8
+ }
9
+
10
+ complete -F _cody cody
@@ -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
@@ -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
@@ -0,0 +1,3 @@
1
+ base:
2
+ stack_naming:
3
+ append_env: false
@@ -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
@@ -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
@@ -0,0 +1,9 @@
1
+ module Cody::Help
2
+ class << self
3
+ def text(namespaced_command)
4
+ path = namespaced_command.to_s.gsub(':','/')
5
+ path = File.expand_path("../help/#{path}.md", __FILE__)
6
+ IO.read(path) if File.exist?(path)
7
+ end
8
+ end
9
+ end