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 | |
---|