cody 0.1.0 → 0.7.0

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.
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