pdk 1.9.1 → 1.10.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -6,6 +6,8 @@ module PDK
6
6
  GIT_DESCRIBE_PATTERN = %r{\A(?<base>.+?)-(?<additional_commits>\d+)-g(?<sha>.+)\Z}
7
7
 
8
8
  def run
9
+ template_uri.git_ref = new_template_version
10
+
9
11
  stage_changes!
10
12
 
11
13
  if current_version == new_version
@@ -50,8 +52,8 @@ module PDK
50
52
  raise PDK::CLI::ExitWithError, e.message
51
53
  end
52
54
 
53
- def template_url
54
- @template_url ||= module_metadata.data['template-url']
55
+ def template_uri
56
+ @template_uri ||= PDK::Util::TemplateURI.new(module_metadata.data['template-url'])
55
57
  end
56
58
 
57
59
  def current_version
@@ -62,6 +64,16 @@ module PDK
62
64
  @new_version ||= fetch_remote_version(new_template_version)
63
65
  end
64
66
 
67
+ def new_template_version
68
+ return options[:'template-ref'] if options[:'template-ref']
69
+
70
+ if template_uri.default? && template_uri.ref_is_tag? && PDK::Util.package_install?
71
+ PDK::Util::TemplateURI.default_template_ref
72
+ else
73
+ template_uri.git_ref
74
+ end
75
+ end
76
+
65
77
  private
66
78
 
67
79
  def current_template_version
@@ -73,27 +85,23 @@ module PDK
73
85
 
74
86
  return data if data.nil?
75
87
 
76
- if data[:base].start_with?('heads/')
77
- "#{data[:base].gsub(%r{^heads/}, '')}@#{data[:sha]}"
88
+ if data[:base] =~ %r{^(?:heads|remotes)/}
89
+ "#{data[:base].gsub(%r{^(heads/|remotes/\w+?/)}, '')}@#{data[:sha]}"
78
90
  else
79
91
  data[:base]
80
92
  end
81
93
  end
82
94
 
83
- def new_template_version
84
- PDK::Util.default_template_ref
85
- end
86
-
87
- def fetch_remote_version(version)
88
- return version unless version.include?('/')
95
+ def fetch_remote_version(template_ref)
96
+ return template_ref unless current_template_version.is_a?(String)
97
+ return template_ref if template_ref == PDK::TEMPLATE_REF
89
98
 
90
- branch = version.partition('/').last
91
99
  sha_length = GIT_DESCRIBE_PATTERN.match(current_template_version)[:sha].length - 1
92
- "#{branch}@#{PDK::Util::Git.ls_remote(template_url, "refs/heads/#{branch}")[0..sha_length]}"
100
+ "#{template_ref}@#{PDK::Util::Git.ls_remote(template_uri.git_remote, template_ref)[0..sha_length]}"
93
101
  end
94
102
 
95
103
  def update_message
96
- format_string = if template_url == PDK::Util.puppetlabs_template_url
104
+ format_string = if template_uri.default?
97
105
  _('Updating %{module_name} using the default template, from %{current_version} to %{new_version}')
98
106
  else
99
107
  _('Updating %{module_name} using the template at %{template_url}, from %{current_version} to %{new_version}')
@@ -101,7 +109,7 @@ module PDK
101
109
 
102
110
  format_string % {
103
111
  module_name: module_metadata.data['name'],
104
- template_url: template_url,
112
+ template_url: template_uri.git_remote,
105
113
  current_version: current_version,
106
114
  new_version: new_version,
107
115
  }
@@ -5,6 +5,7 @@ require 'pdk/util/version'
5
5
  require 'pdk/util/windows'
6
6
  require 'pdk/util/vendored_file'
7
7
  require 'pdk/util/filesystem'
8
+ require 'pdk/util/template_uri'
8
9
 
9
10
  module PDK
10
11
  module Util
@@ -197,63 +198,6 @@ module PDK
197
198
  end
198
199
  module_function :targets_relative_to_pwd
199
200
 
200
- def default_template_url
201
- answer_file_url = PDK.answers['template-url']
202
-
203
- return puppetlabs_template_url if answer_file_url.nil?
204
-
205
- # Ignore answer file template-url if the value is the old or new default.
206
- return puppetlabs_template_url if answer_file_url == 'https://github.com/puppetlabs/pdk-module-template'
207
- return puppetlabs_template_url if answer_file_url == puppetlabs_template_url
208
-
209
- if File.directory?(answer_file_url)
210
- # Instantiating a new TemplateDir object pointing to the directory
211
- # will cause the directory contents to be validated, raising
212
- # ArgumentError if it does not appear to be a valid template.
213
- PDK::Module::TemplateDir.new(answer_file_url) {}
214
- return answer_file_url
215
- end
216
-
217
- raise ArgumentError unless PDK::Util::Git.repo?(answer_file_url)
218
-
219
- answer_file_url
220
- rescue ArgumentError
221
- PDK.logger.warn(
222
- _("Unable to access the previously used template '%{template}', using the default template instead.") % {
223
- template: answer_file_url,
224
- },
225
- )
226
- PDK.answers.update!('template-url' => nil)
227
- return puppetlabs_template_url
228
- end
229
- module_function :default_template_url
230
-
231
- def puppetlabs_template_url
232
- if package_install?
233
- 'file://' + File.join(package_cachedir, 'pdk-templates.git')
234
- else
235
- 'https://github.com/puppetlabs/pdk-templates'
236
- end
237
- end
238
- module_function :puppetlabs_template_url
239
-
240
- def default_template_ref
241
- # TODO: This should respect a --template-ref option if we add that
242
- return 'origin/master' if default_template_url != puppetlabs_template_url
243
-
244
- puppetlabs_template_ref
245
- end
246
- module_function :default_template_ref
247
-
248
- def puppetlabs_template_ref
249
- if PDK::Util.development_mode?
250
- 'origin/master'
251
- else
252
- PDK::TEMPLATE_REF
253
- end
254
- end
255
- module_function :puppetlabs_template_ref
256
-
257
201
  # TO-DO: Refactor replacement of lib/pdk/module/build.rb:metadata to use this function instead
258
202
  def module_metadata
259
203
  PDK::Module::Metadata.from_file(File.join(module_root, 'metadata.json')).data
@@ -4,7 +4,13 @@ module PDK
4
4
  def write_file(path, content)
5
5
  raise ArgumentError unless path.is_a?(String) || path.respond_to?(:to_path)
6
6
 
7
- File.open(path, 'wb') { |f| f.write(content.encode(universal_newline: true)) }
7
+ # Harmonize newlines across platforms.
8
+ content = content.encode(universal_newline: true)
9
+
10
+ # Make sure all written files have a trailing newline.
11
+ content += "\n" unless content[-1] == "\n"
12
+
13
+ File.open(path, 'wb') { |f| f.write(content) }
8
14
  end
9
15
  module_function :write_file
10
16
  end
@@ -1,5 +1,21 @@
1
1
  module PDK
2
2
  module Util
3
+ class GitError < StandardError
4
+ attr_reader :stdout
5
+ attr_reader :stderr
6
+ attr_reader :exit_code
7
+ attr_reader :args
8
+
9
+ def initialze(args, result)
10
+ @args = args
11
+ @stdout = result[:stdout]
12
+ @stderr = result[:stderr]
13
+ @exit_code = result[:exit_code]
14
+
15
+ super(_('Git command failed: git %{args}' % { args: args.join(' ') }))
16
+ end
17
+ end
18
+
3
19
  module Git
4
20
  def self.git_bindir
5
21
  @git_dir ||= File.join('private', 'git', Gem.win_platform? ? 'cmd' : 'bin')
@@ -55,7 +71,27 @@ module PDK
55
71
  git('ls-remote', '--exit-code', maybe_repo)[:exit_code].zero?
56
72
  end
57
73
 
74
+ def self.work_tree?(path)
75
+ return false unless File.directory?(path)
76
+
77
+ Dir.chdir(path) do
78
+ rev_parse = git('rev-parse', '--is-inside-work-tree')
79
+ rev_parse[:exit_code].zero? && rev_parse[:stdout].strip == 'true'
80
+ end
81
+ end
82
+
83
+ def self.work_dir_clean?(repo)
84
+ raise PDK::CLI::ExitWithError, _('Unable to locate git work dir "%{workdir}') % { workdir: repo } unless File.directory?(repo)
85
+ raise PDK::CLI::ExitWithError, _('Unable to locate git dir "%{gitdir}') % { gitdir: repo } unless File.directory?(File.join(repo, '.git'))
86
+
87
+ git('--work-tree', repo, '--git-dir', File.join(repo, '.git'), 'status', '--untracked-files=no', '--porcelain', repo)[:stdout].empty?
88
+ end
89
+
58
90
  def self.ls_remote(repo, ref)
91
+ if File.directory?(repo)
92
+ repo = 'file://' + repo
93
+ end
94
+
59
95
  output = git('ls-remote', '--refs', repo, ref)
60
96
 
61
97
  unless output[:exit_code].zero?
@@ -67,7 +103,16 @@ module PDK
67
103
  end
68
104
 
69
105
  matching_refs = output[:stdout].split("\n").map { |r| r.split("\t") }
70
- matching_refs.find { |_sha, remote_ref| remote_ref == ref }.first
106
+ matching_ref = matching_refs.find { |_sha, remote_ref| remote_ref == "refs/tags/#{ref}" || remote_ref == "refs/remotes/origin/#{ref}" || remote_ref == "refs/heads/#{ref}" }
107
+ raise PDK::CLI::ExitWithError, _('Unable to find a branch or tag named "%{ref}" in %{repo}') % { ref: ref, repo: repo } if matching_ref.nil?
108
+ matching_ref.first
109
+ end
110
+
111
+ def self.describe(path, ref = nil)
112
+ args = ['--git-dir', path, 'describe', '--all', '--long', '--always', ref].compact
113
+ result = git(*args)
114
+ raise PDK::Util::GitError, args, result unless result[:exit_code].zero?
115
+ result[:stdout].strip
71
116
  end
72
117
  end
73
118
  end
@@ -0,0 +1,231 @@
1
+ require 'pdk/util'
2
+
3
+ module PDK
4
+ module Util
5
+ class TemplateURI
6
+ SCP_PATTERN = %r{\A(?!\w+://)(?:(?<user>.+?)@)?(?<host>[^:/]+):(?<path>.+)\z}
7
+
8
+ # XXX Previously
9
+ # - template_uri used to get the string form of the uri when generating the module and written to pdk answers and metadata
10
+ # - template_path or deuri_path used for humans to see and commands to run
11
+ # - uri_path used only internally by the template selection code; move out
12
+ # - template_ref used by git checkout
13
+ attr_reader :uri
14
+
15
+ # input/output formats:
16
+ #
17
+ # file:///c:/foo (git clone location)
18
+ # c:/foo (shell paths)
19
+ # file:///c:/foo#master (only for metadata)
20
+ # c:/foo#master (only for metadata)
21
+ #
22
+ # non output formats:
23
+ #
24
+ # /c:/foo (internal use only)
25
+ # /c:/foo#master (internal use only)
26
+ #
27
+ def initialize(opts_or_uri)
28
+ # If a uri string is passed, skip the valid uri finding code.
29
+ @uri = if opts_or_uri.is_a?(String) || opts_or_uri.is_a?(self.class)
30
+ begin
31
+ Addressable::URI.parse(opts_or_uri)
32
+ rescue Addressable::URI::InvalidURIError
33
+ raise PDK::CLI::FatalError, _('PDK::Util::TemplateURI attempted initialization with a non-uri string: {string}') % { string: opts_or_uri }
34
+ end
35
+ elsif opts_or_uri.is_a?(Addressable::URI)
36
+ opts_or_uri.dup
37
+ else
38
+ self.class.first_valid_uri(self.class.templates(opts_or_uri))
39
+ end
40
+ end
41
+
42
+ def ==(other)
43
+ @uri == other.uri
44
+ end
45
+
46
+ # This is the URI represented in a format suitable for writing to
47
+ # metadata.
48
+ #
49
+ # @returns String
50
+ def metadata_format
51
+ self.class.human_readable(@uri.to_s)
52
+ end
53
+ alias to_s metadata_format
54
+ alias to_str metadata_format
55
+
56
+ # This is the url without a fragment, suitable for git clones.
57
+ #
58
+ # @returns String
59
+ def git_remote
60
+ self.class.git_remote(@uri)
61
+ end
62
+
63
+ def self.git_remote(uri)
64
+ if uri.is_a?(Addressable::URI) && uri.fragment
65
+ human_readable(uri.to_s.chomp('#' + uri.fragment))
66
+ else
67
+ human_readable(uri.to_s)
68
+ end
69
+ end
70
+
71
+ # This is the path of the URI, suitable for accessing directly from the shell.
72
+ # @returns String
73
+ def shell_path
74
+ self.class.human_readable(@uri.path)
75
+ end
76
+
77
+ # @returns String
78
+ def git_ref
79
+ if @uri.fragment
80
+ @uri.fragment
81
+ else
82
+ self.class.default_template_ref
83
+ end
84
+ end
85
+
86
+ def git_ref=(ref)
87
+ @uri.fragment = ref
88
+ end
89
+
90
+ # @returns PDK::Util::TemplateURI
91
+ def self.default_template_uri
92
+ if PDK::Util.package_install?
93
+ PDK::Util::TemplateURI.new(Addressable::URI.new(scheme: 'file', host: '', path: File.join(PDK::Util.package_cachedir, 'pdk-templates.git')))
94
+ else
95
+ PDK::Util::TemplateURI.new('https://github.com/puppetlabs/pdk-templates')
96
+ end
97
+ end
98
+
99
+ def default?
100
+ git_remote == self.class.default_template_uri.git_remote
101
+ end
102
+
103
+ def ref_is_tag?
104
+ PDK::Util::Git.git('ls-remote', '--tags', '--exit-code', git_remote, git_ref)[:exit_code].zero?
105
+ end
106
+
107
+ # `C:...` urls are not URI-safe. They should be of the form `/C:...` to
108
+ # be URI-safe. scp-like urls like `user@host:/path` are not URI-safe
109
+ # either but are not handled here. Should they be?
110
+ #
111
+ # @returns String
112
+ def self.uri_safe(string)
113
+ (Gem.win_platform? && string =~ %r{^[a-zA-Z][\|:]}) ? "/#{string}" : string
114
+ end
115
+
116
+ # If the passed value is a URI-safe windows path such as `/C:...` then it
117
+ # should be changed to a human-friendly `C:...` form. Otherwise the
118
+ # passed value is left alone.
119
+ #
120
+ # @returns String
121
+ def self.human_readable(string)
122
+ (Gem.win_platform? && string =~ %r{^\/[a-zA-Z][\|:]}) ? string[1..-1] : string
123
+ end
124
+
125
+ # @return [Array<Hash{Symbol => Object}>] an array of hashes. Each hash
126
+ # contains 3 keys: :type contains a String that describes the template
127
+ # directory, :url contains a String with the URL to the template
128
+ # directory, and :allow_fallback contains a Boolean that specifies if
129
+ # the lookup process should proceed to the next template directory if
130
+ # the template file is not in this template directory.
131
+ #
132
+ def self.templates(opts)
133
+ explicit_url = opts.fetch(:'template-url', nil)
134
+ explicit_ref = opts.fetch(:'template-ref', nil)
135
+
136
+ # 1. Get the CLI, metadata (or answers if no metadata), and default URIs
137
+ # 2. Construct the hash
138
+ if explicit_url
139
+ # Valid URIs to avoid catching:
140
+ # - absolute local paths
141
+ # - have :'s in paths when preceeded by a slash
142
+ # - have only digits following the : and preceeding a / or end-of-string that is 0-65535
143
+ # The last item is ambiguous in the case of scp/git paths vs. URI port
144
+ # numbers, but can be made unambiguous by making the form to
145
+ # ssh://git@github.com/1234/repo.git or
146
+ # ssh://git@github.com:1234/user/repo.git
147
+ scp_url = explicit_url.match(SCP_PATTERN)
148
+ if Pathname.new(uri_safe(explicit_url)).relative? && scp_url
149
+ explicit_uri = Addressable::URI.new(scheme: 'ssh', user: scp_url[:user], host: scp_url[:host], path: scp_url[:path])
150
+ PDK.logger.warn _('%{scp_uri} appears to be an SCP style URL; it will be converted to an RFC compliant URI: %{rfc_uri}') % {
151
+ scp_uri: explicit_url,
152
+ rfc_uri: explicit_uri.to_s,
153
+ }
154
+ end
155
+ explicit_uri ||= Addressable::URI.parse(uri_safe(explicit_url))
156
+ explicit_uri.fragment = explicit_ref || default_template_ref
157
+ else
158
+ explicit_uri = nil
159
+ end
160
+ metadata_uri = if PDK::Util.module_root && File.file?(File.join(PDK::Util.module_root, 'metadata.json'))
161
+ Addressable::URI.parse(uri_safe(PDK::Util.module_metadata['template-url']))
162
+ else
163
+ nil
164
+ end
165
+ answers_uri = if PDK.answers['template-url'] == 'https://github.com/puppetlabs/pdk-module-template'
166
+ # use the new github template-url if it is still the old one.
167
+ Addressable::URI.parse(default_template_uri)
168
+ elsif PDK.answers['template-url']
169
+ Addressable::URI.parse(uri_safe(PDK.answers['template-url']))
170
+ else
171
+ nil
172
+ end
173
+ default_uri = Addressable::URI.parse(default_template_uri)
174
+ default_uri.fragment = default_template_ref
175
+
176
+ ary = []
177
+ ary << { type: _('--template-url'), uri: explicit_uri, allow_fallback: false } if explicit_url
178
+ ary << { type: _('metadata.json'), uri: metadata_uri, allow_fallback: true } if metadata_uri
179
+ ary << { type: _('PDK answers'), uri: answers_uri, allow_fallback: true } if answers_uri
180
+ ary << { type: _('default'), uri: default_uri, allow_fallback: false }
181
+ ary
182
+ end
183
+
184
+ # @returns String
185
+ def self.default_template_ref
186
+ if PDK::Util.development_mode?
187
+ 'master'
188
+ else
189
+ PDK::TEMPLATE_REF
190
+ end
191
+ end
192
+
193
+ # @returns Addressable::URI
194
+ def self.first_valid_uri(templates_array)
195
+ # 1. Get the four sources of URIs
196
+ # 2. Pick the first non-nil URI
197
+ # 3. Error if the URI is not a valid git repo (missing directory or http 404)
198
+ # 4. Leave updating answers/metadata to other code
199
+ found_template = templates_array.find { |t| valid_template?(t) }
200
+
201
+ raise PDK::CLI::FatalError, _('Unable to find a valid module template to use.') if found_template.nil?
202
+ found_template[:uri]
203
+ end
204
+
205
+ def self.valid_template?(template)
206
+ return false if template.nil? || !template.is_a?(Hash)
207
+ return false if template[:uri].nil? || !template[:uri].is_a?(Addressable::URI)
208
+
209
+ return true if PDK::Util::Git.repo?(git_remote(template[:uri]))
210
+
211
+ path = human_readable(template[:uri].path)
212
+ if File.directory?(path)
213
+ begin
214
+ PDK::Module::TemplateDir.new(path) {}
215
+ return true
216
+ rescue ArgumentError
217
+ nil
218
+ end
219
+ end
220
+
221
+ unless template[:allow_fallback]
222
+ raise PDK::CLI::FatalError, _('Unable to find a valid template at %{uri}') % {
223
+ uri: template[:uri].to_s,
224
+ }
225
+ end
226
+
227
+ false
228
+ end
229
+ end
230
+ end
231
+ end