Skip to content

Commit

Permalink
Port saltstack#51625 to master
Browse files Browse the repository at this point in the history
  • Loading branch information
lordcirth authored and azhukovin committed Apr 28, 2020
1 parent 9cfa050 commit a2d3900
Show file tree
Hide file tree
Showing 6 changed files with 268 additions and 0 deletions.
1 change: 1 addition & 0 deletions doc/ref/modules/all/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -431,6 +431,7 @@ execution modules
smbios
smf_service
smtp
snap
snapper
solaris_fmadm
solaris_group
Expand Down
6 changes: 6 additions & 0 deletions doc/ref/modules/all/salt.modules.snap.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
=================
salt.modules.snap
=================

.. automodule:: salt.modules.snap
:members:
1 change: 1 addition & 0 deletions doc/ref/states/all/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,7 @@ state modules
slack
smartos
smtp
snap
snapper
solrcloud
splunk
Expand Down
6 changes: 6 additions & 0 deletions doc/ref/states/all/salt.states.snap.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
================
salt.states.snap
================

.. automodule:: salt.states.snap
:members:
129 changes: 129 additions & 0 deletions salt/modules/snap.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
# -*- coding: utf-8 -*-
"""
Manage snap packages via Salt
:depends: snapd for distribution
"""

from __future__ import absolute_import, print_function, unicode_literals

import subprocess

import salt.utils.path

SNAP_BINARY_NAME = "snap"

__virtualname__ = "snap"


def __virtual__():
if salt.utils.path.which("snap"):
return __virtualname__

return (
False,
'The snap execution module cannot be loaded: the "snap" binary is not in the path.',
)


def install(pkg, channel=None, refresh=False):
"""
Install the specified snap package from the specified channel.
Returns a dictionary of "result" and "output".
pkg
The snap package name
channel
Optional. The snap channel to install from, eg "beta"
refresh : False
If True, use "snap refresh" instead of "snap install".
This allows changing the channel of a previously installed package.
"""
args = []
ret = {"result": None, "output": ""}

if refresh:
cmd = "refresh"
else:
cmd = "install"

if channel:
args.append("--channel=" + channel)

try:
# Try to run it, merging stderr into output
ret["output"] = subprocess.check_output(
[SNAP_BINARY_NAME, cmd, pkg] + args, stderr=subprocess.STDOUT
)
ret["result"] = True
except subprocess.CalledProcessError as e:
ret["output"] = e.output
ret["result"] = False

return ret


def is_installed(pkg):
"""
Returns True if there is any version of the specified package installed.
pkg
The package name
"""
return bool(versions_installed(pkg))


def remove(pkg):
"""
Remove the specified snap package. Returns a dictionary of "result" and "output".
pkg
The package name
"""
ret = {"result": None, "output": ""}
try:
ret["output"] = subprocess.check_output([SNAP_BINARY_NAME, "remove", pkg])
ret["result"] = True
except subprocess.CalledProcessError as e:
ret["output"] = e.output
ret["result"] = False


# Parse 'snap list' into a dict
def versions_installed(pkg):
"""
Query which version(s) of the specified snap package are installed.
Returns a list of 0 or more dictionaries.
pkg
The package name
"""

try:
# Try to run it, merging stderr into output
output = subprocess.check_output(
[SNAP_BINARY_NAME, "list", pkg], stderr=subprocess.STDOUT
)
except subprocess.CalledProcessError:
return []

lines = output.splitlines()[1:]
ret = []
for item in lines:
# If fields contain spaces this will break.
i = item.split()
# Ignore 'Notes' field
ret.append(
{
"name": i[0],
"version": i[1],
"rev": i[2],
"tracking": i[3],
"publisher": i[4],
}
)

return ret
125 changes: 125 additions & 0 deletions salt/states/snap.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
# -*- coding: utf-8 -*-
"""
Management of snap packages
===============================================
"""
from __future__ import absolute_import, print_function, unicode_literals

import salt.utils.path

__virtualname__ = "snap"


def __virtual__():
if salt.utils.path.which("snap"):
return __virtualname__

return (
False,
'The snap state module cannot be loaded: the "snap" binary is not in the path.',
)


def installed(name, channel=None):
"""
Ensure that the named snap package is installed
name
The snap package
channel
Optional. The channel to install the package from.
"""
ret = {"name": name, "changes": {}, "pchanges": {}, "result": None, "comment": ""}

old = __salt__["snap.versions_installed"](name)
if not old:
if __opts__["test"]:
ret["comment"] = 'Package "{0}" would have been installed'.format(name)
ret["pchanges"]["new"] = name
ret["pchanges"]["old"] = None
ret["result"] = None
return ret

install = __salt__["snap.install"](name, channel=channel)
if install["result"]:
ret["comment"] = 'Package "{0}" was installed'.format(name)
ret["changes"]["new"] = name
ret["changes"]["old"] = None
ret["result"] = True
return ret

ret["comment"] = 'Package "{0}" failed to install'.format(name)
ret["comment"] += "\noutput:\n" + install["output"]
ret["result"] = False
return ret

# Currently snap always returns only one line?
old_channel = old[0]["tracking"]
if old_channel != channel and channel is not None:
if __opts__["test"]:
ret[
"comment"
] = 'Package "{0}" would have been switched to channel {1}'.format(
name, channel
)
ret["pchanges"]["old_channel"] = old_channel
ret["pchanges"]["new_channel"] = channel
ret["result"] = None
return ret

refresh = __salt__["snap.install"](name, channel=channel, refresh=True)
if refresh["result"]:
ret["comment"] = 'Package "{0}" was switched to channel {1}'.format(
name, channel
)
ret["pchanges"]["old_channel"] = old_channel
ret["pchanges"]["new_channel"] = channel
ret["result"] = True
return ret

ret["comment"] = 'Failed to switch Package "{0}" to channel {1}'.format(
name, channel
)
ret["comment"] += "\noutput:\n" + install["output"]
ret["result"] = False
return ret

ret["comment"] = 'Package "{0}" is already installed'.format(name)
if __opts__["test"]:
ret["result"] = None
return ret

ret["result"] = True
return ret


def removed(name):
"""
Ensure that the named snap package is not installed
name
The snap package
"""

ret = {"name": name, "changes": {}, "pchanges": {}, "result": None, "comment": ""}

old = __salt__["snap.versions_installed"](name)
if not old:
ret["comment"] = "Package {0} is not installed".format(name)
ret["result"] = True
return ret

if __opts__["test"]:
ret["comment"] = "Package {0} would have been removed".format(name)
ret["result"] = None
ret["pchanges"]["old"] = old[0]["version"]
ret["pchanges"]["new"] = None
return ret

remove = __salt__["snap.remove"](name)
ret["comment"] = "Package {0} removed".format(name)
ret["result"] = True
ret["changes"]["old"] = old[0]["version"]
ret["changes"]["new"] = None
return ret

0 comments on commit a2d3900

Please sign in to comment.