| 1 | #!/usr/bin/env python
|
|---|
| 2 | # -*- coding: utf-8 -*-
|
|---|
| 3 |
|
|---|
| 4 | """import puppet data to foreman puppet"""
|
|---|
| 5 |
|
|---|
| 6 | __author__ = "Joerg Steffens"
|
|---|
| 7 | __copyright__ = "Copyright 2015, dass IT GmbH"
|
|---|
| 8 | __license__ = "GPL"
|
|---|
| 9 | __version__ = "0.1"
|
|---|
| 10 | __email__ = "joerg.steffens@dass-it.de"
|
|---|
| 11 |
|
|---|
| 12 | import argparse
|
|---|
| 13 | from glob import glob
|
|---|
| 14 | import logging
|
|---|
| 15 | import os
|
|---|
| 16 | #from os import listdir
|
|---|
| 17 | #from os.path import isfile, join
|
|---|
| 18 | from pprint import pprint, pformat
|
|---|
| 19 | import yaml
|
|---|
| 20 |
|
|---|
| 21 |
|
|---|
| 22 | # TODO:
|
|---|
| 23 |
|
|---|
| 24 |
|
|---|
| 25 | def construct_ruby_object(loader, suffix, node):
|
|---|
| 26 | return loader.construct_yaml_map(node)
|
|---|
| 27 |
|
|---|
| 28 | def construct_ruby_sym(loader, node):
|
|---|
| 29 | return loader.construct_yaml_str(node)
|
|---|
| 30 |
|
|---|
| 31 | class ForemanInfo:
|
|---|
| 32 | """ get information from Foreman server """
|
|---|
| 33 | def __init__(self):
|
|---|
| 34 | pass
|
|---|
| 35 |
|
|---|
| 36 | class HostMigrateException(Exception):
|
|---|
| 37 | pass
|
|---|
| 38 |
|
|---|
| 39 | class Host:
|
|---|
| 40 | # set map to None, if class should ge removed
|
|---|
| 41 | classMap = {
|
|---|
| 42 | 'debeka-module-netzwerk': 'debeka_module_netzwerk',
|
|---|
| 43 | 'debeka-hardware-support': 'debeka_hardware_support',
|
|---|
| 44 | 'debeka-systemzugang': 'debeka_systemzugang',
|
|---|
| 45 | 'debeka_virenscanner': 'debeka_virenscanner',
|
|---|
| 46 | 'debeka_omw': 'debeka_omw',
|
|---|
| 47 | 'debeka_smt': 'debeka_smt',
|
|---|
| 48 | 'debeka_datensicherung': 'debeka_datensicherung',
|
|---|
| 49 | 'ntp': 'ntp',
|
|---|
| 50 | 'ssh': 'ssh',
|
|---|
| 51 | }
|
|---|
| 52 |
|
|---|
| 53 | src = {
|
|---|
| 54 | 'hiera': None,
|
|---|
| 55 | 'facts': None,
|
|---|
| 56 | }
|
|---|
| 57 |
|
|---|
| 58 | hammer = {
|
|---|
| 59 | 'options': {
|
|---|
| 60 | 'domain': "--domain server.debeka.de",
|
|---|
| 61 | 'comment': '--comment "import test"',
|
|---|
| 62 | 'architecture': '--architecture "x86_64"',
|
|---|
| 63 | 'operatingsystem': '--operatingsystem "SLES 11 SP3"',
|
|---|
| 64 | # TODO
|
|---|
| 65 | 'root-password': '--root-password "linuxlinux"',
|
|---|
| 66 | 'medium': '--medium smt.debeka-SLES-Pool-x86_64',
|
|---|
| 67 | 'partition-table': '--partition-table debeka-autoyast-sda-lvm',
|
|---|
| 68 | 'build': '--build false',
|
|---|
| 69 | 'interfaces': [],
|
|---|
| 70 | },
|
|---|
| 71 | }
|
|---|
| 72 |
|
|---|
| 73 | def __init__(self, host, destprefix=""):
|
|---|
| 74 | self.logger=logging.getLogger(__name__)
|
|---|
| 75 | self.host=host
|
|---|
| 76 | self.destprefix=destprefix
|
|---|
| 77 | self.logger.info( "* host: " + host )
|
|---|
| 78 | self.hammer['options']['name']='--name ' + destprefix + host
|
|---|
| 79 |
|
|---|
| 80 | def __convert_classes(self):
|
|---|
| 81 | classes=self.src['hiera']['classes']
|
|---|
| 82 | self.logger.debug(classes)
|
|---|
| 83 | self.hammer['puppet-classes'] = []
|
|---|
| 84 | for i in classes:
|
|---|
| 85 | if not self.classMap.has_key(i):
|
|---|
| 86 | self.logger.error("unknown class " + i)
|
|---|
| 87 | return False
|
|---|
| 88 | elif self.classMap[i] != None:
|
|---|
| 89 | self.hammer['puppet-classes'].append(self.classMap[i])
|
|---|
| 90 | # TODO: convert class to puppet-class-id
|
|---|
| 91 | self.logger.debug(self.hammer)
|
|---|
| 92 | # remove all converted data
|
|---|
| 93 | self.src['hiera'].pop('classes')
|
|---|
| 94 |
|
|---|
| 95 | def __convert_netzkarte_alias(self, main, alias, values):
|
|---|
| 96 | self.logger.debug( "alias: " + main + alias + pformat( values ) )
|
|---|
| 97 | #--interface "identifier=test3,attached_to=eth0,virtual=true,managed=true,subnet_id=2,ip=192.168.27.3"
|
|---|
| 98 | interface="identifier=%s,attached_to=%s" % ( main + "_" + alias, main)
|
|---|
| 99 | for key in values:
|
|---|
| 100 | if key == "ipAddr":
|
|---|
| 101 | interface += ",ip=%s" % (values[key])
|
|---|
| 102 | elif key == "prefixlen":
|
|---|
| 103 | # ignore, should come from network
|
|---|
| 104 | pass
|
|---|
| 105 | else:
|
|---|
| 106 | self.logger.error("failed: net interface %s, alias %s: unhandeld key %s" % (main, alias, key))
|
|---|
| 107 | return False
|
|---|
| 108 | self.hammer['options']['interfaces'].append( '--interface="' + interface + ',managed=true,virtual=true"' )
|
|---|
| 109 |
|
|---|
| 110 | def __convert_netzkarte(self):
|
|---|
| 111 | try:
|
|---|
| 112 | netzkarte=self.src['hiera']['debeka-module-netzwerk::netzkarte']
|
|---|
| 113 | except KeyError:
|
|---|
| 114 | # no netzkarte defined. Can this be ignored? Ignore host?
|
|---|
| 115 | raise HostMigrateException("no debeka-module-netzwerk::netzkarte defined")
|
|---|
| 116 | primary = None
|
|---|
| 117 | for name in netzkarte:
|
|---|
| 118 | interface="identifier=%s" % (name)
|
|---|
| 119 | if self.src['facts']['values'].has_key('macaddress_' + name):
|
|---|
| 120 | interface += ",mac=%s" % (self.src['facts']['values']['macaddress_' + name])
|
|---|
| 121 | for key in netzkarte[name]:
|
|---|
| 122 | if key == "ipAddr":
|
|---|
| 123 | interface += ",ip=%s" % (netzkarte[name][key])
|
|---|
| 124 | elif key == "prefixlen":
|
|---|
| 125 | # ignore, should come from network
|
|---|
| 126 | pass
|
|---|
| 127 | elif key == "aliasse":
|
|---|
| 128 | for alias in netzkarte[name][key]:
|
|---|
| 129 | self.__convert_netzkarte_alias(name, alias, netzkarte[name][key][alias])
|
|---|
| 130 | pass
|
|---|
| 131 | else:
|
|---|
| 132 | self.logger.error("failed: unhandeld key " + key)
|
|---|
| 133 | return False
|
|---|
| 134 | # TODO: add subnet_id
|
|---|
| 135 | interface += ",managed=true"
|
|---|
| 136 | if name == "eth0":
|
|---|
| 137 | interface += ",provision=true,primary=true"
|
|---|
| 138 | primary=name
|
|---|
| 139 | self.logger.debug(interface)
|
|---|
| 140 | self.hammer['options']['interfaces'].append( '--interface="' + interface + '"' )
|
|---|
| 141 | # one primary interface must be defined
|
|---|
| 142 | if primary == None:
|
|---|
| 143 | raise HostMigrateException("no primary interface defined")
|
|---|
| 144 |
|
|---|
| 145 | def __set_environment(self):
|
|---|
| 146 | try:
|
|---|
| 147 | environment=self.src['hiera']['umgebung']
|
|---|
| 148 | except KeyError:
|
|---|
| 149 | # not defined
|
|---|
| 150 | # required, so raise
|
|---|
| 151 | raise HostMigrateException( "no environment (umgebung) defined" )
|
|---|
| 152 | #return False
|
|---|
| 153 | self.hammer['options']['environment'] = '--environment ' + environment
|
|---|
| 154 | ## TODO: should it be like this?
|
|---|
| 155 | self.hammer['options']['hostgroup'] = '--hostgroup ' + environment
|
|---|
| 156 | # remove all converted data
|
|---|
| 157 | self.src['hiera'].pop('umgebung')
|
|---|
| 158 |
|
|---|
| 159 |
|
|---|
| 160 | def __read_hiera_data(self):
|
|---|
| 161 | filename="./data/hiera/" + self.host + ".yaml"
|
|---|
| 162 | try:
|
|---|
| 163 | with open( filename , 'r') as hierafile:
|
|---|
| 164 | raw = hierafile.read()
|
|---|
| 165 | # fix syntax error in hiera files
|
|---|
| 166 | # replace
|
|---|
| 167 | # netmask: -
|
|---|
| 168 | # device: -
|
|---|
| 169 | # by
|
|---|
| 170 | # netmask: "-"
|
|---|
| 171 | # device: "-"
|
|---|
| 172 | data=raw.replace( ": -", ": '-'" );
|
|---|
| 173 | except IOError:
|
|---|
| 174 | self.logger.error( "failed to read hiera data for host " + self.host + " from file " + filename)
|
|---|
| 175 | return False
|
|---|
| 176 |
|
|---|
| 177 | try:
|
|---|
| 178 | self.src['hiera']=yaml.load(data)
|
|---|
| 179 | except yaml.scanner.ScannerError as e:
|
|---|
| 180 | self.logger.error(e)
|
|---|
| 181 | return False
|
|---|
| 182 |
|
|---|
| 183 | self.logger.debug( "hiera:\n" + pformat( self.src['hiera']))
|
|---|
| 184 | return True
|
|---|
| 185 |
|
|---|
| 186 | def __read_facts(self):
|
|---|
| 187 | filename = "./data/facts/" + self.host + ".server.debeka.de.yaml"
|
|---|
| 188 | try:
|
|---|
| 189 | stream = open(filename, "r")
|
|---|
| 190 | except IOError:
|
|---|
| 191 | self.logger.error( "failed to read facts for host " + self.host + " from file " + filename)
|
|---|
| 192 | return False
|
|---|
| 193 |
|
|---|
| 194 | yaml.add_multi_constructor(u"!ruby/object:", construct_ruby_object)
|
|---|
| 195 | yaml.add_constructor(u"!ruby/sym", construct_ruby_sym)
|
|---|
| 196 |
|
|---|
| 197 | self.src['facts']=yaml.load(stream)
|
|---|
| 198 | self.logger.debug( "facts:" + pformat(self.src['facts']))
|
|---|
| 199 | return True
|
|---|
| 200 |
|
|---|
| 201 | def get_hammer_options(self):
|
|---|
| 202 | if not (self.__read_hiera_data() and self.__read_facts()):
|
|---|
| 203 | self.logger.error( "failed: aborted host " + self.host )
|
|---|
| 204 | return False
|
|---|
| 205 |
|
|---|
| 206 | self.__convert_classes()
|
|---|
| 207 | self.__convert_netzkarte()
|
|---|
| 208 | self.__set_environment()
|
|---|
| 209 |
|
|---|
| 210 | self.logger.info("hammer:")
|
|---|
| 211 | self.logger.info(pformat(self.hammer))
|
|---|
| 212 |
|
|---|
| 213 |
|
|---|
| 214 |
|
|---|
| 215 | if __name__ == '__main__':
|
|---|
| 216 | logging.basicConfig(format='%(message)s')
|
|---|
| 217 | logger = logging.getLogger(__name__)
|
|---|
| 218 | logger.setLevel(logging.INFO)
|
|---|
| 219 |
|
|---|
| 220 | destprefix="xxxtest-"
|
|---|
| 221 |
|
|---|
| 222 | parser = argparse.ArgumentParser(description='Migrate Debeka Puppet hosts to Foreman Puppet.' )
|
|---|
| 223 | parser.add_argument( '--debug', action='store_true', help="enable debugging output" )
|
|---|
| 224 | parser.add_argument( '--destprefix', dest="prefix", default=destprefix, help="prefix to be added to converted hosts, used for testing (default: '" + destprefix + "')" )
|
|---|
| 225 | parser.add_argument( '--host', help="name of the host that should get converted" )
|
|---|
| 226 | parser.add_argument( '--dir', help="base directory, that contains hiera and facts subdirectories" )
|
|---|
| 227 |
|
|---|
| 228 | args = parser.parse_args()
|
|---|
| 229 |
|
|---|
| 230 | if args.debug:
|
|---|
| 231 | logger.setLevel(logging.DEBUG)
|
|---|
| 232 | logger.debug( "start" )
|
|---|
| 233 |
|
|---|
| 234 | if args.host:
|
|---|
| 235 | host=Host(args.host, args.prefix)
|
|---|
| 236 | host.get_hammer_options()
|
|---|
| 237 | elif args.dir:
|
|---|
| 238 | for i in glob( args.dir + "/hiera/*.yaml" ):
|
|---|
| 239 | #onlyfiles = [ f for f in listdir(mypath) if isfile(join(mypath,f)) ]
|
|---|
| 240 | hostname = os.path.splitext(os.path.basename(i))[0]
|
|---|
| 241 | host=Host(hostname, args.prefix)
|
|---|
| 242 | host.get_hammer_options()
|
|---|
| 243 | else:
|
|---|
| 244 | logger.error( "either specify host or dir" )
|
|---|
| 245 | |
|---|