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,66 @@
1
+ require 'fileutils'
2
+ require 'thor'
3
+
4
+ module Cody
5
+ class Sequence < Thor::Group
6
+ include AwsServices
7
+ include Thor::Actions
8
+
9
+ add_runtime_options! # force, pretend, quiet, skip options
10
+ # https://github.com/erikhuda/thor/blob/master/lib/thor/actions.rb#L49
11
+
12
+ def self.source_paths
13
+ [File.expand_path("../../template", __FILE__)]
14
+ end
15
+
16
+ private
17
+ def override_source_paths(*paths)
18
+ # Using string with instance_eval because block doesnt have access to
19
+ # path at runtime.
20
+ self.class.instance_eval %{
21
+ def self.source_paths
22
+ #{paths.flatten.inspect}
23
+ end
24
+ }
25
+ end
26
+
27
+ def sync_template_repo
28
+ unless git_installed?
29
+ abort "Unable to detect git installation on your system. Git needs to be installed in order to use the --template option."
30
+ end
31
+
32
+ template_path = "#{ENV['HOME']}/.cody/templates/#{full_repo_name}"
33
+ if File.exist?(template_path)
34
+ sh("cd #{template_path} && git pull")
35
+ else
36
+ FileUtils.mkdir_p(File.dirname(template_path))
37
+ sh("git clone #{repo_url} #{template_path}")
38
+ end
39
+ end
40
+
41
+ def full_repo_name
42
+ full_repo = options[:template].split("/")[-2..-1].join("/")
43
+ full_repo = full_repo.split(":").last
44
+ full_repo.sub(".git", "")
45
+ end
46
+
47
+ # normalize repo_url
48
+ def repo_url
49
+ template = options[:template]
50
+ if template.include?('github.com')
51
+ template # leave as is, user has provided full github url
52
+ else
53
+ "https://github.com/#{template}"
54
+ end
55
+ end
56
+
57
+ def git_installed?
58
+ system("type git > /dev/null")
59
+ end
60
+
61
+ def sh(command)
62
+ puts "=> #{command}"
63
+ system(command)
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,82 @@
1
+ require 'yaml'
2
+ require 'render_me_pretty'
3
+
4
+ module Cody
5
+ class Setting
6
+ extend Memoist
7
+ def initialize(check_codebuild_project=true)
8
+ @check_codebuild_project = check_codebuild_project
9
+ end
10
+
11
+ # data contains the settings.yml config. The order or precedence for settings
12
+ # is the project ufo/settings.yml and then the ~/.cody/settings.yml.
13
+ def data
14
+ Cody.check_codebuild_project! if @check_codebuild_project
15
+ return {} unless File.exist?(project_settings_path)
16
+
17
+ # project based settings files
18
+ project = load_file(project_settings_path)
19
+
20
+ user_file = "#{ENV['HOME']}/.cody/settings.yml"
21
+ user = File.exist?(user_file) ? YAML.load_file(user_file) : {}
22
+
23
+ default_file = File.expand_path("default/settings.yml", __dir__)
24
+ default = load_file(default_file)
25
+
26
+ all_envs = default.deep_merge(user.deep_merge(project))
27
+ all_envs = merge_base(all_envs)
28
+ data = all_envs[cb_env] || all_envs["base"] || {}
29
+ data.deep_symbolize_keys
30
+ end
31
+ memoize :data
32
+
33
+ # Resolves infinite problem since Cody.env can be determined from CODY_ENV or settings.yml files.
34
+ # When ufo is determined from settings it should not called Cody.env since that in turn calls
35
+ # Settings.new.data which can then cause an infinite loop.
36
+ def cb_env
37
+ path = "#{cb_root}/.cody/settings.yml"
38
+ if File.exist?(path)
39
+ settings = YAML.load_file(path)
40
+ env = settings.find do |_env, section|
41
+ section ||= {}
42
+ ENV['AWS_PROFILE'] && ENV['AWS_PROFILE'] == section['aws_profile']
43
+ end
44
+ end
45
+
46
+ cb_env = env.first if env
47
+ cb_env = ENV['CODY_ENV'] if ENV['CODY_ENV'] # highest precedence
48
+ cb_env || 'development'
49
+ end
50
+
51
+ private
52
+ def load_file(path)
53
+ return Hash.new({}) unless File.exist?(path)
54
+
55
+ content = RenderMePretty.result(path)
56
+ data = YAML.load(content)
57
+ # If key is is accidentally set to nil it screws up the merge_base later.
58
+ # So ensure that all keys with nil value are set to {}
59
+ data.each do |env, _setting|
60
+ data[env] ||= {}
61
+ end
62
+ data
63
+ end
64
+
65
+ # automatically add base settings to the rest of the environments
66
+ def merge_base(all_envs)
67
+ base = all_envs["base"] || {}
68
+ all_envs.each do |env, settings|
69
+ all_envs[env] = base.merge(settings) unless env == "base"
70
+ end
71
+ all_envs
72
+ end
73
+
74
+ def project_settings_path
75
+ "#{cb_root}/.cody/settings.yml"
76
+ end
77
+
78
+ def cb_root
79
+ ENV["CODEBUILD_ROOT"] || Dir.pwd
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,93 @@
1
+ require "aws-sdk-cloudformation"
2
+
3
+ module Cody
4
+ class Stack
5
+ include AwsServices
6
+
7
+ def initialize(options)
8
+ @options = options
9
+ @project_name = @options[:project_name] || inferred_project_name
10
+ @stack_name = options[:stack_name] || inferred_stack_name(@project_name)
11
+
12
+ @full_project_name = project_name_convention(@project_name)
13
+ @template = {
14
+ "Description" => "CodeBuild Project: #{@full_project_name}",
15
+ "Resources" => {}
16
+ }
17
+ end
18
+
19
+ def run
20
+ options = @options.merge(
21
+ project_name: @project_name,
22
+ full_project_name: @full_project_name,
23
+ )
24
+ project_builder = Project.new(options)
25
+ unless project_builder.exist?
26
+ puts "ERROR: Cody project does not exist: #{project_builder.project_path}".color(:red)
27
+ exit 1
28
+ return
29
+ end
30
+ project = project_builder.run
31
+ @template["Resources"].merge!(project)
32
+
33
+ if project["CodeBuild"]["Properties"]["ServiceRole"] == {"Ref"=>"IamRole"}
34
+ role = Role.new(options).run
35
+ @template["Resources"].merge!(role)
36
+ end
37
+
38
+ schedule = Schedule.new(options).run
39
+ @template["Resources"].merge!(schedule) if schedule
40
+
41
+ template_path = "/tmp/codebuild.yml"
42
+ FileUtils.mkdir_p(File.dirname(template_path))
43
+ IO.write(template_path, YAML.dump(@template))
44
+ puts "Generated CloudFormation template at #{template_path.color(:green)}"
45
+ return if @options[:noop]
46
+ puts "Deploying stack #{@stack_name.color(:green)} with CodeBuild project #{@full_project_name.color(:green)}"
47
+
48
+ begin
49
+ perform
50
+ url_info
51
+ return unless @options[:wait]
52
+ status.wait
53
+ exit 2 unless status.success?
54
+ rescue Aws::CloudFormation::Errors::ValidationError => e
55
+ if e.message.include?("No updates") # No updates are to be performed.
56
+ puts "WARN: #{e.message}".color(:yellow)
57
+ else
58
+ puts "ERROR ValidationError: #{e.message}".color(:red)
59
+ exit 1
60
+ end
61
+ end
62
+ end
63
+
64
+ private
65
+ def url_info
66
+ stack = cfn.describe_stacks(stack_name: @stack_name).stacks.first
67
+ region = `aws configure get region`.strip rescue "us-east-1"
68
+ url = "https://console.aws.amazon.com/cloudformation/home?region=#{region}#/stacks"
69
+ puts "Stack name #{@stack_name.color(:yellow)} status #{stack["stack_status"].color(:yellow)}"
70
+ puts "Here's the CloudFormation url to check for more details #{url}"
71
+ end
72
+
73
+ def status
74
+ @status ||= Cfn::Status.new(@stack_name)
75
+ end
76
+
77
+ def rollback_complete?(stack)
78
+ stack.stack_status == 'ROLLBACK_COMPLETE'
79
+ end
80
+
81
+ def find_stack(stack_name)
82
+ resp = cfn.describe_stacks(stack_name: stack_name)
83
+ resp.stacks.first
84
+ rescue Aws::CloudFormation::Errors::ValidationError => e
85
+ # example: Stack with id demo-web does not exist
86
+ if e.message =~ /Stack with/ && e.message =~ /does not exist/
87
+ nil
88
+ else
89
+ raise
90
+ end
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,69 @@
1
+ module Cody
2
+ class Start
3
+ include AwsServices
4
+
5
+ def initialize(options)
6
+ @options = options
7
+ @project_name = options[:project_name] || inferred_project_name
8
+ @full_project_name = project_name_convention(@project_name)
9
+ end
10
+
11
+ def run
12
+ source_version = @options[:branch] || @options[:source_version] || 'master'
13
+ params = {
14
+ project_name: project_name,
15
+ source_version: source_version
16
+ }
17
+ params[:environment_variables_override] = environment_variables_override if @options[:env_vars]
18
+ resp = codebuild.start_build(params)
19
+ puts "Build started for project: #{project_name}"
20
+ puts "Please check the CodeBuild console for the status."
21
+ puts "Cody Log Url:"
22
+ puts codebuild_log_url(resp.build.id)
23
+ end
24
+
25
+ def environment_variables_override
26
+ @options[:env_vars].map do |s|
27
+ k, v = s.split('=')
28
+ ssm = false
29
+ if /^ssm:(.*)/.match(v)
30
+ v = $1
31
+ ssm = true
32
+ end
33
+
34
+ {
35
+ name: k,
36
+ value: v,
37
+ type: ssm ? "PARAMETER_STORE" : "PLAINTEXT"
38
+ }
39
+ end
40
+ end
41
+
42
+ def project_name
43
+ if project_exists?(@full_project_name)
44
+ @full_project_name
45
+ elsif stack_exists?(@project_name) # allow `cody start STACK_NAME` to work too
46
+ resp = cfn.describe_stack_resources(stack_name: @project_name)
47
+ resource = resp.stack_resources.find do |r|
48
+ r.logical_resource_id == "CodeBuild"
49
+ end
50
+ resource.physical_resource_id # codebuild project name
51
+ else
52
+ puts "ERROR: Unable to find the codebuild project with either full_project_name: #{@full_project_name} or project_name: #{@project_name}".color(:red)
53
+ exit 1
54
+ end
55
+ end
56
+
57
+ private
58
+ def codebuild_log_url(build_id)
59
+ build_id = build_id.split(':').last
60
+ region = `aws configure get region`.strip rescue "us-east-1"
61
+ "https://#{region}.console.aws.amazon.com/codesuite/codebuild/projects/#{project_name}/build/#{project_name}%3A#{build_id}/log"
62
+ end
63
+
64
+ def project_exists?(name)
65
+ resp = codebuild.batch_get_projects(names: [name])
66
+ resp.projects.size > 0
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,12 @@
1
+ module Cody
2
+ class Update < Stack
3
+ def perform
4
+ cfn.update_stack(
5
+ stack_name: @stack_name,
6
+ template_body: YAML.dump(@template),
7
+ capabilities: ["CAPABILITY_IAM"]
8
+ )
9
+ puts "Updating stack #{@stack_name}. Check CloudFormation console for status."
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,17 @@
1
+ module Cody
2
+ module Variables
3
+ def load_variables
4
+ load_variables_file("base")
5
+ load_variables_file(Cody.env)
6
+ # Then load type scope variables, so they take higher precedence
7
+ load_variables_file("base", @options[:type])
8
+ load_variables_file(Cody.env, @options[:type])
9
+ end
10
+
11
+ def load_variables_file(filename, type=nil)
12
+ items = ["#{Cody.root}/.cody", type, "variables/#{filename}.rb"].compact
13
+ path = items.join('/')
14
+ instance_eval(IO.read(path), path) if File.exist?(path)
15
+ end
16
+ end
17
+ end
@@ -1,3 +1,3 @@
1
1
  module Cody
2
- VERSION = "0.1.0"
3
- end
2
+ VERSION = "0.7.0"
3
+ end
@@ -0,0 +1,28 @@
1
+ version: 0.2
2
+
3
+ # Example starter file
4
+ # Edit to fit your needs
5
+
6
+ phases:
7
+ install:
8
+ commands:
9
+ # - uname -a
10
+ # - pwd
11
+ # - ls
12
+ # - env | sort
13
+ # - ls /etc/*release*
14
+ # - cat /etc/*release*
15
+ # - whoami
16
+ # - bundle
17
+ build:
18
+ commands:
19
+ - echo Build started on `date`
20
+ - uptime
21
+ # - bundle exec rspec
22
+ # cache:
23
+ # paths:
24
+ # - /usr/local/bundle
25
+ # - /usr/local/lib/ruby/gems/2.5.0
26
+ # artifacts:
27
+ # files:
28
+ # - result.txt
@@ -0,0 +1,29 @@
1
+ # For methods, refer to the properties of the CloudFormation CodeBuild::Project https://amzn.to/2UTeNlr
2
+ # For convenience methods, refer to the source https://github.com/tongueroo/cody/blob/master/lib/codebuild/dsl/project.rb
3
+
4
+ # name("example-project-name") # recommend leaving unset and codebuild will use a conventional name
5
+ github_url("<%= project_github_url %>")
6
+ linux_image("<%= lookup_managed_image(/ruby:/) %>")
7
+ environment_variables(
8
+ JETS_ENV: "test",
9
+ # API_KEY: "ssm:/codebuild/demo/api_key" # Example of ssm parameter
10
+ )
11
+
12
+ # Some useful helpers:
13
+ # puts "project_name #{project_name}" # IE: demo-web
14
+ # puts "full_project_name #{full_project_name}" # demo-web-development
15
+
16
+ # Uncomment to enable github webhook, the GitHub oauth token needs admin:repo_hook permissions
17
+ # Refer to https://cody.run/docs/github_oauth/
18
+ # triggers(webhook: true)
19
+ # Another example:
20
+ # Docs: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-codebuild-project-projecttriggers.html
21
+ # {type: "EVENT", pattern: "PUSH"} is required
22
+ # Also, note the extra brackets: [[]] is actually the proper format. I know weird.
23
+ # triggers(
24
+ # webhook: true,
25
+ # filter_groups: [[{type: "HEAD_REF", pattern: "my-branch"}, {type: "EVENT", pattern: "PUSH"}]]
26
+ # )
27
+
28
+ # Shorthand to enable all local cache modes
29
+ # local_cache(true)
@@ -0,0 +1,2 @@
1
+ # Example:
2
+ # iam_policy("logs", "ssm")
@@ -0,0 +1,3 @@
1
+ # rate "1 day"
2
+ # or
3
+ # cron("0 10 * * ? *") # Run at 10:00 am (UTC) every day
@@ -0,0 +1,32 @@
1
+ # Cody Files
2
+
3
+ The files in folder are used by cody to build AWS CodeBuild projects. For more info, check out the [cody docs](https://cody.run). Here's a quick start.
4
+
5
+ ## Install Tool
6
+
7
+ gem install cody
8
+
9
+ This installs the `cody` command to manage the AWS CodeBuild project.
10
+
11
+ ## Update Project
12
+
13
+ To update the CodeBuild project:
14
+
15
+ Main services:
16
+
17
+ cody deploy demo
18
+
19
+ If you have multiple codebuild projects associated with the same repo, you can use the `--type` option. Example:
20
+
21
+ cody deploy demo --type deploy
22
+
23
+ ## Start a Deploy
24
+
25
+ To start a CodeBuild build:
26
+
27
+ cody start demo
28
+ cody start demo --type deploy
29
+
30
+ To specify a branch:
31
+
32
+ cody start demo --type deploy -b feature