pray-cli 1.9.2 → 1.11.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +23 -0
- data/lib/pray/cache_clean.rb +76 -0
- data/lib/pray/cli/commands/meta.rb +9 -4
- data/lib/pray/cli/commands/workflow.rb +1 -1
- data/lib/pray/cli/help.rb +1 -1
- data/lib/pray/cli/helpers.rb +2 -1
- data/lib/pray/cli/parse.rb +19 -1
- data/lib/pray/cli.rb +2 -1
- data/lib/pray/compose_dest.rb +59 -0
- data/lib/pray/format_serialize.rb +6 -1
- data/lib/pray/lockfile.rb +32 -6
- data/lib/pray/lockfile_serialize.rb +15 -0
- data/lib/pray/manifest.rb +11 -10
- data/lib/pray/manifest_json.rb +4 -4
- data/lib/pray/manifest_parser.rb +1 -0
- data/lib/pray/manifest_parser_blocks.rb +5 -2
- data/lib/pray/manifest_parser_helpers.rb +34 -5
- data/lib/pray/materialize.rb +5 -3
- data/lib/pray/package_spec.rb +19 -0
- data/lib/pray/path_safety.rb +29 -0
- data/lib/pray/plan.rb +5 -11
- data/lib/pray/registry.rb +6 -13
- data/lib/pray/render.rb +43 -65
- data/lib/pray/render_content.rb +36 -0
- data/lib/pray/render_dest.rb +215 -0
- data/lib/pray/render_layout.rb +21 -0
- data/lib/pray/render_patch.rb +107 -0
- data/lib/pray/resolve.rb +5 -48
- data/lib/pray/resolve_exports.rb +79 -0
- data/lib/pray/tar_validation.rb +4 -1
- data/lib/pray/verify.rb +2 -0
- data/lib/pray/verify_provisioned.rb +55 -0
- data/lib/pray/version.rb +2 -2
- data/lib/pray.rb +6 -0
- metadata +9 -1
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pray
|
|
4
|
+
module RenderPatch
|
|
5
|
+
module_function
|
|
6
|
+
|
|
7
|
+
def patch_rendered_content(existing, fresh)
|
|
8
|
+
existing_segments = split_segments(existing)
|
|
9
|
+
fresh_segments = split_segments(fresh)
|
|
10
|
+
fresh_managed = fresh_segments.filter_map do |segment|
|
|
11
|
+
[segment[:id], segment[:body]] if segment[:kind] == :managed
|
|
12
|
+
end.to_h
|
|
13
|
+
overlap = existing_segments.any? do |segment|
|
|
14
|
+
segment[:kind] == :managed && fresh_managed.key?(segment[:id])
|
|
15
|
+
end
|
|
16
|
+
return fresh unless overlap
|
|
17
|
+
|
|
18
|
+
used = {}
|
|
19
|
+
output = existing_segments.map do |segment|
|
|
20
|
+
next segment[:text] if segment[:kind] == :text
|
|
21
|
+
|
|
22
|
+
used[segment[:id]] = true
|
|
23
|
+
managed_segment(segment[:id], fresh_managed.fetch(segment[:id], segment[:body]))
|
|
24
|
+
end.join
|
|
25
|
+
fresh_segments.each do |segment|
|
|
26
|
+
next unless segment[:kind] == :managed
|
|
27
|
+
next if used[segment[:id]]
|
|
28
|
+
|
|
29
|
+
output << managed_segment(segment[:id], segment[:body])
|
|
30
|
+
end
|
|
31
|
+
output.end_with?("\n") ? output : "#{output}\n"
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def relocate_managed_spans(content, spans)
|
|
35
|
+
positions = marker_positions(lines_of(content))
|
|
36
|
+
spans.map do |span|
|
|
37
|
+
position = positions[span.id]
|
|
38
|
+
next span unless position
|
|
39
|
+
|
|
40
|
+
span.dup.tap do |relocated|
|
|
41
|
+
relocated.open_line = position[0]
|
|
42
|
+
relocated.close_line = position[1]
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def split_segments(content)
|
|
48
|
+
lines = lines_of(content)
|
|
49
|
+
segments = []
|
|
50
|
+
text = +""
|
|
51
|
+
index = 0
|
|
52
|
+
while index < lines.length
|
|
53
|
+
identifier = marker_id(lines[index])
|
|
54
|
+
close = find_closing_marker(lines, index + 1, identifier) if identifier
|
|
55
|
+
if identifier && close
|
|
56
|
+
segments << {kind: :text, text: text} unless text.empty?
|
|
57
|
+
text = +""
|
|
58
|
+
body_lines = lines[(index + 1)...close]
|
|
59
|
+
body = body_lines.empty? ? "" : "#{body_lines.join("\n")}\n"
|
|
60
|
+
segments << {kind: :managed, id: identifier, body: body}
|
|
61
|
+
index = close + 1
|
|
62
|
+
else
|
|
63
|
+
text << "#{lines[index]}\n"
|
|
64
|
+
index += 1
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
segments << {kind: :text, text: text} unless text.empty?
|
|
68
|
+
segments
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def lines_of(content)
|
|
72
|
+
content.lines(chomp: true)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def find_closing_marker(lines, start, identifier)
|
|
76
|
+
(start...lines.length).find { |index| marker_id(lines[index]) == identifier }
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def marker_positions(lines)
|
|
80
|
+
positions = {}
|
|
81
|
+
active = nil
|
|
82
|
+
lines.each_with_index do |line, index|
|
|
83
|
+
identifier = marker_id(line)
|
|
84
|
+
next unless identifier&.match?(/\A[a-z0-9]+\z/)
|
|
85
|
+
|
|
86
|
+
if active.nil?
|
|
87
|
+
active = [identifier, index + 1]
|
|
88
|
+
elsif active[0] == identifier
|
|
89
|
+
positions[identifier] = [active[1], index + 1]
|
|
90
|
+
active = nil
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
positions
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def marker_id(line)
|
|
97
|
+
match = line.strip.match(/\A<!-- pray:(.+) -->\z/)
|
|
98
|
+
identifier = match && match[1]
|
|
99
|
+
identifier unless identifier == "0 ignore-comments"
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def managed_segment(identifier, body)
|
|
103
|
+
content = body.empty? ? "" : "#{body.sub(/\n+\z/, "")}\n"
|
|
104
|
+
"<!-- pray:#{identifier} -->\n#{content}<!-- pray:#{identifier} -->\n"
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
data/lib/pray/resolve.rb
CHANGED
|
@@ -87,6 +87,7 @@ module Pray
|
|
|
87
87
|
errors << "#{declaration.name}: #{error.message}"
|
|
88
88
|
end
|
|
89
89
|
raise Error.resolution(errors.join("\n")) unless errors.empty?
|
|
90
|
+
PackageSpec.warn_resolved_deprecations(packages)
|
|
90
91
|
|
|
91
92
|
local_files = []
|
|
92
93
|
local_errors = []
|
|
@@ -298,40 +299,11 @@ module Pray
|
|
|
298
299
|
end
|
|
299
300
|
|
|
300
301
|
def select_exports(declaration, spec)
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
unless spec.exports.key?(export)
|
|
304
|
-
raise Error.resolution("package #{declaration.name} does not export #{export}")
|
|
305
|
-
end
|
|
306
|
-
end
|
|
307
|
-
return declaration.exports
|
|
308
|
-
end
|
|
309
|
-
|
|
310
|
-
roles = declaration.roles || []
|
|
311
|
-
return spec.exports.keys.sort if roles.empty? && declaration.file.nil?
|
|
312
|
-
|
|
313
|
-
effective_roles = roles.dup
|
|
314
|
-
effective_roles << "file" if declaration.file && !effective_roles.include?("file")
|
|
302
|
+
ResolveExports.select_exports(declaration, spec)
|
|
303
|
+
end
|
|
315
304
|
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
compatible = spec.exports.filter_map do |name, export|
|
|
319
|
-
name if Destination.export_kind_matches_role?(export.kind, role)
|
|
320
|
-
end
|
|
321
|
-
case compatible.length
|
|
322
|
-
when 1
|
|
323
|
-
selected << compatible.first unless selected.include?(compatible.first)
|
|
324
|
-
when 0
|
|
325
|
-
raise Error.resolution(
|
|
326
|
-
"package #{declaration.name} has no export compatible with #{role}"
|
|
327
|
-
)
|
|
328
|
-
else
|
|
329
|
-
raise Error.resolution(
|
|
330
|
-
"package #{declaration.name} has multiple exports compatible with #{role}; set export: \"name\""
|
|
331
|
-
)
|
|
332
|
-
end
|
|
333
|
-
end
|
|
334
|
-
selected
|
|
305
|
+
def load_export_bodies(file_bytes, spec, selected_exports)
|
|
306
|
+
ResolveExports.load_export_bodies(file_bytes, spec, selected_exports)
|
|
335
307
|
end
|
|
336
308
|
|
|
337
309
|
def load_package_file_bytes(root, spec)
|
|
@@ -346,21 +318,6 @@ module Pray
|
|
|
346
318
|
file_bytes
|
|
347
319
|
end
|
|
348
320
|
|
|
349
|
-
def load_export_bodies(file_bytes, spec, selected_exports)
|
|
350
|
-
export_bodies = {}
|
|
351
|
-
selected_exports.each do |export_name|
|
|
352
|
-
entry = spec.exports[export_name]
|
|
353
|
-
raise Error.resolution("package #{spec.name} is missing export #{export_name}") unless entry
|
|
354
|
-
next unless entry.kind == "fragment"
|
|
355
|
-
|
|
356
|
-
bytes = file_bytes[entry.path]
|
|
357
|
-
raise Error.integrity("package file missing for export #{export_name}: #{entry.path}") unless bytes
|
|
358
|
-
|
|
359
|
-
export_bodies[export_name] = Hashing.normalize_line_endings(bytes.force_encoding(Encoding::UTF_8))
|
|
360
|
-
end
|
|
361
|
-
export_bodies
|
|
362
|
-
end
|
|
363
|
-
|
|
364
321
|
def build_skill_file_index(spec)
|
|
365
322
|
index = {}
|
|
366
323
|
spec.exports.each do |export_name, export|
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pray
|
|
4
|
+
module ResolveExports
|
|
5
|
+
module_function
|
|
6
|
+
|
|
7
|
+
def select_exports(declaration, spec)
|
|
8
|
+
unless declaration.exports.empty?
|
|
9
|
+
declaration.exports.each do |export|
|
|
10
|
+
unless spec.exports.key?(export)
|
|
11
|
+
raise Error.resolution("package #{declaration.name} does not export #{export}")
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
return declaration.exports
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
roles = declaration.roles || []
|
|
18
|
+
return spec.exports.keys.sort if roles.empty? && declaration.file.nil?
|
|
19
|
+
|
|
20
|
+
effective_roles = roles.dup
|
|
21
|
+
effective_roles << "file" if declaration.file && !effective_roles.include?("file")
|
|
22
|
+
|
|
23
|
+
selected = []
|
|
24
|
+
effective_roles.each do |role|
|
|
25
|
+
compatible = if role == "fragment"
|
|
26
|
+
fragment_role_exports(spec)
|
|
27
|
+
else
|
|
28
|
+
spec.exports.filter_map do |name, export|
|
|
29
|
+
name if Destination.export_kind_matches_role?(export.kind, role)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
case compatible.length
|
|
33
|
+
when 1
|
|
34
|
+
selected << compatible.first unless selected.include?(compatible.first)
|
|
35
|
+
when 0
|
|
36
|
+
raise Error.resolution(
|
|
37
|
+
"package #{declaration.name} has no export compatible with #{role}"
|
|
38
|
+
)
|
|
39
|
+
else
|
|
40
|
+
raise Error.resolution(
|
|
41
|
+
"package #{declaration.name} has multiple exports compatible with #{role}; set export: \"name\""
|
|
42
|
+
)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
selected
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def load_export_bodies(file_bytes, spec, selected_exports)
|
|
49
|
+
export_bodies = {}
|
|
50
|
+
selected_exports.each do |export_name|
|
|
51
|
+
entry = spec.exports[export_name]
|
|
52
|
+
raise Error.resolution("package #{spec.name} is missing export #{export_name}") unless entry
|
|
53
|
+
next unless %w[fragment file].include?(entry.kind)
|
|
54
|
+
|
|
55
|
+
bytes = file_bytes[entry.path]
|
|
56
|
+
unless bytes
|
|
57
|
+
raise Error.integrity("package file missing for export #{export_name}: #{entry.path}")
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
text = bytes.dup.force_encoding(Encoding::UTF_8)
|
|
61
|
+
unless text.valid_encoding?
|
|
62
|
+
if entry.kind == "fragment"
|
|
63
|
+
raise Error.integrity("package file is not valid utf-8 for export #{export_name}")
|
|
64
|
+
end
|
|
65
|
+
next
|
|
66
|
+
end
|
|
67
|
+
export_bodies[export_name] = Hashing.normalize_line_endings(text)
|
|
68
|
+
end
|
|
69
|
+
export_bodies
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def fragment_role_exports(spec)
|
|
73
|
+
fragments = spec.exports.filter_map { |name, export| name if export.kind == "fragment" }
|
|
74
|
+
return fragments unless fragments.empty?
|
|
75
|
+
|
|
76
|
+
spec.exports.filter_map { |name, export| name if export.kind == "file" }
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
data/lib/pray/tar_validation.rb
CHANGED
|
@@ -102,7 +102,10 @@ module Pray
|
|
|
102
102
|
end
|
|
103
103
|
|
|
104
104
|
def verify_checksum!(header)
|
|
105
|
-
|
|
105
|
+
# The checksum field is eight bytes. Writers fill it differently: six octal
|
|
106
|
+
# digits then NUL and space, seven digits then NUL, or space padding. Read the
|
|
107
|
+
# whole field and let parse_octal strip the terminator.
|
|
108
|
+
stored = parse_octal(header.byteslice(148, 8), "checksum")
|
|
106
109
|
sum = 0
|
|
107
110
|
header.bytes.each_with_index do |byte, index|
|
|
108
111
|
sum += (index >= 148 && index < 156) ? 32 : byte
|
data/lib/pray/verify.rb
CHANGED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pray
|
|
4
|
+
module VerifyProvisioned
|
|
5
|
+
module_function
|
|
6
|
+
|
|
7
|
+
def push_findings(project, report)
|
|
8
|
+
push_exclusive_file_export_findings(project, report)
|
|
9
|
+
Render.planned_provisioned_files(project).each do |file|
|
|
10
|
+
path_text = file.path.to_s.tr("\\", "/")
|
|
11
|
+
absolute = File.join(project.project_root, file.path)
|
|
12
|
+
if File.symlink?(absolute)
|
|
13
|
+
report.findings << VerificationFinding.new(
|
|
14
|
+
kind: "verify_error",
|
|
15
|
+
message: "Provisioned file `#{path_text}` is a symbolic link. Remove the link or choose another destination."
|
|
16
|
+
)
|
|
17
|
+
next
|
|
18
|
+
end
|
|
19
|
+
unless File.file?(absolute)
|
|
20
|
+
report.findings << VerificationFinding.new(
|
|
21
|
+
kind: "verify_error",
|
|
22
|
+
message: "Provisioned file `#{path_text}` from `#{file.package}` is missing. Run `pray install` to materialize it."
|
|
23
|
+
)
|
|
24
|
+
next
|
|
25
|
+
end
|
|
26
|
+
destination_bytes = File.binread(absolute)
|
|
27
|
+
expected_bytes = Render.expected_provisioned_bytes(file.source, project.manifest.symbols || {})
|
|
28
|
+
next if Hashing.sha256_prefixed(destination_bytes) == Hashing.sha256_prefixed(expected_bytes.b)
|
|
29
|
+
|
|
30
|
+
report.findings << VerificationFinding.new(
|
|
31
|
+
kind: "package_integrity",
|
|
32
|
+
message: "Provisioned file `#{path_text}` no longer matches package `#{file.package}`. Run `pray install` to restore it."
|
|
33
|
+
)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def push_exclusive_file_export_findings(project, report)
|
|
38
|
+
project.packages.each do |package|
|
|
39
|
+
destination = package.declaration.file
|
|
40
|
+
next unless destination
|
|
41
|
+
|
|
42
|
+
has_file_export = package.selected_exports.any? do |name|
|
|
43
|
+
export = package.spec.exports[name]
|
|
44
|
+
export && export.kind == "file"
|
|
45
|
+
end
|
|
46
|
+
next if has_file_export
|
|
47
|
+
|
|
48
|
+
report.findings << VerificationFinding.new(
|
|
49
|
+
kind: "verify_error",
|
|
50
|
+
message: "Package `#{package.declaration.name}` declares file: \"#{destination}\" but has no selected file export."
|
|
51
|
+
)
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
data/lib/pray/version.rb
CHANGED
data/lib/pray.rb
CHANGED
|
@@ -20,10 +20,16 @@ require_relative "pray/project_context"
|
|
|
20
20
|
require_relative "pray/environment"
|
|
21
21
|
require_relative "pray/resolve_context"
|
|
22
22
|
require_relative "pray/resolve_source"
|
|
23
|
+
require_relative "pray/resolve_exports"
|
|
23
24
|
require_relative "pray/invocation"
|
|
24
25
|
require_relative "pray/resolve"
|
|
26
|
+
require_relative "pray/compose_dest"
|
|
27
|
+
require_relative "pray/render_patch"
|
|
25
28
|
require_relative "pray/render"
|
|
29
|
+
require_relative "pray/render_dest"
|
|
30
|
+
require_relative "pray/render_layout"
|
|
26
31
|
require_relative "pray/verify_position"
|
|
32
|
+
require_relative "pray/verify_provisioned"
|
|
27
33
|
require_relative "pray/verify"
|
|
28
34
|
require_relative "pray/resource_limits"
|
|
29
35
|
require_relative "pray/http_body"
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: pray-cli
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.11.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Andrei Makarov
|
|
@@ -99,6 +99,7 @@ files:
|
|
|
99
99
|
- lib/pray/archive.rb
|
|
100
100
|
- lib/pray/archive_unpack.rb
|
|
101
101
|
- lib/pray/auth_client.rb
|
|
102
|
+
- lib/pray/cache_clean.rb
|
|
102
103
|
- lib/pray/cli.rb
|
|
103
104
|
- lib/pray/cli/commands/auth.rb
|
|
104
105
|
- lib/pray/cli/commands/distribution.rb
|
|
@@ -113,6 +114,7 @@ files:
|
|
|
113
114
|
- lib/pray/cli/parse_auth.rb
|
|
114
115
|
- lib/pray/cli/parse_trust.rb
|
|
115
116
|
- lib/pray/cli/suggest.rb
|
|
117
|
+
- lib/pray/compose_dest.rb
|
|
116
118
|
- lib/pray/confess.rb
|
|
117
119
|
- lib/pray/config.rb
|
|
118
120
|
- lib/pray/constraint.rb
|
|
@@ -146,8 +148,13 @@ files:
|
|
|
146
148
|
- lib/pray/registry_install.rb
|
|
147
149
|
- lib/pray/registry_integrity.rb
|
|
148
150
|
- lib/pray/render.rb
|
|
151
|
+
- lib/pray/render_content.rb
|
|
152
|
+
- lib/pray/render_dest.rb
|
|
153
|
+
- lib/pray/render_layout.rb
|
|
154
|
+
- lib/pray/render_patch.rb
|
|
149
155
|
- lib/pray/resolve.rb
|
|
150
156
|
- lib/pray/resolve_context.rb
|
|
157
|
+
- lib/pray/resolve_exports.rb
|
|
151
158
|
- lib/pray/resolve_source.rb
|
|
152
159
|
- lib/pray/resource_limits.rb
|
|
153
160
|
- lib/pray/serve.rb
|
|
@@ -164,6 +171,7 @@ files:
|
|
|
164
171
|
- lib/pray/trust_ops.rb
|
|
165
172
|
- lib/pray/verify.rb
|
|
166
173
|
- lib/pray/verify_position.rb
|
|
174
|
+
- lib/pray/verify_provisioned.rb
|
|
167
175
|
- lib/pray/version.rb
|
|
168
176
|
- pray-cli.gemspec
|
|
169
177
|
homepage: https://pray.kisko.dev
|