View
@@ -21,8 +21,9 @@
# $ ipa host-show 'foo.example.com' --all --raw | cut -b 3-
#
# EXAMPLE:
-# $ ./diff.py ${valid_hostname} ${args} --rtype host && echo TRUE || echo FALSE
-# $ ./diff.py ${valid_service} ${args} --rtype service && echo TRUE || echo FALSE
+# $ ./diff.py host ${valid_hostname} ${args} && echo TRUE || echo FALSE
+# $ ./diff.py service ${valid_service} ${args} && echo TRUE || echo FALSE
+# $ ./diff.py user ${valid_login} ${args} && echo TRUE || echo FALSE
#
# where ${} are puppet variables...
#
@@ -34,119 +35,501 @@
import ipalib
from ipapython.ssh import SSHPublicKey
-ipalib.api.bootstrap()
-ipalib.api.load_plugins()
-ipalib.api.finalize()
-ipalib.api.Backend.xmlclient.connect()
+#
+# helper functions
+#
+def p(x, f=lambda x: x):
+ """Pass None values through, otherwise apply function."""
+ if x is None: return None
+ return f(x)
+
+def get(value, default):
+ if value is None: return default
+ else: return value
+
+# NOTE: a lot of places you'll see [0] because everything is wrapped in tuples!
+# IPA does this for some reason, feel free to use the @left(untuple) decorator!
+def untuple(x):
+ """Untuple a single value that was wrapped in a tuple."""
+ assert type(x) == type(()), 'Expected tuple.' # safety check
+ assert len(x) == 1, 'Expected tuple singleton.' # safety check
+ return x[0]
+
+def listclean(x):
+ """Clean empty value lists to match puppets 'manage', but empty."""
+ # NOTE: 0 length list values in ipa are None, ideally they should be (,)
+ # TODO: uncomment this first condition as well if it is ever needed...
+ #if x == []: return None # empties show up as 'None' in freeipa
+ if x == ['']: return None # this is the empty: --argument-value=
+ return x
+
+def lowerize(x):
+ """Transform input value into upper, recursively."""
+ # TODO: dict ?
+ if type(x) == type([]):
+ return [lowerize(i) for i in x] # recurse
+ elif type(x) == type(()):
+ return tuple([lowerize(i) for i in x]) # recurse
+ elif isinstance(x, basestring):
+ return x.lower() # don't recurse
+ else:
+ return x # int's, etc...
+
+def upperize(x):
+ """Transform input value into upper, recursively."""
+ # TODO: dict ?
+ if type(x) == type([]):
+ return [upperize(i) for i in x] # recurse
+ elif type(x) == type(()):
+ return tuple([upperize(i) for i in x]) # recurse
+ elif isinstance(x, basestring):
+ return x.upper() # don't recurse
+ else:
+ return x # int's, etc...
def unicodeize(x):
+ """Transform input value into unicode, recursively."""
+ # TODO: dict ?
if type(x) == type([]):
- return [unicode(i) if isinstance(i, basestring) else i for i in sorted(x)]
+ return [unicodeize(i) for i in x] # recurse
elif type(x) == type(()):
- return tuple([unicode(i) if isinstance(i, basestring) else i for i in sorted(x)])
+ return tuple([unicodeize(i) for i in x]) # recurse
elif isinstance(x, basestring):
- return unicode(x)
+ return unicode(x) # don't recurse
else:
- return x
-
-def process_macaddress(x):
- if x is None: return None # pass through the none's
- if x == []: return None # empties show up as 'None' in freeipa
- # TODO: we should really rewrite this diff.py so that individual
- # data types are given a "compare" function that takes two inputs,
- # instead of blindly sorting, unicodizing and uppercasing things...
- x = [m.upper() for m in x] # ipa expects uppercase mac addresses
- return x
+ return x # int's, etc...
+
+def sshfp(x):
+ """Transform a public ssh key into the ipa style fingerprint."""
+ if type(x) == type([]):
+ return [sshfp(i) for i in x] # recurse
+
+ # this code is the algorithm used in: ipalib/util.py
+ pubkey = SSHPublicKey(x)
+ fp = pubkey.fingerprint_hex_md5()
+ comment = pubkey.comment()
+ if comment: fp = u'%s %s' % (fp, comment)
+ fp = u'%s (%s)' % (fp, pubkey.keytype())
+ return fp
+
+def sshdsp(x):
+ """Transform a public ssh key into the ipa style display."""
+ if type(x) == type([]):
+ return [sshdsp(i) for i in x] # recurse
+
+ # this code is the algorithm used in: ipalib/util.py
+ return SSHPublicKey(x).openssh() # normalize_sshpubkey
+
+#
+# function decorators to wrap cmp functions
+#
+def debug(f):
+ """Function decorator to help debug cmp values."""
+ def r(x, y):
+ if args.debug:
+ # NOTE: f.func_name works if it is closest to function!
+ print 'f:', f.func_name, 'x:', x, 'y:', y
+ return f(x, y)
+
+ return r # we're a function decorator
+
+def lower(f):
+ """Function decorator to lower case x and y inputs."""
+ # NOTE: this shows the longer versions of the decorator...
+ #def r(x, y):
+ # #_x = None if x is None else lowerize(x)
+ # #_y = None if y is None else lowerize(y)
+ # #return f(_x, _y)
+ # return f(p(x, lowerize), p(y, lowerize))
+ #return r
+ return lambda x, y: f(p(x, lowerize), p(y, lowerize))
+
+def upper(f):
+ """Function decorator to upper case x and y inputs."""
+ return lambda x, y: f(p(x, upperize), p(y, upperize))
+
+# TODO: is this unused because of @left(list) ?
+def lists(f):
+ """Function decorator to ensure both inputs are lists."""
+ return lambda x, y: f(p(x, list), p(y, list))
+
+def sort(f):
+ """Function decorator to sort x and y inputs."""
+ return lambda x, y: f(p(x, sorted), p(y, sorted))
+
+def unique(f):
+ """Function decorator to remove duplicates in x and y inputs."""
+ d = lambda z: list(set(z)) # remove duplicates
+ return lambda x, y: f(p(x, d), p(y, d))
+
+def unicoded(f):
+ """Function decorator to unicode x and y inputs including lists, and
+ tuples. Recurses into compound types like lists."""
+ return lambda x, y: f(p(x, unicodeize), p(y, unicodeize))
+
+def left(l=lambda z: z):
+ """Return a function decorator using a lambda l for the left only."""
+ def inner_left(f):
+ """Function decorator to ensure l is applied on the left."""
+ return lambda x, y: f(p(x, l), y)
+ return inner_left
+
+def right(l=lambda z: z):
+ """Return a function decorator using a lambda l for the right only."""
+ def inner_right(f):
+ """Function decorator to ensure l is applied on the right."""
+ return lambda x, y: f(x, p(y, l))
+ return inner_right
+
+# NOTE: we could rewrite lower,upper,lists,sort,unique and etc in terms of this
+def both(l=lambda z: z):
+ """Return a function decorator using a lambda l for both x and y."""
+ def inner_both(f):
+ """Function decorator to ensure l is applied to both x and y."""
+ return lambda x, y: f(p(x, l), p(y, l))
+ return inner_both
+
+#
+# composed decorators
+#
+# http://docs.python.org/2/reference/compound_stmts.html#grammar-token-decorated
+def ipalist(f):
+ # equivalent to decorating with:
+ # @left(list)
+ # @right(listclean)
+ # @unicoded
+ return left(list)(right(listclean)(unicoded(f)))
+
+def ipastr(f):
+ # @left(untuple)
+ # @unicoded
+ return left(untuple)(unicoded(f))
+
+#
+# cmp functions
+#
+@unicoded
+def cmp_default(x, y):
+ return x == y
+
+#
+# host cmp functions
+#
+@left(untuple)
+@lower # TODO: should we drop the @lower ?
+@unicoded
+def cmp_host_primarykey(x, y):
+ return x == y
+
+@left(list)
+@right(listclean)
+@sort
+@upper # ipa expects uppercase mac addresses
+@unicoded
+def cmp_host_macaddress(x, y):
+ return x == y
+
+#@left(list)
+#@right(listclean)
+#@right(sshfp)
+#@unicoded
+#@debug # should usually be closest to the cmp function
+#def cmp_host_sshpubkeyfp(x, y):
+# # in comes lists of ssh keys. we need to transform each one into the
+# # format as returned by freeipa. freeipa returns a tuple of strings!
+# # eg x is usually something like:
+# # (u'AB:98:62:82:C0:74:47:5E:FC:36:F7:5A:D7:8F:8E:FF (ssh-dss)',
+# # u'62:6D:8B:7B:3F:E3:EA:4C:50:4D:86:AA:BF:17:9D:8B (ssh-rsa)')
+# return x == y
+
+@left(list)
+@right(listclean)
+@right(sshdsp)
+@unicoded
+@debug # should usually be closest to the cmp function
+def cmp_host_ipasshpubkey(x, y):
+ # this is only seen when using all=True
+ return x == y
+
+@ipastr
+def cmp_host_l(x, y):
+ return x == y
+
+@ipastr
+def cmp_host_nshostlocation(x, y):
+ return x == y
+
+@ipastr
+def cmp_host_nshardwareplatform(x, y):
+ return x == y
+
+@ipastr
+def cmp_host_nsosversion(x, y):
+ return x == y
+
+@ipastr
+def cmp_host_description(x, y):
+ return x == y
-def process_sshpubkeyfp(x):
- # in comes a list of ssh keys. we need to process each one into the
- # format as returned by freeipa. freeipa returns a tuple of strings
- # eg:
- # (u'AB:98:62:82:C0:74:47:5E:FC:36:F7:5A:D7:8F:8E:FF (ssh-dss)',
- # u'62:6D:8B:7B:3F:E3:EA:4C:50:4D:86:AA:BF:17:9D:8B (ssh-rsa)')
- if x is None: return None # pass through the none's
- if x == []: return None # empties show up as 'None' in freeipa
- if x == ['']: return None # this is the empty --sshpubkey=
- result = []
- for i in x:
- # this code is the algorithm used in: ipalib/util.py
- pubkey = SSHPublicKey(i)
- fp = pubkey.fingerprint_hex_md5()
- comment = pubkey.comment()
- if comment: fp = u'%s %s' % (fp, comment)
- fp = u'%s (%s)' % (fp, pubkey.keytype())
- result.append(fp)
-
- return result
-
-def process_ipakrbauthzdata(x):
+#
+# service cmp functions
+#
+@left(untuple)
+@unicoded
+def cmp_service_primarykey(x, y):
+ return x == y
+
+@left(list)
+@unicoded
+def cmp_service_ipakrbauthzdata(x, y):
# TODO: is it possible that instead of (u'NONE',) some return None ?
- if x is None: return None # pass through the none's
- if x == []: return None # empties show up as 'None' in freeipa
- return x
+ return x == y
+
+#
+# user cmp functions
+#
+@left(untuple)
+@unicoded
+def cmp_user_primarykey(x, y):
+ return x == y
+
+@ipastr
+def cmp_user_givenname(x, y):
+ return x == y
+
+@ipastr
+def cmp_user_sn(x, y):
+ return x == y
+
+@ipastr
+def cmp_user_cn(x, y):
+ return x == y
+
+@ipastr
+def cmp_user_displayname(x, y):
+ return x == y
+
+@ipastr
+def cmp_user_initials(x, y):
+ return x == y
+
+@ipastr
+def cmp_user_krbprincipalname(x, y):
+ return x == y
+
+@ipalist
+def cmp_user_mail(x, y):
+ return x == y
+
+@ipastr
+def cmp_user_gecos(x, y):
+ return x == y
+
+@ipastr
+def cmp_user_uidnumber(x, y):
+ return x == y
+
+@ipastr
+def cmp_user_gidnumber(x, y):
+ return x == y
+
+@ipastr
+def cmp_user_loginshell(x, y):
+ return x == y
+
+@ipastr
+def cmp_user_homedirectory(x, y):
+ return x == y
+
+@left(list)
+@right(listclean)
+@right(sshdsp)
+@unicoded
+def cmp_user_ipasshpubkey(x, y):
+ return x == y
+
+@ipastr
+def cmp_user_street(x, y):
+ return x == y
+
+@ipastr
+def cmp_user_l(x, y):
+ return x == y
+
+@ipastr
+def cmp_user_st(x, y):
+ return x == y
+
+@ipastr
+def cmp_user_postalcode(x, y):
+ return x == y
+
+@ipalist
+def cmp_user_telephonenumber(x, y):
+ return x == y
+
+@ipalist
+def cmp_user_mobile(x, y):
+ return x == y
+
+@ipalist
+def cmp_user_pager(x, y):
+ return x == y
+
+@ipalist
+def cmp_user_facsimiletelephonenumber(x, y):
+ return x == y
+
+@ipastr
+def cmp_user_title(x, y):
+ return x == y
+
+@ipastr
+def cmp_user_ou(x, y):
+ return x == y
-parser = argparse.ArgumentParser()
+@ipastr
+def cmp_user_manager(x, y):
+ return x == y
+
+@ipastr
+def cmp_user_carlicense(x, y):
+ return x == y
+
+#
+# initialize ipa
+#
+ipalib.api.bootstrap()
+ipalib.api.load_plugins()
+ipalib.api.finalize()
+ipalib.api.Backend.xmlclient.connect()
+
+#
+# parser to match ipa arguments
+#
+parser = argparse.ArgumentParser(description='ipa difference engine')
-parser.add_argument('primarykey', action='store') # positional arg
parser.add_argument('--debug', dest='debug', action='store_true', default=False)
parser.add_argument('--not', dest='n', action='store_true', default=False)
-parser.add_argument('--rtype', dest='rtype', action='store', required=True, choices=['host', 'service']) # resource type
-# this is a mapping with dest being the --raw key to look for the data!
+subparsers = parser.add_subparsers(dest='subparser_name')
+
+# parent parser (contains common subparser arguments)
+parent_parser = argparse.ArgumentParser(add_help=False)
+parent_parser.add_argument('primarykey', action='store') # positional arg
+
+# NOTE: this is a mapping with dest being the --raw key to look for the data in
+# NOTE: this --raw key to dest values can be seen by looking in the ipa API.txt
#
-# host rtype arguments
+# 'host' parser
#
-parser.add_argument('--macaddress', dest='macaddress', action='append', default=[]) # list
+parser_host = subparsers.add_parser('host', parents=[parent_parser])
+parser_host.add_argument('--macaddress', dest='macaddress', action='append') # list
# this is actually part of DNS, ignore it...
-#parser.add_argument('--ip-address', dest='ip?', action='store')
-parser.add_argument('--sshpubkey', dest='sshpubkeyfp', action='append', default=[]) # list
-parser.add_argument('--locality', dest='l', action='store')
-parser.add_argument('--location', dest='nshostlocation', action='store')
-parser.add_argument('--platform', dest='nshardwareplatform', action='store')
-parser.add_argument('--os', dest='nsosversion', action='store')
-parser.add_argument('--desc', dest='description', action='store')
+#parser_host.add_argument('--ip-address', dest='ip?', action='store')
+#parser_host.add_argument('--sshpubkey', dest='sshpubkeyfp', action='append')
+parser_host.add_argument('--sshpubkey', dest='ipasshpubkey', action='append')
+parser_host.add_argument('--locality', dest='l', action='store')
+parser_host.add_argument('--location', dest='nshostlocation', action='store')
+parser_host.add_argument('--platform', dest='nshardwareplatform', action='store')
+parser_host.add_argument('--os', dest='nsosversion', action='store')
+parser_host.add_argument('--desc', dest='description', action='store')
#
-# service rtype arguments
+# 'service' parser
#
-parser.add_argument('--pac-type', dest='ipakrbauthzdata', action='append', default=[])
+parser_service = subparsers.add_parser('service', parents=[parent_parser])
+parser_service.add_argument('--pac-type', dest='ipakrbauthzdata', action='append')
+#
+# 'user' parser
+#
+parser_user = subparsers.add_parser('user', parents=[parent_parser])
+parser_user.add_argument('--first', dest='givenname', action='store')
+parser_user.add_argument('--last', dest='sn', action='store')
+parser_user.add_argument('--cn', dest='cn', action='store')
+parser_user.add_argument('--displayname', dest='displayname', action='store')
+parser_user.add_argument('--initials', dest='initials', action='store')
+parser_user.add_argument('--principal', dest='krbprincipalname', action='store')
+parser_user.add_argument('--email', dest='mail', action='append')
+parser_user.add_argument('--gecos', dest='gecos', action='store')
+parser_user.add_argument('--uid', dest='uidnumber', action='store')
+parser_user.add_argument('--gidnumber', dest='gidnumber', action='store')
+parser_user.add_argument('--shell', dest='loginshell', action='store')
+parser_user.add_argument('--homedir', dest='homedirectory', action='store')
+parser_user.add_argument('--sshpubkey', dest='ipasshpubkey', action='append')
+parser_user.add_argument('--street', dest='street', action='store')
+parser_user.add_argument('--city', dest='l', action='store')
+parser_user.add_argument('--state', dest='st', action='store')
+parser_user.add_argument('--postalcode', dest='postalcode', action='store')
+parser_user.add_argument('--phone', dest='telephonenumber', action='append')
+parser_user.add_argument('--mobile', dest='mobile', action='append')
+parser_user.add_argument('--pager', dest='pager', action='append')
+parser_user.add_argument('--fax', dest='facsimiletelephonenumber', action='append')
+parser_user.add_argument('--title', dest='title', action='store')
+parser_user.add_argument('--orgunit', dest='ou', action='store')
+parser_user.add_argument('--manager', dest='manager', action='store')
+parser_user.add_argument('--carlicense', dest='carlicense', action='store')
args = parser.parse_args()
-if args.rtype == 'host':
- # verify each of these keys matches
- verify = [
- 'macaddress',
- 'sshpubkeyfp',
- 'l',
- 'nshostlocation',
- 'nshardwareplatform',
- 'nsosversion',
- 'description',
- ]
-
- # "adjust" each of these keys somehow...
+# TODO: the process dictionaries could probably be generated by argparse data
+if args.subparser_name == 'host':
process = {
- # NOTE: all the list types need a process function to noneify empties
- 'macaddress': process_macaddress,
- 'sshpubkeyfp': process_sshpubkeyfp,
+ 'macaddress': cmp_host_macaddress,
+ #'sshpubkeyfp': cmp_host_sshpubkeyfp,
+ 'ipasshpubkey': cmp_host_ipasshpubkey, # only seen with --all
+ 'l': cmp_host_l,
+ 'nshostlocation': cmp_host_nshostlocation,
+ 'nshardwareplatform': cmp_host_nshardwareplatform,
+ 'nsosversion': cmp_host_nsosversion,
+ 'description': cmp_host_description,
}
-elif args.rtype == 'service':
- verify = [
- 'ipakrbauthzdata',
- ]
+
+elif args.subparser_name == 'service':
+ process = {
+ 'ipakrbauthzdata': cmp_service_ipakrbauthzdata,
+ }
+
+elif args.subparser_name == 'user':
process = {
- 'ipakrbauthzdata': process_ipakrbauthzdata,
+ 'givenname': cmp_user_givenname,
+ 'sn': cmp_user_sn,
+ 'cn': cmp_user_cn,
+ 'displayname': cmp_user_displayname,
+ 'initials': cmp_user_initials,
+ 'krbprincipalname': cmp_user_krbprincipalname,
+ 'mail': cmp_user_mail,
+ 'gecos': cmp_user_gecos,
+ 'uidnumber': cmp_user_uidnumber,
+ 'gidnumber': cmp_user_gidnumber,
+ 'loginshell': cmp_user_loginshell,
+ 'homedirectory': cmp_user_homedirectory,
+ 'ipasshpubkey': cmp_user_ipasshpubkey,
+ 'street': cmp_user_street,
+ 'l': cmp_user_l,
+ 'st': cmp_user_st,
+ 'postalcode': cmp_user_postalcode,
+ 'telephonenumber': cmp_user_telephonenumber,
+ 'mobile': cmp_user_mobile,
+ 'pager': cmp_user_pager,
+ 'facsimiletelephonenumber': cmp_user_facsimiletelephonenumber,
+ 'title': cmp_user_title,
+ 'ou': cmp_user_ou,
+ 'manager': cmp_user_manager,
+ 'carlicense': cmp_user_carlicense,
}
try:
#output = ipalib.api.Command.host_show(fqdn=unicode(args.hostname))
- if args.rtype == 'host':
- output = ipalib.api.Command.host_show(unicode(args.primarykey))
- elif args.rtype == 'service':
- output = ipalib.api.Command.service_show(unicode(args.primarykey))
+ if args.subparser_name == 'host':
+ output = ipalib.api.Command.host_show(unicode(args.primarykey), all=True)
+ elif args.subparser_name == 'service':
+ output = ipalib.api.Command.service_show(unicode(args.primarykey), all=True)
+ elif args.subparser_name == 'user':
+ output = ipalib.api.Command.user_show(unicode(args.primarykey), all=True)
+
except ipalib.errors.NotFound, e:
if args.debug:
print >> sys.stderr, 'Not found'
@@ -159,51 +542,41 @@ def process_ipakrbauthzdata(x):
print args
print result
-# NOTE: a lot of places you'll see [0] because everything is wrapped in tuples!
-if args.rtype == 'host':
- # TODO: should we drop the .lower() checks ?
- x = unicode(args.primarykey.lower())
- y = unicode(result.get('fqdn', '')[0].lower())
-elif args.rtype == 'service':
- x = unicode(args.primarykey)
- y = unicode(result.get('krbprincipalname', '')[0])
-
-assert x == y, "Primary key does not match!" # verify we got the right pk...
-# loop through all the keys to validate
-for i in verify:
- #print i, getattr(args, i)
- #if i in process.keys():
- f = process.get(i, lambda x: x) # function
- #v = getattr(args, i) # value
- v = f(getattr(args, i)) # value after processing
- if v is not None:
- # a is the value we got from the api lookup
- # b is the processed value we got from the command line
- # convert our lists to tuples...
- a = result.get(i, None) # should already be unicode...
- b = v
- if type(b) == type([]):
- if a is None:
- # value is empty, ideally it should return (,)
- sys.exit(1)
- assert type(a) == type(()), ('Expected tuple on: %s' % i)
- a = unicodeize(a) # also sorts...
- b = tuple(unicodeize(b)) # both must be tuples!
- elif type(b) == type(''):
- a = a[0] # unwrap the string from the tuple!
- b = unicodeize(v)
+if args.subparser_name == 'host':
+ compare = cmp_host_primarykey
+ pk = result.get('fqdn', '')
+elif args.subparser_name == 'service':
+ compare = cmp_service_primarykey
+ pk = result.get('krbprincipalname', '')
+elif args.subparser_name == 'user':
+ compare = cmp_user_primarykey
+ pk = result.get('uid', '')
+
+# the pk gets untuples by the @left(untuple) compare decorators
+assert compare(pk, args.primarykey), 'Primary key does not match!'
+
+#
+# loop through all the keys to validate
+#
+for i in process.keys():
+
+ a = result.get(i, None) # value from ipa (in unicode)
+ b = getattr(args, i) # value from command line arg
+
+ compare = process.get(i, cmp_default) # cmp function
+ compare = get(compare, cmp_default) # default None
+
+ # NOTE: the a value (left) must always be the ipa data
+ # the b value (right) must correspond to the arg value
+ watch = (b is not None) # values of None are unmanaged
+ if watch and not(compare(a, b)): # run the cmp!
+ if args.debug:
+ print >> sys.stderr, ('Unmatched on %s between %s and %s' % (i, a, b))
+ if args.n:
+ sys.exit(0)
else:
- # TODO: add support for any other types if we need them
- assert False, ("Unknown datatype: %s" % type(b))
-
- #print i, a, b
- if a != b:
- if args.debug:
- print >> sys.stderr, ('Unmatched on %s between %s and %s' % (i, a, b))
- if args.n:
- sys.exit(0)
- else:
- sys.exit(1)
+ sys.exit(1)
+
if args.n:
sys.exit(1)
else:
View
@@ -108,12 +108,16 @@
$idstart = '16777216', # TODO: what is sensible? i picked 2^24
$idmax = '',
+ $email_domain = '', # defaults to domain
+ $shell = true, # defaults to /bin/sh
+ $homes = true, # defaults to /home
+ # packages products to install ?
$ntp = false, # opposite of ipa-server-install default
$dns = false, # must be set at install time to be used
$dogtag = false,
- $email = '', # defaults to root@domain, used for dns
+ $email = '', # defaults to root@domain, important...
$shorewall = false,
$zone = 'net',
@@ -130,6 +134,7 @@
# a value of true, will automatically add the * character to match all.
$host_excludes = [], # never purge these host excludes...
$service_excludes = [], # never purge these service excludes...
+ $user_excludes = [], # never purge these user excludes...
$ensure = present # TODO: support uninstall with 'absent'
) {
$FW = '$FW' # make using $FW in shorewall easier...
@@ -144,8 +149,45 @@
'' => upcase($valid_domain),
default => upcase($realm),
}
+
+ $default_email_domain = "${email_domain}" ? {
+ '' => "${valid_domain}",
+ default => "${email_domain}",
+ }
+ ipa::server::config { 'emaildomain':
+ value => "${default_email_domain}",
+ }
+
+ $default_shell = type($shell) ? {
+ 'boolean' => $shell ? {
+ false => false, # unmanaged
+ default => '/bin/sh', # the default
+ },
+ default => "${shell}",
+ }
+ # we don't manage if value is false, otherwise it's good to go!
+ if ! (type($shell) == 'boolean' and (! $shell)) {
+ ipa::server::config { 'shell':
+ value => "${default_shell}",
+ }
+ }
+
+ # TODO: the home stuff seems to not use trailing slashes. can i add it?
+ $default_homes = type($homes) ? {
+ 'boolean' => $homes ? {
+ false => false, # unmanaged
+ default => '/home', # the default
+ },
+ default => "${homes}",
+ }
+ if ! (type($homes) == 'boolean' and (! $homes)) {
+ ipa::server::config { 'homes':
+ value => "${default_homes}", # XXX: remove trailing slash if present ?
+ }
+ }
+
$valid_email = $email ? {
- '' => "root@${valid_domain}",
+ '' => "root@${default_email_domain}",
default => "${email}",
}
@@ -362,6 +404,127 @@
}
}
+# FIXME: some values have not been filled in yet. some are missing: --arguments
+define ipa::server::config(
+ $value
+) {
+ $key = "${name}"
+
+ $etype = "${key}" ? { # expected type
+ #'?' => '', # FIXME: dn
+ #'?' => '', # --maxusername
+ 'homes' => 'string',
+ 'shell' => 'string',
+ #'?' => '', # --defaultgroup
+ 'emaildomain' => 'string',
+ #'?' => '', # --searchtimelimit
+ #'?' => '', # --searchrecordslimit
+ 'usersearch' => 'array',
+ 'groupsearch' => 'array',
+ 'migration' => 'boolean',
+ #'?' => '', # FIXME: ipacertificatesubjectbase
+ #'?' => '', # --groupobjectclasses
+ #'?' => '', # --userobjectclasses
+ #'?' => '', # --pwdexpnotify
+ #'?' => '', # --ipaconfigstring
+ #'?' => '', # --ipaselinuxusermaporder
+ #'?' => '', # --ipaselinuxusermapdefault
+ #'?' => '', # --pac-type
+ #'?' => '', # FIXME: cn
+ #'?' => '', # FIXME: objectclass
+ default => '', # missing
+ }
+
+ $option = "${key}" ? {
+ #'?' => 'dn', FIXME
+ #'?' => '--maxusername=',
+ 'homes' => '--homedirectory=',
+ 'shell' => '--defaultshell=',
+ #'?' => '--defaultgroup=',
+ 'emaildomain' => '--emaildomain=',
+ #'?' => '--searchtimelimit=',
+ #'?' => '--searchrecordslimit=',
+ 'usersearch' => '--usersearch=',
+ 'groupsearch' => '--groupsearch=',
+ 'migration' => '--enable-migration=',
+ #'?' => 'ipacertificatesubjectbase', FIXME
+ #'?' => '--groupobjectclasses=',
+ #'?' => '--userobjectclasses=',
+ #'?' => '--pwdexpnotify=',
+ #'?' => '--ipaconfigstring=',
+ #'?' => '--ipaselinuxusermaporder=',
+ #'?' => '--ipaselinuxusermapdefault=',
+ #'?' => '--pac-type=',
+ #'?' => 'cn', FIXME
+ #'?' => 'objectclass', FIXME
+ default => '', # missing
+ }
+
+ $rawkey = "${key}" ? {
+ #'?' => 'dn',
+ #'?' => 'ipamaxusernamelength',
+ 'homes' => 'ipahomesrootdir',
+ 'shell' => 'ipadefaultloginshell',
+ #'?' => 'ipadefaultprimarygroup',
+ 'emaildomain' => 'ipadefaultemaildomain',
+ #'?' => 'ipasearchtimelimit',
+ #'?' => 'ipasearchrecordslimit',
+ 'usersearch' => 'ipausersearchfields',
+ 'groupsearch' => 'ipagroupsearchfields',
+ 'migration' => 'ipamigrationenabled',
+ #'?' => 'ipacertificatesubjectbase',
+ #'?' => 'ipagroupobjectclasses',
+ #'?' => 'ipauserobjectclasses',
+ #'?' => 'ipapwdexpadvnotify',
+ #'?' => 'ipaconfigstring',
+ #'?' => 'ipaselinuxusermaporder',
+ #'?' => 'ipaselinuxusermapdefault',
+ #'?' => 'ipakrbauthzdata',
+ #'?' => 'cn',
+ #'?' => 'objectclass',
+ default => '', # missing
+ }
+
+ if "${option}" == '' or "${etype}" == '' or "${rawkey}" == '' {
+ fail("Key '${key}' is invalid.")
+ }
+
+ if type($value) != "${etype}" {
+ fail("Ipa::Server::Config[${key}] must be type: ${etype}.")
+ }
+
+ # convert to correct type
+ if "${etype}" == 'string' {
+ $safe_value = shellquote($value) # TODO: is this right ?
+ $jchar = '' # pass through the paste binary
+ } elsif "${etype}" == 'array' {
+ $jchar = "${key}" ? { # join char
+ 'usersearch' => ',',
+ 'groupsearch' => ',',
+ default => '',
+ }
+ $safe_value = inline_template('<%= value.join(jchar) %>')
+ } elsif "${etype}" == 'boolean' {
+ $safe_value = $value ? {
+ true => 'TRUE',
+ default => 'FALSE',
+ }
+ $jchar = '' # pass through the paste binary
+ } else {
+ fail("Unknown type: ${etype}.")
+ }
+
+ $cutlength = inline_template('<%= (rawkey.length+2).to_s %>')
+ exec { "/usr/bin/ipa config-mod ${option}'${safe_value}'":
+ unless => "/usr/bin/test \"`/usr/bin/ipa config-show --raw --all | /usr/bin/tr -d ' ' | /bin/grep '^${rawkey}:' | /bin/cut -b ${cutlength}- | /usr/bin/paste -sd '${jchar}'`\" = '${safe_value}'",
+ logoutput => on_failure,
+ require => [
+ Exec['ipa-install'],
+ Exec['ipa-server-kinit'],
+ ],
+ }
+}
+
class ipa::server::host::base {
include ipa::server
include ipa::vardir
@@ -800,7 +963,7 @@
onlyif => "${exists}",
unless => $watch ? {
false => undef, # don't run the diff checker...
- default => "${exists} && ${vardir}/diff.py '${valid_fqdn}' --rtype='host' ${args}",
+ default => "${exists} && ${vardir}/diff.py host '${valid_fqdn}' ${args}",
},
before => "${qargs}" ? { # only if exec exists !
'' => undef,
@@ -1452,7 +1615,7 @@
onlyif => "${exists}",
unless => $watch ? {
false => undef, # don't run the diff checker...
- default => "${exists} && ${vardir}/diff.py '${valid_principal}' --rtype='service' ${args}",
+ default => "${exists} && ${vardir}/diff.py service '${valid_principal}' ${args}",
},
require => [
File["${vardir}/diff.py"],
@@ -1674,6 +1837,611 @@
}
}
+class ipa::server::user::base {
+ include ipa::server
+ include ipa::vardir
+ #$vardir = $::ipa::vardir::module_vardir # with trailing slash
+ $vardir = regsubst($::ipa::vardir::module_vardir, '\/$', '')
+
+ # by default, the following users get installed with freeipa:
+ # admin
+ # since we don't want to purge them, we need to exclude them...
+ $user_always_ignore = ['admin']
+ $user_excludes = $ipa::server::user_excludes
+ $valid_user_excludes = type($user_excludes) ? {
+ 'string' => [$user_excludes],
+ 'array' => $user_excludes,
+ 'boolean' => $user_excludes ? {
+ # TODO: there's probably a better user match expression
+ # this is an expression to prevent all user deletion...
+ #true => ['^[a-zA-Z0-9]*$'],
+ true => ['^[[:alpha:]]{1}[[:alnum:]]*$'],
+ default => false,
+ },
+ default => false, # trigger error...
+ }
+
+ if type($valid_user_excludes) != 'array' {
+ fail('The $user_excludes must be an array.')
+ }
+
+ # directory of system tags which should exist (as managed by puppet)
+ file { "${vardir}/users/":
+ ensure => directory, # make sure this is a directory
+ recurse => true, # recursively manage directory
+ purge => true, # purge all unmanaged files
+ force => true, # also purge subdirs and links
+ owner => root, group => nobody, mode => 600, backup => false,
+ notify => Exec['ipa-clean-users'],
+ require => File["${vardir}/"],
+ }
+
+ # these are template variables for the clean.sh.erb script
+ $id_dir = 'users'
+ $ls_cmd = '/usr/bin/ipa user-find --pkey-only --raw | /usr/bin/tr -d " " | /bin/grep "^uid:" | /bin/cut -b 5-' # show ipa users
+ $rm_cmd = '/usr/bin/ipa user-del ' # delete ipa users
+ $fs_chr = ' '
+ $suffix = '.user'
+ $regexp = $valid_user_excludes
+ $ignore = $user_always_ignore
+
+ # build the clean script
+ file { "${vardir}/clean-users.sh":
+ content => template('ipa/clean.sh.erb'),
+ owner => root,
+ group => nobody,
+ mode => 700, # u=rwx
+ backup => false, # don't backup to filebucket
+ ensure => present,
+ require => File["${vardir}/"],
+ }
+
+ # run the cleanup
+ exec { "${vardir}/clean-users.sh":
+ logoutput => on_failure,
+ refreshonly => true,
+ require => [
+ Exec['ipa-server-kinit'],
+ File["${vardir}/clean-users.sh"],
+ ],
+ alias => 'ipa-clean-users',
+ }
+
+ file { "${vardir}/users/passwords/": # for storing random passwords
+ ensure => directory, # make sure this is a directory
+ recurse => true, # recursively manage directory
+ purge => true, # purge all unmanaged files
+ force => true, # also purge subdirs and links
+ owner => root, group => nobody, mode => 600, backup => false,
+ require => File["${vardir}/users/"],
+ }
+}
+
+define ipa::server::user( # $login or principal as a unique id
+ $login = '', # usually the same as $name, but set manually
+ $instance = '', # as in: user/instance@REALM
+ $domain = '', # must be the empty string by default
+ $realm = '',
+ $principal = true, # after all that, you can override principal...
+
+ # name args
+ $first = '', # required
+ $last = '', # required
+ $cn = true, # full name, defaults to "$first $last"
+ $displayname = true, # defaults to "$first $last"
+ $initials = true, # defaults to $first[0]+$last[0]
+
+ # some of these parameters can be strings, arrays, or boolean specials!
+ $email = true, # comes with a sensible default (false = no)
+ $gecos = true, # old style passwd field, can be set manually
+
+ # special characteristics
+ $uid = true, # either pick a value, or let system assign it!
+ $gid = true, # true means try to match $uid value on create!
+ $shell = true,
+ $home = true,
+ $sshpubkeys = false,
+
+ # password
+ $random = false, # set to true to have the password generated...
+ $password_file = false, # save to file in ${vardir}/ipa/users/passwords/
+ $password_mail = false, # TODO: mail a gpg encrypted password to admin!
+
+ # mailing address section (just plain strings, false is unmanaged)
+ $street = false, # street address
+ $city = false, # city
+ $state = false, # state/province
+ $postalcode = false, # zip/postal code
+
+ # these four accept arrays or a string. false means unmanaged...
+ $phone = false, # telephone number
+ $mobile = false, # mobile telephone number
+ $pager = false, # pager number
+ $fax = false, # fax number
+
+ # other information
+ $jobtitle = false, # job title
+ $orgunit = false, # org. unit (department)
+ $manager = false, # manager (should match an existing user $name)
+ $carlicense = false, # car license (who cares?)
+
+ #$hosts = [], # TODO: add hosts managed by support if exists!
+
+ # special parameters...
+ $watch = true, # manage all changes to this resource, reverting others
+ $modify = true, # modify this resource on puppet changes or not ?
+ $comment = '',
+ $ensure = present # TODO
+) {
+ include ipa::server
+ include ipa::server::user::base
+ include ipa::vardir
+ #$vardir = $::ipa::vardir::module_vardir # with trailing slash
+ $vardir = regsubst($::ipa::vardir::module_vardir, '\/$', '')
+
+ # TODO: a better regexp magician could probably do a better job :)
+ # james/admin@EXAMPLE.COM
+ # james@EXAMPLE.COM
+ # james
+ $r = '^([a-zA-Z][a-zA-Z0-9]*)((/([a-zA-Z][a-zA-Z0-9]*)){0,1}@([A-Z][A-Z\.\-]*)){0,1}$'
+
+ $a = regsubst("${name}", $r, '\1') # login (james)
+ $b = regsubst("${name}", $r, '\4') # instance (admin)
+ $c = regsubst("${name}", $r, '\5') # realm (EXAMPLE.COM)
+
+ # user: first try to get value from arg, then fall back to $a (name)
+ $valid_login = "${login}" ? {
+ '' => "${a}", # get from $name regexp
+ default => "${login}",
+ }
+ if "${valid_login}" == '' {
+ # NOTE: if we see this message it might be a regexp pattern bug
+ fail('The $login must be specified.')
+ }
+
+ # host: first try to get value from arg, then fall back to $b
+ # this is not necessarily the group, but it could be. both are possible
+ # empty values are allowed and possibly even common :)
+ $valid_instance = "${instance}" ? {
+ '' => "${b}", # get from $name regexp
+ default => "${instance}",
+ }
+
+ $valid_domain = "${domain}" ? {
+ '' => "${ipa::server::domain}" ? { # NOTE: server!
+ '' => "${::domain}", # default to global val
+ default => "${ipa::server::domain}", # main!
+ },
+ default => "${domain}",
+ }
+
+ # this error condition is very important because '' is used as trigger!
+ if "${valid_domain}" == '' {
+ fail('The $domain must be specified.')
+ }
+
+ $valid_realm = "${realm}" ? {
+ '' => "${c}" ? { # get from $name regexp
+ '' => upcase($valid_domain), # a backup plan default
+ default => "${c}", # got from $name regexp
+ },
+ default => "${realm}",
+ }
+
+ # sanity checking, this should probably not happen
+ if "${valid_realm}" == '' {
+ fail('The $realm must be specified.')
+ }
+
+ # to be used if principal is generated from the available entered data!
+ $auto_principal = "${valid_instance}" ? {
+ '' => "${valid_login}@${valid_realm}", # no instance !
+ default => "${valid_login}/${valid_instance}@${valid_realm}",
+ }
+
+ $valid_principal = type($principal) ? {
+ 'string' => "${principal}" ? {
+ '' => "${auto_principal}",
+ default => "${principal}", # just do what you want
+ },
+ 'boolean' => $principal ? {
+ false => '', # don't use a principal
+ default => "${auto_principal}",
+ },
+ default => '',
+ }
+
+ if $watch and (! $modify) {
+ fail('You must be able to $modify to be able to $watch.')
+ }
+
+ if "${first}" == '' {
+ fail("The first name is required for: '${valid_login}'.")
+ }
+ if "${last}" == '' {
+ fail("The last name is required for: '${valid_login}'.")
+ }
+
+ $args01 = "${first}" ? {
+ '' => '',
+ default => "--first='${first}'",
+ }
+ $args02 = "${last}" ? {
+ '' => '',
+ default => "--last='${last}'",
+ }
+
+ $args03 = type($cn) ? {
+ 'string' => "--cn='${cn}'",
+ 'boolean' => $cn ? {
+ false => '',
+ default => "--cn='${first} ${last}'",
+ },
+ default => '',
+ }
+
+ $args04 = type($displayname) ? {
+ 'string' => "--displayname='${displayname}'",
+ 'boolean' => $displayname ? {
+ false => '',
+ default => "--displayname='${first} ${last}'",
+ },
+ default => '',
+ }
+
+ $args05 = type($initials) ? {
+ 'string' => "--initials='${displayname}'",
+ 'boolean' => $initials ? {
+ false => '',
+ # NOTE: [0,1] is a version robust way to get index 0...
+ default => sprintf("--initials='%s'", inline_template('<%= first[0,1]+last[0,1] %>')),
+ },
+ default => '',
+ }
+
+ # email can provide a sensible default
+ $default_email_domain = $ipa::server::default_email_domain
+ $valid_email = type($email) ? {
+ 'string' => "${email}" ? {
+ '' => [], # assume managed but empty (rm values)
+ default => ["${email}"],
+ },
+ 'array' => $email,
+ 'boolean' => $email ? {
+ false => '', # unmanaged
+ default => ["${valid_login}@${default_email_domain}"], # sensible default
+ },
+ default => '', # unmanaged
+ }
+ $args06 = type($valid_email) ? {
+ 'array' => inline_template('<% if valid_email == [] %>--email=<% else %><%= valid_email.map {|x| "--email=\'"+x+"\'" }.join(" ") %><% end %>'),
+ default => '', # unmanaged
+ }
+
+ $args07 = type($gecos) ? {
+ 'string' => "--gecos='${gecos}'",
+ 'boolean' => $gecos ? {
+ false => '',
+ default => "--gecos='${first} ${last}'",
+ },
+ default => '',
+ }
+
+ # TODO: validate id ranges ?
+ $args08 = type($uid) ? {
+ 'string' => "--uid='${uid}'",
+ 'integer' => "--uid='${uid}'",
+ default => '',
+ }
+
+ # TODO: validate id ranges ?
+ $args09 = type($gid) ? {
+ 'string' => "--gidnumber='${gid}'",
+ 'integer' => "--gidnumber='${gid}'",
+ 'boolean' => $gid ? {
+ false => '',
+ default => type($uid) ? { # auto try to match uid
+ 'string' => "--gidnumber='${uid}'", # uid !
+ 'integer' => "--gidnumber='${uid}'", # uid !
+ default => '', # auto
+ },
+ },
+ default => '',
+ }
+
+ $default_shell = $ipa::server::default_shell
+ $args10 = type($shell) ? {
+ 'string' => "--shell='${shell}'",
+ 'boolean' => $shell ? {
+ false => '',
+ default => "--shell='${default_shell}'",
+ },
+ default => '',
+ }
+
+ # TODO: the home stuff seems to not use trailing slashes. can i add it?
+ $default_homes = $ipa::server::default_homes
+ $args11 = type($home) ? {
+ 'string' => sprintf("--homedir='%s'", regsubst("${home}" , '\/$', '')),
+ 'boolean' => $home ? {
+ false => '',
+ default => type($default_homes) ? {
+ 'string' => sprintf("--homedir='%s/${valid_login}'", regsubst("${default_homes}" , '\/$', '')),
+ # TODO: warning ?
+ default => '', # can't manage, parent is false
+ },
+ },
+ default => '',
+ }
+
+ # users individual ssh public keys
+ $valid_sshpubkeys = type($sshpubkeys) ? {
+ 'string' => "${sshpubkeys}" ? {
+ '' => [], # assume managed but empty (rm values)
+ default => ["${sshpubkeys}"],
+ },
+ 'array' => $sshpubkeys,
+ default => '', # unmanaged
+ }
+ $args12 = type($valid_sshpubkeys) ? {
+ 'array' => inline_template('<% if valid_sshpubkeys == [] %>--sshpubkey=<% else %><%= valid_sshpubkeys.map {|x| "--sshpubkey=\'"+x+"\'" }.join(" ") %><% end %>'),
+ default => '', # unmanaged
+ }
+
+ # mailing address section
+ $args13 = type($street) ? {
+ 'string' => "--street='${street}'",
+ 'boolean' => $street ? {
+ true => '--street=', # managed
+ default => '', # unmanaged
+ },
+ default => '', # whatever and unmanaged
+ }
+
+ $args14 = type($city) ? {
+ 'string' => "--city='${city}'",
+ 'boolean' => $city ? {
+ true => '--city=',
+ default => '',
+ },
+ default => '',
+ }
+
+ $args15 = type($state) ? { # or province
+ 'string' => "--state='${state}'",
+ 'boolean' => $state ? {
+ true => '--state=',
+ default => '',
+ },
+ default => '',
+ }
+
+ $args16 = type($postalcode) ? {
+ 'string' => "--postalcode='${postalcode}'",
+ 'boolean' => $postalcode ? {
+ true => '--postalcode=',
+ default => '',
+ },
+ default => '',
+ }
+
+ # the following four phone number types can be arrays
+ $valid_phone = type($phone) ? {
+ 'string' => "${phone}" ? {
+ '' => [], # assume managed but empty (rm values)
+ default => ["${phone}"],
+ },
+ 'array' => $phone,
+ default => '', # unmanaged
+ }
+ $args17 = type($valid_phone) ? {
+ 'array' => inline_template('<% if valid_phone == [] %>--phone=<% else %><%= valid_phone.map {|x| "--phone=\'"+x+"\'" }.join(" ") %><% end %>'),
+ default => '', # unmanaged
+ }
+
+ $valid_mobile = type($mobile) ? {
+ 'string' => "${mobile}" ? {
+ '' => [], # assume managed but empty (rm values)
+ default => ["${mobile}"],
+ },
+ 'array' => $mobile,
+ default => '', # unmanaged
+ }
+ $args18 = type($valid_mobile) ? {
+ 'array' => inline_template('<% if valid_mobile == [] %>--mobile=<% else %><%= valid_mobile.map {|x| "--mobile=\'"+x+"\'" }.join(" ") %><% end %>'),
+ default => '', # unmanaged
+ }
+
+ $valid_pager = type($pager) ? {
+ 'string' => "${pager}" ? {
+ '' => [], # assume managed but empty (rm values)
+ default => ["${pager}"],
+ },
+ 'array' => $pager,
+ default => '', # unmanaged
+ }
+ $args19 = type($valid_pager) ? {
+ 'array' => inline_template('<% if valid_pager == [] %>--pager=<% else %><%= valid_pager.map {|x| "--pager=\'"+x+"\'" }.join(" ") %><% end %>'),
+ default => '', # unmanaged
+ }
+
+ $valid_fax = type($fax) ? {
+ 'string' => "${fax}" ? {
+ '' => [], # assume managed but empty (rm values)
+ default => ["${fax}"],
+ },
+ 'array' => $fax,
+ default => '', # unmanaged
+ }
+ $args20 = type($valid_fax) ? {
+ 'array' => inline_template('<% if valid_fax == [] %>--fax=<% else %><%= valid_fax.map {|x| "--fax=\'"+x+"\'" }.join(" ") %><% end %>'),
+ default => '', # unmanaged
+ }
+
+ # other information
+ $args21 = type($jobtitle) ? { # job title
+ 'string' => "--title='${jobtitle}'",
+ 'boolean' => $jobtitle ? {
+ true => '--title=',
+ default => '',
+ },
+ default => '',
+ }
+
+ $args22 = type($orgunit) ? {
+ 'string' => "--orgunit='${orgunit}'",
+ 'boolean' => $orgunit ? {
+ true => '--orgunit=',
+ default => '',
+ },
+ default => '',
+ }
+
+ # manager requires user exists... this lets us match a user principal
+ $valid_manager = regsubst("${manager}", $r, '\1') # login (james)
+ $args23 = type($manager) ? { # this has to match an existing user...
+ 'string' => "--manager='${valid_manager}'",
+ 'boolean' => $manager ? {
+ true => '--manager=',
+ default => '',
+ },
+ default => '',
+ }
+
+ $args24 = type($carlicense) ? {
+ 'string' => "--carlicense='${carlicense}'",
+ 'boolean' => $carlicense ? {
+ true => '--carlicense=',
+ default => '',
+ },
+ default => '',
+ }
+
+ $arglist = ["${args01}", "${args02}", "${args03}", "${args04}", "${args05}", "${args06}", "${args07}", "${args08}", "${args09}", "${args10}", "${args11}", "${args12}", "${args13}", "${args14}", "${args15}", "${args16}", "${args17}", "${args18}", "${args19}", "${args20}", "${args21}", "${args22}", "${args23}", "${args24}"]
+ $args = join(delete($arglist, ''), ' ')
+
+ # switch bad characters for file name friendly characters (unused atm!)
+ # this could be useful if we allow login's with $ and others in them...
+ $valid_login_file = regsubst("${valid_login}", '\$', '-', 'G')
+ file { "${vardir}/users/${valid_login_file}.user":
+ content => "${valid_login}\n${args}\n",
+ owner => root,
+ group => nobody,
+ mode => 600, # u=rw,go=
+ require => File["${vardir}/users/"],
+ ensure => present,
+ }
+
+ if $random and $password_file {
+ file { "${vardir}/users/passwords/${valid_login}.password":
+ # no content! this is a tag, content comes in by echo !
+ owner => root,
+ group => nobody,
+ mode => 600, # u=rw,go=
+ backup => false,
+ require => File["${vardir}/users/passwords/"],
+ ensure => present,
+ }
+ }
+
+ $exists = "/usr/bin/ipa user-show '${valid_login}' > /dev/null 2>&1"
+ # this requires ensures the $manager user exists when we can check that
+ # this melds together the kinit require which is needed by the user add
+ $requires = type($manager) ? {
+ 'string' => "${manager}" ? {
+ '' => Exec['ipa-server-kinit'],
+ default => $watch ? {
+ false => Exec['ipa-server-kinit'],
+ default => [
+ Exec['ipa-server-kinit'],
+ Ipa::Server::User["${manager}"],
+ ],
+ },
+ },
+ default => Exec['ipa-server-kinit'],
+ }
+
+ # principal is only set on user add... it can't be edited afaict
+ $principal_arg = "${valid_principal}" ? { # not shown in ipa gui!
+ '' => '',
+ default => "--principal='${valid_principal}'",
+ }
+
+ $aargs = "${principal_arg}" ? { # principal exists
+ '' => "${args}", # just normal args
+ default => "${principal_arg} ${args}", # pixel perfect...
+ }
+
+ # NOTE: this runs when no user is present...
+ exec { "ipa-server-user-add-${name}": # alias
+ # this has to be here because the command string gets too long
+ # for a puppet $name var and strange things start to happen...
+ command => "/usr/bin/ipa user-add '${valid_login}' ${aargs}",
+ logoutput => on_failure,
+ unless => "${exists}",
+ require => $requires,
+ }
+
+ # NOTE: this runs when we detect that the attributes don't match (diff)
+ if $modify and ("${args}" != '') { # if there are changes to do...
+ #exec { "/usr/bin/ipa user-mod '${valid_login}' ${args}":
+ exec { "ipa-server-user-mod-${name}":
+ command => "/usr/bin/ipa user-mod '${valid_login}' ${args}",
+ logoutput => on_failure,
+ refreshonly => $watch ? {
+ false => true, # when not watching, we
+ default => undef, # refreshonly to change
+ },
+ subscribe => $watch ? {
+ false => File["${vardir}/users/${valid_login_file}.user"],
+ default => undef,
+ },
+ onlyif => "${exists}",
+ unless => $watch ? {
+ false => undef, # don't run the diff checker...
+ default => "${exists} && ${vardir}/diff.py user '${valid_login}' ${args}",
+ },
+ require => [
+ File["${vardir}/diff.py"],
+ Exec['ipa-server-kinit'],
+ # this user-add exec pulls in manager $requires
+ Exec["ipa-server-user-add-${name}"],
+ ],
+ #alias => "ipa-server-user-mod-${name}",
+ }
+ }
+
+ $prog01 = $password_file ? {
+ true => "/bin/cat > ${vardir}/users/passwords/${valid_login}.password",
+ default => '',
+ }
+
+ $gpg_email = $ipa::server::valid_email # admin email
+ #$gpg_key = $ipa::server::TODO
+ $prog02 = $password_mail ? {
+ #true => "/bin/cat | /usr/bin/gpg TODO | /bin/mailx -s 'GPG encrypted password' '${gpg_email}'", # FIXME: add this code!
+ default => '',
+ }
+
+ if $modify and $random {
+ $proglist = ["${prog01}", "${prog02}"]
+ # eg /usr/bin/tee /dev/null >(prog1) >(prog2) >(progN)
+ $progs = join(suffix(prefix(delete($proglist, ''), '>('), ')'), ' ')
+ exec { "ipa-server-user-qmod-${name}":
+ # bash -c is needed because this command uses bashisms!
+ command => "/bin/bash -c \"/usr/bin/ipa user-mod '${valid_login}' --raw --random | /usr/bin/tr -d ' ' | /bin/grep '^randompassword:' | /bin/cut -b 16- | /usr/bin/tee /dev/null ${progs}\"",
+ logoutput => on_failure,
+ onlyif => "/usr/bin/test \"`/usr/bin/ipa user-show '${valid_login}' --raw | /usr/bin/tr -d ' ' | /bin/grep '^has_password:' | /bin/cut -b 14-`\" = 'False'",
+ require => [
+ Exec['ipa-server-kinit'],
+ Exec["ipa-server-user-add-${name}"],
+ #Exec["ipa-server-user-mod-${name}"], # not needed...
+ ],
+ #alias => "ipa-server-user-qmod-${name}",
+ }
+ }
+}
+
# NOTE: use this to deploy all the @@ipa::client::* exported resources on clients
# the $nametag variable should match the $name value of the server/client::host
class ipa::client::deploy(