chef 18.10.17 → 18.11.11
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/Gemfile +15 -7
- data/README.md +0 -1
- data/chef-universal-mingw-ucrt.gemspec +4 -2
- data/chef.gemspec +15 -12
- data/lib/chef/compliance/runner.rb +19 -1
- data/lib/chef/dsl/rest_resource.rb +223 -16
- data/lib/chef/formatters/error_description.rb +8 -1
- data/lib/chef/http/authenticator.rb +17 -4
- data/lib/chef/mixin/template.rb +24 -2
- data/lib/chef/monkey_patches/reline-windows.rb +116 -0
- data/lib/chef/provider/file/content.rb +3 -2
- data/lib/chef/provider/file.rb +1 -1
- data/lib/chef/provider/ifconfig/redhat.rb +1 -1
- data/lib/chef/provider/package/dnf/dnf_helper.py +355 -65
- data/lib/chef/provider/package/dnf/python_helper.rb +7 -4
- data/lib/chef/provider/package/dnf.rb +25 -6
- data/lib/chef/provider/package/yum.rb +1 -1
- data/lib/chef/resource/_rest_resource.rb +4 -2
- data/lib/chef/resource/archive_file.rb +52 -18
- data/lib/chef/resource/chef_client_config.rb +185 -19
- data/lib/chef/resource/chef_vault_secret.rb +1 -1
- data/lib/chef/resource/chocolatey_installer.rb +31 -13
- data/lib/chef/resource/chocolatey_package.rb +1 -1
- data/lib/chef/resource/habitat/habitat_package.rb +1 -1
- data/lib/chef/resource/habitat/habitat_sup.rb +2 -2
- data/lib/chef/resource/habitat_config.rb +1 -1
- data/lib/chef/resource/habitat_service.rb +1 -1
- data/lib/chef/resource/openssl_ec_private_key.rb +1 -1
- data/lib/chef/resource/openssl_ec_public_key.rb +2 -2
- data/lib/chef/resource/openssl_rsa_private_key.rb +1 -1
- data/lib/chef/resource/openssl_rsa_public_key.rb +2 -2
- data/lib/chef/resource/openssl_x509_certificate.rb +1 -1
- data/lib/chef/resource/openssl_x509_request.rb +1 -1
- data/lib/chef/resource/powershell_package_source.rb +1 -1
- data/lib/chef/resource/rhsm_register.rb +1 -1
- data/lib/chef/resource/windows_ad_join.rb +1 -1
- data/lib/chef/resource/windows_certificate.rb +1 -1
- data/lib/chef/resource/windows_service.rb +1 -1
- data/lib/chef/resource/windows_task.rb +1 -1
- data/lib/chef/resource/yum_package.rb +1 -1
- data/lib/chef/resource/yum_repository.rb +2 -2
- data/lib/chef/shell.rb +7 -0
- data/lib/chef/version.rb +1 -1
- metadata +59 -34
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# Author:: Chef Software Inc.
|
|
2
|
+
# Copyright:: Copyright (c) Chef Software Inc.
|
|
3
|
+
# License:: Apache License, Version 2.0
|
|
4
|
+
#
|
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
# you may not use this file except in compliance with the License.
|
|
7
|
+
# You may obtain a copy of the License at
|
|
8
|
+
#
|
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
#
|
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
# See the License for the specific language governing permissions and
|
|
15
|
+
# limitations under the License.
|
|
16
|
+
#
|
|
17
|
+
# == Background
|
|
18
|
+
#
|
|
19
|
+
# On Windows, Reline's process_key_event emits an ESC byte (0x1B) before every
|
|
20
|
+
# character when the :ALT control key flag is set. This is correct for true
|
|
21
|
+
# Alt key combos (Meta sequences), but AltGr on European keyboards – most
|
|
22
|
+
# notably German layouts – is reported by Windows as Ctrl+Alt (RIGHT_CTRL +
|
|
23
|
+
# RIGHT_ALT pressed simultaneously). Characters that require AltGr, such as
|
|
24
|
+
# { } [ ] @ ~ | \, therefore receive both :CTRL and :ALT flags, causing Reline to
|
|
25
|
+
# emit an ESC prefix. In a terminal the result looks like ^[{ instead of {.
|
|
26
|
+
#
|
|
27
|
+
# The corrupt input leaves IRB's parser in an open-string/block state which
|
|
28
|
+
# re-draws continuation prompts endlessly when multi-line blocks are pasted,
|
|
29
|
+
# making chef-shell unusable for German (and many other European) keyboard users.
|
|
30
|
+
#
|
|
31
|
+
# == Fix
|
|
32
|
+
#
|
|
33
|
+
# Only emit the ESC Meta prefix when :ALT is set WITHOUT :CTRL. When both
|
|
34
|
+
# flags are present the keypress is AltGr-originated and the raw character
|
|
35
|
+
# bytes are sufficient and correct.
|
|
36
|
+
#
|
|
37
|
+
# == Affected versions
|
|
38
|
+
#
|
|
39
|
+
# Verified against Reline <= 0.3.x (shipped with Ruby 3.1.x in chef-foundation).
|
|
40
|
+
# The issue was resolved upstream in Reline 0.3.2 / irb 1.6.2 (Ruby 3.2+),
|
|
41
|
+
# so the patch is guarded to apply only when the installed Reline version is
|
|
42
|
+
# affected.
|
|
43
|
+
#
|
|
44
|
+
# == References
|
|
45
|
+
#
|
|
46
|
+
# * Upstream report: https://github.com/ruby/reline/issues/475
|
|
47
|
+
# * Chef PR #9267 (Ruby 2.7 IRB fixes, introduced multiline/singleline wiring)
|
|
48
|
+
# * Chef PR #14919 (Prompt consistency fix for left-arrow on Ruby 3.1/3.2)
|
|
49
|
+
# * Chef PR #15336 (IRB @ALIASES removal compat)
|
|
50
|
+
|
|
51
|
+
if Gem.win_platform? && defined?(Reline::Windows)
|
|
52
|
+
|
|
53
|
+
# Only patch versions where the bug is present. Reline 0.3.2 fixed it
|
|
54
|
+
# upstream. Gem::Version comparison is safe even when reline is a stdlib
|
|
55
|
+
# default gem without a Gemspec entry in loaded_specs.
|
|
56
|
+
reline_version = begin
|
|
57
|
+
Gem::Version.new(Reline::VERSION)
|
|
58
|
+
rescue StandardError
|
|
59
|
+
Gem::Version.new("0")
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
if reline_version < Gem::Version.new("0.3.2")
|
|
63
|
+
Chef::Log.debug("chef-shell: applying Reline::Windows AltGr monkey patch (reline #{Reline::VERSION})")
|
|
64
|
+
|
|
65
|
+
class Reline::Windows
|
|
66
|
+
class << self
|
|
67
|
+
# Reopen process_key_event and change only the ESC-emission guard.
|
|
68
|
+
# All other behavior – surrogate pair handling, KEY_MAP matching, bare
|
|
69
|
+
# control-key suppression – is preserved verbatim from the original.
|
|
70
|
+
def process_key_event(repeat_count, virtual_key_code, virtual_scan_code, char_code, control_key_state)
|
|
71
|
+
# ---- surrogate pair handling (unchanged) --------------------------------
|
|
72
|
+
if char_code.between?(0xD800, 0xDBFF)
|
|
73
|
+
@@hsg = char_code
|
|
74
|
+
return
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
if char_code.between?(0xDC00, 0xDFFF)
|
|
78
|
+
if @@hsg
|
|
79
|
+
char_code = 0x10000 + (@@hsg - 0xD800) * 0x400 + char_code - 0xDC00
|
|
80
|
+
@@hsg = nil
|
|
81
|
+
else
|
|
82
|
+
return # low-surrogate without preceding high-surrogate – ignore
|
|
83
|
+
end
|
|
84
|
+
else
|
|
85
|
+
@@hsg = nil # discard stale high-surrogate
|
|
86
|
+
end
|
|
87
|
+
# -------------------------------------------------------------------------
|
|
88
|
+
|
|
89
|
+
key = KeyEventRecord.new(virtual_key_code, char_code, control_key_state)
|
|
90
|
+
|
|
91
|
+
# KEY_MAP takes priority (arrow keys, Delete, Home, End, Tab, etc.)
|
|
92
|
+
match = KEY_MAP.find { |args,| key.matches?(**args) }
|
|
93
|
+
unless match.nil?
|
|
94
|
+
@@output_buf.concat(match.last)
|
|
95
|
+
return
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Suppress bare modifier-only keypresses (no printable character)
|
|
99
|
+
return if key.char_code == 0 && key.control_keys.any?
|
|
100
|
+
|
|
101
|
+
# ---- THE FIX -----------------------------------------------------------
|
|
102
|
+
# Original: @@output_buf.push("\e".ord) if key.control_keys.include?(:ALT)
|
|
103
|
+
#
|
|
104
|
+
# AltGr on European keyboards reports as Ctrl+Alt (both :CTRL and :ALT
|
|
105
|
+
# present). Only emit the ESC Meta prefix when Alt is pressed WITHOUT
|
|
106
|
+
# Ctrl – i.e. a genuine Meta/Alt sequence, not an AltGr character.
|
|
107
|
+
alt_without_ctrl = key.control_keys.include?(:ALT) && !key.control_keys.include?(:CTRL)
|
|
108
|
+
@@output_buf.push("\e".ord) if alt_without_ctrl
|
|
109
|
+
# -------------------------------------------------------------------------
|
|
110
|
+
|
|
111
|
+
@@output_buf.concat(key.char.bytes)
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
@@ -24,9 +24,10 @@ class Chef
|
|
|
24
24
|
class File
|
|
25
25
|
class Content < Chef::FileContentManagement::ContentBase
|
|
26
26
|
def file_for_provider
|
|
27
|
-
|
|
27
|
+
content = @new_resource.content
|
|
28
|
+
if content
|
|
28
29
|
tempfile = Chef::FileContentManagement::Tempfile.new(@new_resource).tempfile
|
|
29
|
-
tempfile.write(
|
|
30
|
+
tempfile.write(content)
|
|
30
31
|
tempfile.close
|
|
31
32
|
tempfile
|
|
32
33
|
else
|
data/lib/chef/provider/file.rb
CHANGED
|
@@ -192,7 +192,7 @@ class Chef
|
|
|
192
192
|
# be overridden in subclasses.
|
|
193
193
|
def managing_content?
|
|
194
194
|
return true if new_resource.checksum
|
|
195
|
-
return true if
|
|
195
|
+
return true if new_resource.property_is_set?(:content) && @action != :create_if_missing
|
|
196
196
|
|
|
197
197
|
false
|
|
198
198
|
end
|
|
@@ -22,7 +22,7 @@ class Chef
|
|
|
22
22
|
class Provider
|
|
23
23
|
class Ifconfig
|
|
24
24
|
class Redhat < Chef::Provider::Ifconfig
|
|
25
|
-
provides :ifconfig, platform_family:
|
|
25
|
+
provides :ifconfig, platform_family: %w{fedora rhel amazon}
|
|
26
26
|
|
|
27
27
|
def initialize(new_resource, run_context)
|
|
28
28
|
super(new_resource, run_context)
|
|
@@ -1,22 +1,97 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
|
-
#
|
|
2
|
+
#
|
|
3
|
+
# Copyright:: Copyright (c) 2009-2026 Progress Software Corporation and/or its subsidiaries or affiliates. All Rights Reserved.
|
|
4
|
+
# Copyright:: Copyright (c) 2026 Meta Platforms, Inc.
|
|
5
|
+
# Copyright:: Copyright (c) 2026 Phil Dibowitz
|
|
6
|
+
# License:: Apache License, Version 2.0
|
|
7
|
+
#
|
|
8
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
9
|
+
# you may not use this file except in compliance with the License.
|
|
10
|
+
# You may obtain a copy of the License at
|
|
11
|
+
#
|
|
12
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
13
|
+
#
|
|
14
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
15
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
16
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
17
|
+
# See the License for the specific language governing permissions and
|
|
18
|
+
# limitations under the License.
|
|
19
|
+
#
|
|
3
20
|
|
|
4
21
|
import sys
|
|
5
|
-
import dnf
|
|
6
|
-
import hawkey
|
|
7
22
|
import signal
|
|
8
23
|
import os
|
|
9
24
|
import json
|
|
10
25
|
|
|
26
|
+
# to enable debug logging, set the CHEF_DNF_HELPER_DEBUG_FILE environment
|
|
27
|
+
# variable to a file path
|
|
28
|
+
DEBUG_FILE = os.environ.get("CHEF_DNF_HELPER_DEBUG_FILE", None)
|
|
29
|
+
|
|
30
|
+
# Try to import dnf5 first, fall back to dnf4
|
|
31
|
+
try:
|
|
32
|
+
import libdnf5
|
|
33
|
+
import rpm
|
|
34
|
+
|
|
35
|
+
DNF_VERSION = 5
|
|
36
|
+
except ImportError:
|
|
37
|
+
try:
|
|
38
|
+
import dnf
|
|
39
|
+
import hawkey
|
|
40
|
+
|
|
41
|
+
DNF_VERSION = 4
|
|
42
|
+
except ImportError:
|
|
43
|
+
raise RuntimeError(
|
|
44
|
+
"Neither dnf5 (libdnf5) nor dnf4 (dnf) libraries are available"
|
|
45
|
+
)
|
|
46
|
+
|
|
11
47
|
base = None
|
|
12
48
|
|
|
13
|
-
|
|
49
|
+
|
|
50
|
+
def get_base_dnf5(command):
|
|
51
|
+
global base
|
|
52
|
+
if base is None:
|
|
53
|
+
base = libdnf5.base.Base()
|
|
54
|
+
|
|
55
|
+
# Load configuration
|
|
56
|
+
base.load_config()
|
|
57
|
+
|
|
58
|
+
# Set up vars
|
|
59
|
+
base.setup()
|
|
60
|
+
|
|
61
|
+
# Load repositories
|
|
62
|
+
repo_sack = base.get_repo_sack()
|
|
63
|
+
repo_sack.create_repos_from_system_configuration()
|
|
64
|
+
|
|
65
|
+
if "repos" in command:
|
|
66
|
+
for repo_pattern in command["repos"]:
|
|
67
|
+
if "enable" in repo_pattern:
|
|
68
|
+
query = libdnf5.repo.RepoQuery(base)
|
|
69
|
+
query.filter_id(
|
|
70
|
+
repo_pattern["enable"], libdnf5.common.QueryCmp_GLOB
|
|
71
|
+
)
|
|
72
|
+
for repo in query:
|
|
73
|
+
repo.enable()
|
|
74
|
+
if "disable" in repo_pattern:
|
|
75
|
+
query = libdnf5.repo.RepoQuery(base)
|
|
76
|
+
query.filter_id(
|
|
77
|
+
repo_pattern["disable"], libdnf5.common.QueryCmp_GLOB
|
|
78
|
+
)
|
|
79
|
+
for repo in query:
|
|
80
|
+
repo.disable()
|
|
81
|
+
|
|
82
|
+
# Load repositories and create solv files
|
|
83
|
+
repo_sack.load_repos()
|
|
84
|
+
|
|
85
|
+
return base
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def get_sack_dnf4(command):
|
|
14
89
|
global base
|
|
15
90
|
if base is None:
|
|
16
91
|
base = dnf.Base()
|
|
17
92
|
conf = base.conf
|
|
18
93
|
conf.read()
|
|
19
|
-
conf.installroot =
|
|
94
|
+
conf.installroot = "/"
|
|
20
95
|
conf.assumeyes = True
|
|
21
96
|
subst = conf.substitutions
|
|
22
97
|
subst.update_from_etc(conf.installroot)
|
|
@@ -28,136 +103,350 @@ def get_sack():
|
|
|
28
103
|
base.read_all_repos()
|
|
29
104
|
repos = base.repos
|
|
30
105
|
|
|
31
|
-
if
|
|
32
|
-
for repo_pattern in command[
|
|
33
|
-
if
|
|
34
|
-
for repo in repos.get_matching(repo_pattern[
|
|
106
|
+
if "repos" in command:
|
|
107
|
+
for repo_pattern in command["repos"]:
|
|
108
|
+
if "enable" in repo_pattern:
|
|
109
|
+
for repo in repos.get_matching(repo_pattern["enable"]):
|
|
35
110
|
repo.enable()
|
|
36
|
-
if
|
|
37
|
-
for repo in repos.get_matching(repo_pattern[
|
|
111
|
+
if "disable" in repo_pattern:
|
|
112
|
+
for repo in repos.get_matching(repo_pattern["disable"]):
|
|
38
113
|
repo.disable()
|
|
39
114
|
|
|
40
115
|
try:
|
|
41
116
|
base.configure_plugins()
|
|
42
117
|
except AttributeError:
|
|
43
118
|
pass
|
|
44
|
-
base.fill_sack(load_system_repo=
|
|
119
|
+
base.fill_sack(load_system_repo="auto")
|
|
45
120
|
return base.sack
|
|
46
121
|
|
|
47
|
-
|
|
48
|
-
def
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
122
|
+
|
|
123
|
+
def get_sack(command):
|
|
124
|
+
if DNF_VERSION == 5:
|
|
125
|
+
return get_base_dnf5(command)
|
|
126
|
+
else:
|
|
127
|
+
return get_sack_dnf4(command)
|
|
128
|
+
|
|
54
129
|
|
|
55
130
|
def version_tuple(versionstr):
|
|
56
|
-
e =
|
|
131
|
+
e = "0"
|
|
57
132
|
v = None
|
|
58
133
|
r = None
|
|
59
|
-
colon_index = versionstr.find(
|
|
134
|
+
colon_index = versionstr.find(":")
|
|
60
135
|
if colon_index > 0:
|
|
61
136
|
e = str(versionstr[:colon_index])
|
|
62
|
-
dash_index = versionstr.find(
|
|
137
|
+
dash_index = versionstr.find("-")
|
|
63
138
|
if dash_index > 0:
|
|
64
|
-
tmp = versionstr[colon_index + 1:dash_index]
|
|
65
|
-
if tmp !=
|
|
139
|
+
tmp = versionstr[colon_index + 1 : dash_index]
|
|
140
|
+
if tmp != "":
|
|
66
141
|
v = tmp
|
|
67
|
-
arch_index = versionstr.rfind(
|
|
142
|
+
arch_index = versionstr.rfind(".", dash_index)
|
|
68
143
|
if arch_index > 0:
|
|
69
|
-
r = versionstr[dash_index + 1:arch_index]
|
|
144
|
+
r = versionstr[dash_index + 1 : arch_index]
|
|
70
145
|
else:
|
|
71
|
-
r = versionstr[dash_index + 1:]
|
|
146
|
+
r = versionstr[dash_index + 1 :]
|
|
72
147
|
else:
|
|
73
|
-
tmp = versionstr[colon_index + 1:]
|
|
74
|
-
if tmp !=
|
|
148
|
+
tmp = versionstr[colon_index + 1 :]
|
|
149
|
+
if tmp != "":
|
|
75
150
|
v = tmp
|
|
76
151
|
return (e, v, r)
|
|
77
152
|
|
|
78
|
-
|
|
79
|
-
|
|
153
|
+
|
|
154
|
+
# If we pass in 0:1.10 and 1.2 to the dnf5 libraries, it won't compare
|
|
155
|
+
# them correctly, they both need to have epochs or not have epochs. However,
|
|
156
|
+
# unlike dnf4 libraries, it they don't take tuples, so use version_tuple to
|
|
157
|
+
# canonicalize the parts of the version, then reassemble them into a full EVR
|
|
158
|
+
# string.
|
|
159
|
+
def version_canonicalize(versionstr):
|
|
160
|
+
e, v, r = version_tuple(versionstr)
|
|
161
|
+
return f"{e}:{v}-{r}"
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def versioncompare(command):
|
|
165
|
+
versions = command["versions"]
|
|
166
|
+
sack = get_sack(command)
|
|
80
167
|
if (versions[0] is None) or (versions[1] is None):
|
|
81
|
-
outpipe.write(
|
|
168
|
+
outpipe.write("0\n")
|
|
82
169
|
outpipe.flush()
|
|
83
170
|
else:
|
|
84
|
-
|
|
85
|
-
|
|
171
|
+
if DNF_VERSION == 4:
|
|
172
|
+
evr_comparison = dnf.rpm.rpm.labelCompare(
|
|
173
|
+
version_tuple(versions[0]), version_tuple(versions[1])
|
|
174
|
+
)
|
|
175
|
+
outpipe.write("{}\n".format(evr_comparison))
|
|
176
|
+
else:
|
|
177
|
+
# dnf5 version comparison - rpmvercmp handles full EVR strings
|
|
178
|
+
cmp_result = libdnf5.rpm.rpmvercmp(
|
|
179
|
+
version_canonicalize(versions[0]),
|
|
180
|
+
version_canonicalize(versions[1]),
|
|
181
|
+
)
|
|
182
|
+
outpipe.write("{}\n".format(cmp_result))
|
|
86
183
|
outpipe.flush()
|
|
87
184
|
|
|
88
|
-
def query(command):
|
|
89
|
-
sack = get_sack()
|
|
90
185
|
|
|
91
|
-
|
|
186
|
+
def query_dnf4(command):
|
|
187
|
+
sack = get_sack(command)
|
|
188
|
+
|
|
189
|
+
subj = dnf.subject.Subject(command["provides"])
|
|
92
190
|
q = subj.get_best_query(sack, with_provides=True)
|
|
93
191
|
|
|
94
|
-
if command[
|
|
192
|
+
if command["action"] == "whatinstalled":
|
|
95
193
|
# When attempting to figure out what is installed, we should ignore any
|
|
96
194
|
# excludes that are configured, otherwise the "best" query for a given
|
|
97
195
|
# subject may refer to a package that is installed that provides that
|
|
98
196
|
# subject, but we really want to know if a package by that name exists
|
|
99
197
|
# in any available repository
|
|
100
|
-
q = subj.get_best_query(
|
|
101
|
-
|
|
198
|
+
q = subj.get_best_query(
|
|
199
|
+
sack,
|
|
200
|
+
with_provides=True,
|
|
201
|
+
query=sack.query(flags=hawkey.IGNORE_EXCLUDES),
|
|
202
|
+
)
|
|
102
203
|
q = q.installed()
|
|
103
204
|
|
|
104
|
-
if command[
|
|
205
|
+
if command["action"] == "whatavailable":
|
|
105
206
|
q = q.available()
|
|
106
207
|
|
|
107
|
-
if
|
|
208
|
+
if "epoch" in command:
|
|
108
209
|
# We assume that any glob is "*" so just omit the filter since the dnf libraries have no
|
|
109
210
|
# epoch__glob filter. That means "?" wildcards in epochs will fail. The workaround is to
|
|
110
211
|
# not use the version filter here but to put the version with all the globs in the package name.
|
|
111
|
-
if not dnf.util.is_glob_pattern(command[
|
|
112
|
-
q = q.filterm(epoch=int(command[
|
|
113
|
-
if
|
|
114
|
-
if dnf.util.is_glob_pattern(command[
|
|
115
|
-
q = q.filterm(version__glob=command[
|
|
212
|
+
if not dnf.util.is_glob_pattern(command["epoch"]):
|
|
213
|
+
q = q.filterm(epoch=int(command["epoch"]))
|
|
214
|
+
if "version" in command:
|
|
215
|
+
if dnf.util.is_glob_pattern(command["version"]):
|
|
216
|
+
q = q.filterm(version__glob=command["version"])
|
|
116
217
|
else:
|
|
117
|
-
q = q.filterm(version=command[
|
|
118
|
-
if
|
|
119
|
-
if dnf.util.is_glob_pattern(command[
|
|
120
|
-
q = q.filterm(release__glob=command[
|
|
218
|
+
q = q.filterm(version=command["version"])
|
|
219
|
+
if "release" in command:
|
|
220
|
+
if dnf.util.is_glob_pattern(command["release"]):
|
|
221
|
+
q = q.filterm(release__glob=command["release"])
|
|
121
222
|
else:
|
|
122
|
-
q = q.filterm(release=command[
|
|
223
|
+
q = q.filterm(release=command["release"])
|
|
123
224
|
|
|
124
|
-
if
|
|
125
|
-
if dnf.util.is_glob_pattern(command[
|
|
126
|
-
q = q.filterm(arch__glob=command[
|
|
225
|
+
if "arch" in command:
|
|
226
|
+
if dnf.util.is_glob_pattern(command["arch"]):
|
|
227
|
+
q = q.filterm(arch__glob=command["arch"])
|
|
127
228
|
else:
|
|
128
|
-
q = q.filterm(arch=command[
|
|
229
|
+
q = q.filterm(arch=command["arch"])
|
|
129
230
|
|
|
130
231
|
# only apply the default arch query filter if it returns something
|
|
131
|
-
archq = q.filter(arch=[
|
|
232
|
+
archq = q.filter(arch=["noarch", hawkey.detect_arch()])
|
|
132
233
|
if len(archq.run()) > 0:
|
|
133
234
|
q = archq
|
|
134
235
|
|
|
135
236
|
pkgs = q.latest(1).run()
|
|
136
237
|
|
|
137
238
|
if not pkgs:
|
|
138
|
-
outpipe.write(
|
|
239
|
+
outpipe.write("{} nil nil\n".format(command["provides"].split().pop(0)))
|
|
139
240
|
outpipe.flush()
|
|
140
241
|
else:
|
|
141
242
|
# make sure we picked the package with the highest version
|
|
142
243
|
pkgs.sort
|
|
143
244
|
pkg = pkgs.pop()
|
|
144
|
-
outpipe.write(
|
|
245
|
+
outpipe.write(
|
|
246
|
+
"{} {}:{}-{} {}\n".format(
|
|
247
|
+
pkg.name, pkg.epoch, pkg.version, pkg.release, pkg.arch
|
|
248
|
+
)
|
|
249
|
+
)
|
|
250
|
+
outpipe.flush()
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
def log(message):
|
|
254
|
+
if DEBUG_FILE is None:
|
|
255
|
+
return
|
|
256
|
+
with open(DEBUG_FILE, "a") as f:
|
|
257
|
+
f.write(message + "\n")
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
def query_dnf5(command):
|
|
261
|
+
"""
|
|
262
|
+
Query dnf5 for package information based on the command dict.
|
|
263
|
+
|
|
264
|
+
This method does a fair amount of work to try to mimic the behavior
|
|
265
|
+
of "dnf install <foo>". In the DNF4 world, this functionality was
|
|
266
|
+
exposed through the dnf.subject.Subject class. In DNF5, this functionality
|
|
267
|
+
is internal to the Goal class, which you can use, but then you can't get
|
|
268
|
+
a list of matching packages out of - you can simply ask the goal to be
|
|
269
|
+
resolved to a package transaction, and then run or not run that transaction.
|
|
270
|
+
|
|
271
|
+
So instead we combine the nevra filtering and provides filtering to mimic
|
|
272
|
+
the behavior of being able to handle anything that could be passed to
|
|
273
|
+
"dnf install <foo>".
|
|
274
|
+
|
|
275
|
+
Some of the cases we handle are:
|
|
276
|
+
- name only: "foo"
|
|
277
|
+
- name and arch: "foo.x86_64"
|
|
278
|
+
- name and version: "foo-1.2"
|
|
279
|
+
- name, version, release: "foo-1.2-3"
|
|
280
|
+
- name, version, release, arch: "foo-1.2-3.x
|
|
281
|
+
- name with version constraint: "foo >= 1.2"
|
|
282
|
+
- globs: "foo*", "foo-1.2*", "foo-1.2-3*", "foo-1*.*", etc.
|
|
283
|
+
|
|
284
|
+
A full exercising of this functionality testing all known cases is
|
|
285
|
+
in the unittest for the DNF provider.
|
|
286
|
+
"""
|
|
287
|
+
base = get_sack(command)
|
|
288
|
+
q = libdnf5.rpm.PackageQuery(base)
|
|
289
|
+
|
|
290
|
+
# First, we need to know if this parses as a nevra or not, which will
|
|
291
|
+
# inform the rest of our decision tree.
|
|
292
|
+
provides_str = command["provides"]
|
|
293
|
+
try:
|
|
294
|
+
nevra_vector = libdnf5.rpm.Nevra.parse(provides_str)
|
|
295
|
+
except libdnf5.exception.RpmNevraIncorrectInputError:
|
|
296
|
+
# when parse() throws an this exception, it's because there's spaces,
|
|
297
|
+
# or other special characters in it, and the only valid things passed
|
|
298
|
+
# to us that fit that category are constrains like: "foo >= 1.2". So
|
|
299
|
+
# parse it as one of those, add the constraint to the query, and update
|
|
300
|
+
# the name we search for to the parsed name
|
|
301
|
+
nevra_vector = []
|
|
302
|
+
reldep = libdnf5.rpm.Reldep(base, provides_str)
|
|
303
|
+
provides_str = reldep.get_name()
|
|
304
|
+
q.filter_provides(reldep)
|
|
305
|
+
|
|
306
|
+
# unlike the old subject based query, filter_nevra doesn't handle
|
|
307
|
+
# the <name>.<arch> case properly. Further, adding * to arch causes
|
|
308
|
+
# weirdness. So, we detect the arch suffix, and strip it off and add
|
|
309
|
+
# it to the direct arch filter.
|
|
310
|
+
#
|
|
311
|
+
# Unfortunately, since we want to support nearly any possible combination
|
|
312
|
+
# of name, version, release, arch with globs, we have to do some extra work
|
|
313
|
+
# here. parse() will give us an iterable list of possible interpretations
|
|
314
|
+
# of the string. That can include dumb things like for "foo-1.2" the
|
|
315
|
+
# possibility that "2" is an arch. So, we take the arch and see if it's
|
|
316
|
+
# a compatible arch with us (e.g. x86_64 and i686 on x86_64 systems). If
|
|
317
|
+
# we find one that matches, we use that, rip the arch off, add it to the
|
|
318
|
+
# filters.
|
|
319
|
+
#
|
|
320
|
+
# While there may be other entries in the list that are (more) correct,
|
|
321
|
+
# it doesn't matter, we're only need to detect if a valid arch was specified
|
|
322
|
+
# so we can handle that manually.
|
|
323
|
+
nevra = None
|
|
324
|
+
for n in nevra_vector:
|
|
325
|
+
log(
|
|
326
|
+
f" => Possible interpretation: n:{n.get_name()} v:{n.get_version()} r:{n.get_release()} a:{n.get_arch()}"
|
|
327
|
+
)
|
|
328
|
+
arch = n.get_arch()
|
|
329
|
+
if arch != "" and rpm.archscore(arch) > 0:
|
|
330
|
+
log(f" => Selected interpretation with arch: {arch}")
|
|
331
|
+
nevra = n
|
|
332
|
+
break
|
|
333
|
+
|
|
334
|
+
# if we found a nevra with a valid arch, use that arch
|
|
335
|
+
if nevra is not None:
|
|
336
|
+
arch = nevra.get_arch()
|
|
337
|
+
name = nevra.get_name()
|
|
338
|
+
if arch and provides_str.endswith(arch):
|
|
339
|
+
command["arch"] = nevra.get_arch()
|
|
340
|
+
# strip of ".<arch>" from the end of provides_str
|
|
341
|
+
provides_str = provides_str[: -(len(arch) + 1)]
|
|
342
|
+
|
|
343
|
+
# in order to get the behavior of "dnf install <blah>" we have to add
|
|
344
|
+
# '*' to the end in order to make stuff like "chef_rpm-1.2" work.
|
|
345
|
+
if not provides_str.endswith("*"):
|
|
346
|
+
provides_str += "*"
|
|
347
|
+
|
|
348
|
+
log(f" => provides_str after processing: {provides_str}")
|
|
349
|
+
log(f" => command after processing: {command}")
|
|
350
|
+
if command["action"] == "whatinstalled":
|
|
351
|
+
q.filter_installed()
|
|
352
|
+
|
|
353
|
+
if command["action"] == "whatavailable":
|
|
354
|
+
q.filter_available()
|
|
355
|
+
|
|
356
|
+
# Apply version filters
|
|
357
|
+
if "epoch" in command:
|
|
358
|
+
if "*" not in command["epoch"] and "?" not in command["epoch"]:
|
|
359
|
+
q.filter_epoch(int(command["epoch"]))
|
|
360
|
+
|
|
361
|
+
if "version" in command:
|
|
362
|
+
if "*" in command["version"] or "?" in command["version"]:
|
|
363
|
+
q.filter_version(command["version"], libdnf5.common.QueryCmp_GLOB)
|
|
364
|
+
else:
|
|
365
|
+
q.filter_version(command["version"])
|
|
366
|
+
|
|
367
|
+
if "release" in command:
|
|
368
|
+
if "*" in command["release"] or "?" in command["release"]:
|
|
369
|
+
q.filter_release(command["release"], libdnf5.common.QueryCmp_GLOB)
|
|
370
|
+
else:
|
|
371
|
+
q.filter_release(command["release"])
|
|
372
|
+
|
|
373
|
+
if "arch" in command:
|
|
374
|
+
if "*" in command["arch"] or "?" in command["arch"]:
|
|
375
|
+
q.filter_arch(command["arch"], libdnf5.common.QueryCmp_GLOB)
|
|
376
|
+
else:
|
|
377
|
+
q.filter_arch(command["arch"])
|
|
378
|
+
|
|
379
|
+
# now, we try by nevra search, and *IF* that returns nothing, then
|
|
380
|
+
# do a provides search. Combined with the work above to handle various
|
|
381
|
+
# name conventions, this gets is roughly compatible with the old
|
|
382
|
+
# dnf4 "subject" calls.
|
|
383
|
+
nevra_q = libdnf5.rpm.PackageQuery(q)
|
|
384
|
+
nevra_q.filter_nevra(provides_str, libdnf5.common.QueryCmp_GLOB)
|
|
385
|
+
if not nevra_q.empty():
|
|
386
|
+
q = nevra_q
|
|
387
|
+
else:
|
|
388
|
+
q.filter_provides(provides_str, libdnf5.common.QueryCmp_GLOB)
|
|
389
|
+
|
|
390
|
+
# Filter by architecture (prefer noarch and native arch)
|
|
391
|
+
# Get the system architecture from vars
|
|
392
|
+
detected_arch = base.get_vars().get_value("arch")
|
|
393
|
+
archq = libdnf5.rpm.PackageQuery(q)
|
|
394
|
+
archq.filter_arch(["noarch", detected_arch])
|
|
395
|
+
|
|
396
|
+
if not archq.empty():
|
|
397
|
+
q = archq
|
|
398
|
+
|
|
399
|
+
# Get latest packages
|
|
400
|
+
q.filter_latest_evr()
|
|
401
|
+
|
|
402
|
+
pkgs = list(q)
|
|
403
|
+
log(f" => pkgs from query: {pkgs}")
|
|
404
|
+
|
|
405
|
+
if not pkgs:
|
|
406
|
+
outpipe.write("{} nil nil\n".format(command["provides"].split().pop(0)))
|
|
145
407
|
outpipe.flush()
|
|
408
|
+
else:
|
|
409
|
+
# Sort and get the highest version
|
|
410
|
+
pkgs.sort(
|
|
411
|
+
key=lambda p: (p.get_epoch(), p.get_version(), p.get_release()),
|
|
412
|
+
reverse=True,
|
|
413
|
+
)
|
|
414
|
+
pkg = pkgs[0]
|
|
415
|
+
outpipe.write(
|
|
416
|
+
"{} {}:{}-{} {}\n".format(
|
|
417
|
+
pkg.get_name(),
|
|
418
|
+
pkg.get_epoch(),
|
|
419
|
+
pkg.get_version(),
|
|
420
|
+
pkg.get_release(),
|
|
421
|
+
pkg.get_arch(),
|
|
422
|
+
)
|
|
423
|
+
)
|
|
424
|
+
outpipe.flush()
|
|
425
|
+
|
|
426
|
+
|
|
427
|
+
def query(command):
|
|
428
|
+
if DNF_VERSION == 5:
|
|
429
|
+
query_dnf5(command)
|
|
430
|
+
else:
|
|
431
|
+
query_dnf4(command)
|
|
432
|
+
|
|
146
433
|
|
|
147
434
|
# the design of this helper is that it should try to be 'brittle' and fail hard and exit in order
|
|
148
435
|
# to keep process tables clean. additional error handling should probably be added to the retry loop
|
|
149
436
|
# on the ruby side.
|
|
150
437
|
def exit_handler(signal, frame):
|
|
151
|
-
if base is not None:
|
|
438
|
+
if DNF_VERSION == 4 and base is not None:
|
|
152
439
|
base.close()
|
|
153
440
|
sys.exit(0)
|
|
154
441
|
|
|
442
|
+
|
|
155
443
|
def setup_exit_handler():
|
|
156
444
|
signal.signal(signal.SIGINT, exit_handler)
|
|
157
445
|
signal.signal(signal.SIGHUP, exit_handler)
|
|
158
446
|
signal.signal(signal.SIGPIPE, exit_handler)
|
|
159
447
|
signal.signal(signal.SIGQUIT, exit_handler)
|
|
160
448
|
|
|
449
|
+
|
|
161
450
|
if len(sys.argv) < 3:
|
|
162
451
|
inpipe = sys.stdin
|
|
163
452
|
outpipe = sys.stdout
|
|
@@ -185,14 +474,15 @@ try:
|
|
|
185
474
|
except ValueError:
|
|
186
475
|
raise RuntimeError("bad json parse")
|
|
187
476
|
|
|
188
|
-
|
|
477
|
+
log(f"COMMAND: {command}")
|
|
478
|
+
if command["action"] == "whatinstalled":
|
|
189
479
|
query(command)
|
|
190
|
-
elif command[
|
|
480
|
+
elif command["action"] == "whatavailable":
|
|
191
481
|
query(command)
|
|
192
|
-
elif command[
|
|
193
|
-
versioncompare(command
|
|
482
|
+
elif command["action"] == "versioncompare":
|
|
483
|
+
versioncompare(command)
|
|
194
484
|
else:
|
|
195
485
|
raise RuntimeError("bad command")
|
|
196
486
|
finally:
|
|
197
|
-
if base is not None:
|
|
487
|
+
if DNF_VERSION == 4 and base is not None:
|
|
198
488
|
base.close()
|