dpl 2.0.0.alpha.12 → 2.0.2.beta.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +30 -2
  3. data/CONTRIBUTING.md +3 -3
  4. data/Gemfile +1 -0
  5. data/Gemfile.lock +385 -0
  6. data/README.md +352 -141
  7. data/lib/dpl/assets/dpl/README.erb.md +1 -1
  8. data/lib/dpl/ctx/test.rb +4 -1
  9. data/lib/dpl/helper/interpolate.rb +24 -1
  10. data/lib/dpl/provider.rb +11 -3
  11. data/lib/dpl/provider/dsl.rb +25 -0
  12. data/lib/dpl/providers.rb +4 -0
  13. data/lib/dpl/providers/anynines.rb +2 -0
  14. data/lib/dpl/providers/azure_web_apps.rb +2 -0
  15. data/lib/dpl/providers/bintray.rb +2 -0
  16. data/lib/dpl/providers/bluemixcloudfoundry.rb +3 -1
  17. data/lib/dpl/providers/boxfuse.rb +2 -0
  18. data/lib/dpl/providers/cargo.rb +3 -1
  19. data/lib/dpl/providers/cloud66.rb +2 -0
  20. data/lib/dpl/providers/cloudfiles.rb +2 -0
  21. data/lib/dpl/providers/cloudformation.rb +1 -0
  22. data/lib/dpl/providers/cloudfoundry.rb +5 -1
  23. data/lib/dpl/providers/codedeploy.rb +13 -4
  24. data/lib/dpl/providers/convox.rb +2 -0
  25. data/lib/dpl/providers/datica.rb +2 -0
  26. data/lib/dpl/providers/ecr.rb +127 -0
  27. data/lib/dpl/providers/elasticbeanstalk.rb +14 -6
  28. data/lib/dpl/providers/engineyard.rb +2 -0
  29. data/lib/dpl/providers/firebase.rb +2 -0
  30. data/lib/dpl/providers/flynn.rb +33 -0
  31. data/lib/dpl/providers/gae.rb +2 -0
  32. data/lib/dpl/providers/gcs.rb +2 -0
  33. data/lib/dpl/providers/git_push.rb +269 -0
  34. data/lib/dpl/providers/gleis.rb +3 -1
  35. data/lib/dpl/providers/hackage.rb +2 -0
  36. data/lib/dpl/providers/hephy.rb +2 -0
  37. data/lib/dpl/providers/heroku.rb +2 -0
  38. data/lib/dpl/providers/heroku/api.rb +1 -1
  39. data/lib/dpl/providers/heroku/git.rb +1 -1
  40. data/lib/dpl/providers/lambda.rb +5 -3
  41. data/lib/dpl/providers/launchpad.rb +2 -0
  42. data/lib/dpl/providers/netlify.rb +2 -0
  43. data/lib/dpl/providers/npm.rb +2 -0
  44. data/lib/dpl/providers/nuget.rb +39 -0
  45. data/lib/dpl/providers/openshift.rb +2 -0
  46. data/lib/dpl/providers/opsworks.rb +2 -0
  47. data/lib/dpl/providers/packagecloud.rb +2 -0
  48. data/lib/dpl/providers/pages.rb +2 -0
  49. data/lib/dpl/providers/pages/api.rb +1 -1
  50. data/lib/dpl/providers/pages/git.rb +5 -5
  51. data/lib/dpl/providers/puppetforge.rb +2 -0
  52. data/lib/dpl/providers/pypi.rb +2 -0
  53. data/lib/dpl/providers/releases.rb +3 -1
  54. data/lib/dpl/providers/rubygems.rb +2 -0
  55. data/lib/dpl/providers/s3.rb +4 -2
  56. data/lib/dpl/providers/scalingo.rb +2 -0
  57. data/lib/dpl/providers/script.rb +2 -0
  58. data/lib/dpl/providers/snap.rb +8 -2
  59. data/lib/dpl/providers/surge.rb +3 -1
  60. data/lib/dpl/providers/testfairy.rb +2 -0
  61. data/lib/dpl/providers/transifex.rb +2 -0
  62. data/lib/dpl/version.rb +1 -1
  63. metadata +8 -3
@@ -1,7 +1,7 @@
1
1
  # Dpl [![Build Status](https://travis-ci.com/travis-ci/dpl.svg?branch=master)](https://travis-ci.com/travis-ci/dpl) [![Code Climate](https://codeclimate.com/github/travis-ci/dpl.svg)](https://codeclimate.com/github/travis-ci/dpl) [![Coverage Status](https://coveralls.io/repos/travis-ci/dpl/badge.svg?branch=master&service=github&cache=2019-08-09_17:00)](https://coveralls.io/github/travis-ci/dpl?branch=master) [![Gem Version](https://img.shields.io/gem/v/dpl)](http://rubygems.org/gems/dpl) [![Yard Docs](http://img.shields.io/badge/yard-docs-blue.svg)](http://rubydoc.info/github/travis-ci/dpl)
2
2
 
3
3
  This version of the README documents dpl v2, the next major version of dpl.
4
- The REAMDE for dpl v1, the version that is currently used in production on
4
+ The README for dpl v1, the version that is currently used in production on
5
5
  Travis CI can be found [here](https://github.com/travis-ci/dpl/blob/v1/README.md).
6
6
 
7
7
  Dpl is command line tool for deploying code, html, packages, or build artifacts
@@ -36,10 +36,13 @@ module Dpl
36
36
  end
37
37
 
38
38
  def gems_require(gems)
39
- gems.each { |gem| gem_require(gem) }
39
+ gems.each { |gem| gem_require(*gem) }
40
40
  end
41
41
 
42
42
  def gem_require(name, version = nil, opts = {})
43
+ # not sure why this is needed. bundler should take care of this, but
44
+ # it does not for octokit for whatever reason
45
+ require opts[:require] || name rescue nil
43
46
  cmds << "[gem:require] #{name} (#{version}, #{opts})"
44
47
  end
45
48
 
@@ -52,6 +52,14 @@ module Dpl
52
52
  Interpolator.new(str, self, args || {}, opts).apply
53
53
  end
54
54
 
55
+ # Interpolation variables as declared by the provider.
56
+ #
57
+ # By default this contains string option names, but additional
58
+ # methods can be added using Provider::Dsl#vars.
59
+ def vars
60
+ self.class.vars
61
+ end
62
+
55
63
  # Obfuscates the given string.
56
64
  #
57
65
  # Replaces all but the first N characters with asterisks, and paddes
@@ -71,6 +79,7 @@ module Dpl
71
79
  PATTERN = /%\{(\$?[\w]+)\}/
72
80
  ENV_VAR = /^\$[A-Z_]+$/
73
81
  UPCASE = /^[A-Z_]+$/
82
+ UNKNOWN = '[unknown variable: %s]'
74
83
 
75
84
  def apply
76
85
  str = interpolate(self.str.to_s)
@@ -102,7 +111,9 @@ module Dpl
102
111
  end
103
112
 
104
113
  def lookup(key)
105
- if mod = modifier(key)
114
+ if vars? && !var?(key)
115
+ UNKNOWN % key
116
+ elsif mod = modifier(key)
106
117
  key = key.to_s.sub("#{mod}d_", '')
107
118
  obj.send(mod, lookup(key))
108
119
  elsif key.to_s =~ ENV_VAR
@@ -121,6 +132,18 @@ module Dpl
121
132
  def modifier(key)
122
133
  MODIFIER.detect { |mod| key.to_s.start_with?("#{mod}d_") }
123
134
  end
135
+
136
+ def var?(key)
137
+ vars.include?(key)
138
+ end
139
+
140
+ def vars
141
+ opts[:vars]
142
+ end
143
+
144
+ def vars?
145
+ !!vars
146
+ end
124
147
  end
125
148
  end
126
149
  end
@@ -145,8 +145,6 @@ module Dpl
145
145
 
146
146
  abstract
147
147
 
148
- arg :provider, 'The provider name', required: true
149
-
150
148
  opt '--cleanup', 'Clean up build artifacts from the Git working directory before the deployment', negate: %w(skip)
151
149
  opt '--run CMD', 'Commands to execute after the deployment finished successfully', type: :array
152
150
  opt '--stage NAME', 'Execute the given stage(s) only', type: :array, internal: true, default: STAGES
@@ -154,6 +152,16 @@ module Dpl
154
152
  opt '--fold', 'Wrap log output in folds', internal: true
155
153
  opt '--edge', internal: true
156
154
 
155
+ vars *%i(
156
+ git_author_email
157
+ git_author_name
158
+ git_branch
159
+ git_commit_author
160
+ git_commit_msg
161
+ git_sha
162
+ git_tag
163
+ )
164
+
157
165
  msgs before_install: 'Installing deployment dependencies',
158
166
  before_setup: 'Setting the build environment up for the deployment',
159
167
  setup_git_ssh: 'Setting up git-ssh',
@@ -545,7 +553,7 @@ module Dpl
545
553
 
546
554
  # Double quotes the given string.
547
555
  def quote(str)
548
- %("#{str.gsub('"', '\"')}")
556
+ %("#{str.to_s.gsub('"', '\"')}")
549
557
  end
550
558
 
551
559
  # Outdents the given string.
@@ -2,6 +2,15 @@ require 'dpl/helper/squiggle'
2
2
  require 'dpl/helper/wrap'
3
3
  require 'dpl/provider/status'
4
4
 
5
+ # TODO figure out how to allow adding domain specific behavior like this to Cl
6
+ class Cl::Opt
7
+ OPTS << :interpolate
8
+
9
+ def interpolate?
10
+ opts[:interpolate]
11
+ end
12
+ end
13
+
5
14
  module Dpl
6
15
  class Provider < Cl::Cmd
7
16
  # DSL available on the provider's class body.
@@ -36,6 +45,22 @@ module Dpl
36
45
  status ? @status = Status.new(self, status, msg) : @status
37
46
  end
38
47
 
48
+ # Declare additional variables available for interpolation.
49
+ #
50
+ # Interpolating strings, when these exposed to the user, should safelist
51
+ # which variables are available. Options declared on a provider are
52
+ # always available, except if they are flags, arrays, internal, or
53
+ # secrets. This method can be used to allow additional variables, e.g.
54
+ # from the git context.
55
+ def vars(*vars)
56
+ return self.vars.concat(vars) if vars.any?
57
+ return @vars if instance_variable_defined?(:@vars)
58
+ vars = superclass.respond_to?(:vars) ? superclass.vars : []
59
+ reject = %i(flag array internal interpolate secret)
60
+ opts = reject.inject(self.opts) { |opts, attr| opts.reject(&:"#{attr}?") }
61
+ @vars = vars.dup.concat(opts.map(&:name)).uniq.sort - [:strategy]
62
+ end
63
+
39
64
  # @!method env
40
65
  # Declare an environment variable prefix to accept env vars as options
41
66
  #
@@ -11,12 +11,15 @@ require 'dpl/providers/cloudfiles'
11
11
  require 'dpl/providers/cloudformation'
12
12
  require 'dpl/providers/cloudfoundry'
13
13
  require 'dpl/providers/codedeploy'
14
+ require 'dpl/providers/ecr'
14
15
  require 'dpl/providers/convox'
15
16
  require 'dpl/providers/elasticbeanstalk'
16
17
  require 'dpl/providers/engineyard'
17
18
  require 'dpl/providers/firebase'
19
+ require 'dpl/providers/flynn'
18
20
  require 'dpl/providers/gae'
19
21
  require 'dpl/providers/gcs'
22
+ require 'dpl/providers/git_push'
20
23
  require 'dpl/providers/gleis'
21
24
  require 'dpl/providers/hackage'
22
25
  require 'dpl/providers/hephy'
@@ -25,6 +28,7 @@ require 'dpl/providers/lambda'
25
28
  require 'dpl/providers/launchpad'
26
29
  require 'dpl/providers/netlify'
27
30
  require 'dpl/providers/npm'
31
+ require 'dpl/providers/nuget'
28
32
  require 'dpl/providers/openshift'
29
33
  require 'dpl/providers/opsworks'
30
34
  require 'dpl/providers/packagecloud'
@@ -1,6 +1,8 @@
1
1
  module Dpl
2
2
  module Providers
3
3
  class Anynines < Provider
4
+ register :anynines
5
+
4
6
  status :alpha
5
7
 
6
8
  description sq(<<-str)
@@ -1,6 +1,8 @@
1
1
  module Dpl
2
2
  module Providers
3
3
  class AzureWebApps < Provider
4
+ register :azure_web_apps
5
+
4
6
  status :alpha
5
7
 
6
8
  full_name 'Azure Web Apps'
@@ -5,6 +5,8 @@ require 'find'
5
5
  module Dpl
6
6
  module Providers
7
7
  class Bintray < Provider
8
+ register :bintray
9
+
8
10
  status :stable
9
11
 
10
12
  description sq(<<-str)
@@ -1,7 +1,9 @@
1
1
  module Dpl
2
2
  module Providers
3
3
  class Bluemixcloudfoundry < Provider
4
- status :alpha
4
+ register :bluemixcloudfoundry
5
+
6
+ status :stable
5
7
 
6
8
  full_name 'Bluemix Cloud Foundry'
7
9
 
@@ -1,6 +1,8 @@
1
1
  module Dpl
2
2
  module Providers
3
3
  class Boxfuse < Provider
4
+ register :boxfuse
5
+
4
6
  status :alpha
5
7
 
6
8
  description sq(<<-str)
@@ -1,7 +1,9 @@
1
1
  module Dpl
2
2
  module Providers
3
3
  class Cargo < Provider
4
- status :beta
4
+ register :cargo
5
+
6
+ status :stable
5
7
 
6
8
  description sq(<<-str)
7
9
  tbd
@@ -1,6 +1,8 @@
1
1
  module Dpl
2
2
  module Providers
3
3
  class Cloud66 < Provider
4
+ register :cloud66
5
+
4
6
  status :alpha
5
7
 
6
8
  description sq(<<-str)
@@ -1,6 +1,8 @@
1
1
  module Dpl
2
2
  module Providers
3
3
  class Cloudfiles < Provider
4
+ register :cloudfiles
5
+
4
6
  status :alpha
5
7
 
6
8
  full_name 'Cloud Files'
@@ -1,6 +1,7 @@
1
1
  module Dpl
2
2
  module Providers
3
3
  class Cloudformation < Provider
4
+ register :cloudformation
4
5
  status :stable
5
6
 
6
7
  full_name 'AWS CloudFormation'
@@ -1,6 +1,8 @@
1
1
  module Dpl
2
2
  module Providers
3
3
  class Cloudfoundry < Provider
4
+ register :cloudfoundry
5
+
4
6
  status :stable
5
7
 
6
8
  full_name 'Cloud Foundry'
@@ -20,10 +22,11 @@ module Dpl
20
22
  opt '--buildpack PACK', 'Buildpack name or Git URL'
21
23
  opt '--manifest FILE', 'Path to the manifest'
22
24
  opt '--skip_ssl_validation', 'Skip SSL validation'
25
+ opt '--deployment_strategy STRATEGY', 'Deployment strategy, either rolling or null'
23
26
  opt '--v3', 'Use the v3 API version to push the application'
24
27
  opt '--logout', default: true, internal: true
25
28
 
26
- cmds install: 'test $(uname) = "Linux" && rel="linux64-binary" || rel="macosx64"; wget "https://cli.run.pivotal.io/stable?release=${rel}&source=github" -qO cf.tgz && tar -zxvf cf.tgz && rm cf.tgz',
29
+ cmds install: 'test $(uname) = "Linux" && rel="linux64-binary" || rel="macosx64"; wget "https://cli.run.pivotal.io/stable?release=${rel}&version=v7&source=github" -qO cf.tgz && tar -zxvf cf.tgz && rm cf.tgz',
27
30
  api: './cf api %{api} %{skip_ssl_validation_opt}',
28
31
  login: './cf login -u %{username} -p %{password} -o %{organization} -s %{space}',
29
32
  push: './cf %{push_cmd} %{push_args}',
@@ -68,6 +71,7 @@ module Dpl
68
71
  args = []
69
72
  args << quote(app_name) if app_name?
70
73
  args << "-f #{manifest}" if manifest?
74
+ args << "--strategy #{deployment_strategy}" if deployment_strategy?
71
75
  args.join(' ')
72
76
  end
73
77
 
@@ -3,7 +3,9 @@ module Dpl
3
3
  # split this up to CodeDeploy::Github and CodeDeploy::S3 using the
4
4
  # revision_type, in order to make opts more strict
5
5
  class Codedeploy < Provider
6
- status :alpha
6
+ register :codedeploy
7
+
8
+ status :stable
7
9
 
8
10
  full_name 'AWS Code Deploy'
9
11
 
@@ -30,7 +32,7 @@ module Dpl
30
32
  opt '--wait_until_deployed', 'Wait until the deployment has finished'
31
33
  opt '--bundle_type TYPE', 'Bundle type of the revision'
32
34
  opt '--key KEY', 'S3 bucket key of the revision'
33
- opt '--description DESCR', 'Description of the revision'
35
+ opt '--description DESCR', 'Description of the revision', interpolate: true
34
36
  opt '--endpoint ENDPOINT', 'S3 endpoint url'
35
37
 
36
38
  msgs login: 'Using Access Key: %{access_key_id}',
@@ -44,6 +46,8 @@ module Dpl
44
46
  unknown_revision_type: 'Unknown revision type %p',
45
47
  unknown_bundle_type: 'Unknown bundle type'
46
48
 
49
+ vars :build_number
50
+
47
51
  def login
48
52
  info :login
49
53
  end
@@ -80,7 +84,12 @@ module Dpl
80
84
  def wait_until_deployed(id)
81
85
  print :waiting_for_deploy
82
86
  status = poll(id) until %w(Succeeded Failed Stopped).include?(status)
83
- info :finished_deploy, status
87
+ case status
88
+ when 'Succeeded'
89
+ info :finished_deploy, status
90
+ else
91
+ error :finished_deploy, status
92
+ end
84
93
  end
85
94
 
86
95
  def poll(id)
@@ -152,7 +161,7 @@ module Dpl
152
161
  end
153
162
 
154
163
  def description
155
- super || interpolate(msg(:description))
164
+ interpolate(super || msg(:description), vars: vars)
156
165
  end
157
166
 
158
167
  def build_number
@@ -1,6 +1,8 @@
1
1
  module Dpl
2
2
  module Providers
3
3
  class Convox < Provider
4
+ register :convox
5
+
4
6
  status :dev
5
7
 
6
8
  description sq(<<-str)
@@ -1,6 +1,8 @@
1
1
  module Dpl
2
2
  module Providers
3
3
  class Datica < Provider
4
+ register :datica
5
+
4
6
  status :dev
5
7
 
6
8
  register :datica, :catalyze
@@ -0,0 +1,127 @@
1
+ module Dpl
2
+ module Providers
3
+ class Ecr < Provider
4
+ status :alpha
5
+
6
+ full_name 'AWS ECR'
7
+
8
+ description sq(<<-str)
9
+ tbd
10
+ str
11
+
12
+ gem 'aws-sdk-ecr', '~> 1.0'
13
+ # gem 'docker-api', '~> 1.34'
14
+ gem 'json'
15
+
16
+ env :aws
17
+
18
+ opt '--access_key_id ID', 'AWS access key', required: true, secret: true
19
+ opt '--secret_access_key KEY', 'AWS secret access key', required: true, secret: true
20
+ opt '--account_id ID', 'AWS Account ID', note: 'Required if the repository is owned by a different account than the IAM user'
21
+ opt '--source SOURCE', 'Image to push', note: 'can be the id or the name and optional tag (e.g. mysql:5.6)', required: true
22
+ opt '--target TARGET', 'Comma separated list of partial repository names to push to', eg: 'image-one:tag,image-two', required: true
23
+ opt '--region REGION', 'Comma separated list of regions to push to', default: 'us-east-1'
24
+
25
+ msgs login: 'Using Access Key: %{access_key_id}',
26
+ auth_region: 'Authenticated with %{url}',
27
+ deploy: 'Pushing image %{source} to regions %{regions} as %{targets}',
28
+ image_pushed: 'Pushed image %{source} to region %{region} as %{target}'
29
+
30
+ cmds login: 'docker login -u %{user} -p %{pass} %{url}',
31
+ tag: 'docker tag %{source} %{url}/%{repo}:%{tag}',
32
+ push: 'docker push %{url}/%{repo}'
33
+
34
+ errs unknown_image: 'Image %{source} not found in the local Docker repository'
35
+
36
+ attr_reader :endpoints
37
+
38
+ def login
39
+ info :login
40
+ auth_regions
41
+ end
42
+
43
+ def validate
44
+ # TODO validate the image exists locally
45
+ end
46
+
47
+ def deploy
48
+ info :deploy, regions: regions.join(', '), targets: targets.join(', ')
49
+ regions.product(targets).each do |region, target|
50
+ push(region, target)
51
+ end
52
+ end
53
+
54
+ private
55
+
56
+ def push(region, target)
57
+ url, repo, tag = endpoints[region], *target.split(':')
58
+ shell :tag, url: url, repo: repo, tag: tag || 'latest'
59
+ shell :push, url: url, repo: repo
60
+ info :image_pushed, region: region, target: target
61
+ end
62
+
63
+ def auth_regions
64
+ @endpoints = regions.map { |region| [region, auth_region(region)] }.to_h
65
+ end
66
+
67
+ def auth_region(region)
68
+ token = auth_token(region)
69
+ user, pass = parse_auth(token.authorization_token)
70
+ url = token.proxy_endpoint
71
+ shell :login, user: user, pass: pass, url: url, echo: false, silent: true
72
+ info :auth_region, url: url
73
+ strip_protocol(url)
74
+ end
75
+
76
+ def auth_token(region)
77
+ ecr(region).get_authorization_token(registry_ids).authorization_data[0]
78
+ end
79
+
80
+ def registry_ids
81
+ account_id? ? { registry_ids: [account_id] } : {}
82
+ end
83
+
84
+ def regions
85
+ # not sure how this was meant to be normalized when being a YAML list
86
+ region.split(',')
87
+ end
88
+
89
+ def targets
90
+ # not sure how this was meant to be normalized when being a YAML list
91
+ target.split(',')
92
+ end
93
+
94
+ def creds
95
+ @creds ||= only(opts, :access_key_id, :secret_access_key)
96
+ end
97
+
98
+ def ecr(region)
99
+ Aws::ECR::Client.new(region: region, **creds)
100
+ end
101
+
102
+ def parse_auth(str)
103
+ user, pass = Base64.decode64(str).split(':')
104
+ [user, pass.chomp]
105
+ end
106
+
107
+ def strip_protocol(url)
108
+ url.sub(/^https?:\/\//, '')
109
+ end
110
+
111
+ def progress(events)
112
+ events.split("\r\n").each do |event|
113
+ event = JSON.parse(event)
114
+ if e = event['error']
115
+ error e
116
+ elsif %w(Preparing Pushing).include?(event['status'])
117
+ nil
118
+ elsif event['id']
119
+ info "#{event['status']} [#{event['id']}]"
120
+ elsif event['status']
121
+ info event['status']
122
+ end
123
+ end
124
+ end
125
+ end
126
+ end
127
+ end