Commit 27e27f31 authored by Rafael Monnerat's avatar Rafael Monnerat

Includes initial version of slapos.package.update

parent b8fe1252
include CHANGES.txt
from setuptools import setup
version = '0.0.1.1'
name = 'slapos.package'
long_description = open("README.txt").read() + "\n" + \
open("CHANGES.txt").read() + "\n"
setup(name=name,
version=version,
description="SlapOS Package Utils",
long_description=long_description,
classifiers=[
"Programming Language :: Python",
],
keywords='slapos package update',
license='GPLv3',
url='http://www.slapos.org',
author='VIFIB',
packages=['slapos.package'],
include_package_data=True,
install_requires=[
'slapos.libnetworkcache',
'iniparse',
],
zip_safe=False,
entry_points={
'console_scripts': [
'slapos-update = slapos.package.update:main',
]
},
test_suite="slapos.package.test",
)
# See http://peak.telecommunity.com/DevCenter/setuptools#namespace-packages
try:
__import__('pkg_resources').declare_namespace(__name__)
except ImportError:
from pkgutil import extend_path
__path__ = extend_path(__path__, __name__)
# See http://peak.telecommunity.com/DevCenter/setuptools#namespace-packages
try:
__import__('pkg_resources').declare_namespace(__name__)
except ImportError:
from pkgutil import extend_path
__path__ = extend_path(__path__, __name__)
import os
import subprocess
import sys
def do_update():
print 'Updating slapprepare'
subprocess.call(['easy_install', '-U', 'slapprepare'])
def main():
if '--no-update' in sys.argv:
sys.argv.remove('--no-update')
else:
do_update()
args = [
os.path.join(os.path.dirname(sys.argv[0]), 'slapprepare-raw')
] + sys.argv[1:]
subprocess.call(args)
import os
import subprocess
from distribution import PackageManager
class SlapError(Exception):
"""
Slap error
"""
def __init__(self, message):
self.msg = message
class UsageError(SlapError):
pass
class ExecError(SlapError):
pass
class BasePromise(PackageManager):
systemctl_path_list = ["/bin/systemctl",
"/usr/bin/systemctl"]
def log(self, message):
""" For now only prints, but it is usefull for test purpose """
print message
def _isSystemd(self):
""" Dectect if Systemd is used """
for systemctl_path in self.systemctl_path_list:
if os.path.exists(systemctl_path):
return True
return False
def _service(self, name, action, stdout=None, stderr=None, dry_run=False):
"""
Wrapper invokation of service or systemctl by identifying what it is available.
"""
if self._isSystemd():
self._call(['systemctl', action, name], stdout=stdout, stderr=stderr, dry_run=dry_run)
else:
self._call(['service', name, action], stdout=stdout, stderr=stderr, dry_run=dry_run)
def _call(self, cmd_args, stdout=None, stderr=None, dry_run=False):
"""
Wrapper for subprocess.call() which'll secure the usage of external program's.
Args:
cmd_args: list of strings representing the command and all it's needed args
stdout/stderr: only precise PIPE (from subprocess) if you don't want the
command to create output on the regular stream
"""
self.log("Calling: %s" % ' '.join(cmd_args))
if not dry_run:
p = sub.Popen(cmd_args, stdout=stdout, stderr=stderr)
output, err = p.communicate()
return output, err
class BasePackagePromise(BasePromise):
package_name = None
binary_path = None
def checkConsistency(self, fixit=0, **kw):
is_ok = True
if self.binary is not None and \
not os.path.exists(self.binary_path):
is_ok = False
if self._isUpgradable(self.package_name):
is_ok = False
if not is_ok and fixit:
return self.fixConsistency(**kw)
return is_ok
def fixConsistency(self, **kw):
self._installSoftware(self.package_name)
return self.checkConsistency(fixit=0, **kw)
import platform
import re
_distributor_id_file_re = re.compile("(?:DISTRIB_ID\s*=)\s*(.*)", re.I)
_release_file_re = re.compile("(?:DISTRIB_RELEASE\s*=)\s*(.*)", re.I)
_codename_file_re = re.compile("(?:DISTRIB_CODENAME\s*=)\s*(.*)", re.I)
def patched_linux_distribution(distname='', version='', id='',
supported_dists=platform._supported_dists,
full_distribution_name=1):
# check for the Debian/Ubuntu /etc/lsb-release file first, needed so
# that the distribution doesn't get identified as Debian.
try:
etclsbrel = open("/etc/lsb-release", "rU")
for line in etclsbrel:
m = _distributor_id_file_re.search(line)
if m:
_u_distname = m.group(1).strip()
m = _release_file_re.search(line)
if m:
_u_version = m.group(1).strip()
m = _codename_file_re.search(line)
if m:
_u_id = m.group(1).strip()
if _u_distname and _u_version:
return (_u_distname, _u_version, _u_id)
except (EnvironmentError, UnboundLocalError):
pass
return platform.linux_distribution(distname, version, id, supported_dists, full_distribution_name)
class PackageManager:
def getDistributionName(self):
return patched_linux_distribution()[0]
def getVersion(self):
return patched_linux_distribution()[1]
def _call(self, *args, **kw):
""" This is implemented in BasePromise """
raise NotImplemented
def _getDistribitionHandler(self):
distribution_name = self.getDistributionName()
if distribution_name.lower() == 'opensuse':
return Zypper()
elif distribution_name.lower() in ['debian', 'ubuntu']:
return AptGet()
raise NotImplemented("Distribution (%s) is not Supported!" % distribution_name)
def _purgeRepository(self):
""" Remove all repositories """
return self._getDistribitionHandler().purgeRepository(self._call)
def _addRepository(self, url, alias):
""" Add a repository """
return self._getDistribitionHandler().addRepository(self._call, url, alias)
def _updateRepository(self):
""" Add a repository """
return self._getDistribitionHandler().updateRepository(self._call)
def _installSoftware(self, name):
""" Upgrade softwares """
return self._getDistribitionHandler().installSoftware(self._call, name)
def _updateSoftware(self):
""" Upgrade softwares """
return self._getDistribitionHandler().updateSoftware(self._call)
def updateSystem(self):
""" Dist-Upgrade of system """
return self._getDistribitionHandler().updateSystem(self._call)
# This helper implements API for package handling
class AptGet:
def purgeRepository(self, caller):
""" Remove all repositories """
raise NotImplemented
def addRepository(self, caller, url, alias):
""" Add a repository """
raise NotImplemented
def updateRepository(self, caller):
""" Add a repository """
caller(['apt-get', 'update'], stdout=None)
def installSoftware(self, caller, name):
""" Instal Software """
self.updateRepository(caller)
caller(["apt-get", "install", "-y", name], stdout=None)
def isUpgradable(self, caller, name):
output, err = caller(["apt-get", "upgrade", "--dry-run"])
for line in output.splitlines():
if line.startswith("Inst %s" % name):
return True
return False
def updateSoftware(self, caller):
""" Upgrade softwares """
self.updateRepository(caller)
caller(["apt-get", "upgrade"], stdout=None)
def updateSystem(self, caller):
""" Dist-Upgrade of system """
caller(['apt-get', 'dist-upgrade', '-y'], stdout=None)
class Zypper:
def purgeRepository(self, caller):
"""Remove all repositories"""
listing, err = caller(['zypper', 'lr'])
while listing.count('\n') > 2:
output, err = caller(['zypper', 'rr', '1'], stdout=None)
listing, err = caller(['zypper', 'lr'])
def addRepository(self, caller, url, alias):
""" Add a repository """
output, err = caller(['zypper', 'ar', '-fc', url, alias], stdout=None)
def updateRepository(self, caller):
""" Add a repository """
caller(['zypper', '--gpg-auto-import-keys', 'up', '-Dly'], stdout=None)
def isUpgradable(self, caller, name):
output, err = caller(['zypper', '--gpg-auto-import-keys', 'up', '-ly'])
for line in output.splitlines():
if line.startswith("'%s' is already installed." % name):
return False
return True
def installSoftware(self, caller, name):
""" Instal Software """
self.updateRepository(caller)
caller(['zypper', '--gpg-auto-import-keys', 'up', '-ly', name], stdout=None)
def updateSoftware(self, caller):
""" Upgrade softwares """
caller(['zypper', '--gpg-auto-import-keys', 'up', '-ly'], stdout=None)
def updateSystem(self, caller):
""" Dist-Upgrade of system """
caller(['zypper', '--gpg-auto-import-keys', 'dup', '-ly'], stdout=None)
import sample
promise_list = (
sample.Promise,
)
from slapos.package.base_promise import BasePromise
import os
class Promise(BasePromise):
configuration_file_path = '/etc/HOSTNAME'
def checkConsistency(self, fixit=0, **kw):
is_ok = False
computer_id = kw["computer_id"]
self.log("Setting hostname in : %s" % self.configuration_file_path)
if os.path.exists(self.configuration_file_path):
is_ok = computer_id in open(self.configuration_file_path, 'r').read()
if not is_ok and fixit:
return self.fixConsistency(**kw)
return is_ok
def fixConsistency(self, **kw):
"""Configures hostname daemon"""
computer_id = kw["computer_id"]
open(self.configuration_file_path, 'w').write("%s\n" % computer_id)
self._call(['hostname', '-F', self.configuration_file_path])
return self.checkConsistency(fixit=0, **kw)
from _util import _call
SLAPOS_MARK = '# Added by SlapOS\n'
class Promise:
def checkConsistency(self, fixit=0, **kw):
is_ok = False
if not is_ok and fixit:
return self.fixConsistency(**kw)
return is_ok
def fixConsistency(self, **kw)
"""Configures NTP daemon"""
server = "server pool.ntp.org"
old_ntp = open('/etc/ntp.conf', 'r').readlines()
new_ntp = open('/etc/ntp.conf', 'w')
for line in old_ntp:
if line.startswith('server'):
continue
new_ntp.write(line)
new_ntp.write(SLAPOS_MARK)
new_ntp.write(server + '\n')
new_ntp.close()
_call(['chkconfig', '--add', 'ntp'])
_call(['chkconfig', 'ntp', 'on'])
_call(['systemctl', 'enable', 'ntp.service'])
_call(['systemctl', 'restart', 'ntp.service'])
return self.checkConsistency(fixit=0, **kw)
from slapos.package.base_promise import BasePackagePromise
class Promise(BasePackagePromise):
package_name = "ntp"
binary_path = '/usr/sbin/ntpd'
from slapos.package.base_promise import BasePromise
SLAPOS_MARK = '# Added by SlapOS\n'
class Promise(BasePromise):
configuration_file_path = '/etc/ntp.conf'
def checkConsistency(self, fixit=0, **kw):
is_ok = False
server = "server pool.ntp.org"
old_ntp = open(self.configuration_file_path, 'r').readlines()
for line in old_ntp:
if line.startswith('server pool.ntp.org'):
continue
is_ok = True
if not is_ok and fixit:
return self.fixConsistency(**kw)
return is_ok
def fixConsistency(self, **kw)
"""Configures NTP daemon"""
server = "server pool.ntp.org"
old_ntp = open(self.configuration_file_path, 'r').readlines()
new_ntp = open(self.configuration_file_path, 'w')
for line in old_ntp:
if line.startswith('server'):
continue
new_ntp.write(line)
new_ntp.write(SLAPOS_MARK)
new_ntp.write(server + '\n')
new_ntp.close()
self._chkconfig('ntp', 'add')
self._chkconfig('ntp', 'on')
self._service('ntp.service', 'enable')
self._service['ntp.service', 'restart')
return self.checkConsistency(fixit=0, **kw)
class Promise:
def checkConsistency(self, fixit=0, **kw):
print "checkConsistency invoked"
if fixit:
self.fixConsistency()
def fixConsistency(self, **kw):
print "Fixed Consistency"
#
# hosts This file describes a number of hostname-to-address
# mappings for the TCP/IP subsystem. It is mostly
# used at boot time, when no name servers are running.
# On small systems, this file can be used instead of a
# "named" name server.
# Syntax:
#
# IP-Address Full-Qualified-Hostname Short-Hostname
#
127.0.0.1 %(computer_id)s localhost
# special IPv6 addresses
::1 %(computer_id)s localhost ipv6-localhost ipv6-loopback
fe00::0 ipv6-localnet
ff00::0 ipv6-mcastprefix
ff02::1 ipv6-allnodes
ff02::2 ipv6-allrouters
ff02::3 ipv6-allhosts
from slapos.package.base_promise import BasePromise
import os
import unittest
def _fake_call(self, *args, **kw):
self.last_call = args
class testBasePromiseCase(unittest.TestCase):
def setUp(self):
BasePromise._call = _fake_call
def testIsSystemd(self):
promise = BasePromise()
systemctl_path = "/tmp/_testBasePromiseCase_systemctl_test"
promise.systemctl_path_list = ["/tmp/_testBasePromiseCase_systemctl_test"]
if os.path.exists(systemctl_path):
os.remove(systemctl_path)
self.assertFalse(promise._isSystemd())
open(systemctl_path, "w").write("echo test")
self.assertTrue(promise._isSystemd())
def testSystemctl(self):
promise = BasePromise()
def _fake_true_isSystemd():
return True
promise._isSystemd = _fake_true_isSystemd
promise._service("service_name", "service_action")
self.assertEqual(promise.last_call,
(['systemctl', 'service_action', 'service_name'],))
def testService(self):
promise = BasePromise()
def _fake_false_isSystemd():
return False
promise._isSystemd = _fake_false_isSystemd
promise._service("service_name", "service_action")
self.assertEqual(promise.last_call,
(['service', 'service_name', 'service_action'],))
from slapos.package.promise import hostname
import os
import unittest
def _fake_call(self, *args, **kw):
self.last_call = (args, kw)
class testPromiseHostnameCase(unittest.TestCase):
def setUp(self):
hostname.Promise._call = _fake_call
def testHostnameCheckConsistency(self):
promise = hostname.Promise()
promise.configuration_file_path = "/tmp/hostname_for_test"
self.assertFalse(promise.checkConsistency(computer_id="TESTING"))
def testHostnameFixConsistency(self):
hostname.Promise._call = _fake_call
promise = hostname.Promise()
promise.hostname_path = "/tmp/hostname_for_test_fix"
if os.path.exists(promise.hostname_path):
os.remove(promise.hostname_path)
self.assertFalse(promise.checkConsistency(computer_id="TESTING"))
self.assertTrue(promise.fixConsistency(computer_id="TESTING"))
self.assertEqual(promise.last_call,
((['hostname', '-F', '/tmp/hostname_for_test_fix'],), {})
)
from slapos.package.promise import ntp
import os
import unittest
NTP_CONTENT = """
server 0.ubuntu.pool.ntp.org
"""
def _fake_call(self, *args, **kw):
self.last_call = (args, kw)
class testPromiseHostnameCase(unittest.TestCase):
def setUp(self):
ntp.Promise._call = _fake_call
def testHostnameCheckConsistency(self):
promise = ntp.Promise()
self.assertFalse(promise.checkConsistency(computer_id="TESTING"))
def testHostnameFixConsistency(self):
hostname.Promise._call = _fake_call
promise = ntp.Promise()
promise.hostname_path = "/tmp/hostname_for_test_fix"
if os.path.exists(promise.hostname_path):
os.remove(promise.hostname_path)
self.assertFalse(promise.checkConsistency(computer_id="TESTING"))
self.assertTrue(promise.fixConsistency(computer_id="TESTING"))
self.assertEqual(promise.last_call,
((['hostname', '-F', '/tmp/hostname_for_test_fix'],), {})
)
#!/usr/bin/python
# -*- coding: utf-8 -*-
##############################################################################
#
# Copyright (c) 2012 Vifib SARL and Contributors.
# All Rights Reserved.
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsibility of assessing all potential
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
# guarantees and support are strongly advised to contract a Free Software
# Service Company
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 3
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
##############################################################################
import ConfigParser
import datetime
import logging
from optparse import OptionParser, Option
import os
import subprocess as sub
import sys
import tempfile
import _autoupdate
from slapos.networkcachehelper import helper_download_network_cached_to_file
# create console handler and set level to warning
ch = logging.StreamHandler()
ch.setLevel(logging.WARNING)
# create formatter
formatter = logging.Formatter("%(asctime)s - %(levelname)s - %(name)s - %(message)s")
# add formatter to ch
ch.setFormatter(formatter)
class Parser(OptionParser):
"""
Parse all arguments.
"""
def __init__(self, usage=None, version=None):
"""
Initialize all options possibles.
"""
OptionParser.__init__(self, usage=usage, version=version,
option_list=[
Option("--slapos-configuration",
default='/etc/opt/slapos/slapos.cfg',
help="Path to slapos configuration file"),
Option("--srv-file",
default='/srv/slapupdate',
help="Server status file."),
Option("-v", "--verbose",
default=False,
action="store_true",
help="Verbose output."),
Option("-n", "--dry-run",
help="Simulate the execution steps",
default=False,
action="store_true"),
])
def check_args(self):
"""
Check arguments
"""
(options, args) = self.parse_args()
return options
class NetworkCache ():
def __init__(self, slapos_conf):
if os.path.exists(slapos_conf):
network_cache_info = ConfigParser.RawConfigParser()
network_cache_info.read(slapos_conf)
self.download_binary_cache_url = network_cache_info.get('networkcache', 'download-binary-cache-url')
self.download_cache_url = network_cache_info.get('networkcache', 'download-cache-url')
self.download_binary_dir_url = network_cache_info.get('networkcache', 'download-binary-dir-url')
self.signature_certificate_list = network_cache_info.get('networkcache', 'signature-certificate-list')
else:
self.download_binary_cache_url = "http://www.shacache.org/shacache"
self.download_cache_url = "https://www.shacache.org/shacache"
self.download_binary_dir_url = "http://www.shacache.org/shadir"
self.signature_certificate_list = ""
self.signature_certificate_list = """
-----BEGIN CERTIFICATE-----
MIIB9jCCAV+gAwIBAgIJANd3qMXJcWPgMA0GCSqGSIb3DQEBBQUAMBMxETAPBgNV
BAMMCENPTVAtOTAyMCAXDTEyMDkwNDEyMDM1OFoYDzIxMTIwODExMTIwMzU4WjAT
MREwDwYDVQQDDAhDT01QLTkwMjCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA
nhfhuGEO3gsQXkYjbhMFzY3b74bAcOvT3vwYd3V+V38XYC2Ds7ugyKIp3EisrREN
8bRzxLTJjEwNhBjdS3GfFt/8uxB7hoKul4lEtdYmM4NCIlLbmoXwoJOVYzL7QWNg
4uMEm9Bf46zhgY3ZNgCLELn8YgUDSr/dfdSDnN7wpoUCAwEAAaNQME4wHQYDVR0O
BBYEFHgf9WN8LY9BZvtAcWrGtk6rCTq3MB8GA1UdIwQYMBaAFHgf9WN8LY9BZvtA
cWrGtk6rCTq3MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADgYEAIFHRgb3D
7va0+kUmw6xidJf9t4X5bkhOt/FBKpJK3tXctIN6DYPlPcemktMmrnYtYimq387I
NYK/ZbWhfgv7VZqJ2OUvGaDQIm9oAuRfcu7QCbEzN10RibLLRKdDDgIhb34iLxVg
jlD7tZ2DbRKHu5FadsKWNZpqC9H0BRLjBwY=
-----END CERTIFICATE-----
""" + self.signature_certificate_list
def download_info_from_networkcache(path, slapos_conf):
"""
Download a tar of the repository from cache, and untar it.
"""
shacache = NetworkCache(slapos_conf)
def strategy(entry_list):
"""
Get the latest entry.
"""
timestamp = 0
best_entry = None
for entry in entry_list:
if entry['timestamp'] > timestamp:
best_entry = entry
return best_entry
return helper_download_network_cached_to_file(
path=path,
directory_key='slapos-under-testing-key',
required_key_list=['timestamp'],
strategy=strategy,
# Then we give a lot of not interesting things
dir_url=shacache.download_binary_dir_url,
cache_url=shacache.download_binary_cache_url,
signature_certificate_list=shacache.signature_certificate_list,
)
def get_info_from_master(config):
"""
Get status information and return its path
"""
info, path = tempfile.mkstemp()
if not download_info_from_networkcache(
path, config.slapos_configuration) == False:
print open(path).read()
return path
else:
raise ValueError("No result from shacache")
def save_current_state(current_state, config):
"""
Will save ConfigParser to config file
"""
file = open(config.srv_file, "w")
current_state.write(file)
file.close()
def checkConsistency(*args, **kw):
print "CHECK CONSISTENCY %s" % ((args, kw),)
def update_machine(config):
"""
Will fetch information from web and update and/or reboot
machine if needed
"""
# Define logger for update_machine
logger = logging.getLogger('Updating your machine')
logger.setLevel(logging.DEBUG)
# add ch to logger
logger.addHandler(ch)
# Get configuration
current_state = ConfigParser.RawConfigParser()
current_state.read(config.srv_file)
next_state = ConfigParser.RawConfigParser()
next_state_file = get_info_from_master(config)
next_state.read(next_state_file)
os.remove(next_state_file)
config.getSystemInfo(current_state, next_state)
config.displayConfig()
# Check if run for first time
if config.first_time:
current_state.add_section('system')
current_state.set('system', 'reboot', config.today.isoformat())
current_state.set('system', 'upgrade', config.today.isoformat())
save_current_state(current_state, config)
# Purge repositories list and add new ones
checkConsistency()
else:
if config.last_upgrade < config.upgrade:
# Purge repositories list and add new ones
current_state.set('system', 'upgrade', config.today.isoformat())
save_current_state(current_state, config)
checkConcistency()
else:
logger.info("Your system is up to date")
if config.last_reboot < config.reboot:
current_state.set('system', 'reboot', config.today.isoformat())
save_current_state(current_state, config)
# Class containing all parameters needed for configuration
class Config:
def setConfig(self, option_dict):
"""
Set options given by parameters.
"""
# Set options parameters
for option, value in option_dict.__dict__.items():
setattr(self, option, value)
self.today = datetime.date.today()
# Define logger for register
self.logger = logging.getLogger('slapupdate configuration')
self.logger.setLevel(logging.DEBUG)
# add ch to logger
self.logger.addHandler(ch)
if self.verbose:
ch.setLevel(logging.DEBUG)
def getSystemInfo(self, current_state, next_state):
"""
Extract information from config file and server file
"""
self.reboot = datetime.datetime.strptime(next_state.get('system', 'reboot'),
"%Y-%m-%d").date()
self.upgrade = datetime.datetime.strptime(next_state.get('system', 'upgrade'),
"%Y-%m-%d").date()
self.opensuse_version = next_state.getfloat('system', 'opensuse_version')
if not current_state.has_section('system'):
self.first_time = True
else:
self.first_time = False
self.last_reboot = datetime.datetime.strptime(
current_state.get('system', 'reboot'),
"%Y-%m-%d").date()
self.last_upgrade = datetime.datetime.strptime(
current_state.get('system', 'upgrade'),
"%Y-%m-%d").date()
def displayConfig(self):
"""
Display Config
"""
self.logger.debug("reboot %s" % self.reboot)
self.logger.debug("upgrade %s" % self.upgrade)
self.logger.debug("suse version %s" % self.opensuse_version)
if not self.first_time:
self.logger.debug("Last reboot : %s" % self.last_reboot)
self.logger.debug("Last upgrade : %s" % self.last_upgrade)
def main():
"""Update computer and slapos"""
usage = "usage: %s [options] " % sys.argv[0]
# Parse arguments
config = Config()
config.setConfig(Parser(usage=usage).check_args())
#config.displayUserConfig()
update_machine(config)
sys.exit()
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment