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: mysql.py
""" Module to provide MySQL compatibility to salt. :depends: - Python module: MySQLdb, mysqlclient, or PyMYSQL :configuration: In order to connect to MySQL, certain configuration is required in either the relevant minion config (/etc/salt/minion), or pillar. Some sample configs might look like:: mysql.host: 'localhost' mysql.port: 3306 mysql.user: 'root' mysql.pass: '' mysql.db: 'mysql' mysql.unix_socket: '/tmp/mysql.sock' mysql.charset: 'utf8' You can also use a defaults file:: mysql.default_file: '/etc/mysql/debian.cnf' .. versionchanged:: 2014.1.0 \'charset\' connection argument added. This is a MySQL charset, not a python one. .. versionchanged:: 0.16.2 Connection arguments from the minion config file can be overridden on the CLI by using the arguments defined :mod:`here <salt.states.mysql_user>`. Additionally, it is now possible to setup a user with no password. """ import copy import hashlib import logging import os import re import shlex import sys import time import salt.utils.data import salt.utils.files import salt.utils.stringutils try: # Trying to import MySQLdb import MySQLdb import MySQLdb.converters import MySQLdb.cursors from MySQLdb import OperationalError from MySQLdb.constants import CLIENT, FIELD_TYPE, FLAG except ImportError: try: # MySQLdb import failed, try to import PyMySQL import pymysql pymysql.install_as_MySQLdb() import MySQLdb import MySQLdb.converters import MySQLdb.cursors from MySQLdb import OperationalError from MySQLdb.constants import CLIENT, FIELD_TYPE, FLAG except ImportError: MySQLdb = None try: import sqlparse HAS_SQLPARSE = True except ImportError: HAS_SQLPARSE = False log = logging.getLogger(__name__) __grants__ = [ "ALL PRIVILEGES", "ALTER", "ALTER ROUTINE", "BACKUP_ADMIN", "BINLOG_ADMIN", "BINLOG ADMIN", # MariaDB since 10.5.2 "BINLOG MONITOR", # MariaDB since 10.5.2 "BINLOG REPLAY", # MariaDB since 10.5.2 "CONNECTION_ADMIN", "CONNECTION ADMIN", # MariaDB since 10.5.2 "CREATE", "CREATE ROLE", "CREATE ROUTINE", "CREATE TABLESPACE", "CREATE TEMPORARY TABLES", "CREATE USER", "CREATE VIEW", "DELETE", "DELETE HISTORY", # MariaDB since 10.3.4 "DROP", "DROP ROLE", "ENCRYPTION_KEY_ADMIN", "EVENT", "EXECUTE", "FEDERATED ADMIN", # MariaDB since 10.5.2 "FILE", "GRANT OPTION", "GROUP_REPLICATION_ADMIN", "INDEX", "INSERT", "LOCK TABLES", "PERSIST_RO_VARIABLES_ADMIN", "PROCESS", "READ_ONLY ADMIN", # MariaDB since 10.5.2 "REFERENCES", "RELOAD", "REPLICA MONITOR", # MariaDB since 10.5.9 "REPLICATION CLIENT", "REPLICATION MASTER ADMIN", # MariaDB since 10.5.2 "REPLICATION REPLICA", # MariaDB since 10.5.1 "REPLICATION SLAVE", "REPLICATION_SLAVE_ADMIN", "REPLICATION SLAVE ADMIN", # MariaDB since 10.5.2 "RESOURCE_GROUP_ADMIN", "RESOURCE_GROUP_USER", "ROLE_ADMIN", "SELECT", "SET USER", # MariaDB since 10.5.2 "SET_USER_ID", "SHOW DATABASES", "SHOW VIEW", "SHUTDOWN", "SLAVE MONITOR", # MariaDB since 10.5.9 "SUPER", "SYSTEM_VARIABLES_ADMIN", "TRIGGER", "UPDATE", "USAGE", "XA_RECOVER_ADMIN", ] __ssl_options_parameterized__ = ["CIPHER", "ISSUER", "SUBJECT"] __ssl_options__ = __ssl_options_parameterized__ + ["SSL", "X509"] __all_privileges__ = [ "ALTER", "ALTER ROUTINE", "BACKUP_ADMIN", "BINLOG_ADMIN", "CONNECTION_ADMIN", "CREATE", "CREATE ROLE", "CREATE ROUTINE", "CREATE TABLESPACE", "CREATE TEMPORARY TABLES", "CREATE USER", "CREATE VIEW", "DELETE", "DROP", "DROP ROLE", "ENCRYPTION_KEY_ADMIN", "EVENT", "EXECUTE", "FILE", "GROUP_REPLICATION_ADMIN", "INDEX", "INSERT", "LOCK TABLES", "PERSIST_RO_VARIABLES_ADMIN", "PROCESS", "REFERENCES", "RELOAD", "REPLICATION CLIENT", "REPLICATION SLAVE", "REPLICATION_SLAVE_ADMIN", "RESOURCE_GROUP_ADMIN", "RESOURCE_GROUP_USER", "ROLE_ADMIN", "SELECT", "SET_USER_ID", "SHOW DATABASES", "SHOW VIEW", "SHUTDOWN", "SUPER", "SYSTEM_VARIABLES_ADMIN", "TRIGGER", "UPDATE", "XA_RECOVER_ADMIN", ] r''' DEVELOPER NOTE: ABOUT arguments management, escapes, formats, arguments and security of SQL. A general rule of SQL security is to use queries with _execute call in this code using args parameter to let MySQLdb manage the arguments proper escaping. Another way of escaping values arguments could be '{0!r}'.format(), using __repr__ to ensure things get properly used as strings. But this could lead to three problems: * In ANSI mode, which is available on MySQL, but not by default, double quotes " should not be used as a string delimiters, in ANSI mode this is an identifier delimiter (like `). * Some rare exploits with bad multibytes management, either on python or MySQL could defeat this barrier, bindings internal escape functions should manage theses cases. * Unicode strings in Python 2 will include the 'u' before the repr'ed string, like so: Python 2.7.10 (default, May 26 2015, 04:16:29) [GCC 5.1.0] on linux2 Type "help", "copyright", "credits" or "license" for more information. >>> u'something something {0!r}'.format(u'foo') u"something something u'foo'" So query with arguments should use a paramstyle defined in PEP249: http://www.python.org/dev/peps/pep-0249/#paramstyle We use pyformat, which means 'SELECT * FROM foo WHERE bar=%(myval)s' used with {'myval': 'some user input'} So far so good. But this cannot be used for identifier escapes. Identifiers are database names, table names and column names. Theses names are not values and do not follow the same escape rules (see quote_identifier function for details on `_ and % escape policies on identifiers). Using value escaping on identifier could fool the SQL engine (badly escaping quotes and not doubling ` characters. So for identifiers a call to quote_identifier should be done and theses identifiers should then be added in strings with format, but without __repr__ filter. Note also that when using query with arguments in _execute all '%' characters used in the query should get escaped to '%%' fo MySQLdb, but should not be escaped if the query runs without arguments. This is managed by _execute() and quote_identifier. This is not the same as escaping '%' to '\%' or '_' to '\%' when using a LIKE query (example in db_exists), as this escape is there to avoid having _ or % characters interpreted in LIKE queries. The string parted of the first query could become (still used with args dictionary for myval): 'SELECT * FROM {0} WHERE bar=%(myval)s'.format(quote_identifier('user input')) Check integration tests if you find a hole in theses strings and escapes rules Finally some examples to sum up. Given a name f_o%o`b'a"r, in python that would be """f_o%o`b'a"r""". I'll avoid python syntax for clarity: The MySQL way of writing this name is: value : 'f_o%o`b\'a"r' (managed by MySQLdb) identifier : `f_o%o``b'a"r` db identifier in general GRANT: `f\_o\%o``b'a"r` db identifier in table GRANT : `f_o%o``b'a"r` in mySQLdb, query with args : `f_o%%o``b'a"r` (as identifier) in mySQLdb, query without args: `f_o%o``b'a"r` (as identifier) value in a LIKE query : 'f\_o\%o`b\'a"r' (quotes managed by MySQLdb) And theses could be mixed, in a like query value with args: 'f\_o\%%o`b\'a"r' ''' def __virtual__(): """ Confirm that a python mysql client is installed. """ return bool(MySQLdb), "No python mysql client installed." if MySQLdb is None else "" def __mysql_hash_password(password): _password = hashlib.sha1(password.encode()).digest() _password = "*{}".format(hashlib.sha1(_password).hexdigest().upper()) return _password def __check_table(name, table, **connection_args): dbc = _connect(**connection_args) if dbc is None: return {} cur = dbc.cursor(MySQLdb.cursors.DictCursor) s_name = quote_identifier(name) s_table = quote_identifier(table) # identifiers cannot be used as values qry = "CHECK TABLE {}.{}".format(s_name, s_table) _execute(cur, qry) results = cur.fetchall() log.debug(results) return results def __repair_table(name, table, **connection_args): dbc = _connect(**connection_args) if dbc is None: return {} cur = dbc.cursor(MySQLdb.cursors.DictCursor) s_name = quote_identifier(name) s_table = quote_identifier(table) # identifiers cannot be used as values qry = "REPAIR TABLE {}.{}".format(s_name, s_table) _execute(cur, qry) results = cur.fetchall() log.debug(results) return results def __optimize_table(name, table, **connection_args): dbc = _connect(**connection_args) if dbc is None: return {} cur = dbc.cursor(MySQLdb.cursors.DictCursor) s_name = quote_identifier(name) s_table = quote_identifier(table) # identifiers cannot be used as values qry = "OPTIMIZE TABLE {}.{}".format(s_name, s_table) _execute(cur, qry) results = cur.fetchall() log.debug(results) return results def __password_column(**connection_args): if "mysql.password_column" in __context__: return __context__["mysql.password_column"] dbc = _connect(**connection_args) if dbc is None: return "Password" cur = dbc.cursor() qry = ( "SELECT column_name from information_schema.COLUMNS " "WHERE table_schema=%(schema)s and table_name=%(table)s " "and column_name=%(column)s" ) args = {"schema": "mysql", "table": "user", "column": "Password"} _execute(cur, qry, args) if int(cur.rowcount) > 0: __context__["mysql.password_column"] = "Password" else: __context__["mysql.password_column"] = "authentication_string" return __context__["mysql.password_column"] def __get_auth_plugin(user, host, **connection_args): dbc = _connect(**connection_args) if dbc is None: return [] cur = dbc.cursor(MySQLdb.cursors.DictCursor) try: qry = "SELECT plugin FROM mysql.user WHERE User=%(user)s and Host=%(host)s" args = {"user": user, "host": host} _execute(cur, qry, args) except MySQLdb.OperationalError as exc: err = "MySQL Error {}: {}".format(*exc.args) __context__["mysql.error"] = err log.error(err) return "mysql_native_password" results = cur.fetchall() log.debug(results) if results: return results[0].get("plugin", "mysql_native_password") else: return "mysql_native_password" def _connect(**kwargs): """ wrap authentication credentials here """ connargs = dict() def _connarg(name, key=None, get_opts=True): """ Add key to connargs, only if name exists in our kwargs or, if get_opts is true, as mysql.<name> in __opts__ or __pillar__ If get_opts is true, evaluate in said order - kwargs, opts then pillar. To avoid collision with other functions, kwargs-based connection arguments are prefixed with 'connection_' (i.e. 'connection_host', 'connection_user', etc.). """ if key is None: key = name if name in kwargs: connargs[key] = kwargs[name] elif get_opts: prefix = "connection_" if name.startswith(prefix): try: name = name[len(prefix) :] except IndexError: return val = __salt__["config.option"]("mysql.{}".format(name), None) if val is not None: connargs[key] = val # If a default file is explicitly passed to kwargs, don't grab the # opts/pillar settings, as it can override info in the defaults file if "connection_default_file" in kwargs: get_opts = False else: get_opts = True connargs["client_flag"] = 0 available_client_flags = {} for flag in dir(CLIENT): if not flag.startswith("__"): available_client_flags[flag.lower()] = getattr(CLIENT, flag) for flag in kwargs.get("client_flags", []): if available_client_flags.get(flag): connargs["client_flag"] |= available_client_flags[flag] else: log.error("MySQL client flag %s not valid, ignoring.", flag) _connarg("connection_host", "host", get_opts) _connarg("connection_user", "user", get_opts) _connarg("connection_pass", "passwd", get_opts) _connarg("connection_port", "port", get_opts) _connarg("connection_db", "db", get_opts) _connarg("connection_conv", "conv", get_opts) _connarg("connection_unix_socket", "unix_socket", get_opts) _connarg("connection_default_file", "read_default_file", get_opts) _connarg("connection_default_group", "read_default_group", get_opts) # MySQLdb states that this is required for charset usage # but in fact it's more than it's internally activated # when charset is used, activating use_unicode here would # retrieve utf8 strings as unicode() objects in salt # and we do not want that. # _connarg('connection_use_unicode', 'use_unicode') connargs["use_unicode"] = False _connarg("connection_charset", "charset") # Ensure MySQldb knows the format we use for queries with arguments MySQLdb.paramstyle = "pyformat" for key in copy.deepcopy(connargs): if not connargs[key]: del connargs[key] if ( connargs.get("passwd", True) is None ): # If present but set to None. (Extreme edge case.) log.warning("MySQL password of None found. Attempting passwordless login.") connargs.pop("passwd") try: dbc = MySQLdb.connect(**connargs) except OperationalError as exc: err = "MySQL Error {}: {}".format(*exc.args) __context__["mysql.error"] = err log.error(err) return None except MySQLdb.err.InternalError as exc: err = "MySQL Error {}: {}".format(*exc.args) __context__["mysql.error"] = err log.error(err) return None dbc.autocommit(True) return dbc def _grant_to_tokens(grant): """ This should correspond fairly closely to the YAML rendering of a mysql_grants state which comes out as follows: OrderedDict([ ('whatever_identifier', OrderedDict([ ('mysql_grants.present', [ OrderedDict([('database', 'testdb.*')]), OrderedDict([('user', 'testuser')]), OrderedDict([('grant', 'ALTER, SELECT, LOCK TABLES')]), OrderedDict([('host', 'localhost')]) ] ) ]) ) ]) :param grant: An un-parsed MySQL GRANT statement str, like "GRANT SELECT, ALTER, LOCK TABLES ON `mydb`.* TO 'testuser'@'localhost'" or a dictionary with 'qry' and 'args' keys for 'user' and 'host'. :return: A Python dict with the following keys/values: - user: MySQL User - host: MySQL host - grant: [grant1, grant2] (ala SELECT, USAGE, etc) - database: MySQL DB """ log.debug("_grant_to_tokens entry '%s'", grant) dict_mode = False if isinstance(grant, dict): dict_mode = True # Everything coming in dictionary form was made for a MySQLdb execute # call and contain a '%%' escaping of '%' characters for MySQLdb # that we should remove here. grant_sql = grant.get("qry", "undefined").replace("%%", "%") sql_args = grant.get("args", {}) host = sql_args.get("host", "undefined") user = sql_args.get("user", "undefined") else: grant_sql = grant user = "" # the replace part is for presence of ` character in the db name # the shell escape is \` but mysql escape is ``. Spaces should not be # exploded as users or db names could contain spaces. # Examples of splitting: # "GRANT SELECT, LOCK TABLES, UPDATE, CREATE ON `test ``(:=saltdb)`.* # TO 'foo'@'localhost' WITH GRANT OPTION" # ['GRANT', 'SELECT', ',', 'LOCK', 'TABLES', ',', 'UPDATE', ',', 'CREATE', # 'ON', '`test `', '`(:=saltdb)`', '.', '*', 'TO', "'foo'", '@', # "'localhost'", 'WITH', 'GRANT', 'OPTION'] # # 'GRANT SELECT, INSERT, UPDATE, CREATE ON `te s.t\'"sa;ltdb`.`tbl ``\'"xx` # TO \'foo \' bar\'@\'localhost\'' # ['GRANT', 'SELECT', ',', 'INSERT', ',', 'UPDATE', ',', 'CREATE', 'ON', # '`te s.t\'"sa;ltdb`', '.', '`tbl `', '`\'"xx`', 'TO', "'foo '", "bar'", # '@', "'localhost'"] # # "GRANT USAGE ON *.* TO 'user \";--,?:&/\\'@'localhost'" # ['GRANT', 'USAGE', 'ON', '*', '.', '*', 'TO', '\'user ";--,?:&/\\\'', # '@', "'localhost'"] lex = shlex.shlex(grant_sql) lex.quotes = "'`" lex.whitespace_split = False lex.commenters = "" lex.wordchars += '"' exploded_grant = list(lex) grant_tokens = [] multiword_statement = [] position_tracker = 1 # Skip the initial 'GRANT' word token database = "" phrase = "grants" column = False current_grant = "" for token in exploded_grant[position_tracker:]: if token == "," and phrase == "grants": position_tracker += 1 continue if token == "(" and phrase == "grants": position_tracker += 1 column = True continue if token == ")" and phrase == "grants": position_tracker += 1 column = False continue if token == "ON" and phrase == "grants": phrase = "db" position_tracker += 1 continue elif token == "TO" and phrase == "tables": phrase = "user" position_tracker += 1 continue elif token == "@" and phrase == "pre-host": phrase = "host" position_tracker += 1 continue if phrase == "grants": # Read-ahead if ( exploded_grant[position_tracker + 1] == "," or exploded_grant[position_tracker + 1] == "ON" or exploded_grant[position_tracker + 1] in ["(", ")"] ): # End of token detected if multiword_statement: multiword_statement.append(token) grant_tokens.append(" ".join(multiword_statement)) multiword_statement = [] else: if not column: current_grant = token else: token = "{}.{}".format(current_grant, token) grant_tokens.append(token) else: # This is a multi-word, ala LOCK TABLES multiword_statement.append(token) elif phrase == "db": # the shlex splitter may have split on special database characters ` database += token # Read-ahead try: if exploded_grant[position_tracker + 1] == ".": phrase = "tables" except IndexError: break elif phrase == "tables": database += token elif phrase == "user": if dict_mode: break else: user += token # Read-ahead if exploded_grant[position_tracker + 1] == "@": phrase = "pre-host" elif phrase == "host": host = token break position_tracker += 1 try: if not dict_mode: user = user.strip("'") host = host.strip("'") log.debug( "grant to token '%s'::'%s'::'%s'::'%s'", user, host, grant_tokens, database ) except UnboundLocalError: host = "" return dict(user=user, host=host, grant=grant_tokens, database=database) def _resolve_grant_aliases(grants, server_version): """ There can be a situation where the database supports grants "A" and "B", where "B" is an alias for "A". In that case, when you want to grant "B" to a user, the database will actually report it added "A". We need to resolve those aliases to not report (wrong) errors. :param grants: the tokenized grants :param server_version: version string of the connected database """ if "MariaDB" not in server_version: return grants mariadb_version_compare_replication_replica = "10.5.1" mariadb_version_compare_binlog_monitor = "10.5.2" mariadb_version_compare_slave_monitor = "10.5.9" resolved_tokens = [] for token in grants: if ( salt.utils.versions.version_cmp( server_version, mariadb_version_compare_replication_replica ) >= 0 ): if token == "REPLICATION REPLICA": # https://mariadb.com/kb/en/grant/#replication-replica resolved_tokens.append("REPLICATION SLAVE") continue if ( salt.utils.versions.version_cmp( server_version, mariadb_version_compare_binlog_monitor ) >= 0 ): if token == "REPLICATION CLIENT": # https://mariadb.com/kb/en/grant/#replication-client resolved_tokens.append("BINLOG MONITOR") continue if ( salt.utils.versions.version_cmp( server_version, mariadb_version_compare_slave_monitor ) >= 0 ): if token == "REPLICA MONITOR": # https://mariadb.com/kb/en/grant/#replica-monitor resolved_tokens.append("SLAVE MONITOR") continue resolved_tokens.append(token) return resolved_tokens def quote_identifier(identifier, for_grants=False): r""" Return an identifier name (column, table, database, etc) escaped for MySQL This means surrounded by "`" character and escaping this character inside. It also means doubling the '%' character for MySQLdb internal usage. :param identifier: the table, column or database identifier :param for_grants: is False by default, when using database names on grant queries you should set it to True to also escape "_" and "%" characters as requested by MySQL. Note that theses characters should only be escaped when requesting grants on the database level (`my\_\%db`.*) but not for table level grants (`my_%db`.`foo`) CLI Example: .. code-block:: bash salt '*' mysql.quote_identifier 'foo`bar' """ if for_grants: return ( "`" + identifier.replace("`", "``").replace("_", r"\_").replace("%", r"%%") + "`" ) else: return "`" + identifier.replace("`", "``").replace("%", "%%") + "`" def _execute(cur, qry, args=None): """ Internal wrapper around MySQLdb cursor.execute() function MySQLDb does not apply the same filters when arguments are used with the query. For example '%' characters on the query must be encoded as '%%' and will be restored as '%' when arguments are applied. But when there're no arguments the '%%' is not managed. We cannot apply Identifier quoting in a predictable way if the query are not always applying the same filters. So this wrapper ensure this escape is not made if no arguments are used. """ if args is None or args == {}: qry = qry.replace("%%", "%") log.debug("Doing query: %s", qry) return cur.execute(qry) else: log.debug("Doing query: %s args: %s ", qry, repr(args)) return cur.execute(qry, args) def _sanitize_comments(content): # Remove comments which might affect line by line parsing # Regex should remove any text beginning with # (or --) not inside of ' or " if not HAS_SQLPARSE: log.error( "_sanitize_comments unavailable, no python sqlparse library installed." ) return content return sqlparse.format(content, strip_comments=True) def query(database, query, **connection_args): """ Run an arbitrary SQL query and return the results or the number of affected rows. CLI Example: .. code-block:: bash salt '*' mysql.query mydb "UPDATE mytable set myfield=1 limit 1" Return data: .. code-block:: python {'query time': {'human': '39.0ms', 'raw': '0.03899'}, 'rows affected': 1L} CLI Example: .. code-block:: bash salt '*' mysql.query mydb "SELECT id,name,cash from users limit 3" Return data: .. code-block:: python {'columns': ('id', 'name', 'cash'), 'query time': {'human': '1.0ms', 'raw': '0.001'}, 'results': ((1L, 'User 1', Decimal('110.000000')), (2L, 'User 2', Decimal('215.636756')), (3L, 'User 3', Decimal('0.040000'))), 'rows returned': 3L} CLI Example: .. code-block:: bash salt '*' mysql.query mydb 'INSERT into users values (null,"user 4", 5)' Return data: .. code-block:: python {'query time': {'human': '25.6ms', 'raw': '0.02563'}, 'rows affected': 1L} CLI Example: .. code-block:: bash salt '*' mysql.query mydb 'DELETE from users where id = 4 limit 1' Return data: .. code-block:: python {'query time': {'human': '39.0ms', 'raw': '0.03899'}, 'rows affected': 1L} Jinja Example: Run a query on ``mydb`` and use row 0, column 0's data. .. code-block:: jinja {{ salt['mysql.query']('mydb', 'SELECT info from mytable limit 1')['results'][0][0] }} """ # Doesn't do anything about sql warnings, e.g. empty values on an insert. # I don't think it handles multiple queries at once, so adding "commit" # might not work. # The following 3 lines stops MySQLdb from converting the MySQL results # into Python objects. It leaves them as strings. orig_conv = MySQLdb.converters.conversions conv_iter = iter(orig_conv) conv = dict(zip(conv_iter, [str] * len(orig_conv))) # some converters are lists, do not break theses conv_mysqldb = {"MYSQLDB": True} if conv_mysqldb.get(MySQLdb.__package__.upper()): conv[FIELD_TYPE.BLOB] = [ (FLAG.BINARY, str), ] conv[FIELD_TYPE.STRING] = [ (FLAG.BINARY, str), ] conv[FIELD_TYPE.VAR_STRING] = [ (FLAG.BINARY, str), ] conv[FIELD_TYPE.VARCHAR] = [ (FLAG.BINARY, str), ] connection_args.update({"connection_db": database, "connection_conv": conv}) dbc = _connect(**connection_args) if dbc is None: return {} cur = dbc.cursor() start = time.time() log.debug("Using db: %s to run query %s", database, query) try: affected = _execute(cur, query) except OperationalError as exc: err = "MySQL Error {}: {}".format(*exc.args) __context__["mysql.error"] = err log.error(err) return False results = cur.fetchall() elapsed = time.time() - start if elapsed < 0.200: elapsed_h = str(round(elapsed * 1000, 1)) + "ms" else: elapsed_h = str(round(elapsed, 2)) + "s" ret = {} ret["query time"] = {"human": elapsed_h, "raw": str(round(elapsed, 5))} select_keywords = ["SELECT", "SHOW", "DESC"] select_query = False for keyword in select_keywords: if query.upper().strip().startswith(keyword): select_query = True break if select_query: ret["rows returned"] = affected columns = () for column in cur.description: columns += (column[0],) ret["columns"] = columns ret["results"] = results return ret else: ret["rows affected"] = affected return ret def file_query(database, file_name, **connection_args): """ Run an arbitrary SQL query from the specified file and return the the number of affected rows. .. versionadded:: 2017.7.0 database database to run script inside file_name File name of the script. This can be on the minion, or a file that is reachable by the fileserver CLI Example: .. code-block:: bash salt '*' mysql.file_query mydb file_name=/tmp/sqlfile.sql salt '*' mysql.file_query mydb file_name=salt://sqlfile.sql Return data: .. code-block:: python {'query time': {'human': '39.0ms', 'raw': '0.03899'}, 'rows affected': 1L} """ if not HAS_SQLPARSE: log.error("mysql.file_query unavailable, no python sqlparse library installed.") return False if any( file_name.startswith(proto) for proto in ("salt://", "http://", "https://", "swift://", "s3://") ): file_name = __salt__["cp.cache_file"](file_name) if os.path.exists(file_name): with salt.utils.files.fopen(file_name, "r") as ifile: contents = salt.utils.stringutils.to_unicode(ifile.read()) else: log.error('File "%s" does not exist', file_name) return False query_string = "" ret = { "rows returned": 0, "columns": [], "results": [], "rows affected": 0, "query time": {"raw": 0}, } contents = _sanitize_comments(contents) # Walk the each line of the sql file to get accurate row affected results for line in contents.splitlines(): if not re.search(r"[^-;]+;", line): # keep appending lines that don't end in ; query_string = query_string + line else: query_string = ( query_string + line ) # append lines that end with ; and run query query_result = query(database, query_string, **connection_args) query_string = "" if query_result is False: # Fail out on error return False if "query time" in query_result: ret["query time"]["raw"] += float(query_result["query time"]["raw"]) if "rows returned" in query_result: ret["rows returned"] += query_result["rows returned"] if "columns" in query_result: ret["columns"].append(query_result["columns"]) if "results" in query_result: ret["results"].append(query_result["results"]) if "rows affected" in query_result: ret["rows affected"] += query_result["rows affected"] ret["query time"]["human"] = str(round(float(ret["query time"]["raw"]), 2)) + "s" ret["query time"]["raw"] = round(float(ret["query time"]["raw"]), 5) # Remove empty keys in ret ret = {k: v for k, v in ret.items() if v} return ret def status(**connection_args): """ Return the status of a MySQL server using the output from the ``SHOW STATUS`` query. CLI Example: .. code-block:: bash salt '*' mysql.status """ dbc = _connect(**connection_args) if dbc is None: return {} cur = dbc.cursor() qry = "SHOW STATUS" try: _execute(cur, qry) except OperationalError as exc: err = "MySQL Error {}: {}".format(*exc.args) __context__["mysql.error"] = err log.error(err) return {} ret = {} for _ in range(cur.rowcount): row = cur.fetchone() ret[row[0]] = row[1] return ret def version(**connection_args): """ Return the version of a MySQL server using the output from the ``SELECT VERSION()`` query. CLI Example: .. code-block:: bash salt '*' mysql.version """ if "mysql.version" in __context__: return __context__["mysql.version"] dbc = _connect(**connection_args) if dbc is None: return "" cur = dbc.cursor() qry = "SELECT VERSION()" try: _execute(cur, qry) except MySQLdb.OperationalError as exc: err = "MySQL Error {}: {}".format(*exc.args) __context__["mysql.error"] = err log.error(err) return "" try: __context__["mysql.version"] = salt.utils.data.decode(cur.fetchone()[0]) return __context__["mysql.version"] except IndexError: return "" def slave_lag(**connection_args): """ Return the number of seconds that a slave SQL server is lagging behind the master, if the host is not a slave it will return -1. If the server is configured to be a slave for replication but slave IO is not running then -2 will be returned. If there was an error connecting to the database or checking the slave status, -3 will be returned. CLI Example: .. code-block:: bash salt '*' mysql.slave_lag """ dbc = _connect(**connection_args) if dbc is None: return -3 cur = dbc.cursor(MySQLdb.cursors.DictCursor) qry = "show slave status" try: _execute(cur, qry) except MySQLdb.OperationalError as exc: err = "MySQL Error {}: {}".format(*exc.args) __context__["mysql.error"] = err log.error(err) return -3 results = cur.fetchone() if cur.rowcount == 0: # Server is not a slave if master is not defined. Return empty tuple # in this case. Could probably check to see if Slave_IO_Running and # Slave_SQL_Running are both set to 'Yes' as well to be really really # sure that it is a slave. return -1 else: if results["Slave_IO_Running"] == "Yes": return results["Seconds_Behind_Master"] else: # Replication is broken if you get here. return -2 def free_slave(**connection_args): """ Frees a slave from its master. This is a WIP, do not use. CLI Example: .. code-block:: bash salt '*' mysql.free_slave """ slave_db = _connect(**connection_args) if slave_db is None: return "" slave_cur = slave_db.cursor(MySQLdb.cursors.DictCursor) slave_cur.execute("show slave status") slave_status = slave_cur.fetchone() master = {"host": slave_status["Master_Host"]} try: # Try to connect to the master and flush logs before promoting to # master. This may fail if the master is no longer available. # I am also assuming that the admin password is the same on both # servers here, and only overriding the host option in the connect # function. master_db = _connect(**master) if master_db is None: return "" master_cur = master_db.cursor() master_cur.execute("flush logs") master_db.close() except MySQLdb.OperationalError: pass slave_cur.execute("stop slave") slave_cur.execute("reset master") slave_cur.execute("change master to MASTER_HOST=") slave_cur.execute("show slave status") results = slave_cur.fetchone() if results is None: return "promoted" else: return "failed" # Database related actions def db_list(**connection_args): """ Return a list of databases of a MySQL server using the output from the ``SHOW DATABASES`` query. CLI Example: .. code-block:: bash salt '*' mysql.db_list """ dbc = _connect(**connection_args) if dbc is None: return [] cur = dbc.cursor() qry = "SHOW DATABASES" try: _execute(cur, qry) except MySQLdb.OperationalError as exc: err = "MySQL Error {}: {}".format(*exc.args) __context__["mysql.error"] = err log.error(err) return [] ret = [] results = cur.fetchall() for dbs in results: ret.append(dbs[0]) log.debug(ret) return ret def alter_db(name, character_set=None, collate=None, **connection_args): """ Modify database using ``ALTER DATABASE %(dbname)s CHARACTER SET %(charset)s COLLATE %(collation)s;`` query. CLI Example: .. code-block:: bash salt '*' mysql.alter_db testdb charset='latin1' """ dbc = _connect(**connection_args) if dbc is None: return [] cur = dbc.cursor() existing = db_get(name, **connection_args) # escaping database name is not required because of backticks in query expression qry = "ALTER DATABASE `{}` CHARACTER SET {} COLLATE {};".format( name, character_set or existing.get("character_set"), collate or existing.get("collate"), ) args = {} try: if _execute(cur, qry, args): log.info("DB '%s' altered", name) return True except MySQLdb.OperationalError as exc: err = "MySQL Error {}: {}".format(*exc.args) __context__["mysql.error"] = err log.error(err) return False def db_get(name, **connection_args): """ Return a list of databases of a MySQL server using the output from the ``SELECT DEFAULT_CHARACTER_SET_NAME, DEFAULT_COLLATION_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME='dbname';`` query. CLI Example: .. code-block:: bash salt '*' mysql.db_get test """ dbc = _connect(**connection_args) if dbc is None: return [] cur = dbc.cursor() qry = ( "SELECT DEFAULT_CHARACTER_SET_NAME, DEFAULT_COLLATION_NAME FROM " "INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME=%(dbname)s;" ) args = {"dbname": name} try: _execute(cur, qry, args) except MySQLdb.OperationalError as exc: err = "MySQL Error {}: {}".format(*exc.args) __context__["mysql.error"] = err log.error(err) return [] if cur.rowcount: rows = cur.fetchall() return {"character_set": rows[0][0], "collate": rows[0][1]} return {} def db_tables(name, **connection_args): """ Shows the tables in the given MySQL database (if exists) CLI Example: .. code-block:: bash salt '*' mysql.db_tables 'database' """ if not db_exists(name, **connection_args): log.info("Database '%s' does not exist", name) return False dbc = _connect(**connection_args) if dbc is None: return [] cur = dbc.cursor() s_name = quote_identifier(name) # identifiers cannot be used as values qry = "SHOW TABLES IN {}".format(s_name) try: _execute(cur, qry) except MySQLdb.OperationalError as exc: err = "MySQL Error {}: {}".format(*exc.args) __context__["mysql.error"] = err log.error(err) return [] ret = [] results = cur.fetchall() for table in results: ret.append(table[0]) log.debug(ret) return ret def db_exists(name, **connection_args): """ Checks if a database exists on the MySQL server. CLI Example: .. code-block:: bash salt '*' mysql.db_exists 'dbname' """ dbc = _connect(**connection_args) if dbc is None: return False cur = dbc.cursor() # Warn: here db identifier is not backtyped but should be # escaped as a string value. Note also that LIKE special characters # '_' and '%' should also be escaped. args = {"dbname": name} qry = "SHOW DATABASES LIKE %(dbname)s;" try: _execute(cur, qry, args) except MySQLdb.OperationalError as exc: err = "MySQL Error {}: {}".format(*exc.args) __context__["mysql.error"] = err log.error(err) return False cur.fetchall() return cur.rowcount == 1 def db_create(name, character_set=None, collate=None, **connection_args): """ Adds a databases to the MySQL server. name The name of the database to manage character_set The character set, if left empty the MySQL default will be used collate The collation, if left empty the MySQL default will be used CLI Example: .. code-block:: bash salt '*' mysql.db_create 'dbname' salt '*' mysql.db_create 'dbname' 'utf8' 'utf8_general_ci' """ # check if db exists if db_exists(name, **connection_args): log.info("DB '%s' already exists", name) return False # db doesn't exist, proceed dbc = _connect(**connection_args) if dbc is None: return False cur = dbc.cursor() s_name = quote_identifier(name) # identifiers cannot be used as values qry = "CREATE DATABASE IF NOT EXISTS {}".format(s_name) args = {} if character_set is not None: qry += " CHARACTER SET %(character_set)s" args["character_set"] = character_set if collate is not None: qry += " COLLATE %(collate)s" args["collate"] = collate qry += ";" try: if _execute(cur, qry, args): log.info("DB '%s' created", name) return True except MySQLdb.OperationalError as exc: err = "MySQL Error {}: {}".format(*exc.args) __context__["mysql.error"] = err log.error(err) return False def db_remove(name, **connection_args): """ Removes a databases from the MySQL server. CLI Example: .. code-block:: bash salt '*' mysql.db_remove 'dbname' """ # check if db exists if not db_exists(name, **connection_args): log.info("DB '%s' does not exist", name) return False if name in ("mysql", "information_scheme"): log.info("DB '%s' may not be removed", name) return False # db does exists, proceed dbc = _connect(**connection_args) if dbc is None: return False cur = dbc.cursor() s_name = quote_identifier(name) # identifiers cannot be used as values qry = "DROP DATABASE {};".format(s_name) try: _execute(cur, qry) except MySQLdb.OperationalError as exc: err = "MySQL Error {}: {}".format(*exc.args) __context__["mysql.error"] = err log.error(err) return False if not db_exists(name, **connection_args): log.info("Database '%s' has been removed", name) return True log.info("Database '%s' has not been removed", name) return False # User related actions def user_list(**connection_args): """ Return a list of users on a MySQL server CLI Example: .. code-block:: bash salt '*' mysql.user_list """ dbc = _connect(**connection_args) if dbc is None: return [] cur = dbc.cursor(MySQLdb.cursors.DictCursor) try: qry = "SELECT User,Host FROM mysql.user" _execute(cur, qry) except MySQLdb.OperationalError as exc: err = "MySQL Error {}: {}".format(*exc.args) __context__["mysql.error"] = err log.error(err) return [] results = cur.fetchall() log.debug(results) return results def _mysql_user_exists( user, host="localhost", password=None, password_hash=None, passwordless=False, unix_socket=False, password_column=None, auth_plugin="mysql_native_password", **connection_args ): server_version = salt.utils.data.decode(version(**connection_args)) compare_version = "8.0.11" qry = "SELECT User,Host FROM mysql.user WHERE User = %(user)s AND Host = %(host)s" args = {} args["user"] = user args["host"] = host if salt.utils.data.is_true(passwordless): if salt.utils.data.is_true(unix_socket): qry += " AND plugin=%(unix_socket)s" args["unix_socket"] = "auth_socket" else: qry += " AND " + password_column + " = ''" elif password: if salt.utils.versions.version_cmp(server_version, compare_version) >= 0: if auth_plugin == "mysql_native_password": _password = __mysql_hash_password(str(password)) qry += " AND " + password_column + " = %(password)s" args["password"] = str(_password) else: err = "Unable to verify password." log.error(err) __context__["mysql.error"] = err else: qry += " AND " + password_column + " = PASSWORD(%(password)s)" args["password"] = str(password) elif password_hash: qry += " AND " + password_column + " = %(password)s" args["password"] = password_hash return qry, args def _mariadb_user_exists( user, host="localhost", password=None, password_hash=None, passwordless=False, unix_socket=False, password_column=None, auth_plugin="mysql_native_password", **connection_args ): qry = "SELECT User,Host FROM mysql.user WHERE User = %(user)s AND Host = %(host)s" args = {} args["user"] = user args["host"] = host if salt.utils.data.is_true(passwordless): if salt.utils.data.is_true(unix_socket): qry += " AND plugin=%(unix_socket)s" args["unix_socket"] = "unix_socket" else: qry += " AND " + password_column + " = ''" elif password: qry += " AND " + password_column + " = PASSWORD(%(password)s)" args["password"] = str(password) elif password_hash: qry += " AND " + password_column + " = %(password)s" args["password"] = password_hash return qry, args def user_exists( user, host="localhost", password=None, password_hash=None, passwordless=False, unix_socket=False, password_column=None, **connection_args ): """ Checks if a user exists on the MySQL server. A login can be checked to see if passwordless login is permitted by omitting ``password`` and ``password_hash``, and using ``passwordless=True``. .. versionadded:: 0.16.2 The ``passwordless`` option was added. CLI Example: .. code-block:: bash salt '*' mysql.user_exists 'username' 'hostname' 'password' salt '*' mysql.user_exists 'username' 'hostname' password_hash='hash' salt '*' mysql.user_exists 'username' passwordless=True salt '*' mysql.user_exists 'username' password_column='authentication_string' """ run_verify = False server_version = salt.utils.data.decode(version(**connection_args)) if not server_version and password: # Did we fail to connect with the user we are checking # Its password might have previously change with the same command/state # Clear the previous error __context__["mysql.error"] = None connection_args["connection_pass"] = password server_version = salt.utils.data.decode(version(**connection_args)) if not server_version: last_err = __context__["mysql.error"] err = ( "MySQL Error: Unable to fetch current server version. Last error was:" ' "{}"'.format(last_err) ) log.error(err) return False dbc = _connect(**connection_args) # Did we fail to connect with the user we are checking # Its password might have previously change with the same command/state if ( dbc is None and __context__["mysql.error"].startswith( "MySQL Error 1045: Access denied for user '{}'@".format(user) ) and password ): # Clear the previous error __context__["mysql.error"] = None connection_args["connection_pass"] = password dbc = _connect(**connection_args) if dbc is None: return False if not password_column: password_column = __password_column(**connection_args) auth_plugin = __get_auth_plugin(user, host, **connection_args) cur = dbc.cursor() if "MariaDB" in server_version: qry, args = _mariadb_user_exists( user, host, password, password_hash, passwordless, unix_socket, password_column=password_column, auth_plugin=auth_plugin, **connection_args ) else: qry, args = _mysql_user_exists( user, host, password, password_hash, passwordless, unix_socket, password_column=password_column, auth_plugin=auth_plugin, **connection_args ) try: _execute(cur, qry, args) except MySQLdb.OperationalError as exc: err = "MySQL Error {}: {}".format(*exc.args) __context__["mysql.error"] = err log.error(err) return False return cur.rowcount == 1 def user_info(user, host="localhost", **connection_args): """ Get full info on a MySQL user CLI Example: .. code-block:: bash salt '*' mysql.user_info root localhost """ dbc = _connect(**connection_args) if dbc is None: return False cur = dbc.cursor(MySQLdb.cursors.DictCursor) qry = "SELECT * FROM mysql.user WHERE User = %(user)s AND Host = %(host)s" args = {} args["user"] = user args["host"] = host try: _execute(cur, qry, args) except MySQLdb.OperationalError as exc: err = "MySQL Error {}: {}".format(*exc.args) __context__["mysql.error"] = err log.error(err) return False result = cur.fetchone() log.debug(result) return result def _mysql_user_create( user, host="localhost", password=None, password_hash=None, allow_passwordless=False, unix_socket=False, password_column=None, auth_plugin="mysql_native_password", **connection_args ): server_version = salt.utils.data.decode(version(**connection_args)) compare_version = "8.0.11" qry = "CREATE USER %(user)s@%(host)s" args = {} args["user"] = user args["host"] = host if unix_socket: if not plugin_status("auth_socket", **connection_args): err = "The auth_socket plugin is not enabled." log.error(err) __context__["mysql.error"] = err qry = False else: if host == "localhost": qry += " IDENTIFIED WITH auth_socket" else: log.error("Auth via unix_socket can be set only for host=localhost") __context__["mysql.error"] = err qry = False else: if not salt.utils.data.is_true(allow_passwordless): if password is not None: if ( salt.utils.versions.version_cmp(server_version, compare_version) >= 0 ): args["auth_plugin"] = auth_plugin qry += " IDENTIFIED WITH %(auth_plugin)s BY %(password)s" else: qry += " IDENTIFIED BY %(password)s" args["password"] = str(password) elif password_hash is not None: if ( salt.utils.versions.version_cmp(server_version, compare_version) >= 0 ): args["auth_plugin"] = auth_plugin qry += " IDENTIFIED WITH %(auth_plugin)s AS %(password)s" else: qry += " IDENTIFIED BY PASSWORD %(password)s" args["password"] = password_hash else: log.error( "password or password_hash must be specified, unless " "allow_passwordless=True" ) qry = False return qry, args def _mariadb_user_create( user, host="localhost", password=None, password_hash=None, allow_passwordless=False, unix_socket=False, password_column=None, auth_plugin="mysql_native_password", **connection_args ): qry = "CREATE USER %(user)s@%(host)s" args = {} args["user"] = user args["host"] = host if unix_socket: if not plugin_status("unix_socket", **connection_args): err = "The unix_socket plugin is not enabled." log.error(err) __context__["mysql.error"] = err qry = False else: if host == "localhost": qry += " IDENTIFIED VIA unix_socket" else: log.error("Auth via unix_socket can be set only for host=localhost") __context__["mysql.error"] = err qry = False else: if not salt.utils.data.is_true(allow_passwordless): if password is not None: qry += " IDENTIFIED BY %(password)s" args["password"] = str(password) elif password_hash is not None: qry += " IDENTIFIED BY PASSWORD %(password)s" args["password"] = password_hash else: log.error( "password or password_hash must be specified, unless " "allow_passwordless=True" ) qry = False return qry, args def user_create( user, host="localhost", password=None, password_hash=None, allow_passwordless=False, unix_socket=False, password_column=None, auth_plugin="mysql_native_password", **connection_args ): """ Creates a MySQL user host Host for which this user/password combo applies password The password to use for the new user. Will take precedence over the ``password_hash`` option if both are specified. password_hash The password in hashed form. Be sure to quote the password because YAML doesn't like the ``*``. A password hash can be obtained from the mysql command-line client like so:: mysql> SELECT PASSWORD('mypass'); +-------------------------------------------+ | PASSWORD('mypass') | +-------------------------------------------+ | *6C8989366EAF75BB670AD8EA7A7FC1176A95CEF4 | +-------------------------------------------+ 1 row in set (0.00 sec) allow_passwordless If ``True``, then ``password`` and ``password_hash`` can be omitted (or set to ``None``) to permit a passwordless login. unix_socket If ``True`` and allow_passwordless is ``True`` then will be used unix_socket auth plugin. password_column The password column to use in the user table. auth_plugin The authentication plugin to use, default is to use the mysql_native_password plugin. .. versionadded:: 0.16.2 The ``allow_passwordless`` option was added. CLI Examples: .. code-block:: bash salt '*' mysql.user_create 'username' 'hostname' 'password' salt '*' mysql.user_create 'username' 'hostname' password_hash='hash' salt '*' mysql.user_create 'username' 'hostname' allow_passwordless=True """ server_version = salt.utils.data.decode(version(**connection_args)) if not server_version and password: # Did we fail to connect with the user we are checking # Its password might have previously change with the same command/state # Clear the previous error __context__["mysql.error"] = None connection_args["connection_pass"] = password server_version = salt.utils.data.decode(version(**connection_args)) if not server_version: last_err = __context__["mysql.error"] err = ( "MySQL Error: Unable to fetch current server version. Last error was:" ' "{}"'.format(last_err) ) log.error(err) return False if user_exists(user, host, **connection_args): log.info("User '%s'@'%s' already exists", user, host) return False dbc = _connect(**connection_args) if dbc is None: return False if not password_column: password_column = __password_column(**connection_args) cur = dbc.cursor() if "MariaDB" in server_version: qry, args = _mariadb_user_create( user, host, password, password_hash, allow_passwordless, unix_socket, password_column=password_column, auth_plugin=auth_plugin, **connection_args ) else: qry, args = _mysql_user_create( user, host, password, password_hash, allow_passwordless, unix_socket, password_column=password_column, auth_plugin=auth_plugin, **connection_args ) if isinstance(qry, bool): return qry try: _execute(cur, qry, args) except MySQLdb.OperationalError as exc: err = "MySQL Error {}: {}".format(*exc.args) __context__["mysql.error"] = err log.error(err) return False if user_exists( user, host, password, password_hash, password_column=password_column, **connection_args ): msg = "User '{}'@'{}' has been created".format(user, host) if not any((password, password_hash)): msg += " with passwordless login" log.info(msg) return True log.info("User '%s'@'%s' was not created", user, host) return False def _mysql_user_chpass( user, host="localhost", password=None, password_hash=None, allow_passwordless=False, unix_socket=None, password_column=None, auth_plugin="mysql_native_password", **connection_args ): server_version = salt.utils.data.decode(version(**connection_args)) compare_version = "8.0.11" args = {} if password is not None: if salt.utils.versions.version_cmp(server_version, compare_version) >= 0: password_sql = "%(password)s" else: password_sql = "PASSWORD(%(password)s)" args["password"] = password elif password_hash is not None: password_sql = "%(password)s" args["password"] = password_hash elif not salt.utils.data.is_true(allow_passwordless): log.error( "password or password_hash must be specified, unless " "allow_passwordless=True" ) return False else: password_sql = "''" args["user"] = user args["host"] = host if salt.utils.versions.version_cmp(server_version, compare_version) >= 0: args["auth_plugin"] = auth_plugin qry = "ALTER USER %(user)s@%(host)s IDENTIFIED WITH %(auth_plugin)s " if password is not None: qry += "BY %(password)s;" elif password_hash is not None: qry += "AS %(password)s;" else: qry = ( "UPDATE mysql.user SET " + password_column + "=" + password_sql + " WHERE User=%(user)s AND Host = %(host)s;" ) if salt.utils.data.is_true(allow_passwordless) and salt.utils.data.is_true( unix_socket ): if host == "localhost": if not plugin_status("auth_socket", **connection_args): err = "The auth_socket plugin is not enabled." log.error(err) __context__["mysql.error"] = err qry = False else: args["unix_socket"] = "auth_socket" if ( salt.utils.versions.version_cmp(server_version, compare_version) >= 0 ): qry = ( "ALTER USER %(user)s@%(host)s IDENTIFIED WITH %(unix_socket)s" " AS %(user)s;" ) else: qry = ( "UPDATE mysql.user SET " + password_column + "=" + password_sql + ", plugin=%(unix_socket)s" + " WHERE User=%(user)s AND Host = %(host)s;" ) else: log.error("Auth via unix_socket can be set only for host=localhost") return qry, args def _mariadb_user_chpass( user, host="localhost", password=None, password_hash=None, allow_passwordless=False, unix_socket=None, password_column=None, auth_plugin="mysql_native_password", **connection_args ): server_version = salt.utils.data.decode(version(**connection_args)) compare_version = "10.4" args = {} if password is not None: password_sql = "PASSWORD(%(password)s)" args["password"] = password elif password_hash is not None: password_sql = "%(password)s" args["password"] = password_hash elif not salt.utils.data.is_true(allow_passwordless): log.error( "password or password_hash must be specified, unless " "allow_passwordless=True" ) return False else: password_sql = "''" args["user"] = user args["host"] = host if salt.utils.versions.version_cmp(server_version, compare_version) >= 0: args["auth_plugin"] = auth_plugin qry = "ALTER USER %(user)s@%(host)s IDENTIFIED VIA %(auth_plugin)s USING " qry += password_sql else: qry = ( "UPDATE mysql.user SET " + password_column + "=" + password_sql + " WHERE User=%(user)s AND Host = %(host)s;" ) if salt.utils.data.is_true(allow_passwordless) and salt.utils.data.is_true( unix_socket ): if host == "localhost": if not plugin_status("unix_socket", **connection_args): err = "The unix_socket plugin is not enabled." log.error(err) __context__["mysql.error"] = err qry = False else: args["unix_socket"] = "unix_socket" qry = ( "UPDATE mysql.user SET " + password_column + "=" + password_sql + ", plugin=%(unix_socket)s" + " WHERE User=%(user)s AND Host = %(host)s;" ) else: log.error("Auth via unix_socket can be set only for host=localhost") return qry, args def user_chpass( user, host="localhost", password=None, password_hash=None, allow_passwordless=False, unix_socket=None, password_column=None, **connection_args ): """ Change password for a MySQL user host Host for which this user/password combo applies password The password to set for the new user. Will take precedence over the ``password_hash`` option if both are specified. password_hash The password in hashed form. Be sure to quote the password because YAML doesn't like the ``*``. A password hash can be obtained from the mysql command-line client like so:: mysql> SELECT PASSWORD('mypass'); +-------------------------------------------+ | PASSWORD('mypass') | +-------------------------------------------+ | *6C8989366EAF75BB670AD8EA7A7FC1176A95CEF4 | +-------------------------------------------+ 1 row in set (0.00 sec) allow_passwordless If ``True``, then ``password`` and ``password_hash`` can be omitted (or set to ``None``) to permit a passwordless login. .. versionadded:: 0.16.2 The ``allow_passwordless`` option was added. CLI Examples: .. code-block:: bash salt '*' mysql.user_chpass frank localhost newpassword salt '*' mysql.user_chpass frank localhost password_hash='hash' salt '*' mysql.user_chpass frank localhost allow_passwordless=True """ server_version = salt.utils.data.decode(version(**connection_args)) if not server_version and password: # Did we fail to connect with the user we are checking # Its password might have previously change with the same command/state # Clear the previous error __context__["mysql.error"] = None connection_args["connection_pass"] = password server_version = salt.utils.data.decode(version(**connection_args)) if not server_version: last_err = __context__["mysql.error"] err = ( "MySQL Error: Unable to fetch current server version. Last error was:" ' "{}"'.format(last_err) ) log.error(err) return False if not user_exists(user, host, **connection_args): log.info("User '%s'@'%s' does not exists", user, host) return False dbc = _connect(**connection_args) if dbc is None: return False if not password_column: password_column = __password_column(**connection_args) auth_plugin = __get_auth_plugin(user, host, **connection_args) cur = dbc.cursor() if "MariaDB" in server_version: qry, args = _mariadb_user_chpass( user, host, password, password_hash, allow_passwordless, unix_socket, password_column=password_column, auth_plugin=auth_plugin, **connection_args ) else: qry, args = _mysql_user_chpass( user, host, password, password_hash, allow_passwordless, unix_socket, password_column=password_column, auth_plugin=auth_plugin, **connection_args ) try: result = _execute(cur, qry, args) except MySQLdb.OperationalError as exc: err = "MySQL Error {}: {}".format(*exc.args) __context__["mysql.error"] = err log.error(err) return False compare_version = "10.4.0" if "MariaDB" in server_version else "8.0.11" res = False if salt.utils.versions.version_cmp(server_version, compare_version) >= 0: _execute(cur, "FLUSH PRIVILEGES;") res = True else: if result: _execute(cur, "FLUSH PRIVILEGES;") res = True if res: log.info( "Password for user '%s'@'%s' has been %s", user, host, "changed" if any((password, password_hash)) else "cleared", ) return True else: log.info( "Password for user '%s'@'%s' was not %s", user, host, "changed" if any((password, password_hash)) else "cleared", ) return False def user_remove(user, host="localhost", **connection_args): """ Delete MySQL user CLI Example: .. code-block:: bash salt '*' mysql.user_remove frank localhost """ if not user_exists(user, host, **connection_args): err = "User '%s'@'%s' does not exists", user, host __context__["mysql.error"] = err log.info(err) return False dbc = _connect(**connection_args) if dbc is None: return False cur = dbc.cursor() qry = "DROP USER %(user)s@%(host)s" args = {} args["user"] = user args["host"] = host try: _execute(cur, qry, args) except MySQLdb.OperationalError as exc: err = "MySQL Error {}: {}".format(*exc.args) __context__["mysql.error"] = err log.error(err) return False if not user_exists(user, host, **connection_args): log.info("User '%s'@'%s' has been removed", user, host) return True log.info("User '%s'@'%s' has NOT been removed", user, host) return False def tokenize_grant(grant): """ External wrapper function :param grant: :return: dict CLI Example: .. code-block:: bash salt '*' mysql.tokenize_grant \ "GRANT SELECT, INSERT ON testdb.* TO 'testuser'@'localhost'" """ return _grant_to_tokens(grant) # Maintenance def db_check(name, table=None, **connection_args): """ Repairs the full database or just a given table CLI Example: .. code-block:: bash salt '*' mysql.db_check dbname salt '*' mysql.db_check dbname dbtable """ ret = [] if table is None: # we need to check all tables tables = db_tables(name, **connection_args) for table in tables: log.info("Checking table '%s' in db '%s'..", name, table) ret.append(__check_table(name, table, **connection_args)) else: log.info("Checking table '%s' in db '%s'..", name, table) ret = __check_table(name, table, **connection_args) return ret def db_repair(name, table=None, **connection_args): """ Repairs the full database or just a given table CLI Example: .. code-block:: bash salt '*' mysql.db_repair dbname """ ret = [] if table is None: # we need to repair all tables tables = db_tables(name, **connection_args) for table in tables: log.info("Repairing table '%s' in db '%s'..", name, table) ret.append(__repair_table(name, table, **connection_args)) else: log.info("Repairing table '%s' in db '%s'..", name, table) ret = __repair_table(name, table, **connection_args) return ret def db_optimize(name, table=None, **connection_args): """ Optimizes the full database or just a given table CLI Example: .. code-block:: bash salt '*' mysql.db_optimize dbname """ ret = [] if table is None: # we need to optimize all tables tables = db_tables(name, **connection_args) for table in tables: log.info("Optimizing table '%s' in db '%s'..", name, table) ret.append(__optimize_table(name, table, **connection_args)) else: log.info("Optimizing table '%s' in db '%s'..", name, table) ret = __optimize_table(name, table, **connection_args) return ret # Grants def __grant_normalize(grant): # MySQL normalizes ALL to ALL PRIVILEGES, we do the same so that # grant_exists and grant_add ALL work correctly if grant.strip().upper() == "ALL": grant = "ALL PRIVILEGES" # Grants are paste directly in SQL, must filter it exploded_grants = __grant_split(grant) for chkgrant, _ in exploded_grants: if chkgrant.strip().upper() not in __grants__: raise Exception("Invalid grant : '{}'".format(chkgrant)) return grant def __grant_split(grant): pattern = re.compile(r"([\w\s]+)(\([^)(]*\))?\s*,?") return pattern.findall(grant) def __ssl_option_sanitize(ssl_option): new_ssl_option = [] # Like most other "salt dsl" YAML structures, ssl_option is a list of single-element dicts for opt in ssl_option: key = next(iter(opt.keys())) normal_key = key.strip().upper() if normal_key not in __ssl_options__: raise Exception("Invalid SSL option : '{}'".format(key)) if normal_key in __ssl_options_parameterized__: # SSL option parameters (cipher, issuer, subject) are pasted directly to SQL so # we need to sanitize for single quotes... new_ssl_option.append( "{} '{}'".format(normal_key, opt[key].replace("'", "")) ) # omit if falsey elif opt[key]: new_ssl_option.append(normal_key) return " REQUIRE " + " AND ".join(new_ssl_option) def __grant_generate( grant, database, user, host="localhost", grant_option=False, escape=True, ssl_option=False, ): """ Validate grants and build the query that could set the given grants Note that this query contains arguments for user and host but not for grants or database. """ # TODO: Re-order the grant so it is according to the # SHOW GRANTS for xxx@yyy query (SELECT comes first, etc) grant = re.sub(r"\s*,\s*", ", ", grant).upper() grant = __grant_normalize(grant) db_part = database.rpartition(".") dbc = db_part[0] table = db_part[2] if escape: if dbc != "*": # _ and % are authorized on GRANT queries and should get escaped # on the db name, but only if not requesting a table level grant dbc = quote_identifier(dbc, for_grants=(table == "*")) if table != "*": table = quote_identifier(table) # identifiers cannot be used as values, and same thing for grants qry = "GRANT {} ON {}.{} TO %(user)s@%(host)s".format(grant, dbc, table) args = {} args["user"] = user args["host"] = host if ssl_option and isinstance(ssl_option, list): qry += __ssl_option_sanitize(ssl_option) if salt.utils.data.is_true(grant_option): qry += " WITH GRANT OPTION" log.debug("Grant Query generated: %s args %s", qry, repr(args)) return {"qry": qry, "args": args} def user_grants(user, host="localhost", **connection_args): """ Shows the grants for the given MySQL user (if it exists) CLI Example: .. code-block:: bash salt '*' mysql.user_grants 'frank' 'localhost' """ if not user_exists(user, host, **connection_args): log.info("User '%s'@'%s' does not exist", user, host) return False dbc = _connect(**connection_args) if dbc is None: return False cur = dbc.cursor() qry = "SHOW GRANTS FOR %(user)s@%(host)s" args = {} args["user"] = user args["host"] = host try: _execute(cur, qry, args) except MySQLdb.OperationalError as exc: err = "MySQL Error {}: {}".format(*exc.args) __context__["mysql.error"] = err log.error(err) return False ret = [] results = salt.utils.data.decode(cur.fetchall()) for grant in results: tmp = grant[0].split(" IDENTIFIED BY")[0] if "WITH GRANT OPTION" in grant[0] and "WITH GRANT OPTION" not in tmp: tmp = "{} WITH GRANT OPTION".format(tmp) ret.append(tmp) log.debug(ret) return ret def grant_exists( grant, database, user, host="localhost", grant_option=False, escape=True, **connection_args ): """ Checks to see if a grant exists in the database CLI Example: .. code-block:: bash salt '*' mysql.grant_exists \ 'SELECT,INSERT,UPDATE,...' 'database.*' 'frank' 'localhost' """ server_version = salt.utils.data.decode(version(**connection_args)) if not server_version: last_err = __context__["mysql.error"] err = 'MySQL Error: Unable to fetch current server version. Last error was: "{}"'.format( last_err ) log.error(err) return False if "ALL" in grant.upper(): if ( salt.utils.versions.version_cmp(server_version, "8.0") >= 0 and "MariaDB" not in server_version and database == "*.*" ): grant = ",".join([i for i in __all_privileges__]) else: grant = "ALL PRIVILEGES" try: target = __grant_generate(grant, database, user, host, grant_option, escape) except Exception as exc: # pylint: disable=broad-except log.error("Error during grant generation.") log.error(exc) return False grants = user_grants(user, host, **connection_args) if grants is False: log.error( "Grant does not exist or may not be ordered properly. In some cases, " "this could also indicate a connection error. Check your configuration." ) return False # Combine grants that match the same database _grants = {} for grant in grants: grant_token = _grant_to_tokens(grant) grant_token["grant"] = _resolve_grant_aliases( grant_token["grant"], server_version ) if grant_token["database"] not in _grants: _grants[grant_token["database"]] = { "user": grant_token["user"], "database": grant_token["database"], "host": grant_token["host"], "grant": grant_token["grant"], } else: _grants[grant_token["database"]]["grant"].extend(grant_token["grant"]) target_tokens = _grant_to_tokens(target) target_tokens["grant"] = _resolve_grant_aliases( target_tokens["grant"], server_version ) for database, grant_tokens in _grants.items(): try: _grant_tokens = {} _target_tokens = {} _grant_matches = [ True if i in grant_tokens["grant"] else False for i in target_tokens["grant"] ] for item in ["user", "database", "host"]: _grant_tokens[item] = ( grant_tokens[item] .replace('"', "") .replace("\\", "") .replace("`", "") ) _target_tokens[item] = ( target_tokens[item] .replace('"', "") .replace("\\", "") .replace("`", "") ) if ( _grant_tokens["user"] == _target_tokens["user"] and _grant_tokens["database"] == _target_tokens["database"] and _grant_tokens["host"] == _target_tokens["host"] and all(_grant_matches) ): return True else: log.debug("grants mismatch '%s'<>'%s'", grant_tokens, target_tokens) except Exception as exc: # pylint: disable=broad-except # Fallback to strict parsing log.exception(exc) if grants is not False and target in grants: log.debug("Grant exists.") return True log.debug("Grant does not exist, or is perhaps not ordered properly?") return False def grant_add( grant, database, user, host="localhost", grant_option=False, escape=True, ssl_option=False, **connection_args ): """ Adds a grant to the MySQL server. For database, make sure you specify database.table or database.* CLI Example: .. code-block:: bash salt '*' mysql.grant_add \ 'SELECT,INSERT,UPDATE,...' 'database.*' 'frank' 'localhost' """ dbc = _connect(**connection_args) if dbc is None: return False cur = dbc.cursor() # Avoid spaces problems grant = grant.strip() try: qry = __grant_generate( grant, database, user, host, grant_option, escape, ssl_option ) except Exception: # pylint: disable=broad-except log.error("Error during grant generation") return False try: _execute(cur, qry["qry"], qry["args"]) except (MySQLdb.OperationalError, MySQLdb.ProgrammingError) as exc: err = "MySQL Error {}: {}".format(*exc.args) __context__["mysql.error"] = err log.error(err) return False if grant_exists( grant, database, user, host, grant_option, escape, **connection_args ): log.info( "Grant '%s' on '%s' for user '%s' has been added", grant, database, user ) return True log.info( "Grant '%s' on '%s' for user '%s' has NOT been added", grant, database, user ) return False def grant_revoke( grant, database, user, host="localhost", grant_option=False, escape=True, **connection_args ): """ Removes a grant from the MySQL server. CLI Example: .. code-block:: bash salt '*' mysql.grant_revoke \ 'SELECT,INSERT,UPDATE' 'database.*' 'frank' 'localhost' """ dbc = _connect(**connection_args) if dbc is None: return False cur = dbc.cursor() grant = __grant_normalize(grant) if salt.utils.data.is_true(grant_option): grant += ", GRANT OPTION" db_part = database.rpartition(".") dbc = db_part[0] table = db_part[2] if dbc != "*": # _ and % are authorized on GRANT queries and should get escaped # on the db name, but only if not requesting a table level grant s_database = quote_identifier(dbc, for_grants=(table == "*")) if dbc == "*": # add revoke for *.* # before the modification query send to mysql will looks like # REVOKE SELECT ON `*`.* FROM %(user)s@%(host)s s_database = dbc if table != "*": table = quote_identifier(table) # identifiers cannot be used as values, same thing for grants qry = "REVOKE {} ON {}.{} FROM %(user)s@%(host)s;".format(grant, s_database, table) args = {} args["user"] = user args["host"] = host try: _execute(cur, qry, args) except MySQLdb.OperationalError as exc: err = "MySQL Error {}: {}".format(*exc.args) __context__["mysql.error"] = err log.error(err) return False if not grant_exists( grant, database, user, host, grant_option, escape, **connection_args ): log.info( "Grant '%s' on '%s' for user '%s' has been revoked", grant, database, user, ) return True log.info( "Grant '%s' on '%s' for user '%s' has NOT been revoked", grant, database, user, ) return False def processlist(**connection_args): """ Retrieves the processlist from the MySQL server via "SHOW FULL PROCESSLIST". Returns: a list of dicts, with each dict representing a process: .. code-block:: python {'Command': 'Query', 'Host': 'localhost', 'Id': 39, 'Info': 'SHOW FULL PROCESSLIST', 'Rows_examined': 0, 'Rows_read': 1, 'Rows_sent': 0, 'State': None, 'Time': 0, 'User': 'root', 'db': 'mysql'} CLI Example: .. code-block:: bash salt '*' mysql.processlist """ ret = [] dbc = _connect(**connection_args) if dbc is None: return [] cur = dbc.cursor() _execute(cur, "SHOW FULL PROCESSLIST") hdr = [c[0] for c in cur.description] for _ in range(cur.rowcount): row = cur.fetchone() idx_r = {} for idx_j, value_j in enumerate(hdr): idx_r[hdr[idx_j]] = row[idx_j] ret.append(idx_r) cur.close() return ret def __do_query_into_hash(conn, sql_str): """ Perform the query that is passed to it (sql_str). Returns: results in a dict. """ mod = sys._getframe().f_code.co_name log.debug("%s<--(%s)", mod, sql_str) rtn_results = [] try: cursor = conn.cursor() except MySQLdb.MySQLError: log.error("%s: Can't get cursor for SQL->%s", mod, sql_str) cursor.close() log.debug("%s-->", mod) return rtn_results try: _execute(cursor, sql_str) except MySQLdb.MySQLError: log.error("%s: try to execute : SQL->%s", mod, sql_str) cursor.close() log.debug("%s-->", mod) return rtn_results qrs = cursor.fetchall() for row_data in qrs: col_cnt = 0 row = {} for col_data in cursor.description: col_name = col_data[0] row[col_name] = row_data[col_cnt] col_cnt += 1 rtn_results.append(row) cursor.close() log.debug("%s-->", mod) return rtn_results def get_master_status(**connection_args): """ Retrieves the master status from the minion. Returns:: {'host.domain.com': {'Binlog_Do_DB': '', 'Binlog_Ignore_DB': '', 'File': 'mysql-bin.000021', 'Position': 107}} CLI Example: .. code-block:: bash salt '*' mysql.get_master_status """ mod = sys._getframe().f_code.co_name log.debug("%s<--", mod) conn = _connect(**connection_args) if conn is None: return [] rtnv = __do_query_into_hash(conn, "SHOW MASTER STATUS") conn.close() # check for if this minion is not a master if not rtnv: rtnv.append([]) log.debug("%s-->%s", mod, len(rtnv[0])) return rtnv[0] def get_slave_status(**connection_args): """ Retrieves the slave status from the minion. Returns:: {'host.domain.com': {'Connect_Retry': 60, 'Exec_Master_Log_Pos': 107, 'Last_Errno': 0, 'Last_Error': '', 'Last_IO_Errno': 0, 'Last_IO_Error': '', 'Last_SQL_Errno': 0, 'Last_SQL_Error': '', 'Master_Host': 'comet.scion-eng.com', 'Master_Log_File': 'mysql-bin.000021', 'Master_Port': 3306, 'Master_SSL_Allowed': 'No', 'Master_SSL_CA_File': '', 'Master_SSL_CA_Path': '', 'Master_SSL_Cert': '', 'Master_SSL_Cipher': '', 'Master_SSL_Key': '', 'Master_SSL_Verify_Server_Cert': 'No', 'Master_Server_Id': 1, 'Master_User': 'replu', 'Read_Master_Log_Pos': 107, 'Relay_Log_File': 'klo-relay-bin.000071', 'Relay_Log_Pos': 253, 'Relay_Log_Space': 553, 'Relay_Master_Log_File': 'mysql-bin.000021', 'Replicate_Do_DB': '', 'Replicate_Do_Table': '', 'Replicate_Ignore_DB': '', 'Replicate_Ignore_Server_Ids': '', 'Replicate_Ignore_Table': '', 'Replicate_Wild_Do_Table': '', 'Replicate_Wild_Ignore_Table': '', 'Seconds_Behind_Master': 0, 'Skip_Counter': 0, 'Slave_IO_Running': 'Yes', 'Slave_IO_State': 'Waiting for master to send event', 'Slave_SQL_Running': 'Yes', 'Until_Condition': 'None', 'Until_Log_File': '', 'Until_Log_Pos': 0}} CLI Example: .. code-block:: bash salt '*' mysql.get_slave_status """ mod = sys._getframe().f_code.co_name log.debug("%s<--", mod) conn = _connect(**connection_args) if conn is None: return [] rtnv = __do_query_into_hash(conn, "SHOW SLAVE STATUS") conn.close() # check for if this minion is not a slave if not rtnv: rtnv.append([]) log.debug("%s-->%s", mod, len(rtnv[0])) return rtnv[0] def showvariables(**connection_args): """ Retrieves the show variables from the minion. Returns:: show variables full dict CLI Example: .. code-block:: bash salt '*' mysql.showvariables """ mod = sys._getframe().f_code.co_name log.debug("%s<--", mod) conn = _connect(**connection_args) if conn is None: return [] rtnv = __do_query_into_hash(conn, "SHOW VARIABLES") conn.close() if not rtnv: rtnv.append([]) log.debug("%s-->%s", mod, len(rtnv[0])) return rtnv def showglobal(**connection_args): """ Retrieves the show global variables from the minion. Returns:: show global variables full dict CLI Example: .. code-block:: bash salt '*' mysql.showglobal """ mod = sys._getframe().f_code.co_name log.debug("%s<--", mod) conn = _connect(**connection_args) if conn is None: return [] rtnv = __do_query_into_hash(conn, "SHOW GLOBAL VARIABLES") conn.close() if not rtnv: rtnv.append([]) log.debug("%s-->%s", mod, len(rtnv[0])) return rtnv def verify_login(user, password=None, **connection_args): """ Attempt to login using the provided credentials. If successful, return true. Otherwise, return False. CLI Example: .. code-block:: bash salt '*' mysql.verify_login root password """ # Override the connection args for username and password connection_args["connection_user"] = user connection_args["connection_pass"] = password dbc = _connect(**connection_args) if dbc is None: # Clear the mysql.error if unable to connect # if the connection fails, we simply return False if "mysql.error" in __context__: del __context__["mysql.error"] return False return True def plugins_list(**connection_args): """ Return a list of plugins and their status from the ``SHOW PLUGINS`` query. CLI Example: .. code-block:: bash salt '*' mysql.plugins_list """ dbc = _connect(**connection_args) if dbc is None: return [] cur = dbc.cursor() qry = "SHOW PLUGINS" try: _execute(cur, qry) except MySQLdb.OperationalError as exc: err = "MySQL Error {}: {}".format(*exc.args) __context__["mysql.error"] = err log.error(err) return [] ret = [] results = cur.fetchall() for dbs in results: ret.append({"name": dbs[0], "status": dbs[1]}) log.debug(ret) return ret def plugin_add(name, soname=None, **connection_args): """ Add a plugina. CLI Example: .. code-block:: bash salt '*' mysql.plugin_add auth_socket """ if not name: log.error("Plugin name is required.") return False if plugin_status(name, **connection_args): log.error("Plugin %s is already installed.", name) return True dbc = _connect(**connection_args) if dbc is None: return False cur = dbc.cursor() qry = "INSTALL PLUGIN {}".format(name) if soname: qry += ' SONAME "{}"'.format(soname) else: qry += ' SONAME "{}.so"'.format(name) try: _execute(cur, qry) except MySQLdb.OperationalError as exc: err = "MySQL Error {}: {}".format(*exc.args) __context__["mysql.error"] = err log.error(err) return False return True def plugin_remove(name, **connection_args): """ Remove a plugin. CLI Example: .. code-block:: bash salt '*' mysql.plugin_remove auth_socket """ if not name: log.error("Plugin name is required.") return False if not plugin_status(name, **connection_args): log.error("Plugin %s is not installed.", name) return True dbc = _connect(**connection_args) if dbc is None: return False cur = dbc.cursor() qry = "UNINSTALL PLUGIN {}".format(name) args = {} args["name"] = name try: _execute(cur, qry) except MySQLdb.OperationalError as exc: err = "MySQL Error {}: {}".format(*exc.args) __context__["mysql.error"] = err log.error(err) return False return True def plugin_status(name, **connection_args): """ Return the status of a plugin. CLI Example: .. code-block:: bash salt '*' mysql.plugin_status auth_socket """ if not name: log.error("Plugin name is required.") return False dbc = _connect(**connection_args) if dbc is None: return "" cur = dbc.cursor() qry = ( "SELECT PLUGIN_STATUS FROM INFORMATION_SCHEMA.PLUGINS WHERE PLUGIN_NAME =" " %(name)s" ) args = {} args["name"] = name try: _execute(cur, qry, args) except MySQLdb.OperationalError as exc: err = "MySQL Error {}: {}".format(*exc.args) __context__["mysql.error"] = err log.error(err) return "" try: status = cur.fetchone() if status is None: return "" else: return status[0] except IndexError: return ""
Save