golden hour
/opt/saltstack/salt/lib/python3.10/site-packages/salt/modules
⬆️ Go Up
Upload
File/Folder
Size
Actions
__init__.py
35 B
Del
OK
__pycache__
-
Del
OK
acme.py
12.74 KB
Del
OK
aix_group.py
4.12 KB
Del
OK
aix_shadow.py
1.93 KB
Del
OK
aixpkg.py
24.15 KB
Del
OK
aliases.py
5.07 KB
Del
OK
alternatives.py
5.71 KB
Del
OK
ansiblegate.py
19.51 KB
Del
OK
apache.py
12.47 KB
Del
OK
apcups.py
2.15 KB
Del
OK
apf.py
3.09 KB
Del
OK
apkpkg.py
16 KB
Del
OK
aptly.py
15.28 KB
Del
OK
aptpkg.py
116.64 KB
Del
OK
archive.py
48.63 KB
Del
OK
arista_pyeapi.py
22.06 KB
Del
OK
artifactory.py
24.78 KB
Del
OK
at.py
10.72 KB
Del
OK
at_solaris.py
8.51 KB
Del
OK
augeas_cfg.py
13.93 KB
Del
OK
aws_sqs.py
6.55 KB
Del
OK
azurearm_compute.py
20.8 KB
Del
OK
azurearm_dns.py
15.8 KB
Del
OK
azurearm_network.py
82.99 KB
Del
OK
azurearm_resource.py
35.75 KB
Del
OK
bamboohr.py
7.36 KB
Del
OK
baredoc.py
11.13 KB
Del
OK
bcache.py
28.97 KB
Del
OK
beacons.py
27.82 KB
Del
OK
bigip.py
69.11 KB
Del
OK
bluez_bluetooth.py
6.76 KB
Del
OK
boto3_elasticache.py
37.34 KB
Del
OK
boto3_elasticsearch.py
53.17 KB
Del
OK
boto3_route53.py
39.82 KB
Del
OK
boto3_sns.py
12.93 KB
Del
OK
boto_apigateway.py
61.86 KB
Del
OK
boto_asg.py
35.69 KB
Del
OK
boto_cfn.py
7.95 KB
Del
OK
boto_cloudfront.py
12.75 KB
Del
OK
boto_cloudtrail.py
14.45 KB
Del
OK
boto_cloudwatch.py
10.99 KB
Del
OK
boto_cloudwatch_event.py
9.48 KB
Del
OK
boto_cognitoidentity.py
14.63 KB
Del
OK
boto_datapipeline.py
6.94 KB
Del
OK
boto_dynamodb.py
14.98 KB
Del
OK
boto_ec2.py
79.27 KB
Del
OK
boto_efs.py
14.05 KB
Del
OK
boto_elasticache.py
23.69 KB
Del
OK
boto_elasticsearch_domain.py
15.85 KB
Del
OK
boto_elb.py
35.53 KB
Del
OK
boto_elbv2.py
10.78 KB
Del
OK
boto_iam.py
75.62 KB
Del
OK
boto_iot.py
26.2 KB
Del
OK
boto_kinesis.py
19.62 KB
Del
OK
boto_kms.py
17.29 KB
Del
OK
boto_lambda.py
35.05 KB
Del
OK
boto_rds.py
34.92 KB
Del
OK
boto_route53.py
32.55 KB
Del
OK
boto_s3.py
4.24 KB
Del
OK
boto_s3_bucket.py
31.8 KB
Del
OK
boto_secgroup.py
25.22 KB
Del
OK
boto_sns.py
7.22 KB
Del
OK
boto_sqs.py
6.43 KB
Del
OK
boto_ssm.py
3.63 KB
Del
OK
boto_vpc.py
113.08 KB
Del
OK
bower.py
5.85 KB
Del
OK
bridge.py
10.81 KB
Del
OK
bsd_shadow.py
6.25 KB
Del
OK
btrfs.py
33.66 KB
Del
OK
cabal.py
3.79 KB
Del
OK
capirca_acl.py
40.04 KB
Del
OK
cassandra_cql.py
54.16 KB
Del
OK
cassandra_mod.py
4.3 KB
Del
OK
celery.py
3.33 KB
Del
OK
ceph.py
15.82 KB
Del
OK
chassis.py
1.52 KB
Del
OK
chef.py
4.66 KB
Del
OK
chocolatey.py
41.55 KB
Del
OK
chronos.py
2.89 KB
Del
OK
chroot.py
11.73 KB
Del
OK
cimc.py
23.02 KB
Del
OK
ciscoconfparse_mod.py
14.79 KB
Del
OK
cisconso.py
3.83 KB
Del
OK
cloud.py
9.39 KB
Del
OK
cmdmod.py
163.73 KB
Del
OK
composer.py
10.31 KB
Del
OK
config.py
16.98 KB
Del
OK
consul.py
69.3 KB
Del
OK
container_resource.py
12.94 KB
Del
OK
cp.py
31.98 KB
Del
OK
cpan.py
5.54 KB
Del
OK
cron.py
28.09 KB
Del
OK
cryptdev.py
10.08 KB
Del
OK
csf.py
16.04 KB
Del
OK
cyg.py
8.32 KB
Del
OK
daemontools.py
5.41 KB
Del
OK
data.py
3.85 KB
Del
OK
datadog_api.py
7.76 KB
Del
OK
ddns.py
7.12 KB
Del
OK
deb_apache.py
7.41 KB
Del
OK
deb_postgres.py
4.18 KB
Del
OK
debconfmod.py
4.06 KB
Del
OK
debian_ip.py
64.91 KB
Del
OK
debian_service.py
6.55 KB
Del
OK
debuild_pkgbuild.py
34.68 KB
Del
OK
defaults.py
6.55 KB
Del
OK
devinfo.py
9.07 KB
Del
OK
devmap.py
627 B
Del
OK
dig.py
8.75 KB
Del
OK
disk.py
30.82 KB
Del
OK
djangomod.py
7.53 KB
Del
OK
dnsmasq.py
5.71 KB
Del
OK
dnsutil.py
11.51 KB
Del
OK
dockercompose.py
32.62 KB
Del
OK
dockermod.py
224.85 KB
Del
OK
dpkg_lowpkg.py
12.94 KB
Del
OK
drac.py
10.97 KB
Del
OK
dracr.py
38.53 KB
Del
OK
drbd.py
7.19 KB
Del
OK
dummyproxy_pkg.py
2.46 KB
Del
OK
dummyproxy_service.py
2.91 KB
Del
OK
ebuildpkg.py
38.74 KB
Del
OK
eix.py
1.58 KB
Del
OK
elasticsearch.py
51.44 KB
Del
OK
environ.py
8.96 KB
Del
OK
eselect.py
4.99 KB
Del
OK
esxcluster.py
1.66 KB
Del
OK
esxdatacenter.py
1.68 KB
Del
OK
esxi.py
2.79 KB
Del
OK
esxvm.py
1.63 KB
Del
OK
etcd_mod.py
8.56 KB
Del
OK
ethtool.py
11.12 KB
Del
OK
event.py
7.67 KB
Del
OK
extfs.py
8.78 KB
Del
OK
file.py
232.18 KB
Del
OK
firewalld.py
20.51 KB
Del
OK
freebsd_sysctl.py
4.99 KB
Del
OK
freebsd_update.py
6.19 KB
Del
OK
freebsdjail.py
7.16 KB
Del
OK
freebsdkmod.py
6.17 KB
Del
OK
freebsdpkg.py
17.04 KB
Del
OK
freebsdports.py
13.13 KB
Del
OK
freebsdservice.py
12.53 KB
Del
OK
freezer.py
10.2 KB
Del
OK
gcp_addon.py
4.07 KB
Del
OK
gem.py
10.6 KB
Del
OK
genesis.py
21.75 KB
Del
OK
gentoo_service.py
9.18 KB
Del
OK
gentoolkitmod.py
8.39 KB
Del
OK
git.py
172.01 KB
Del
OK
github.py
53.19 KB
Del
OK
glanceng.py
4.69 KB
Del
OK
glassfish.py
19.49 KB
Del
OK
glusterfs.py
19.55 KB
Del
OK
gnomedesktop.py
6.85 KB
Del
OK
google_chat.py
1.52 KB
Del
OK
gpg.py
39.09 KB
Del
OK
grafana4.py
30.27 KB
Del
OK
grains.py
21.81 KB
Del
OK
groupadd.py
11.87 KB
Del
OK
grub_legacy.py
3.08 KB
Del
OK
guestfs.py
2.37 KB
Del
OK
hadoop.py
3.76 KB
Del
OK
haproxyconn.py
10.17 KB
Del
OK
hashutil.py
6.77 KB
Del
OK
heat.py
25.25 KB
Del
OK
helm.py
39.27 KB
Del
OK
hg.py
7.16 KB
Del
OK
highstate_doc.py
22.76 KB
Del
OK
hosts.py
10.47 KB
Del
OK
http.py
3.76 KB
Del
OK
icinga2.py
4.46 KB
Del
OK
idem.py
1.75 KB
Del
OK
ifttt.py
2.28 KB
Del
OK
ilo.py
15.98 KB
Del
OK
incron.py
7.68 KB
Del
OK
influxdb08mod.py
15.07 KB
Del
OK
influxdbmod.py
16.13 KB
Del
OK
infoblox.py
17.53 KB
Del
OK
ini_manage.py
14.63 KB
Del
OK
inspectlib
-
Del
OK
inspector.py
8.19 KB
Del
OK
introspect.py
4.02 KB
Del
OK
iosconfig.py
14.78 KB
Del
OK
ipmi.py
25.47 KB
Del
OK
ipset.py
17.97 KB
Del
OK
iptables.py
57.44 KB
Del
OK
iwtools.py
3.99 KB
Del
OK
jboss7.py
20.51 KB
Del
OK
jboss7_cli.py
15.23 KB
Del
OK
jenkinsmod.py
11.9 KB
Del
OK
jinja.py
2.66 KB
Del
OK
jira_mod.py
7.07 KB
Del
OK
junos.py
73.96 KB
Del
OK
k8s.py
24.87 KB
Del
OK
kapacitor.py
5.37 KB
Del
OK
kerberos.py
5.42 KB
Del
OK
kernelpkg_linux_apt.py
6.71 KB
Del
OK
kernelpkg_linux_yum.py
7.26 KB
Del
OK
key.py
1007 B
Del
OK
keyboard.py
2.64 KB
Del
OK
keystone.py
43.14 KB
Del
OK
keystoneng.py
21.82 KB
Del
OK
keystore.py
7.18 KB
Del
OK
kmod.py
7.65 KB
Del
OK
kubeadm.py
34.64 KB
Del
OK
kubernetesmod.py
46.77 KB
Del
OK
launchctl_service.py
9.69 KB
Del
OK
layman.py
4.22 KB
Del
OK
ldap3.py
18.81 KB
Del
OK
ldapmod.py
5.9 KB
Del
OK
libcloud_compute.py
23.48 KB
Del
OK
libcloud_dns.py
9.73 KB
Del
OK
libcloud_loadbalancer.py
13.14 KB
Del
OK
libcloud_storage.py
12.16 KB
Del
OK
linux_acl.py
7.7 KB
Del
OK
linux_ip.py
5.55 KB
Del
OK
linux_lvm.py
17.86 KB
Del
OK
linux_service.py
4.64 KB
Del
OK
linux_shadow.py
12.96 KB
Del
OK
linux_sysctl.py
7.5 KB
Del
OK
localemod.py
11.84 KB
Del
OK
locate.py
2.58 KB
Del
OK
logadm.py
9.44 KB
Del
OK
logmod.py
1.25 KB
Del
OK
logrotate.py
7.72 KB
Del
OK
lvs.py
11.54 KB
Del
OK
lxc.py
147.27 KB
Del
OK
lxd.py
90.07 KB
Del
OK
mac_assistive.py
11.37 KB
Del
OK
mac_brew_pkg.py
19.91 KB
Del
OK
mac_desktop.py
2.77 KB
Del
OK
mac_group.py
6.62 KB
Del
OK
mac_keychain.py
6.39 KB
Del
OK
mac_pkgutil.py
2.84 KB
Del
OK
mac_portspkg.py
11.36 KB
Del
OK
mac_power.py
13.29 KB
Del
OK
mac_service.py
19.64 KB
Del
OK
mac_shadow.py
14.23 KB
Del
OK
mac_softwareupdate.py
14.52 KB
Del
OK
mac_sysctl.py
5.13 KB
Del
OK
mac_system.py
15.07 KB
Del
OK
mac_timezone.py
8.34 KB
Del
OK
mac_user.py
16.41 KB
Del
OK
mac_xattr.py
6.27 KB
Del
OK
macdefaults.py
2.33 KB
Del
OK
macpackage.py
6.66 KB
Del
OK
makeconf.py
17.31 KB
Del
OK
mandrill.py
6.31 KB
Del
OK
marathon.py
5.36 KB
Del
OK
match.py
13 KB
Del
OK
mattermost.py
3.4 KB
Del
OK
mdadm_raid.py
9.86 KB
Del
OK
mdata.py
3.38 KB
Del
OK
memcached.py
6.13 KB
Del
OK
mine.py
18.84 KB
Del
OK
minion.py
7.68 KB
Del
OK
mod_random.py
7.18 KB
Del
OK
modjk.py
12.48 KB
Del
OK
mongodb.py
29.75 KB
Del
OK
monit.py
5.51 KB
Del
OK
moosefs.py
3.87 KB
Del
OK
mount.py
58.44 KB
Del
OK
mssql.py
14.64 KB
Del
OK
msteams.py
2.11 KB
Del
OK
munin.py
2.4 KB
Del
OK
mysql.py
90.66 KB
Del
OK
nacl.py
9.72 KB
Del
OK
nagios.py
6.53 KB
Del
OK
nagios_rpc.py
5.09 KB
Del
OK
namecheap_domains.py
12.84 KB
Del
OK
namecheap_domains_dns.py
5.93 KB
Del
OK
namecheap_domains_ns.py
4.51 KB
Del
OK
namecheap_ssl.py
25.69 KB
Del
OK
namecheap_users.py
2.4 KB
Del
OK
napalm_bgp.py
9.72 KB
Del
OK
napalm_formula.py
11.33 KB
Del
OK
napalm_mod.py
61.37 KB
Del
OK
napalm_netacl.py
28.59 KB
Del
OK
napalm_network.py
93.22 KB
Del
OK
napalm_ntp.py
10.22 KB
Del
OK
napalm_probes.py
13.25 KB
Del
OK
napalm_route.py
5.09 KB
Del
OK
napalm_snmp.py
7.05 KB
Del
OK
napalm_users.py
6.49 KB
Del
OK
napalm_yang_mod.py
20.28 KB
Del
OK
netaddress.py
1.6 KB
Del
OK
netbox.py
32.22 KB
Del
OK
netbsd_sysctl.py
3.92 KB
Del
OK
netbsdservice.py
6.43 KB
Del
OK
netmiko_mod.py
19.61 KB
Del
OK
netscaler.py
27.02 KB
Del
OK
network.py
63.42 KB
Del
OK
neutron.py
44.92 KB
Del
OK
neutronng.py
15.02 KB
Del
OK
nexus.py
22.95 KB
Del
OK
nfs3.py
3.9 KB
Del
OK
nftables.py
33.58 KB
Del
OK
nginx.py
3.83 KB
Del
OK
nilrt_ip.py
36.18 KB
Del
OK
nix.py
8.03 KB
Del
OK
nova.py
19.6 KB
Del
OK
npm.py
10.4 KB
Del
OK
nspawn.py
41.35 KB
Del
OK
nxos.py
24.65 KB
Del
OK
nxos_api.py
14.72 KB
Del
OK
nxos_upgrade.py
14.74 KB
Del
OK
omapi.py
3.6 KB
Del
OK
openbsd_sysctl.py
3.74 KB
Del
OK
openbsdpkg.py
10.97 KB
Del
OK
openbsdrcctl_service.py
6.33 KB
Del
OK
openbsdservice.py
8.31 KB
Del
OK
openscap.py
2.81 KB
Del
OK
openstack_config.py
3.21 KB
Del
OK
openstack_mng.py
2.71 KB
Del
OK
openvswitch.py
17.19 KB
Del
OK
opkg.py
49.67 KB
Del
OK
opsgenie.py
3.29 KB
Del
OK
oracle.py
5.82 KB
Del
OK
osquery.py
24.93 KB
Del
OK
out.py
2.53 KB
Del
OK
pacmanpkg.py
31.92 KB
Del
OK
pagerduty.py
4.7 KB
Del
OK
pagerduty_util.py
13.48 KB
Del
OK
pam.py
2.01 KB
Del
OK
panos.py
61.05 KB
Del
OK
parallels.py
19.85 KB
Del
OK
parted_partition.py
21.53 KB
Del
OK
pcs.py
14.11 KB
Del
OK
pdbedit.py
10.79 KB
Del
OK
pecl.py
3.79 KB
Del
OK
peeringdb.py
8.39 KB
Del
OK
pf.py
9.51 KB
Del
OK
philips_hue.py
1.55 KB
Del
OK
pillar.py
21.37 KB
Del
OK
pip.py
53.42 KB
Del
OK
pkg_resource.py
12.3 KB
Del
OK
pkgin.py
17.29 KB
Del
OK
pkgng.py
61.07 KB
Del
OK
pkgutil.py
9.85 KB
Del
OK
portage_config.py
22.73 KB
Del
OK
postfix.py
16.24 KB
Del
OK
postgres.py
88.24 KB
Del
OK
poudriere.py
7.85 KB
Del
OK
powerpath.py
2.57 KB
Del
OK
proxy.py
11.49 KB
Del
OK
ps.py
20.89 KB
Del
OK
publish.py
10.25 KB
Del
OK
puppet.py
10.9 KB
Del
OK
purefa.py
33.59 KB
Del
OK
purefb.py
13.69 KB
Del
OK
pushbullet.py
1.88 KB
Del
OK
pushover_notify.py
3.48 KB
Del
OK
pw_group.py
4.62 KB
Del
OK
pw_user.py
12.47 KB
Del
OK
pyenv.py
6.93 KB
Del
OK
qemu_img.py
1.53 KB
Del
OK
qemu_nbd.py
3.28 KB
Del
OK
quota.py
6.43 KB
Del
OK
rabbitmq.py
38.4 KB
Del
OK
rallydev.py
6.09 KB
Del
OK
random_org.py
23.76 KB
Del
OK
rbac_solaris.py
16.05 KB
Del
OK
rbenv.py
10.75 KB
Del
OK
rdp.py
6.08 KB
Del
OK
rebootmgr.py
7.68 KB
Del
OK
redismod.py
16.36 KB
Del
OK
reg.py
16.36 KB
Del
OK
rest_pkg.py
2.26 KB
Del
OK
rest_sample_utils.py
558 B
Del
OK
rest_service.py
3.63 KB
Del
OK
restartcheck.py
24.1 KB
Del
OK
restconf.py
3.15 KB
Del
OK
ret.py
1.27 KB
Del
OK
rh_ip.py
38.55 KB
Del
OK
rh_service.py
16.61 KB
Del
OK
riak.py
5.19 KB
Del
OK
rpm_lowpkg.py
27.67 KB
Del
OK
rpmbuild_pkgbuild.py
24.53 KB
Del
OK
rsync.py
8.04 KB
Del
OK
runit.py
17.17 KB
Del
OK
rvm.py
11.1 KB
Del
OK
s3.py
9.93 KB
Del
OK
s6.py
3.62 KB
Del
OK
salt_proxy.py
4.48 KB
Del
OK
salt_version.py
4.58 KB
Del
OK
saltcheck.py
46.66 KB
Del
OK
saltcloudmod.py
954 B
Del
OK
saltutil.py
57.49 KB
Del
OK
schedule.py
50.81 KB
Del
OK
scp_mod.py
6.22 KB
Del
OK
scsi.py
2.66 KB
Del
OK
sdb.py
2.48 KB
Del
OK
seed.py
8.87 KB
Del
OK
selinux.py
24.2 KB
Del
OK
sensehat.py
7.79 KB
Del
OK
sensors.py
1.3 KB
Del
OK
serverdensity_device.py
8.1 KB
Del
OK
servicenow.py
4.36 KB
Del
OK
slack_notify.py
7.83 KB
Del
OK
slackware_service.py
6.84 KB
Del
OK
slsutil.py
19.04 KB
Del
OK
smartos_imgadm.py
12.04 KB
Del
OK
smartos_nictagadm.py
6.46 KB
Del
OK
smartos_virt.py
5.21 KB
Del
OK
smartos_vmadm.py
26.2 KB
Del
OK
smbios.py
10.05 KB
Del
OK
smf_service.py
8.52 KB
Del
OK
smtp.py
5.41 KB
Del
OK
snapper.py
27.14 KB
Del
OK
solaris_fmadm.py
11.2 KB
Del
OK
solaris_group.py
2.8 KB
Del
OK
solaris_shadow.py
7.98 KB
Del
OK
solaris_system.py
3.72 KB
Del
OK
solaris_user.py
11.06 KB
Del
OK
solarisipspkg.py
18.68 KB
Del
OK
solarispkg.py
15.4 KB
Del
OK
solr.py
45.54 KB
Del
OK
solrcloud.py
14.63 KB
Del
OK
splunk.py
8.14 KB
Del
OK
splunk_search.py
8.76 KB
Del
OK
sqlite3.py
2.54 KB
Del
OK
ssh.py
43.89 KB
Del
OK
ssh_pkg.py
1.08 KB
Del
OK
ssh_service.py
3.39 KB
Del
OK
state.py
82.34 KB
Del
OK
status.py
57.79 KB
Del
OK
statuspage.py
14.67 KB
Del
OK
supervisord.py
11.15 KB
Del
OK
suse_apache.py
2.45 KB
Del
OK
suse_ip.py
35.72 KB
Del
OK
svn.py
10.75 KB
Del
OK
swarm.py
13.5 KB
Del
OK
swift.py
5.53 KB
Del
OK
sysbench.py
6.62 KB
Del
OK
sysfs.py
6.61 KB
Del
OK
syslog_ng.py
31.52 KB
Del
OK
sysmod.py
22.59 KB
Del
OK
sysrc.py
3.38 KB
Del
OK
system.py
19.28 KB
Del
OK
system_profiler.py
3.54 KB
Del
OK
systemd_service.py
46.29 KB
Del
OK
telegram.py
3.28 KB
Del
OK
telemetry.py
12.87 KB
Del
OK
temp.py
831 B
Del
OK
test.py
15.4 KB
Del
OK
test_virtual.py
237 B
Del
OK
testinframod.py
9.92 KB
Del
OK
textfsm_mod.py
16.22 KB
Del
OK
timezone.py
19.98 KB
Del
OK
tls.py
58.63 KB
Del
OK
tomcat.py
18.59 KB
Del
OK
trafficserver.py
10.44 KB
Del
OK
transactional_update.py
35.83 KB
Del
OK
travisci.py
2.05 KB
Del
OK
tuned.py
2.34 KB
Del
OK
twilio_notify.py
2.95 KB
Del
OK
udev.py
3.72 KB
Del
OK
upstart_service.py
16.92 KB
Del
OK
uptime.py
3.23 KB
Del
OK
useradd.py
22.63 KB
Del
OK
uwsgi.py
996 B
Del
OK
vagrant.py
20.4 KB
Del
OK
varnish.py
3.08 KB
Del
OK
vault.py
15.61 KB
Del
OK
vbox_guest.py
10.55 KB
Del
OK
vboxmanage.py
14.71 KB
Del
OK
vcenter.py
1.61 KB
Del
OK
victorops.py
6.54 KB
Del
OK
virt.py
287.71 KB
Del
OK
virtualenv_mod.py
15.09 KB
Del
OK
vmctl.py
9.56 KB
Del
OK
vsphere.py
380.41 KB
Del
OK
webutil.py
3.66 KB
Del
OK
win_auditpol.py
4.74 KB
Del
OK
win_autoruns.py
2.29 KB
Del
OK
win_certutil.py
4.55 KB
Del
OK
win_dacl.py
32.27 KB
Del
OK
win_disk.py
1.8 KB
Del
OK
win_dism.py
20.7 KB
Del
OK
win_dns_client.py
4.19 KB
Del
OK
win_dsc.py
27.54 KB
Del
OK
win_event.py
22.32 KB
Del
OK
win_file.py
64.39 KB
Del
OK
win_firewall.py
20.15 KB
Del
OK
win_groupadd.py
11.27 KB
Del
OK
win_iis.py
68.78 KB
Del
OK
win_ip.py
11.43 KB
Del
OK
win_lgpo.py
491.76 KB
Del
OK
win_lgpo_reg.py
17.9 KB
Del
OK
win_license.py
2.72 KB
Del
OK
win_network.py
13.9 KB
Del
OK
win_ntp.py
1.8 KB
Del
OK
win_path.py
11.12 KB
Del
OK
win_pkg.py
86.43 KB
Del
OK
win_pki.py
15.8 KB
Del
OK
win_powercfg.py
9.85 KB
Del
OK
win_psget.py
8.97 KB
Del
OK
win_servermanager.py
14.21 KB
Del
OK
win_service.py
32.96 KB
Del
OK
win_shadow.py
3.03 KB
Del
OK
win_shortcut.py
16.49 KB
Del
OK
win_smtp_server.py
17.67 KB
Del
OK
win_snmp.py
13.38 KB
Del
OK
win_status.py
16.94 KB
Del
OK
win_system.py
40.61 KB
Del
OK
win_task.py
79.17 KB
Del
OK
win_timezone.py
13.3 KB
Del
OK
win_useradd.py
27.39 KB
Del
OK
win_wua.py
38.29 KB
Del
OK
win_wusa.py
5.88 KB
Del
OK
winrepo.py
6.09 KB
Del
OK
wordpress.py
4.71 KB
Del
OK
x509.py
63.1 KB
Del
OK
x509_v2.py
74.15 KB
Del
OK
xapi_virt.py
24.07 KB
Del
OK
xbpspkg.py
15.84 KB
Del
OK
xfs.py
15.35 KB
Del
OK
xml.py
2.14 KB
Del
OK
xmpp.py
5.28 KB
Del
OK
yaml.py
1.94 KB
Del
OK
yumpkg.py
116.5 KB
Del
OK
zabbix.py
97.55 KB
Del
OK
zcbuildout.py
28.16 KB
Del
OK
zenoss.py
5.64 KB
Del
OK
zfs.py
34.44 KB
Del
OK
zk_concurrency.py
11.16 KB
Del
OK
znc.py
2.26 KB
Del
OK
zoneadm.py
15.05 KB
Del
OK
zonecfg.py
21.85 KB
Del
OK
zookeeper.py
14.72 KB
Del
OK
zpool.py
44.02 KB
Del
OK
zypperpkg.py
94.87 KB
Del
OK
Edit: aptpkg.py
""" Support for APT (Advanced Packaging Tool) .. important:: If you feel that Salt should be using this module to manage packages on a minion, and it is using a different module (or gives an error similar to *'pkg.install' is not available*), see :ref:`here <module-provider-override>`. For repository management, the ``python-apt`` package must be installed. """ import copy import datetime import fnmatch import logging import os import pathlib import re import shutil import tempfile import time from urllib.error import HTTPError from urllib.request import Request as _Request from urllib.request import urlopen as _urlopen import salt.config import salt.syspaths import salt.utils.args import salt.utils.data import salt.utils.environment import salt.utils.files import salt.utils.functools import salt.utils.itertools import salt.utils.json import salt.utils.path import salt.utils.pkg import salt.utils.pkg.deb import salt.utils.stringutils import salt.utils.systemd import salt.utils.versions import salt.utils.yaml from salt.exceptions import ( CommandExecutionError, CommandNotFoundError, MinionError, SaltInvocationError, ) from salt.modules.cmdmod import _parse_env from salt.utils.versions import warn_until_date log = logging.getLogger(__name__) # pylint: disable=import-error try: from aptsources.sourceslist import SourceEntry, SourcesList HAS_APT = True except ImportError: HAS_APT = False try: import apt_pkg HAS_APTPKG = True except ImportError: HAS_APTPKG = False try: import softwareproperties.ppa HAS_SOFTWAREPROPERTIES = True except ImportError: HAS_SOFTWAREPROPERTIES = False # pylint: enable=import-error APT_LISTS_PATH = "/var/lib/apt/lists" PKG_ARCH_SEPARATOR = ":" # Source format for urllib fallback on PPA handling LP_SRC_FORMAT = "deb http://ppa.launchpad.net/{0}/{1}/ubuntu {2} main" LP_PVT_SRC_FORMAT = "deb https://{0}private-ppa.launchpad.net/{1}/{2}/ubuntu {3} main" _MODIFY_OK = frozenset( ["uri", "comps", "architectures", "disabled", "file", "dist", "signedby"] ) DPKG_ENV_VARS = { "APT_LISTBUGS_FRONTEND": "none", "APT_LISTCHANGES_FRONTEND": "none", "DEBIAN_FRONTEND": "noninteractive", "UCF_FORCE_CONFFOLD": "1", } # Define the module's virtual name __virtualname__ = "pkg" def __virtual__(): """ Confirm this module is on a Debian-based system """ # If your minion is running an OS which is Debian-based but does not have # an "os_family" grain of Debian, then the proper fix is NOT to check for # the minion's "os_family" grain here in the __virtual__. The correct fix # is to add the value from the minion's "os" grain to the _OS_FAMILY_MAP # dict in salt/grains/core.py, so that we assign the correct "os_family" # grain to the minion. if __grains__.get("os_family") == "Debian": return __virtualname__ return False, "The pkg module could not be loaded: unsupported OS family" def __init__(opts): """ For Debian and derivative systems, set up a few env variables to keep apt happy and non-interactive. """ if __virtual__() == __virtualname__: # Export these puppies so they persist os.environ.update(DPKG_ENV_VARS) def _invalid(line): """ This is a workaround since python3-apt does not support the signed-by argument. This function was removed from the class to ensure users using the python3-apt module or not can use the signed-by option. """ disabled = False invalid = False comment = "" line = line.strip() if not line: invalid = True return disabled, invalid, comment, "" if line.startswith("#"): disabled = True line = line[1:] idx = line.find("#") if idx > 0: comment = line[idx + 1 :] line = line[:idx] cdrom_match = re.match(r"(.*)(cdrom:.*/)(.*)", line.strip()) if cdrom_match: repo_line = ( [p.strip() for p in cdrom_match.group(1).split()] + [cdrom_match.group(2).strip()] + [p.strip() for p in cdrom_match.group(3).split()] ) else: repo_line = line.strip().split() if ( not repo_line or repo_line[0] not in ["deb", "deb-src", "rpm", "rpm-src"] or len(repo_line) < 3 ): invalid = True return disabled, invalid, comment, repo_line if repo_line[1].startswith("["): if not any(x.endswith("]") for x in repo_line[1:]): invalid = True return disabled, invalid, comment, repo_line return disabled, invalid, comment, repo_line if not HAS_APT: class SourceEntry: # pylint: disable=function-redefined def __init__(self, line, file=None): self.invalid = False self.comps = [] self.disabled = False self.comment = "" self.dist = "" self.type = "" self.uri = "" self.line = line self.architectures = [] self.signedby = "" self.file = file if not self.file: self.file = str(pathlib.Path(os.sep, "etc", "apt", "sources.list")) self._parse_sources(line) def str(self): return self.repo_line() def repo_line(self): """ Return the repo line for the sources file """ repo_line = [] if self.invalid: return self.line if self.disabled: repo_line.append("#") repo_line.append(self.type) opts = _get_opts(self.line) if self.architectures: archs = ",".join(self.architectures) opts["arch"]["full"] = "arch={}".format(archs) opts["arch"]["value"] = self.architectures if self.signedby: opts["signedby"]["full"] = "signed-by={}".format(self.signedby) opts["signedby"]["value"] = self.signedby ordered_opts = [ opt_type for opt_type, opt in opts.items() if opt["full"] != "" ] for opt in opts.values(): if opt["full"] != "": ordered_opts[opt["index"]] = opt["full"] if ordered_opts: repo_line.append("[{}]".format(" ".join(ordered_opts))) repo_line += [self.uri, self.dist, " ".join(self.comps)] if self.comment: repo_line.append("#{}".format(self.comment)) return " ".join(repo_line) + "\n" def _parse_sources(self, line): """ Parse lines from sources files """ self.disabled, self.invalid, self.comment, repo_line = _invalid(line) if self.invalid: return False if repo_line[1].startswith("["): repo_line = [x for x in (line.strip("[]") for line in repo_line) if x] opts = _get_opts(self.line) self.architectures.extend(opts["arch"]["value"]) self.signedby = opts["signedby"]["value"] for opt in opts.keys(): opt = opts[opt]["full"] if opt: try: repo_line.pop(repo_line.index(opt)) except ValueError: repo_line.pop(repo_line.index("[" + opt + "]")) self.type = repo_line[0] self.uri = repo_line[1] self.dist = repo_line[2] self.comps = repo_line[3:] return True class SourcesList: # pylint: disable=function-redefined def __init__(self): self.list = [] self.files = [ pathlib.Path(os.sep, "etc", "apt", "sources.list"), pathlib.Path(os.sep, "etc", "apt", "sources.list.d"), ] for file in self.files: if file.is_dir(): for fp in file.glob("**/*.list"): self.add_file(file=fp) else: self.add_file(file) def __iter__(self): yield from self.list def add_file(self, file): """ Add the lines of a file to self.list """ if file.is_file(): with salt.utils.files.fopen(str(file)) as source: for line in source: self.list.append(SourceEntry(line, file=str(file))) else: log.debug("The apt sources file %s does not exist", file) def add(self, type, uri, dist, orig_comps, architectures, signedby): opts_count = [] opts_line = "" if architectures: architectures = "arch={}".format(",".join(architectures)) opts_count.append(architectures) if signedby: signedby = "signed-by={}".format(signedby) opts_count.append(signedby) if len(opts_count) > 1: opts_line = "[" + " ".join(opts_count) + "]" elif len(opts_count) == 1: opts_line = "[" + "".join(opts_count) + "]" repo_line = [ type, opts_line, uri, dist, " ".join(orig_comps), ] return SourceEntry(" ".join([line for line in repo_line if line.strip()])) def remove(self, source): """ remove a source from the list of sources """ self.list.remove(source) def save(self): """ write all of the sources from the list of sources to the file. """ filemap = {} with tempfile.TemporaryDirectory() as tmpdir: for source in self.list: fname = pathlib.Path(tmpdir, pathlib.Path(source.file).name) with salt.utils.files.fopen(str(fname), "a") as fp: fp.write(source.repo_line()) if source.file not in filemap: filemap[source.file] = {"tmp": fname} for fp in filemap: shutil.move(str(filemap[fp]["tmp"]), fp) def _get_ppa_info_from_launchpad(owner_name, ppa_name): """ Idea from softwareproperties.ppa. Uses urllib2 which sacrifices server cert verification. This is used as fall-back code or for secure PPAs :param owner_name: :param ppa_name: :return: """ lp_url = "https://launchpad.net/api/1.0/~{}/+archive/{}".format( owner_name, ppa_name ) request = _Request(lp_url, headers={"Accept": "application/json"}) lp_page = _urlopen(request) return salt.utils.json.load(lp_page) def _reconstruct_ppa_name(owner_name, ppa_name): """ Stringify PPA name from args. """ return "ppa:{}/{}".format(owner_name, ppa_name) def _call_apt(args, scope=True, **kwargs): """ Call apt* utilities. """ cmd = [] if ( scope and salt.utils.systemd.has_scope(__context__) and __salt__["config.get"]("systemd.scope", True) ): cmd.extend(["systemd-run", "--scope", "--description", '"{}"'.format(__name__)]) cmd.extend(args) params = { "output_loglevel": "trace", "python_shell": False, "env": salt.utils.environment.get_module_environment(globals()), } params.update(kwargs) cmd_ret = __salt__["cmd.run_all"](cmd, **params) count = 0 while "Could not get lock" in cmd_ret.get("stderr", "") and count < 10: count += 1 log.warning("Waiting for dpkg lock release: retrying... %s/100", count) time.sleep(2**count) cmd_ret = __salt__["cmd.run_all"](cmd, **params) return cmd_ret def _warn_software_properties(repo): """ Warn of missing python-software-properties package. """ log.warning( "The 'python-software-properties' package is not installed. " "For more accurate support of PPA repositories, you should " "install this package." ) log.warning("Best guess at ppa format: %s", repo) def normalize_name(name): """ Strips the architecture from the specified package name, if necessary. CLI Example: .. code-block:: bash salt '*' pkg.normalize_name zsh:amd64 """ try: pkgname, pkgarch = name.rsplit(PKG_ARCH_SEPARATOR, 1) except ValueError: pkgname = name pkgarch = __grains__["osarch"] return pkgname if pkgarch in (__grains__["osarch"], "all", "any") else name def parse_arch(name): """ Parse name and architecture from the specified package name. CLI Example: .. code-block:: bash salt '*' pkg.parse_arch zsh:amd64 """ try: _name, _arch = name.rsplit(PKG_ARCH_SEPARATOR, 1) except ValueError: _name, _arch = name, None return {"name": _name, "arch": _arch} def latest_version(*names, **kwargs): """ Return the latest version of the named package available for upgrade or installation. If more than one package name is specified, a dict of name/version pairs is returned. If the latest version of a given package is already installed, an empty string will be returned for that package. A specific repo can be requested using the ``fromrepo`` keyword argument. cache_valid_time .. versionadded:: 2016.11.0 Skip refreshing the package database if refresh has already occurred within <value> seconds CLI Example: .. code-block:: bash salt '*' pkg.latest_version <package name> salt '*' pkg.latest_version <package name> fromrepo=unstable salt '*' pkg.latest_version <package1> <package2> <package3> ... """ refresh = salt.utils.data.is_true(kwargs.pop("refresh", True)) show_installed = salt.utils.data.is_true(kwargs.pop("show_installed", False)) if "repo" in kwargs: raise SaltInvocationError( "The 'repo' argument is invalid, use 'fromrepo' instead" ) fromrepo = kwargs.pop("fromrepo", None) cache_valid_time = kwargs.pop("cache_valid_time", 0) if not names: return "" ret = {} # Initialize the dict with empty strings for name in names: ret[name] = "" pkgs = list_pkgs(versions_as_list=True) repo = ["-o", "APT::Default-Release={}".format(fromrepo)] if fromrepo else None # Refresh before looking for the latest version available if refresh: refresh_db(cache_valid_time) for name in names: cmd = ["apt-cache", "-q", "policy", name] if repo is not None: cmd.extend(repo) out = _call_apt(cmd, scope=False) candidate = "" for line in salt.utils.itertools.split(out["stdout"], "\n"): if "Candidate" in line: comps = line.split() if len(comps) >= 2: candidate = comps[-1] if candidate.lower() == "(none)": candidate = "" break installed = pkgs.get(name, []) if not installed: ret[name] = candidate elif installed and show_installed: ret[name] = candidate elif candidate: # If there are no installed versions that are greater than or equal # to the install candidate, then the candidate is an upgrade, so # add it to the return dict if not any( salt.utils.versions.compare( ver1=x, oper=">=", ver2=candidate, cmp_func=version_cmp ) for x in installed ): ret[name] = candidate # Return a string if only one package name passed if len(names) == 1: return ret[names[0]] return ret # available_version is being deprecated available_version = salt.utils.functools.alias_function( latest_version, "available_version" ) def version(*names, **kwargs): """ Returns a string representing the package version or an empty string if not installed. If more than one package name is specified, a dict of name/version pairs is returned. CLI Example: .. code-block:: bash salt '*' pkg.version <package name> salt '*' pkg.version <package1> <package2> <package3> ... """ return __salt__["pkg_resource.version"](*names, **kwargs) def refresh_db(cache_valid_time=0, failhard=False, **kwargs): """ Updates the APT database to latest packages based upon repositories Returns a dict, with the keys being package databases and the values being the result of the update attempt. Values can be one of the following: - ``True``: Database updated successfully - ``False``: Problem updating database - ``None``: Database already up-to-date cache_valid_time .. versionadded:: 2016.11.0 Skip refreshing the package database if refresh has already occurred within <value> seconds failhard If False, return results of Err lines as ``False`` for the package database that encountered the error. If True, raise an error with a list of the package databases that encountered errors. CLI Example: .. code-block:: bash salt '*' pkg.refresh_db """ # Remove rtag file to keep multiple refreshes from happening in pkg states salt.utils.pkg.clear_rtag(__opts__) failhard = salt.utils.data.is_true(failhard) ret = {} error_repos = list() if cache_valid_time: try: latest_update = os.stat(APT_LISTS_PATH).st_mtime now = time.time() log.debug( "now: %s, last update time: %s, expire after: %s seconds", now, latest_update, cache_valid_time, ) if latest_update + cache_valid_time > now: return ret except TypeError as exp: log.warning( "expected integer for cache_valid_time parameter, failed with: %s", exp ) except OSError as exp: log.warning("could not stat cache directory due to: %s", exp) call = _call_apt(["apt-get", "-q", "update"], scope=False) if call["retcode"] != 0: comment = "" if "stderr" in call: comment += call["stderr"] raise CommandExecutionError(comment) else: out = call["stdout"] for line in out.splitlines(): cols = line.split() if not cols: continue ident = " ".join(cols[1:]) if "Get" in cols[0]: # Strip filesize from end of line ident = re.sub(r" \[.+B\]$", "", ident) ret[ident] = True elif "Ign" in cols[0]: ret[ident] = False elif "Hit" in cols[0]: ret[ident] = None elif "Err" in cols[0]: ret[ident] = False error_repos.append(ident) if failhard and error_repos: raise CommandExecutionError( "Error getting repos: {}".format(", ".join(error_repos)) ) return ret def install( name=None, refresh=False, fromrepo=None, skip_verify=False, debconf=None, pkgs=None, sources=None, reinstall=False, downloadonly=False, ignore_epoch=False, **kwargs, ): """ .. versionchanged:: 2015.8.12,2016.3.3,2016.11.0 On minions running systemd>=205, `systemd-run(1)`_ is now used to isolate commands which modify installed packages from the ``salt-minion`` daemon's control group. This is done to keep systemd from killing any apt-get/dpkg commands spawned by Salt when the ``salt-minion`` service is restarted. (see ``KillMode`` in the `systemd.kill(5)`_ manpage for more information). If desired, usage of `systemd-run(1)`_ can be suppressed by setting a :mod:`config option <salt.modules.config.get>` called ``systemd.scope``, with a value of ``False`` (no quotes). .. _`systemd-run(1)`: https://www.freedesktop.org/software/systemd/man/systemd-run.html .. _`systemd.kill(5)`: https://www.freedesktop.org/software/systemd/man/systemd.kill.html Install the passed package, add refresh=True to update the dpkg database. name The name of the package to be installed. Note that this parameter is ignored if either "pkgs" or "sources" is passed. Additionally, please note that this option can only be used to install packages from a software repository. To install a package file manually, use the "sources" option. 32-bit packages can be installed on 64-bit systems by appending the architecture designation (``:i386``, etc.) to the end of the package name. CLI Example: .. code-block:: bash salt '*' pkg.install <package name> refresh Whether or not to refresh the package database before installing. cache_valid_time .. versionadded:: 2016.11.0 Skip refreshing the package database if refresh has already occurred within <value> seconds fromrepo Specify a package repository to install from (e.g., ``apt-get -t unstable install somepackage``) skip_verify Skip the GPG verification check (e.g., ``--allow-unauthenticated``, or ``--force-bad-verify`` for install from package file). debconf Provide the path to a debconf answers file, processed before installation. version Install a specific version of the package, e.g. 1.2.3~0ubuntu0. Ignored if "pkgs" or "sources" is passed. .. versionchanged:: 2018.3.0 version can now contain comparison operators (e.g. ``>1.2.3``, ``<=2.0``, etc.) reinstall : False Specifying reinstall=True will use ``apt-get install --reinstall`` rather than simply ``apt-get install`` for requested packages that are already installed. If a version is specified with the requested package, then ``apt-get install --reinstall`` will only be used if the installed version matches the requested version. .. versionadded:: 2015.8.0 ignore_epoch : False Only used when the version of a package is specified using a comparison operator (e.g. ``>4.1``). If set to ``True``, then the epoch will be ignored when comparing the currently-installed version to the desired version. .. versionadded:: 2018.3.0 Multiple Package Installation Options: pkgs A list of packages to install from a software repository. Must be passed as a python list. CLI Example: .. code-block:: bash salt '*' pkg.install pkgs='["foo", "bar"]' salt '*' pkg.install pkgs='["foo", {"bar": "1.2.3-0ubuntu0"}]' sources A list of DEB packages to install. Must be passed as a list of dicts, with the keys being package names, and the values being the source URI or local path to the package. Dependencies are automatically resolved and marked as auto-installed. 32-bit packages can be installed on 64-bit systems by appending the architecture designation (``:i386``, etc.) to the end of the package name. .. versionchanged:: 2014.7.0 CLI Example: .. code-block:: bash salt '*' pkg.install sources='[{"foo": "salt://foo.deb"},{"bar": "salt://bar.deb"}]' force_yes Passes ``--force-yes`` to the apt-get command. Don't use this unless you know what you're doing. .. versionadded:: 0.17.4 install_recommends Whether to install the packages marked as recommended. Default is True. .. versionadded:: 2015.5.0 only_upgrade Only upgrade the packages, if they are already installed. Default is False. .. versionadded:: 2015.5.0 force_conf_new Always install the new version of any configuration files. .. versionadded:: 2015.8.0 Returns a dict containing the new package names and versions:: {'<package>': {'old': '<old-version>', 'new': '<new-version>'}} """ _refresh_db = False if salt.utils.data.is_true(refresh): _refresh_db = True if "version" in kwargs and kwargs["version"]: _refresh_db = False _latest_version = latest_version(name, refresh=False, show_installed=True) _version = kwargs.get("version") # If the versions don't match, refresh is True, otherwise no need # to refresh if not _latest_version == _version: _refresh_db = True if pkgs: _refresh_db = False for pkg in pkgs: if isinstance(pkg, dict): _name = next(iter(pkg.keys())) _latest_version = latest_version( _name, refresh=False, show_installed=True ) _version = pkg[_name] # If the versions don't match, refresh is True, otherwise # no need to refresh if not _latest_version == _version: _refresh_db = True else: # No version specified, so refresh should be True _refresh_db = True if debconf: __salt__["debconf.set_file"](debconf) try: pkg_params, pkg_type = __salt__["pkg_resource.parse_targets"]( name, pkgs, sources, **kwargs ) except MinionError as exc: raise CommandExecutionError(exc) # Support old "repo" argument repo = kwargs.get("repo", "") if not fromrepo and repo: fromrepo = repo if not pkg_params: return {} cmd_prefix = [] old = list_pkgs() targets = [] downgrade = [] to_reinstall = {} errors = [] if pkg_type == "repository": pkg_params_items = list(pkg_params.items()) has_comparison = [ x for x, y in pkg_params_items if y is not None and (y.startswith("<") or y.startswith(">")) ] _available = ( list_repo_pkgs(*has_comparison, byrepo=False, **kwargs) if has_comparison else {} ) else: pkg_params_items = [] for pkg_source in pkg_params: if "lowpkg.bin_pkg_info" in __salt__: deb_info = __salt__["lowpkg.bin_pkg_info"](pkg_source) else: deb_info = None if deb_info is None: log.error( "pkg.install: Unable to get deb information for %s. " "Version comparisons will be unavailable.", pkg_source, ) pkg_params_items.append([pkg_source]) else: pkg_params_items.append( [deb_info["name"], pkg_source, deb_info["version"]] ) # Build command prefix cmd_prefix.extend(["apt-get", "-q", "-y"]) if kwargs.get("force_yes", False): cmd_prefix.append("--force-yes") if "force_conf_new" in kwargs and kwargs["force_conf_new"]: cmd_prefix.extend(["-o", "DPkg::Options::=--force-confnew"]) else: cmd_prefix.extend(["-o", "DPkg::Options::=--force-confold"]) cmd_prefix += ["-o", "DPkg::Options::=--force-confdef"] if "install_recommends" in kwargs: if not kwargs["install_recommends"]: cmd_prefix.append("--no-install-recommends") else: cmd_prefix.append("--install-recommends") if "only_upgrade" in kwargs and kwargs["only_upgrade"]: cmd_prefix.append("--only-upgrade") if skip_verify: cmd_prefix.append("--allow-unauthenticated") if fromrepo and pkg_type == "repository": cmd_prefix.extend(["-t", fromrepo]) cmd_prefix.append("install") for pkg_item_list in pkg_params_items: if pkg_type == "repository": pkgname, version_num = pkg_item_list if name and pkgs is None and kwargs.get("version") and len(pkg_params) == 1: # Only use the 'version' param if 'name' was not specified as a # comma-separated list version_num = kwargs["version"] else: try: pkgname, pkgpath, version_num = pkg_item_list except ValueError: pkgname = None pkgpath = pkg_item_list[0] version_num = None if version_num is None: if pkg_type == "repository": if reinstall and pkgname in old: to_reinstall[pkgname] = pkgname else: targets.append(pkgname) else: targets.append(pkgpath) else: # If we are installing a package file and not one from the repo, # and version_num is not None, then we can assume that pkgname is # not None, since the only way version_num is not None is if DEB # metadata parsing was successful. if pkg_type == "repository": # Remove leading equals sign(s) to keep from building a pkgstr # with multiple equals (which would be invalid) version_num = version_num.lstrip("=") if pkgname in has_comparison: candidates = _available.get(pkgname, []) target = salt.utils.pkg.match_version( version_num, candidates, cmp_func=version_cmp, ignore_epoch=ignore_epoch, ) if target is None: errors.append( "No version matching '{}{}' could be found " "(available: {})".format( pkgname, version_num, ", ".join(candidates) if candidates else None, ) ) continue else: version_num = target pkgstr = "{}={}".format(pkgname, version_num) else: pkgstr = pkgpath cver = old.get(pkgname, "") if ( reinstall and cver and salt.utils.versions.compare( ver1=version_num, oper="==", ver2=cver, cmp_func=version_cmp ) ): to_reinstall[pkgname] = pkgstr elif not cver or salt.utils.versions.compare( ver1=version_num, oper=">=", ver2=cver, cmp_func=version_cmp ): targets.append(pkgstr) else: downgrade.append(pkgstr) if fromrepo and not sources: log.info("Targeting repo '%s'", fromrepo) cmds = [] all_pkgs = [] if targets: all_pkgs.extend(targets) cmd = copy.deepcopy(cmd_prefix) cmd.extend(targets) cmds.append(cmd) if downgrade: cmd = copy.deepcopy(cmd_prefix) if pkg_type == "repository" and "--force-yes" not in cmd: # Downgrading requires --force-yes. Insert this before 'install' cmd.insert(-1, "--force-yes") cmd.extend(downgrade) cmds.append(cmd) if downloadonly: cmd.append("--download-only") if to_reinstall: all_pkgs.extend(to_reinstall) cmd = copy.deepcopy(cmd_prefix) if not sources: cmd.append("--reinstall") cmd.extend([x for x in to_reinstall.values()]) cmds.append(cmd) if not cmds: ret = {} else: cache_valid_time = kwargs.pop("cache_valid_time", 0) if _refresh_db: refresh_db(cache_valid_time) env = _parse_env(kwargs.get("env")) env.update(DPKG_ENV_VARS.copy()) hold_pkgs = get_selections(state="hold").get("hold", []) # all_pkgs contains the argument to be passed to apt-get install, which # when a specific version is requested will be in the format # name=version. Strip off the '=' if present so we can compare the # held package names against the packages we are trying to install. targeted_names = [x.split("=")[0] for x in all_pkgs] to_unhold = [x for x in hold_pkgs if x in targeted_names] if to_unhold: unhold(pkgs=to_unhold) for cmd in cmds: out = _call_apt(cmd, **kwargs) if out["retcode"] != 0 and out["stderr"]: errors.append(out["stderr"]) __context__.pop("pkg.list_pkgs", None) new = list_pkgs() ret = salt.utils.data.compare_dicts(old, new) for pkgname in to_reinstall: if pkgname not in ret or pkgname in old: ret.update( { pkgname: { "old": old.get(pkgname, ""), "new": new.get(pkgname, ""), } } ) if to_unhold: hold(pkgs=to_unhold) if errors: raise CommandExecutionError( "Problem encountered installing package(s)", info={"errors": errors, "changes": ret}, ) return ret def _uninstall(action="remove", name=None, pkgs=None, **kwargs): """ remove and purge do identical things but with different apt-get commands, this function performs the common logic. """ try: pkg_params = __salt__["pkg_resource.parse_targets"](name, pkgs)[0] except MinionError as exc: raise CommandExecutionError(exc) old = list_pkgs() old_removed = list_pkgs(removed=True) targets = [x for x in pkg_params if x in old] if action == "purge": targets.extend([x for x in pkg_params if x in old_removed]) if not targets: return {} cmd = ["apt-get", "-q", "-y", action] cmd.extend(targets) env = _parse_env(kwargs.get("env")) env.update(DPKG_ENV_VARS.copy()) out = _call_apt(cmd, env=env) if out["retcode"] != 0 and out["stderr"]: errors = [out["stderr"]] else: errors = [] __context__.pop("pkg.list_pkgs", None) new = list_pkgs() new_removed = list_pkgs(removed=True) changes = salt.utils.data.compare_dicts(old, new) if action == "purge": ret = { "removed": salt.utils.data.compare_dicts(old_removed, new_removed), "installed": changes, } else: ret = changes if errors: raise CommandExecutionError( "Problem encountered removing package(s)", info={"errors": errors, "changes": ret}, ) return ret def autoremove(list_only=False, purge=False): """ .. versionadded:: 2015.5.0 Remove packages not required by another package using ``apt-get autoremove``. list_only : False Only retrieve the list of packages to be auto-removed, do not actually perform the auto-removal. purge : False Also remove package config data when autoremoving packages. .. versionadded:: 2015.8.0 CLI Example: .. code-block:: bash salt '*' pkg.autoremove salt '*' pkg.autoremove list_only=True salt '*' pkg.autoremove purge=True """ cmd = [] if list_only: ret = [] cmd.extend(["apt-get", "--assume-no"]) if purge: cmd.append("--purge") cmd.append("autoremove") out = _call_apt(cmd, ignore_retcode=True)["stdout"] found = False for line in out.splitlines(): if found is True: if line.startswith(" "): ret.extend(line.split()) else: found = False elif "The following packages will be REMOVED:" in line: found = True ret.sort() return ret else: old = list_pkgs() cmd.extend(["apt-get", "--assume-yes"]) if purge: cmd.append("--purge") cmd.append("autoremove") _call_apt(cmd, ignore_retcode=True) __context__.pop("pkg.list_pkgs", None) new = list_pkgs() return salt.utils.data.compare_dicts(old, new) def remove(name=None, pkgs=None, **kwargs): """ .. versionchanged:: 2015.8.12,2016.3.3,2016.11.0 On minions running systemd>=205, `systemd-run(1)`_ is now used to isolate commands which modify installed packages from the ``salt-minion`` daemon's control group. This is done to keep systemd from killing any apt-get/dpkg commands spawned by Salt when the ``salt-minion`` service is restarted. (see ``KillMode`` in the `systemd.kill(5)`_ manpage for more information). If desired, usage of `systemd-run(1)`_ can be suppressed by setting a :mod:`config option <salt.modules.config.get>` called ``systemd.scope``, with a value of ``False`` (no quotes). .. _`systemd-run(1)`: https://www.freedesktop.org/software/systemd/man/systemd-run.html .. _`systemd.kill(5)`: https://www.freedesktop.org/software/systemd/man/systemd.kill.html Remove packages using ``apt-get remove``. name The name of the package to be deleted. Multiple Package Options: pkgs A list of packages to delete. Must be passed as a python list. The ``name`` parameter will be ignored if this option is passed. .. versionadded:: 0.16.0 Returns a dict containing the changes. CLI Example: .. code-block:: bash salt '*' pkg.remove <package name> salt '*' pkg.remove <package1>,<package2>,<package3> salt '*' pkg.remove pkgs='["foo", "bar"]' """ return _uninstall(action="remove", name=name, pkgs=pkgs, **kwargs) def purge(name=None, pkgs=None, **kwargs): """ .. versionchanged:: 2015.8.12,2016.3.3,2016.11.0 On minions running systemd>=205, `systemd-run(1)`_ is now used to isolate commands which modify installed packages from the ``salt-minion`` daemon's control group. This is done to keep systemd from killing any apt-get/dpkg commands spawned by Salt when the ``salt-minion`` service is restarted. (see ``KillMode`` in the `systemd.kill(5)`_ manpage for more information). If desired, usage of `systemd-run(1)`_ can be suppressed by setting a :mod:`config option <salt.modules.config.get>` called ``systemd.scope``, with a value of ``False`` (no quotes). .. _`systemd-run(1)`: https://www.freedesktop.org/software/systemd/man/systemd-run.html .. _`systemd.kill(5)`: https://www.freedesktop.org/software/systemd/man/systemd.kill.html Remove packages via ``apt-get purge`` along with all configuration files. name The name of the package to be deleted. Multiple Package Options: pkgs A list of packages to delete. Must be passed as a python list. The ``name`` parameter will be ignored if this option is passed. .. versionadded:: 0.16.0 Returns a dict containing the changes. CLI Example: .. code-block:: bash salt '*' pkg.purge <package name> salt '*' pkg.purge <package1>,<package2>,<package3> salt '*' pkg.purge pkgs='["foo", "bar"]' """ return _uninstall(action="purge", name=name, pkgs=pkgs, **kwargs) def upgrade(refresh=True, dist_upgrade=False, **kwargs): """ .. versionchanged:: 2015.8.12,2016.3.3,2016.11.0 On minions running systemd>=205, `systemd-run(1)`_ is now used to isolate commands which modify installed packages from the ``salt-minion`` daemon's control group. This is done to keep systemd from killing any apt-get/dpkg commands spawned by Salt when the ``salt-minion`` service is restarted. (see ``KillMode`` in the `systemd.kill(5)`_ manpage for more information). If desired, usage of `systemd-run(1)`_ can be suppressed by setting a :mod:`config option <salt.modules.config.get>` called ``systemd.scope``, with a value of ``False`` (no quotes). .. _`systemd-run(1)`: https://www.freedesktop.org/software/systemd/man/systemd-run.html .. _`systemd.kill(5)`: https://www.freedesktop.org/software/systemd/man/systemd.kill.html Upgrades all packages via ``apt-get upgrade`` or ``apt-get dist-upgrade`` if ``dist_upgrade`` is ``True``. Returns a dictionary containing the changes: .. code-block:: python {'<package>': {'old': '<old-version>', 'new': '<new-version>'}} dist_upgrade Whether to perform the upgrade using dist-upgrade vs upgrade. Default is to use upgrade. .. versionadded:: 2014.7.0 refresh : True If ``True``, the apt cache will be refreshed first. By default, this is ``True`` and a refresh is performed. cache_valid_time .. versionadded:: 2016.11.0 Skip refreshing the package database if refresh has already occurred within <value> seconds download_only (or downloadonly) Only download the packages, don't unpack or install them. Use downloadonly to be in line with yum and zypper module. .. versionadded:: 2018.3.0 force_conf_new Always install the new version of any configuration files. .. versionadded:: 2015.8.0 allow_downgrades Allow apt to downgrade packages without a prompt. .. versionadded:: 3005 CLI Example: .. code-block:: bash salt '*' pkg.upgrade """ cache_valid_time = kwargs.pop("cache_valid_time", 0) if salt.utils.data.is_true(refresh): refresh_db(cache_valid_time) old = list_pkgs() if "force_conf_new" in kwargs and kwargs["force_conf_new"]: dpkg_options = ["--force-confnew"] else: dpkg_options = ["--force-confold", "--force-confdef"] cmd = [ "apt-get", "-q", "-y", ] for option in dpkg_options: cmd.append("-o") cmd.append("DPkg::Options::={}".format(option)) if kwargs.get("force_yes", False): cmd.append("--force-yes") if kwargs.get("skip_verify", False): cmd.append("--allow-unauthenticated") if kwargs.get("download_only", False) or kwargs.get("downloadonly", False): cmd.append("--download-only") if kwargs.get("allow_downgrades", False): cmd.append("--allow-downgrades") cmd.append("dist-upgrade" if dist_upgrade else "upgrade") result = _call_apt(cmd, env=DPKG_ENV_VARS.copy()) __context__.pop("pkg.list_pkgs", None) new = list_pkgs() ret = salt.utils.data.compare_dicts(old, new) if result["retcode"] != 0: raise CommandExecutionError( "Problem encountered upgrading packages", info={"changes": ret, "result": result}, ) return ret def hold(name=None, pkgs=None, sources=None, **kwargs): # pylint: disable=W0613 """ .. versionadded:: 2014.7.0 Set package in 'hold' state, meaning it will not be upgraded. name The name of the package, e.g., 'tmux' CLI Example: .. code-block:: bash salt '*' pkg.hold <package name> pkgs A list of packages to hold. Must be passed as a python list. CLI Example: .. code-block:: bash salt '*' pkg.hold pkgs='["foo", "bar"]' """ if not name and not pkgs and not sources: raise SaltInvocationError("One of name, pkgs, or sources must be specified.") if pkgs and sources: raise SaltInvocationError("Only one of pkgs or sources can be specified.") targets = [] if pkgs: targets.extend(pkgs) elif sources: for source in sources: targets.append(next(iter(source))) else: targets.append(name) ret = {} for target in targets: if isinstance(target, dict): target = next(iter(target)) ret[target] = {"name": target, "changes": {}, "result": False, "comment": ""} state = get_selections(pattern=target, state="hold") if not state: ret[target]["comment"] = "Package {} not currently held.".format(target) elif not salt.utils.data.is_true(state.get("hold", False)): if "test" in __opts__ and __opts__["test"]: ret[target].update(result=None) ret[target]["comment"] = "Package {} is set to be held.".format(target) else: result = set_selections(selection={"hold": [target]}) ret[target].update(changes=result[target], result=True) ret[target]["comment"] = "Package {} is now being held.".format(target) else: ret[target].update(result=True) ret[target]["comment"] = "Package {} is already set to be held.".format( target ) return ret def unhold(name=None, pkgs=None, sources=None, **kwargs): # pylint: disable=W0613 """ .. versionadded:: 2014.7.0 Set package current in 'hold' state to install state, meaning it will be upgraded. name The name of the package, e.g., 'tmux' CLI Example: .. code-block:: bash salt '*' pkg.unhold <package name> pkgs A list of packages to unhold. Must be passed as a python list. CLI Example: .. code-block:: bash salt '*' pkg.unhold pkgs='["foo", "bar"]' """ if not name and not pkgs and not sources: raise SaltInvocationError("One of name, pkgs, or sources must be specified.") if pkgs and sources: raise SaltInvocationError("Only one of pkgs or sources can be specified.") targets = [] if pkgs: targets.extend(pkgs) elif sources: for source in sources: targets.append(next(iter(source))) else: targets.append(name) ret = {} for target in targets: if isinstance(target, dict): target = next(iter(target)) ret[target] = {"name": target, "changes": {}, "result": False, "comment": ""} state = get_selections(pattern=target) if not state: ret[target]["comment"] = "Package {} does not have a state.".format(target) elif salt.utils.data.is_true(state.get("hold", False)): if "test" in __opts__ and __opts__["test"]: ret[target].update(result=None) ret[target]["comment"] = "Package {} is set not to be held.".format( target ) else: result = set_selections(selection={"install": [target]}) ret[target].update(changes=result[target], result=True) ret[target]["comment"] = "Package {} is no longer being held.".format( target ) else: ret[target].update(result=True) ret[target]["comment"] = "Package {} is already set not to be held.".format( target ) return ret def _list_pkgs_from_context(versions_as_list, removed, purge_desired): """ Use pkg list from __context__ """ if removed: ret = copy.deepcopy(__context__["pkg.list_pkgs"]["removed"]) else: ret = copy.deepcopy(__context__["pkg.list_pkgs"]["purge_desired"]) if not purge_desired: ret.update(__context__["pkg.list_pkgs"]["installed"]) if not versions_as_list: __salt__["pkg_resource.stringify"](ret) return ret def list_pkgs( versions_as_list=False, removed=False, purge_desired=False, **kwargs ): # pylint: disable=W0613 """ List the packages currently installed in a dict:: {'<package_name>': '<version>'} removed If ``True``, then only packages which have been removed (but not purged) will be returned. purge_desired If ``True``, then only packages which have been marked to be purged, but can't be purged due to their status as dependencies for other installed packages, will be returned. Note that these packages will appear in installed .. versionchanged:: 2014.1.1 Packages in this state now correctly show up in the output of this function. CLI Example: .. code-block:: bash salt '*' pkg.list_pkgs salt '*' pkg.list_pkgs versions_as_list=True """ versions_as_list = salt.utils.data.is_true(versions_as_list) removed = salt.utils.data.is_true(removed) purge_desired = salt.utils.data.is_true(purge_desired) if "pkg.list_pkgs" in __context__ and kwargs.get("use_context", True): return _list_pkgs_from_context(versions_as_list, removed, purge_desired) ret = {"installed": {}, "removed": {}, "purge_desired": {}} cmd = [ "dpkg-query", "--showformat", "${Status} ${Package} ${Version} ${Architecture}\n", "-W", ] out = __salt__["cmd.run_stdout"](cmd, output_loglevel="trace", python_shell=False) # Typical lines of output: # install ok installed zsh 4.3.17-1ubuntu1 amd64 # deinstall ok config-files mc 3:4.8.1-2ubuntu1 amd64 for line in out.splitlines(): cols = line.split() try: linetype, status, name, version_num, arch = ( cols[x] for x in (0, 2, 3, 4, 5) ) except (ValueError, IndexError): continue if __grains__.get("cpuarch", "") == "x86_64": osarch = __grains__.get("osarch", "") if arch != "all" and osarch == "amd64" and osarch != arch: name += ":{}".format(arch) if cols: if ("install" in linetype or "hold" in linetype) and "installed" in status: __salt__["pkg_resource.add_pkg"](ret["installed"], name, version_num) elif "deinstall" in linetype: __salt__["pkg_resource.add_pkg"](ret["removed"], name, version_num) elif "purge" in linetype and status == "installed": __salt__["pkg_resource.add_pkg"]( ret["purge_desired"], name, version_num ) for pkglist_type in ("installed", "removed", "purge_desired"): __salt__["pkg_resource.sort_pkglist"](ret[pkglist_type]) __context__["pkg.list_pkgs"] = copy.deepcopy(ret) if removed: ret = ret["removed"] else: ret = copy.deepcopy(__context__["pkg.list_pkgs"]["purge_desired"]) if not purge_desired: ret.update(__context__["pkg.list_pkgs"]["installed"]) if not versions_as_list: __salt__["pkg_resource.stringify"](ret) return ret def _get_upgradable(dist_upgrade=True, **kwargs): """ Utility function to get upgradable packages Sample return data: { 'pkgname': '1.2.3-45', ... } """ cmd = ["apt-get", "--just-print"] if dist_upgrade: cmd.append("dist-upgrade") else: cmd.append("upgrade") try: cmd.extend(["-o", "APT::Default-Release={}".format(kwargs["fromrepo"])]) except KeyError: pass call = _call_apt(cmd) if call["retcode"] != 0: msg = "Failed to get upgrades" for key in ("stderr", "stdout"): if call[key]: msg += ": " + call[key] break raise CommandExecutionError(msg) else: out = call["stdout"] # rexp parses lines that look like the following: # Conf libxfont1 (1:1.4.5-1 Debian:testing [i386]) rexp = re.compile("(?m)^Conf " "([^ ]+) " r"\(([^ ]+)") # Package name # Version keys = ["name", "version"] _get = lambda l, k: l[keys.index(k)] upgrades = rexp.findall(out) ret = {} for line in upgrades: name = _get(line, "name") version_num = _get(line, "version") ret[name] = version_num return ret def list_upgrades(refresh=True, dist_upgrade=True, **kwargs): """ List all available package upgrades. refresh Whether to refresh the package database before listing upgrades. Default: True. cache_valid_time .. versionadded:: 2016.11.0 Skip refreshing the package database if refresh has already occurred within <value> seconds dist_upgrade Whether to list the upgrades using dist-upgrade vs upgrade. Default is to use dist-upgrade. CLI Example: .. code-block:: bash salt '*' pkg.list_upgrades """ cache_valid_time = kwargs.pop("cache_valid_time", 0) if salt.utils.data.is_true(refresh): refresh_db(cache_valid_time) return _get_upgradable(dist_upgrade, **kwargs) def upgrade_available(name, **kwargs): """ Check whether or not an upgrade is available for a given package CLI Example: .. code-block:: bash salt '*' pkg.upgrade_available <package name> """ return latest_version(name) != "" def version_cmp(pkg1, pkg2, ignore_epoch=False, **kwargs): """ Do a cmp-style comparison on two packages. Return -1 if pkg1 < pkg2, 0 if pkg1 == pkg2, and 1 if pkg1 > pkg2. Return None if there was a problem making the comparison. ignore_epoch : False Set to ``True`` to ignore the epoch when comparing versions .. versionadded:: 2015.8.10,2016.3.2 CLI Example: .. code-block:: bash salt '*' pkg.version_cmp '0.2.4-0ubuntu1' '0.2.4.1-0ubuntu1' """ normalize = lambda x: str(x).split(":", 1)[-1] if ignore_epoch else str(x) # both apt_pkg.version_compare and _cmd_quote need string arguments. pkg1 = normalize(pkg1) pkg2 = normalize(pkg2) # if we have apt_pkg, this will be quickier this way # and also do not rely on shell. if HAS_APTPKG: try: # the apt_pkg module needs to be manually initialized apt_pkg.init_system() # if there is a difference in versions, apt_pkg.version_compare will # return an int representing the difference in minor versions, or # 1/-1 if the difference is smaller than minor versions. normalize # to -1, 0 or 1. try: ret = apt_pkg.version_compare(pkg1, pkg2) except TypeError: ret = apt_pkg.version_compare(str(pkg1), str(pkg2)) return 1 if ret > 0 else -1 if ret < 0 else 0 except Exception: # pylint: disable=broad-except # Try to use shell version in case of errors w/python bindings pass try: for oper, ret in (("lt", -1), ("eq", 0), ("gt", 1)): cmd = ["dpkg", "--compare-versions", pkg1, oper, pkg2] retcode = __salt__["cmd.retcode"]( cmd, output_loglevel="trace", python_shell=False, ignore_retcode=True ) if retcode == 0: return ret except Exception as exc: # pylint: disable=broad-except log.error(exc) return None def _get_opts(line): """ Return all opts in [] for a repo line """ get_opts = re.search(r"\[(.*=.*)\]", line) ret = { "arch": {"full": "", "value": "", "index": 0}, "signedby": {"full": "", "value": "", "index": 0}, } if not get_opts: return ret opts = get_opts.group(0).strip("[]") architectures = [] for idx, opt in enumerate(opts.split()): if opt.startswith("arch"): architectures.extend(opt.split("=", 1)[1].split(",")) ret["arch"]["full"] = opt ret["arch"]["value"] = architectures ret["arch"]["index"] = idx elif opt.startswith("signed-by"): ret["signedby"]["full"] = opt ret["signedby"]["value"] = opt.split("=", 1)[1] ret["signedby"]["index"] = idx else: other_opt = opt.split("=", 1)[0] ret[other_opt] = {} ret[other_opt]["full"] = opt ret[other_opt]["value"] = opt.split("=", 1)[1] ret[other_opt]["index"] = idx return ret def _split_repo_str(repo): """ Return APT source entry as a dictionary """ entry = SourceEntry(repo) invalid = entry.invalid if not HAS_APT: signedby = entry.signedby else: signedby = _get_opts(line=repo)["signedby"].get("value", "") if signedby: # python3-apt does not support signedby. So if signedby # is in the repo we have to check our code to see if the # repo is invalid ourselves. _, invalid, _, _ = _invalid(repo) return { "invalid": invalid, "type": entry.type, "architectures": entry.architectures, "uri": entry.uri, "dist": entry.dist, "comps": entry.comps, "signedby": signedby, } def _consolidate_repo_sources(sources): """ Consolidate APT sources. """ if not isinstance(sources, SourcesList): raise TypeError("'{}' not a '{}'".format(type(sources), SourcesList)) consolidated = {} delete_files = set() base_file = SourceEntry("").file repos = [s for s in sources.list if not s.invalid] for repo in repos: key = str( ( getattr(repo, "architectures", []), repo.disabled, repo.type, repo.uri, repo.dist, ) ) if key in consolidated: combined = consolidated[key] combined_comps = set(repo.comps).union(set(combined.comps)) consolidated[key].comps = list(combined_comps) else: consolidated[key] = SourceEntry(repo.line) if repo.file != base_file: delete_files.add(repo.file) sources.list = list(consolidated.values()) sources.save() for file_ in delete_files: try: os.remove(file_) except OSError: pass return sources def list_repo_pkgs(*args, **kwargs): # pylint: disable=unused-import """ .. versionadded:: 2017.7.0 Returns all available packages. Optionally, package names (and name globs) can be passed and the results will be filtered to packages matching those names. This function can be helpful in discovering the version or repo to specify in a :mod:`pkg.installed <salt.states.pkg.installed>` state. The return data will be a dictionary mapping package names to a list of version numbers, ordered from newest to oldest. For example: .. code-block:: python { 'bash': ['4.3-14ubuntu1.1', '4.3-14ubuntu1'], 'nginx': ['1.10.0-0ubuntu0.16.04.4', '1.9.15-0ubuntu1'] } CLI Examples: .. code-block:: bash salt '*' pkg.list_repo_pkgs salt '*' pkg.list_repo_pkgs foo bar baz """ if args: # Get only information about packages in args cmd = ["apt-cache", "show"] + [arg for arg in args] else: # Get information about all available packages cmd = ["apt-cache", "dump"] out = _call_apt(cmd, scope=False, ignore_retcode=True) ret = {} pkg_name = None new_pkg = re.compile("^Package: (.+)") for line in salt.utils.itertools.split(out["stdout"], "\n"): if not line.strip(): continue try: cur_pkg = new_pkg.match(line).group(1) except AttributeError: pass else: if cur_pkg != pkg_name: pkg_name = cur_pkg continue comps = line.strip().split(None, 1) if comps[0] == "Version:": ret.setdefault(pkg_name, []).append(comps[1]) return ret def _skip_source(source): """ Decide to skip source or not. :param source: :return: """ if source.invalid: if ( source.uri and source.type and source.type in ("deb", "deb-src", "rpm", "rpm-src") ): pieces = source.mysplit(source.line) if pieces[1].strip()[0] == "[": options = pieces.pop(1).strip("[]").split() if len(options) > 0: log.debug( "Source %s will be included although is marked invalid", source.uri, ) return False return True else: return True return False def list_repos(**kwargs): """ Lists all repos in the sources.list (and sources.lists.d) files CLI Example: .. code-block:: bash salt '*' pkg.list_repos salt '*' pkg.list_repos disabled=True """ repos = {} sources = SourcesList() for source in sources.list: if _skip_source(source): continue if not HAS_APT: signedby = source.signedby else: signedby = _get_opts(line=source.line)["signedby"].get("value", "") repo = {} repo["file"] = source.file repo["comps"] = getattr(source, "comps", []) repo["disabled"] = source.disabled repo["dist"] = source.dist repo["type"] = source.type repo["uri"] = source.uri repo["line"] = source.line.strip() repo["architectures"] = getattr(source, "architectures", []) repo["signedby"] = signedby repos.setdefault(source.uri, []).append(repo) return repos def get_repo(repo, **kwargs): """ Display a repo from the sources.list / sources.list.d The repo passed in needs to be a complete repo entry. CLI Examples: .. code-block:: bash salt '*' pkg.get_repo "myrepo definition" """ ppa_auth = kwargs.get("ppa_auth", None) # we have to be clever about this since the repo definition formats # are a bit more "loose" than in some other distributions if repo.startswith("ppa:") and __grains__["os"] in ("Ubuntu", "Mint", "neon"): # This is a PPA definition meaning special handling is needed # to derive the name. dist = __grains__["oscodename"] owner_name, ppa_name = repo[4:].split("/") if ppa_auth: auth_info = "{}@".format(ppa_auth) repo = LP_PVT_SRC_FORMAT.format(auth_info, owner_name, ppa_name, dist) else: if HAS_SOFTWAREPROPERTIES: try: if hasattr(softwareproperties.ppa, "PPAShortcutHandler"): repo = softwareproperties.ppa.PPAShortcutHandler(repo).expand( dist )[0] else: repo = softwareproperties.ppa.expand_ppa_line(repo, dist)[0] except NameError as name_error: raise CommandExecutionError( "Could not find ppa {}: {}".format(repo, name_error) ) else: repo = LP_SRC_FORMAT.format(owner_name, ppa_name, dist) repos = list_repos() if repos: try: repo_entry = _split_repo_str(repo) if ppa_auth: uri_match = re.search("(http[s]?://)(.+)", repo_entry["uri"]) if uri_match: if not uri_match.group(2).startswith(ppa_auth): repo_entry["uri"] = "{}{}@{}".format( uri_match.group(1), ppa_auth, uri_match.group(2) ) except SyntaxError: raise CommandExecutionError( "Error: repo '{}' is not a well formatted definition".format(repo) ) for source in repos.values(): for sub in source: if ( sub["type"] == repo_entry["type"] and sub["uri"].rstrip("/") == repo_entry["uri"].rstrip("/") and sub["dist"] == repo_entry["dist"] ): if not repo_entry["comps"]: return sub for comp in repo_entry["comps"]: if comp in sub.get("comps", []): return sub return {} def del_repo(repo, **kwargs): """ Delete a repo from the sources.list / sources.list.d If the .list file is in the sources.list.d directory and the file that the repo exists in does not contain any other repo configuration, the file itself will be deleted. The repo passed in must be a fully formed repository definition string. CLI Examples: .. code-block:: bash salt '*' pkg.del_repo "myrepo definition" """ is_ppa = False if repo.startswith("ppa:") and __grains__["os"] in ("Ubuntu", "Mint", "neon"): # This is a PPA definition meaning special handling is needed # to derive the name. is_ppa = True dist = __grains__["oscodename"] if not HAS_SOFTWAREPROPERTIES: _warn_software_properties(repo) owner_name, ppa_name = repo[4:].split("/") if "ppa_auth" in kwargs: auth_info = "{}@".format(kwargs["ppa_auth"]) repo = LP_PVT_SRC_FORMAT.format(auth_info, dist, owner_name, ppa_name) else: repo = LP_SRC_FORMAT.format(owner_name, ppa_name, dist) else: if hasattr(softwareproperties.ppa, "PPAShortcutHandler"): repo = softwareproperties.ppa.PPAShortcutHandler(repo).expand(dist)[0] else: repo = softwareproperties.ppa.expand_ppa_line(repo, dist)[0] sources = SourcesList() repos = [s for s in sources.list if not s.invalid] if repos: deleted_from = dict() try: repo_entry = _split_repo_str(repo) except SyntaxError: raise SaltInvocationError( "Error: repo '{}' not a well formatted definition".format(repo) ) for source in repos: if ( source.type == repo_entry["type"] and source.architectures == repo_entry["architectures"] and source.uri.rstrip("/") == repo_entry["uri"].rstrip("/") and source.dist == repo_entry["dist"] ): s_comps = set(source.comps) r_comps = set(repo_entry["comps"]) if s_comps.intersection(r_comps) or (not s_comps and not r_comps): deleted_from[source.file] = 0 source.comps = list(s_comps.difference(r_comps)) if not source.comps: try: sources.remove(source) except ValueError: pass # PPAs are special and can add deb-src where expand_ppa_line # doesn't always reflect this. Lets just cleanup here for good # measure if ( is_ppa and repo_entry["type"] == "deb" and source.type == "deb-src" and source.uri == repo_entry["uri"] and source.dist == repo_entry["dist"] ): s_comps = set(source.comps) r_comps = set(repo_entry["comps"]) if s_comps.intersection(r_comps) or (not s_comps and not r_comps): deleted_from[source.file] = 0 source.comps = list(s_comps.difference(r_comps)) if not source.comps: try: sources.remove(source) except ValueError: pass sources.save() if deleted_from: ret = "" for source in sources: if source.file in deleted_from: deleted_from[source.file] += 1 for repo_file, count in deleted_from.items(): msg = "Repo '{0}' has been removed from {1}.\n" if count == 0 and "sources.list.d/" in repo_file: if os.path.isfile(repo_file): msg = "File {1} containing repo '{0}' has been removed." try: os.remove(repo_file) except OSError: pass ret += msg.format(repo, repo_file) # explicit refresh after a repo is deleted refresh_db() return ret raise CommandExecutionError( "Repo {} doesn't exist in the sources.list(s)".format(repo) ) def _convert_if_int(value): """ .. versionadded:: 2017.7.0 Convert to an int if necessary. :param str value: The value to check/convert. :return: The converted or passed value. :rtype: bool|int|str """ try: value = int(str(value)) except ValueError: pass return value def _parse_repo_keys_output(cmd_ret): """ """ ret = dict() repo_keys = list() lines = [line for line in cmd_ret.splitlines() if line.strip()] # Reference for the meaning of each item in the colon-separated # record can be found here: https://goo.gl/KIZbvp for line in lines: items = [ _convert_if_int(item.strip()) if item.strip() else None for item in line.split(":") ] key_props = dict() if len(items) < 2: log.debug("Skipping line: %s", line) continue if items[0] in ("pub", "sub"): key_props.update( { "algorithm": items[3], "bits": items[2], "capability": items[11], "date_creation": items[5], "date_expiration": items[6], "keyid": str(items[4]), "validity": items[1], } ) if items[0] == "pub": repo_keys.append(key_props) else: repo_keys[-1]["subkey"] = key_props elif items[0] == "fpr": if repo_keys[-1].get("subkey", False): repo_keys[-1]["subkey"].update({"fingerprint": items[9]}) else: repo_keys[-1].update({"fingerprint": items[9]}) elif items[0] == "uid": repo_keys[-1].update({"uid": items[9], "uid_hash": items[7]}) for repo_key in repo_keys: ret[repo_key["keyid"]] = repo_key return ret def get_repo_keys(aptkey=True, keydir=None): """ .. versionadded:: 2017.7.0 List known repo key details. :param bool aptkey: Use the binary apt-key. :param str keydir: The directory path to save keys. The default directory is /etc/apt/keyrings/ which is the recommended path for adding third party keys. This argument is only used when aptkey is False. :return: A dictionary containing the repo keys. :rtype: dict CLI Examples: .. code-block:: bash salt '*' pkg.get_repo_keys """ if not salt.utils.path.which("apt-key"): aptkey = False if not aptkey: if not keydir: keydir = pathlib.Path("/etc", "apt", "keyrings") if not isinstance(keydir, pathlib.Path): keydir = pathlib.Path(keydir) if not keydir.is_dir(): log.error( "The directory %s does not exist. Please create this directory only writable by root", keydir, ) return False ret_output = [] for file in os.listdir(str(keydir)): key_file = keydir / file cmd_ret = __salt__["cmd.run_all"]( [ "gpg", "--no-default-keyring", "--keyring", key_file, "--list-keys", "--with-colons", ] ) ret_output.append(cmd_ret["stdout"]) ret = _parse_repo_keys_output(" ".join(ret_output)) # The double usage of '--with-fingerprint' is necessary in order to # retrieve the fingerprint of the subkey. else: cmd = [ "apt-key", "adv", "--batch", "--list-public-keys", "--with-fingerprint", "--with-fingerprint", "--with-colons", "--fixed-list-mode", ] cmd_ret = _call_apt(cmd, scope=False) if cmd_ret["retcode"] != 0: log.error(cmd_ret["stderr"]) return ret ret = _parse_repo_keys_output(cmd_ret["stdout"]) return ret def _decrypt_key(key): """ Check if the key needs to be decrypted. If it needs to be decrypt it, do so with the gpg binary. """ try: with salt.utils.files.fopen(key, "r") as fp: if fp.read().strip("-").startswith("BEGIN PGP"): if not salt.utils.path.which("gpg"): log.error( "Detected an ASCII armored key %s and the gpg binary is not available. Not decrypting the key.", key, ) return False decrypted_key = str(key) + ".decrypted" cmd = ["gpg", "--yes", "--output", decrypted_key, "--dearmor", key] if not __salt__["cmd.run_all"](cmd)["retcode"] == 0: log.error("Failed to decrypt the key %s", key) return decrypted_key except UnicodeDecodeError: log.debug("Key is not ASCII Armored. Do not need to decrypt") return key def add_repo_key( path=None, text=None, keyserver=None, keyid=None, saltenv="base", aptkey=True, keydir=None, keyfile=None, ): """ .. versionadded:: 2017.7.0 Add a repo key using ``apt-key add``. :param str path: The path of the key file to import. :param str text: The key data to import, in string form. :param str keyserver: The server to download the repo key specified by the keyid. :param str keyid: The key id of the repo key to add. :param str saltenv: The environment the key file resides in. :param bool aptkey: Use the binary apt-key. :param str keydir: The directory path to save keys. The default directory is /etc/apt/keyrings/ which is the recommended path for adding third party keys. This argument is only used when aptkey is False. :param str keyfile: The name of the key to add. This is only required when aptkey is False and you are using a keyserver. This argument is only used when aptkey is False. :return: A boolean representing whether the repo key was added. :rtype: bool .. warning:: The apt-key binary is deprecated and will last be available in Debian 11 and Ubuntu 22.04. It is recommended to use aptkey=False when using this module. CLI Examples: .. code-block:: bash salt '*' pkg.add_repo_key 'salt://apt/sources/test.key' salt '*' pkg.add_repo_key text="'$KEY1'" salt '*' pkg.add_repo_key keyserver='keyserver.example' keyid='0000AAAA' """ if not keydir: keydir = pathlib.Path("/etc", "apt", "keyrings") if not isinstance(keydir, pathlib.Path): keydir = pathlib.Path(keydir) if not aptkey and not keydir.is_dir(): log.error( "The directory %s does not exist. Please create this directory only writable by root", keydir, ) return False if not salt.utils.path.which("apt-key"): aptkey = False cmd = ["apt-key"] kwargs = {} # If the keyid is provided or determined, check it against the existing # repo key ids to determine whether it needs to be imported. if keyid: for current_keyid in get_repo_keys(aptkey=aptkey, keydir=keydir): if current_keyid[-(len(keyid)) :] == keyid: log.debug("The keyid '%s' already present: %s", keyid, current_keyid) return True if path: cached_source_path = __salt__["cp.cache_file"](path, saltenv) if not cached_source_path: log.error("Unable to get cached copy of file: %s", path) return False if not aptkey: key = _decrypt_key(cached_source_path) if not key: return False key = pathlib.Path(str(key)) if not keyfile: keyfile = key.name if keyfile.endswith(".decrypted"): keyfile = keyfile[:-10] shutil.copyfile(str(key), str(keydir / keyfile)) return True else: cmd.extend(["add", cached_source_path]) elif text: log.debug("Received value: %s", text) cmd.extend(["add", "-"]) kwargs.update({"stdin": text}) elif keyserver: if not keyid: error_msg = "No keyid or keyid too short for keyserver: {}".format( keyserver ) raise SaltInvocationError(error_msg) if not aptkey: if not keyfile: log.error( "You must define the name of the key file to save the key. See keyfile argument" ) return False cmd = [ "gpg", "--no-default-keyring", "--keyring", keydir / keyfile, "--keyserver", keyserver, "--recv-keys", keyid, ] else: cmd.extend(["adv", "--batch", "--keyserver", keyserver, "--recv", keyid]) elif keyid: error_msg = "No keyserver specified for keyid: {}".format(keyid) raise SaltInvocationError(error_msg) else: raise TypeError( "{}() takes at least 1 argument (0 given)".format(add_repo_key.__name__) ) cmd_ret = _call_apt(cmd, **kwargs) if cmd_ret["retcode"] == 0: return True log.error("Unable to add repo key: %s", cmd_ret["stderr"]) return False def _get_key_from_id(keydir, keyid): """ Find and return the key file from the keyid. """ if not len(keyid) in (8, 16): log.error("The keyid needs to be either 8 or 16 characters") return False for file in os.listdir(str(keydir)): key_file = keydir / file key_output = __salt__["cmd.run_all"]( [ "gpg", "--no-default-keyring", "--keyring", key_file, "--list-keys", "--with-colons", ] ) ret = _parse_repo_keys_output(key_output["stdout"]) for key in ret: if ret[key]["keyid"].endswith(keyid): return key_file log.error("Could not find the key file for keyid: %s", keyid) return False def del_repo_key(name=None, aptkey=True, keydir=None, **kwargs): """ .. versionadded:: 2015.8.0 Remove a repo key using ``apt-key del`` name Repo from which to remove the key. Unnecessary if ``keyid`` is passed. keyid The KeyID of the GPG key to remove keyid_ppa : False If set to ``True``, the repo's GPG key ID will be looked up from ppa.launchpad.net and removed. .. note:: Setting this option to ``True`` requires that the ``name`` param also be passed. aptkey Use the binary apt-key. keydir The directory path to save keys. The default directory is /etc/apt/keyrings/ which is the recommended path for adding third party keys. .. warning:: The apt-key binary is deprecated and will last be available in Debian 11 and Ubuntu 22.04. It is recommended to use aptkey=False when using this module. CLI Examples: .. code-block:: bash salt '*' pkg.del_repo_key keyid=0123ABCD salt '*' pkg.del_repo_key name='ppa:foo/bar' keyid_ppa=True """ if not keydir: keydir = pathlib.Path("/etc", "apt", "keyrings") if not isinstance(keydir, pathlib.Path): keydir = pathlib.Path(keydir) if not aptkey and not keydir.is_dir(): log.error( "The directory %s does not exist. Please create this directory only writable by root", keydir, ) return False if not salt.utils.path.which("apt-key"): aptkey = False if kwargs.get("keyid_ppa", False): if isinstance(name, str) and name.startswith("ppa:"): owner_name, ppa_name = name[4:].split("/") ppa_info = _get_ppa_info_from_launchpad(owner_name, ppa_name) keyid = ppa_info["signing_key_fingerprint"][-8:] else: raise SaltInvocationError("keyid_ppa requires that a PPA be passed") else: if "keyid" in kwargs: keyid = kwargs.get("keyid") else: raise SaltInvocationError("keyid or keyid_ppa and PPA name must be passed") if not aptkey: key_file = _get_key_from_id(keydir=keydir, keyid=keyid) if not key_file: return False pathlib.Path(key_file).unlink() else: result = _call_apt(["apt-key", "del", keyid], scope=False) if result["retcode"] != 0: msg = "Failed to remove keyid {0}" if result["stderr"]: msg += ": {}".format(result["stderr"]) raise CommandExecutionError(msg) return keyid def mod_repo(repo, saltenv="base", aptkey=True, **kwargs): """ Modify one or more values for a repo. If the repo does not exist, it will be created, so long as the definition is well formed. For Ubuntu the ``ppa:<project>/repo`` format is acceptable. ``ppa:`` format can only be used to create a new repository. The following options are available to modify a repo definition: architectures A comma-separated list of supported architectures, e.g. ``amd64`` If this option is not set, all architectures (configured in the system) will be used. comps A comma separated list of components for the repo, e.g. ``main`` file A file name to be used keyserver Keyserver to get gpg key from keyid Key ID or a list of key IDs to load with the ``keyserver`` argument key_url URL to a GPG key to add to the APT GPG keyring key_text GPG key in string form to add to the APT GPG keyring .. versionadded:: 2018.3.0 consolidate : False If ``True``, will attempt to de-duplicate and consolidate sources comments Sometimes you want to supply additional information, but not as enabled configuration. All comments provided here will be joined into a single string and appended to the repo configuration with a comment marker (#) before it. .. versionadded:: 2015.8.9 refresh : True Enable or disable (True or False) refreshing of the apt package database. The previous ``refresh_db`` argument was deprecated in favor of ``refresh```. The ``refresh_db`` argument will still continue to work to ensure backwards compatibility, but please change to using the preferred ``refresh``. .. note:: Due to the way keys are stored for APT, there is a known issue where the key won't be updated unless another change is made at the same time. Keys should be properly added on initial configuration. CLI Examples: .. code-block:: bash salt '*' pkg.mod_repo 'myrepo definition' uri=http://new/uri salt '*' pkg.mod_repo 'myrepo definition' comps=main,universe """ if "refresh_db" in kwargs: refresh = kwargs["refresh_db"] else: refresh = kwargs.get("refresh", True) if not salt.utils.path.which("apt-key"): aptkey = False # to ensure no one sets some key values that _shouldn't_ be changed on the # object itself, this is just a white-list of "ok" to set properties if repo.startswith("ppa:"): if __grains__["os"] in ("Ubuntu", "Mint", "neon"): # secure PPAs cannot be supported as of the time of this code # implementation via apt-add-repository. The code path for # secure PPAs should be the same as urllib method if salt.utils.path.which("apt-add-repository") and "ppa_auth" not in kwargs: repo_info = get_repo(repo) if repo_info: return {repo: repo_info} else: env = None http_proxy_url = _get_http_proxy_url() if http_proxy_url: env = { "http_proxy": http_proxy_url, "https_proxy": http_proxy_url, } if float(__grains__["osrelease"]) < 12.04: cmd = ["apt-add-repository", repo] else: cmd = ["apt-add-repository", "-y", repo] out = _call_apt(cmd, env=env, scope=False, **kwargs) if out["retcode"]: raise CommandExecutionError( "Unable to add PPA '{}'. '{}' exited with " "status {!s}: '{}' ".format( repo[4:], cmd, out["retcode"], out["stderr"] ) ) # explicit refresh when a repo is modified. if refresh: refresh_db() return {repo: out} else: if not HAS_SOFTWAREPROPERTIES: _warn_software_properties(repo) else: log.info("Falling back to urllib method for private PPA") # fall back to urllib style try: owner_name, ppa_name = repo[4:].split("/", 1) except ValueError: raise CommandExecutionError( "Unable to get PPA info from argument. " 'Expected format "<PPA_OWNER>/<PPA_NAME>" ' "(e.g. saltstack/salt) not found. Received " "'{}' instead.".format(repo[4:]) ) dist = __grains__["oscodename"] # ppa has a lot of implicit arguments. Make them explicit. # These will defer to any user-defined variants kwargs["dist"] = dist ppa_auth = "" if "file" not in kwargs: filename = "/etc/apt/sources.list.d/{0}-{1}-{2}.list" kwargs["file"] = filename.format(owner_name, ppa_name, dist) try: launchpad_ppa_info = _get_ppa_info_from_launchpad( owner_name, ppa_name ) if "ppa_auth" not in kwargs: kwargs["keyid"] = launchpad_ppa_info["signing_key_fingerprint"] else: if "keyid" not in kwargs: error_str = ( "Private PPAs require a keyid to be specified: {0}/{1}" ) raise CommandExecutionError( error_str.format(owner_name, ppa_name) ) except HTTPError as exc: raise CommandExecutionError( "Launchpad does not know about {}/{}: {}".format( owner_name, ppa_name, exc ) ) except IndexError as exc: raise CommandExecutionError( "Launchpad knows about {}/{} but did not " "return a fingerprint. Please set keyid " "manually: {}".format(owner_name, ppa_name, exc) ) if "keyserver" not in kwargs: kwargs["keyserver"] = "keyserver.ubuntu.com" if "ppa_auth" in kwargs: if not launchpad_ppa_info["private"]: raise CommandExecutionError( "PPA is not private but auth credentials passed: {}".format( repo ) ) # assign the new repo format to the "repo" variable # so we can fall through to the "normal" mechanism # here. if "ppa_auth" in kwargs: ppa_auth = "{}@".format(kwargs["ppa_auth"]) repo = LP_PVT_SRC_FORMAT.format( ppa_auth, owner_name, ppa_name, dist ) else: repo = LP_SRC_FORMAT.format(owner_name, ppa_name, dist) else: raise CommandExecutionError( 'cannot parse "ppa:" style repo definitions: {}'.format(repo) ) sources = SourcesList() if kwargs.get("consolidate", False): # attempt to de-dup and consolidate all sources # down to entries in sources.list # this option makes it easier to keep the sources # list in a "sane" state. # # this should remove duplicates, consolidate comps # for a given source down to one line # and eliminate "invalid" and comment lines # # the second side effect is removal of files # that are not the main sources.list file sources = _consolidate_repo_sources(sources) repos = [] for source in sources: if HAS_APT: _, invalid, _, _ = _invalid(source.line) if not invalid: repos.append(source) else: repos.append(source) mod_source = None try: repo_entry = _split_repo_str(repo) if repo_entry.get("invalid"): raise SaltInvocationError( f"Name {repo} is not valid. This must be the complete repo entry as seen in the sources file" ) except SyntaxError: raise SyntaxError( "Error: repo '{}' not a well formatted definition".format(repo) ) full_comp_list = {comp.strip() for comp in repo_entry["comps"]} no_proxy = __salt__["config.option"]("no_proxy") kwargs["signedby"] = ( pathlib.Path(repo_entry["signedby"]) if repo_entry["signedby"] else "" ) if not aptkey and not kwargs["signedby"]: raise SaltInvocationError("missing 'signedby' option when apt-key is missing") if "keyid" in kwargs: keyid = kwargs.pop("keyid", None) keyserver = kwargs.pop("keyserver", None) if not keyid or not keyserver: error_str = "both keyserver and keyid options required." raise NameError(error_str) if not isinstance(keyid, list): keyid = [keyid] for key in keyid: if isinstance( key, int ): # yaml can make this an int, we need the hex version key = hex(key) if not aptkey: imported = False output = get_repo_keys(aptkey=aptkey, keydir=kwargs["signedby"].parent) if output.get(key): imported = True else: cmd = ["apt-key", "export", key] output = __salt__["cmd.run_stdout"](cmd, python_shell=False, **kwargs) imported = output.startswith("-----BEGIN PGP") if keyserver: if not imported: http_proxy_url = _get_http_proxy_url() if http_proxy_url and keyserver not in no_proxy: cmd = [ "apt-key", "adv", "--batch", "--keyserver-options", "http-proxy={}".format(http_proxy_url), "--keyserver", keyserver, "--logger-fd", "1", "--recv-keys", key, ] else: if not aptkey: key_file = kwargs["signedby"] if not add_repo_key( keyid=key, keyserver=keyserver, aptkey=False, keydir=key_file.parent, keyfile=key_file, ): raise CommandExecutionError( f"Error: Could not add key: {key}" ) else: cmd = [ "apt-key", "adv", "--batch", "--keyserver", keyserver, "--logger-fd", "1", "--recv-keys", key, ] ret = _call_apt(cmd, scope=False, **kwargs) if ret["retcode"] != 0: raise CommandExecutionError( "Error: key retrieval failed: {}".format( ret["stdout"] ) ) elif "key_url" in kwargs: key_url = kwargs["key_url"] fn_ = pathlib.Path(__salt__["cp.cache_file"](key_url, saltenv)) if not fn_: raise CommandExecutionError("Error: file not found: {}".format(key_url)) if kwargs["signedby"] and fn_.name != kwargs["signedby"].name: # override the signedby defined in the name with the # one defined in kwargs. new_path = fn_.parent / kwargs["signedby"].name fn_.rename(new_path) fn_ = new_path if not aptkey: func_kwargs = {} if kwargs.get("signedby"): func_kwargs["keydir"] = kwargs.get("signedby").parent if not add_repo_key(path=str(fn_), aptkey=False, **func_kwargs): raise CommandExecutionError(f"Error: Could not add key: {str(fn_)}") else: cmd = ["apt-key", "add", str(fn_)] out = __salt__["cmd.run_stdout"](cmd, python_shell=False, **kwargs) if not out.upper().startswith("OK"): raise CommandExecutionError( "Error: failed to add key from {}".format(key_url) ) elif "key_text" in kwargs: key_text = kwargs["key_text"] cmd = ["apt-key", "add", "-"] out = __salt__["cmd.run_stdout"]( cmd, stdin=key_text, python_shell=False, **kwargs ) if not out.upper().startswith("OK"): raise CommandExecutionError( "Error: failed to add key:\n{}".format(key_text) ) if "comps" in kwargs: kwargs["comps"] = [comp.strip() for comp in kwargs["comps"].split(",")] full_comp_list |= set(kwargs["comps"]) else: kwargs["comps"] = list(full_comp_list) if "architectures" in kwargs: kwargs["architectures"] = kwargs["architectures"].split(",") else: kwargs["architectures"] = repo_entry["architectures"] if "disabled" in kwargs: kwargs["disabled"] = salt.utils.data.is_true(kwargs["disabled"]) elif "enabled" in kwargs: kwargs["disabled"] = not salt.utils.data.is_true(kwargs["enabled"]) kw_type = kwargs.get("type") kw_dist = kwargs.get("dist") for apt_source in repos: # This series of checks will identify the starting source line # and the resulting source line. The idea here is to ensure # we are not returning bogus data because the source line # has already been modified on a previous run. repo_matches = ( apt_source.type == repo_entry["type"] and apt_source.uri.rstrip("/") == repo_entry["uri"].rstrip("/") and apt_source.dist == repo_entry["dist"] ) kw_matches = apt_source.dist == kw_dist and apt_source.type == kw_type if repo_matches or kw_matches: for comp in full_comp_list: if comp in getattr(apt_source, "comps", []): mod_source = apt_source if not apt_source.comps: mod_source = apt_source if kwargs["architectures"] != apt_source.architectures: mod_source = apt_source if mod_source: break if "comments" in kwargs: kwargs["comments"] = salt.utils.pkg.deb.combine_comments(kwargs["comments"]) repo_source_entry = SourceEntry(repo) if not mod_source: mod_source = SourceEntry(repo) if "comments" in kwargs: mod_source.comment = kwargs["comments"] sources.list.append(mod_source) elif "comments" in kwargs: mod_source.comment = kwargs["comments"] mod_source.line = repo_source_entry.line if not mod_source.line.endswith("\n"): mod_source.line = mod_source.line + "\n" for key in kwargs: if key in _MODIFY_OK and hasattr(mod_source, key): setattr(mod_source, key, kwargs[key]) if mod_source.uri != repo_entry["uri"]: mod_source.uri = repo_entry["uri"] mod_source.line = mod_source.str() sources.save() # on changes, explicitly refresh if refresh: refresh_db() if not HAS_APT: signedby = mod_source.signedby else: signedby = _get_opts(repo)["signedby"].get("value", "") return { repo: { "architectures": getattr(mod_source, "architectures", []), "comps": mod_source.comps, "disabled": mod_source.disabled, "file": mod_source.file, "type": mod_source.type, "uri": mod_source.uri, "line": mod_source.line, "signedby": signedby, } } def file_list(*packages, **kwargs): """ List the files that belong to a package. Not specifying any packages will return a list of _every_ file on the system's package database (not generally recommended). CLI Examples: .. code-block:: bash salt '*' pkg.file_list httpd salt '*' pkg.file_list httpd postfix salt '*' pkg.file_list """ return __salt__["lowpkg.file_list"](*packages) def file_dict(*packages, **kwargs): """ List the files that belong to a package, grouped by package. Not specifying any packages will return a list of _every_ file on the system's package database (not generally recommended). CLI Examples: .. code-block:: bash salt '*' pkg.file_dict httpd salt '*' pkg.file_dict httpd postfix salt '*' pkg.file_dict """ return __salt__["lowpkg.file_dict"](*packages) def _expand_repo_def(os_name, os_codename=None, **kwargs): """ Take a repository definition and expand it to the full pkg repository dict that can be used for comparison. This is a helper function to make the Debian/Ubuntu apt sources sane for comparison in the pkgrepo states. This is designed to be called from pkgrepo states and will have little use being called on the CLI. """ if "repo" not in kwargs: raise SaltInvocationError("missing 'repo' argument") sanitized = {} repo = kwargs["repo"] if repo.startswith("ppa:") and os_name in ("Ubuntu", "Mint", "neon"): dist = os_codename owner_name, ppa_name = repo[4:].split("/", 1) if "ppa_auth" in kwargs: auth_info = "{}@".format(kwargs["ppa_auth"]) repo = LP_PVT_SRC_FORMAT.format(auth_info, owner_name, ppa_name, dist) else: if HAS_SOFTWAREPROPERTIES: if hasattr(softwareproperties.ppa, "PPAShortcutHandler"): repo = softwareproperties.ppa.PPAShortcutHandler(repo).expand(dist)[ 0 ] else: repo = softwareproperties.ppa.expand_ppa_line(repo, dist)[0] else: repo = LP_SRC_FORMAT.format(owner_name, ppa_name, dist) if "file" not in kwargs: filename = "/etc/apt/sources.list.d/{0}-{1}-{2}.list" kwargs["file"] = filename.format(owner_name, ppa_name, dist) source_entry = SourceEntry(repo) for list_args in ("architectures", "comps"): if list_args in kwargs: kwargs[list_args] = [ kwarg.strip() for kwarg in kwargs[list_args].split(",") ] for kwarg in _MODIFY_OK: if kwarg in kwargs: setattr(source_entry, kwarg, kwargs[kwarg]) source_list = SourcesList() kwargs = {} if not HAS_APT: signedby = source_entry.signedby kwargs["signedby"] = signedby else: signedby = _get_opts(repo)["signedby"].get("value", "") _source_entry = source_list.add( type=source_entry.type, uri=source_entry.uri, dist=source_entry.dist, orig_comps=getattr(source_entry, "comps", []), architectures=getattr(source_entry, "architectures", []), **kwargs, ) if hasattr(_source_entry, "set_enabled"): _source_entry.set_enabled(not source_entry.disabled) else: _source_entry.disabled = source_entry.disabled _source_entry.line = _source_entry.repo_line() sanitized["file"] = _source_entry.file sanitized["comps"] = getattr(_source_entry, "comps", []) sanitized["disabled"] = _source_entry.disabled sanitized["dist"] = _source_entry.dist sanitized["type"] = _source_entry.type sanitized["uri"] = _source_entry.uri sanitized["line"] = _source_entry.line.strip() sanitized["architectures"] = getattr(_source_entry, "architectures", []) sanitized["signedby"] = signedby if HAS_APT and signedby: # python3-apt does not supported the signed-by opt currently. # creating the line with all opts including signed-by if signedby not in sanitized["line"]: line = sanitized["line"].split() repo_opts = _get_opts(repo) opts_order = [ opt_type for opt_type, opt_def in repo_opts.items() if opt_def["full"] != "" ] for opt in repo_opts: if "index" in repo_opts[opt]: idx = repo_opts[opt]["index"] opts_order[idx] = repo_opts[opt]["full"] opts = "[" + " ".join(opts_order) + "]" if line[1].startswith("["): line[1] = opts else: line.insert(1, opts) sanitized["line"] = " ".join(line) return sanitized def expand_repo_def(**kwargs): """ Take a repository definition and expand it to the full pkg repository dict that can be used for comparison. This is a helper function to make the Debian/Ubuntu apt sources sane for comparison in the pkgrepo states. This is designed to be called from pkgrepo states and will have little use being called on the CLI. CLI Examples: .. code-block:: bash NOT USABLE IN THE CLI """ warn_until_date( "20240101", "The pkg.expand_repo_def function is deprecated and set for removal " "after {date}. This is only unsed internally by the apt pkg state " "module. If that's not the case, please file an new issue requesting " "the removal of this deprecation warning", stacklevel=3, ) if "os_name" not in kwargs: kwargs["os_name"] = __grains__["os"] if "os_codename" not in kwargs: if "lsb_distrib_codename" in kwargs: kwargs["os_codename"] = kwargs["lsb_distrib_codename"] else: kwargs["os_codename"] = __grains__.get("oscodename") return _expand_repo_def(**kwargs) def _parse_selections(dpkgselection): """ Parses the format from ``dpkg --get-selections`` and return a format that pkg.get_selections and pkg.set_selections work with. """ ret = {} if isinstance(dpkgselection, str): dpkgselection = dpkgselection.split("\n") for line in dpkgselection: if line: _pkg, _state = line.split() if _state in ret: ret[_state].append(_pkg) else: ret[_state] = [_pkg] return ret def get_selections(pattern=None, state=None): """ View package state from the dpkg database. Returns a dict of dicts containing the state, and package names: .. code-block:: python {'<host>': {'<state>': ['pkg1', ... ] }, ... } CLI Example: .. code-block:: bash salt '*' pkg.get_selections salt '*' pkg.get_selections 'python-*' salt '*' pkg.get_selections state=hold salt '*' pkg.get_selections 'openssh*' state=hold """ ret = {} cmd = ["dpkg", "--get-selections"] cmd.append(pattern if pattern else "*") stdout = __salt__["cmd.run_stdout"]( cmd, output_loglevel="trace", python_shell=False ) ret = _parse_selections(stdout) if state: return {state: ret.get(state, [])} return ret # TODO: allow state=None to be set, and that *args will be set to that state # TODO: maybe use something similar to pkg_resources.pack_pkgs to allow a list # passed to selection, with the default state set to whatever is passed by the # above, but override that if explicitly specified # TODO: handle path to selection file from local fs as well as from salt file # server def set_selections(path=None, selection=None, clear=False, saltenv="base"): """ Change package state in the dpkg database. The state can be any one of, documented in ``dpkg(1)``: - install - hold - deinstall - purge This command is commonly used to mark specific packages to be held from being upgraded, that is, to be kept at a certain version. When a state is changed to anything but being held, then it is typically followed by ``apt-get -u dselect-upgrade``. Note: Be careful with the ``clear`` argument, since it will start with setting all packages to deinstall state. Returns a dict of dicts containing the package names, and the new and old versions: .. code-block:: python {'<host>': {'<package>': {'new': '<new-state>', 'old': '<old-state>'} }, ... } CLI Example: .. code-block:: bash salt '*' pkg.set_selections selection='{"install": ["netcat"]}' salt '*' pkg.set_selections selection='{"hold": ["openssh-server", "openssh-client"]}' salt '*' pkg.set_selections salt://path/to/file salt '*' pkg.set_selections salt://path/to/file clear=True """ ret = {} if not path and not selection: return ret if path and selection: err = ( "The 'selection' and 'path' arguments to " "pkg.set_selections are mutually exclusive, and cannot be " "specified together" ) raise SaltInvocationError(err) if isinstance(selection, str): try: selection = salt.utils.yaml.safe_load(selection) except ( salt.utils.yaml.parser.ParserError, salt.utils.yaml.scanner.ScannerError, ) as exc: raise SaltInvocationError("Improperly-formatted selection: {}".format(exc)) if path: path = __salt__["cp.cache_file"](path, saltenv) with salt.utils.files.fopen(path, "r") as ifile: content = [salt.utils.stringutils.to_unicode(x) for x in ifile.readlines()] selection = _parse_selections(content) if selection: valid_states = ("install", "hold", "deinstall", "purge") bad_states = [x for x in selection if x not in valid_states] if bad_states: raise SaltInvocationError( "Invalid state(s): {}".format(", ".join(bad_states)) ) if clear: cmd = ["dpkg", "--clear-selections"] if not __opts__["test"]: result = _call_apt(cmd, scope=False) if result["retcode"] != 0: err = "Running dpkg --clear-selections failed: {}".format( result["stderr"] ) log.error(err) raise CommandExecutionError(err) sel_revmap = {} for _state, _pkgs in get_selections().items(): sel_revmap.update({_pkg: _state for _pkg in _pkgs}) for _state, _pkgs in selection.items(): for _pkg in _pkgs: if _state == sel_revmap.get(_pkg): continue cmd = ["dpkg", "--set-selections"] cmd_in = "{} {}".format(_pkg, _state) if not __opts__["test"]: result = _call_apt(cmd, scope=False, stdin=cmd_in) if result["retcode"] != 0: log.error("failed to set state %s for package %s", _state, _pkg) else: ret[_pkg] = {"old": sel_revmap.get(_pkg), "new": _state} return ret def owner(*paths, **kwargs): """ .. versionadded:: 2014.7.0 Return the name of the package that owns the file. Multiple file paths can be passed. Like :mod:`pkg.version <salt.modules.aptpkg.version>`, if a single path is passed, a string will be returned, and if multiple paths are passed, a dictionary of file/package name pairs will be returned. If the file is not owned by a package, or is not present on the minion, then an empty string will be returned for that path. CLI Example: .. code-block:: bash salt '*' pkg.owner /usr/bin/apachectl salt '*' pkg.owner /usr/bin/apachectl /usr/bin/basename """ if not paths: return "" ret = {} for path in paths: cmd = ["dpkg", "-S", path] output = __salt__["cmd.run_stdout"]( cmd, output_loglevel="trace", python_shell=False ) ret[path] = output.split(":")[0] if "no path found" in ret[path].lower(): ret[path] = "" if len(ret) == 1: return next(iter(ret.values())) return ret def show(*names, **kwargs): """ .. versionadded:: 2019.2.0 Runs an ``apt-cache show`` on the passed package names, and returns the results in a nested dictionary. The top level of the return data will be the package name, with each package name mapping to a dictionary of version numbers to any additional information returned by ``apt-cache show``. filter An optional comma-separated list (or quoted Python list) of case-insensitive keys on which to filter. This allows one to restrict the information returned for each package to a smaller selection of pertinent items. refresh : False If ``True``, the apt cache will be refreshed first. By default, no refresh is performed. CLI Examples: .. code-block:: bash salt myminion pkg.show gawk salt myminion pkg.show 'nginx-*' salt myminion pkg.show 'nginx-*' filter=description,provides """ kwargs = salt.utils.args.clean_kwargs(**kwargs) refresh = kwargs.pop("refresh", False) filter_ = salt.utils.args.split_input( kwargs.pop("filter", []), lambda x: str(x) if not isinstance(x, str) else x.lower(), ) if kwargs: salt.utils.args.invalid_kwargs(kwargs) if refresh: refresh_db() if not names: return {} result = _call_apt(["apt-cache", "show"] + list(names), scope=False) def _add(ret, pkginfo): name = pkginfo.pop("Package", None) version = pkginfo.pop("Version", None) if name is not None and version is not None: ret.setdefault(name, {}).setdefault(version, {}).update(pkginfo) def _check_filter(key): key = key.lower() return True if key in ("package", "version") or not filter_ else key in filter_ ret = {} pkginfo = {} for line in salt.utils.itertools.split(result["stdout"], "\n"): line = line.strip() if line: try: key, val = (x.strip() for x in line.split(":", 1)) except ValueError: pass else: if _check_filter(key): pkginfo[key] = val else: # We've reached a blank line, which separates packages _add(ret, pkginfo) # Clear out the pkginfo dict for the next package pkginfo = {} continue # Make sure to add whatever was in the pkginfo dict when we reached the end # of the output. _add(ret, pkginfo) return ret def info_installed(*names, **kwargs): """ Return the information of the named package(s) installed on the system. .. versionadded:: 2015.8.1 names The names of the packages for which to return information. failhard Whether to throw an exception if none of the packages are installed. Defaults to True. .. versionadded:: 2016.11.3 CLI Example: .. code-block:: bash salt '*' pkg.info_installed <package1> salt '*' pkg.info_installed <package1> <package2> <package3> ... salt '*' pkg.info_installed <package1> failhard=false """ kwargs = salt.utils.args.clean_kwargs(**kwargs) failhard = kwargs.pop("failhard", True) if kwargs: salt.utils.args.invalid_kwargs(kwargs) ret = dict() for pkg_name, pkg_nfo in __salt__["lowpkg.info"](*names, failhard=failhard).items(): t_nfo = dict() if pkg_nfo.get("status", "ii")[1] != "i": continue # return only packages that are really installed # Translate dpkg-specific keys to a common structure for key, value in pkg_nfo.items(): if key == "package": t_nfo["name"] = value elif key == "origin": t_nfo["vendor"] = value elif key == "section": t_nfo["group"] = value elif key == "maintainer": t_nfo["packager"] = value elif key == "homepage": t_nfo["url"] = value elif key == "status": continue # only installed pkgs are returned, no need for status else: t_nfo[key] = value ret[pkg_name] = t_nfo return ret def _get_http_proxy_url(): """ Returns the http_proxy_url if proxy_username, proxy_password, proxy_host, and proxy_port config values are set. Returns a string. """ http_proxy_url = "" host = __salt__["config.option"]("proxy_host") port = __salt__["config.option"]("proxy_port") username = __salt__["config.option"]("proxy_username") password = __salt__["config.option"]("proxy_password") # Set http_proxy_url for use in various internet facing actions...eg apt-key adv if host and port: if username and password: http_proxy_url = "http://{}:{}@{}:{}".format(username, password, host, port) else: http_proxy_url = "http://{}:{}".format(host, port) return http_proxy_url def list_downloaded(root=None, **kwargs): """ .. versionadded:: 3000 List prefetched packages downloaded by apt in the local disk. root operate on a different root directory. CLI Example: .. code-block:: bash salt '*' pkg.list_downloaded """ CACHE_DIR = "/var/cache/apt" if root: CACHE_DIR = os.path.join(root, os.path.relpath(CACHE_DIR, os.path.sep)) ret = {} for root, dirnames, filenames in salt.utils.path.os_walk(CACHE_DIR): for filename in fnmatch.filter(filenames, "*.deb"): package_path = os.path.join(root, filename) pkg_info = __salt__["lowpkg.bin_pkg_info"](package_path) pkg_timestamp = int(os.path.getctime(package_path)) ret.setdefault(pkg_info["name"], {})[pkg_info["version"]] = { "path": package_path, "size": os.path.getsize(package_path), "creation_date_time_t": pkg_timestamp, "creation_date_time": datetime.datetime.utcfromtimestamp( pkg_timestamp ).isoformat(), } return ret def services_need_restart(**kwargs): """ .. versionadded:: 3003 List services that use files which have been changed by the package manager. It might be needed to restart them. Requires checkrestart from the debian-goodies package. CLI Examples: .. code-block:: bash salt '*' pkg.services_need_restart """ if not salt.utils.path.which_bin(["checkrestart"]): raise CommandNotFoundError( "'checkrestart' is needed. It is part of the 'debian-goodies' " "package which can be installed from official repositories." ) cmd = ["checkrestart", "--machine", "--package"] services = set() cr_output = __salt__["cmd.run_stdout"](cmd, python_shell=False) for line in cr_output.split("\n"): if not line.startswith("SERVICE:"): continue end_of_name = line.find(",") service = line[8:end_of_name] # skip "SERVICE:" services.add(service) return list(services)
Save