pdk 1.9.1 → 1.10.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.
@@ -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