mirror of
https://github.com/saltstack/salt.git
synced 2025-04-16 17:50:20 +00:00
Initial commit of Salt Package Manager
This commit is contained in:
parent
0458780bbd
commit
4a95b11652
5 changed files with 350 additions and 0 deletions
51
salt/cli/spm.py
Normal file
51
salt/cli/spm.py
Normal file
|
@ -0,0 +1,51 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
'''
|
||||
salt.cli.spm
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
Salt's spm cli parser.
|
||||
|
||||
'''
|
||||
|
||||
# Import Python libs
|
||||
from __future__ import absolute_import, print_function
|
||||
import os.path
|
||||
import logging
|
||||
|
||||
# Import Salt libs
|
||||
import salt.utils.parsers as parsers
|
||||
import salt.version
|
||||
import salt.syspaths as syspaths
|
||||
|
||||
# Import 3rd-party libs
|
||||
import salt.ext.six as six
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class SPM(six.with_metaclass(parsers.OptionParserMeta, # pylint: disable=W0232
|
||||
parsers.OptionParser, parsers.ConfigDirMixIn,
|
||||
parsers.LogLevelMixIn, parsers.MergeConfigMixIn)):
|
||||
'''
|
||||
The cli parser object used to fire up the salt spm system.
|
||||
'''
|
||||
|
||||
VERSION = salt.version.__version__
|
||||
|
||||
# ConfigDirMixIn config filename attribute
|
||||
_config_filename_ = 'spm'
|
||||
# LogLevelMixIn attributes
|
||||
_default_logging_logfile_ = os.path.join(syspaths.LOGS_DIR, 'spm')
|
||||
|
||||
def setup_config(self):
|
||||
return salt.config.spm_config(self.get_config_file_path())
|
||||
|
||||
def run(self):
|
||||
'''
|
||||
Run the api
|
||||
'''
|
||||
import salt.client.spm
|
||||
self.parse_args()
|
||||
self.setup_logfile_logger()
|
||||
client = salt.client.spm.SPMClient(self.config)
|
||||
client.run(self.args)
|
255
salt/client/spm.py
Normal file
255
salt/client/spm.py
Normal file
|
@ -0,0 +1,255 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
'''
|
||||
This module provides the point of entry to SPM, the Salt Package Manager
|
||||
'''
|
||||
from __future__ import absolute_import
|
||||
# Import Python libs
|
||||
import os
|
||||
import yaml
|
||||
import tarfile
|
||||
import tempfile
|
||||
import shutil
|
||||
import msgpack
|
||||
|
||||
# Import Salt libs
|
||||
import salt.config
|
||||
import salt.utils
|
||||
import salt.utils.http as http
|
||||
import salt.syspaths as syspaths
|
||||
|
||||
|
||||
class SPMClient(object):
|
||||
'''
|
||||
Provide an SPM Client
|
||||
'''
|
||||
def __init__(self, opts=None):
|
||||
if not opts:
|
||||
opts = salt.config.client_config(
|
||||
os.environ.get(
|
||||
'SALT_MASTER_CONFIG',
|
||||
os.path.join(syspaths.CONFIG_DIR, 'master')
|
||||
)
|
||||
)
|
||||
self.opts = opts
|
||||
|
||||
def run(self, args):
|
||||
'''
|
||||
Run the SPM command
|
||||
'''
|
||||
command = args[0]
|
||||
if command == 'install':
|
||||
self._install(args[1])
|
||||
elif command == 'local_install':
|
||||
self._local_install(args[1])
|
||||
elif command == 'remove':
|
||||
self._remove(args)
|
||||
elif command == 'build':
|
||||
self._build(args)
|
||||
elif command == 'update_repo':
|
||||
self._download_repo_metadata()
|
||||
elif command == 'create_repo':
|
||||
self._create_repo(args)
|
||||
|
||||
def _local_install(self, package_file):
|
||||
'''
|
||||
Install a package from a file
|
||||
'''
|
||||
out_path = self.opts['file_roots']['base'][0]
|
||||
print('Locally installing package {0} to {1}'.format(package_file, out_path))
|
||||
|
||||
if not os.path.exists(package_file):
|
||||
print('File not found')
|
||||
return False
|
||||
|
||||
if not os.path.exists(out_path):
|
||||
os.mkdir(out_path)
|
||||
|
||||
formula_tar = tarfile.open(package_file, 'r:bz2')
|
||||
formula_tar.extractall(out_path)
|
||||
formula_tar.close()
|
||||
# Save file list and checksums in local repo index (msgpack)
|
||||
|
||||
def _traverse_repos(self, callback):
|
||||
'''
|
||||
Traverse through all repo files and apply the functionality provided in
|
||||
the callback to them
|
||||
'''
|
||||
repo_files = []
|
||||
if os.path.exists(self.opts['spm_repos_config']):
|
||||
repo_files.append(self.opts['spm_repos_config'])
|
||||
|
||||
for (dirpath, dirnames, filenames) in os.walk('{0}.d'.format(self.opts['spm_repos_config'])):
|
||||
for repo_file in filenames:
|
||||
if not repo_file.endswith('.repo'):
|
||||
continue
|
||||
repo_files.append(repo_file)
|
||||
|
||||
if not os.path.exists(self.opts['spm_cache_dir']):
|
||||
os.makedirs(self.opts['spm_cache_dir'])
|
||||
|
||||
for repo_file in repo_files:
|
||||
repo_path = '{0}.d/{1}'.format(self.opts['spm_repos_config'], repo_file)
|
||||
with salt.utils.fopen(repo_path) as rph:
|
||||
repo_data = yaml.safe_load(rph)
|
||||
for repo in repo_data:
|
||||
if repo_data[repo].get('enabled', True) is False:
|
||||
continue
|
||||
callback(repo, repo_data[repo])
|
||||
|
||||
def _download_repo_metadata(self):
|
||||
'''
|
||||
Connect to all repos and download metadata
|
||||
'''
|
||||
def _update_metadata(repo, repo_info):
|
||||
dl_path = '{0}/SPM-METADATA.yml'.format(repo_info['url'])
|
||||
if dl_path.startswith('file://'):
|
||||
dl_path = dl_path.replace('file://', '')
|
||||
with salt.utils.fopen(dl_path, 'r') as rpm:
|
||||
metadata = yaml.safe_load(rpm)
|
||||
else:
|
||||
response = http.query(
|
||||
'{0}/SPM-MANIFEST.yml'.format(dl_path),
|
||||
)
|
||||
metadata = response.get('dict', {})
|
||||
cache_path = '{0}/{1}.p'.format(
|
||||
self.opts['spm_cache_dir'],
|
||||
repo
|
||||
)
|
||||
|
||||
with salt.utils.fopen(cache_path, 'w') as cph:
|
||||
msgpack.dump(metadata, cph)
|
||||
|
||||
self._traverse_repos(_update_metadata)
|
||||
|
||||
def _get_repo_metadata(self):
|
||||
'''
|
||||
Return cached repo metadata
|
||||
'''
|
||||
metadata = {}
|
||||
def _read_metadata(repo, repo_info):
|
||||
cache_path = '{0}/{1}.p'.format(
|
||||
self.opts['spm_cache_dir'],
|
||||
repo
|
||||
)
|
||||
|
||||
with salt.utils.fopen(cache_path, 'r') as cph:
|
||||
metadata[repo] = {
|
||||
'info': repo_info,
|
||||
'packages': msgpack.load(cph),
|
||||
}
|
||||
|
||||
self._traverse_repos(_read_metadata)
|
||||
return metadata
|
||||
|
||||
def _create_repo(self, args):
|
||||
'''
|
||||
Scan a directory and create an SPM-METADATA.yml file which describes
|
||||
all of the SPM files in that directory.
|
||||
'''
|
||||
if args[1] == '.':
|
||||
repo_path = os.environ['PWD']
|
||||
else:
|
||||
repo_path = args[1]
|
||||
|
||||
repo_metadata = {}
|
||||
for (dirpath, dirnames, filenames) in os.walk(repo_path):
|
||||
for spm_file in filenames:
|
||||
if not spm_file.endswith('.spm'):
|
||||
continue
|
||||
spm_path = '{0}/{1}'.format(repo_path, spm_file)
|
||||
if not tarfile.is_tarfile(spm_path):
|
||||
continue
|
||||
comps = spm_file.split('-')
|
||||
spm_name = '-'.join(comps[:-2])
|
||||
spm_fh = tarfile.open(spm_path, 'r:bz2')
|
||||
formula_handle = spm_fh.extractfile('{0}/FORMULA.yml'.format(spm_name))
|
||||
formula_conf = yaml.safe_load(formula_handle.read())
|
||||
repo_metadata[spm_name] = formula_conf.copy()
|
||||
repo_metadata[spm_name]['filename'] = spm_file
|
||||
|
||||
metadata_filename = '{0}/SPM-METADATA.yml'.format(repo_path)
|
||||
with salt.utils.fopen(metadata_filename, 'w') as mfh:
|
||||
yaml.dump(repo_metadata, mfh, indent=4, canonical=False, default_flow_style=False)
|
||||
|
||||
print('Wrote {0}'.format(metadata_filename))
|
||||
|
||||
def _install(self, package):
|
||||
'''
|
||||
Install a package from a repo
|
||||
'''
|
||||
print('Installing package {0}'.format(package))
|
||||
repo_metadata = self._get_repo_metadata()
|
||||
for repo in repo_metadata:
|
||||
repo_info = repo_metadata[repo]
|
||||
if package in repo_metadata[repo]['packages']:
|
||||
cache_path = '{0}/{1}'.format(
|
||||
self.opts['spm_cache_dir'],
|
||||
repo
|
||||
)
|
||||
dl_path = '{0}/{1}'.format(repo_info['info']['url'], repo_info['packages'][package]['filename'])
|
||||
out_file = '{0}/{1}'.format(cache_path, repo_info['packages'][package]['filename'])
|
||||
if not os.path.exists(cache_path):
|
||||
os.makedirs(cache_path)
|
||||
|
||||
if dl_path.startswith('file://'):
|
||||
dl_path = dl_path.replace('file://', '')
|
||||
shutil.copyfile(dl_path, out_file)
|
||||
else:
|
||||
http.query(dl_path, text_out=out_file)
|
||||
|
||||
self._local_install(out_file)
|
||||
return
|
||||
|
||||
def _remove(self, args):
|
||||
'''
|
||||
Remove a package
|
||||
'''
|
||||
package = args[1]
|
||||
print('Removing package {0}'.format(package))
|
||||
# Look at local repo index
|
||||
# Find files that have not changed and remove them
|
||||
# Leave directories in place that still have files in them
|
||||
|
||||
def _build(self, args):
|
||||
'''
|
||||
Build a package
|
||||
'''
|
||||
self.abspath = args[1]
|
||||
comps = self.abspath.split('/')
|
||||
self.relpath = comps[-1]
|
||||
|
||||
formula_path = '{0}/FORMULA.yml'.format(self.abspath)
|
||||
formula_conf = {}
|
||||
if os.path.exists(formula_path):
|
||||
with salt.utils.fopen(formula_path) as fp_:
|
||||
formula_conf = yaml.safe_load(fp_)
|
||||
else:
|
||||
print('File not found')
|
||||
return False
|
||||
|
||||
out_path = '{0}/{1}-{2}-{3}.spm'.format(
|
||||
self.opts['spm_build_dir'],
|
||||
formula_conf['name'],
|
||||
formula_conf['version'],
|
||||
formula_conf['release'],
|
||||
)
|
||||
|
||||
if not os.path.exists(self.opts['spm_build_dir']):
|
||||
os.mkdir(self.opts['spm_build_dir'])
|
||||
|
||||
formula_tar = tarfile.open(out_path, 'w:bz2')
|
||||
formula_tar.add(self.abspath, formula_conf['name'], exclude=self._exclude)
|
||||
formula_tar.close()
|
||||
|
||||
print(formula_path)
|
||||
return formula_path
|
||||
|
||||
def _exclude(self, name):
|
||||
'''
|
||||
Exclude based on opts
|
||||
'''
|
||||
for item in self.opts['spm_build_exclude']:
|
||||
exclude_name = '{0}/{1}'.format(self.abspath, item)
|
||||
if name.startswith(exclude_name):
|
||||
return True
|
||||
return False
|
|
@ -1072,6 +1072,18 @@ DEFAULT_API_OPTS = {
|
|||
# <---- Salt master settings overridden by Salt-API ----------------------
|
||||
}
|
||||
|
||||
DEFAULT_SPM_OPTS = {
|
||||
# ----- Salt master settings overridden by SPM --------------------->
|
||||
'reactor_roots': '/srv/reactor',
|
||||
'spm_logfile': '/var/log/salt/spm',
|
||||
# spm_repos_config also includes a .d/ directory
|
||||
'spm_repos_config': '/etc/salt/spm.repos',
|
||||
'spm_build_dir': '/srv/spm',
|
||||
'spm_cache_dir': os.path.join(salt.syspaths.CACHE_DIR, 'spm'),
|
||||
'spm_build_exclude': ['.git'],
|
||||
# <---- Salt master settings overridden by SPM ----------------------
|
||||
}
|
||||
|
||||
VM_CONFIG_DEFAULTS = {
|
||||
'default_include': 'cloud.profiles.d/*.conf',
|
||||
}
|
||||
|
@ -2730,3 +2742,16 @@ def api_config(path):
|
|||
defaults.update(DEFAULT_API_OPTS)
|
||||
|
||||
return client_config(path, defaults=defaults)
|
||||
|
||||
|
||||
def spm_config(path):
|
||||
'''
|
||||
Read in the salt master config file and add additional configs that
|
||||
need to be stubbed out for spm
|
||||
'''
|
||||
# Let's grab a copy of salt's master default opts
|
||||
defaults = DEFAULT_MASTER_OPTS
|
||||
# Let's override them with spm's required defaults
|
||||
defaults.update(DEFAULT_SPM_OPTS)
|
||||
|
||||
return client_config(path, defaults=defaults)
|
||||
|
|
|
@ -355,3 +355,12 @@ def salt_main():
|
|||
SystemExit('\nExiting gracefully on Ctrl-c'),
|
||||
err,
|
||||
hardcrash, trace=trace)
|
||||
|
||||
|
||||
def salt_spm():
|
||||
'''
|
||||
The main function for spm, the Salt Package Manager
|
||||
'''
|
||||
import salt.cli.spm
|
||||
spm = salt.cli.spm.SPM() # pylint: disable=E1120
|
||||
spm.run()
|
||||
|
|
10
scripts/spm
Executable file
10
scripts/spm
Executable file
|
@ -0,0 +1,10 @@
|
|||
#!/usr/bin/env python
|
||||
'''
|
||||
Publish commands to the salt system from the command line on the master.
|
||||
'''
|
||||
|
||||
from salt.scripts import salt_spm
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
salt_spm()
|
Loading…
Add table
Reference in a new issue