Add user and group creation to blueprint
[[customize.user]] and [[customize.group]]
This commit is contained in:
parent
25bec0b50d
commit
07ea61be77
@ -84,6 +84,78 @@ def repo_to_ks(r, url="url"):
|
|||||||
return cmd
|
return cmd
|
||||||
|
|
||||||
|
|
||||||
|
def write_ks_user(f, user):
|
||||||
|
""" Write kickstart user and sshkey entry
|
||||||
|
|
||||||
|
:param f: kickstart file object
|
||||||
|
:type f: open file object
|
||||||
|
:param user: A blueprint user dictionary
|
||||||
|
:type user: dict
|
||||||
|
|
||||||
|
If the entry contains a ssh key, use sshkey to write it
|
||||||
|
All of the user fields are optional, except name, write out a kickstart user entry
|
||||||
|
with whatever options are relevant.
|
||||||
|
"""
|
||||||
|
if "name" not in user:
|
||||||
|
raise RuntimeError("user entry requires a name")
|
||||||
|
|
||||||
|
# ssh key uses the sshkey kickstart command
|
||||||
|
if "key" in user:
|
||||||
|
f.write('sshkey --user %s "%s"\n' % (user["name"], user["key"]))
|
||||||
|
|
||||||
|
# Write out the user kickstart command, much of it is optional
|
||||||
|
f.write("user --name %s" % user["name"])
|
||||||
|
if "home" in user:
|
||||||
|
f.write(" --homedir %s" % user["home"])
|
||||||
|
|
||||||
|
if "password" in user:
|
||||||
|
if any(user["password"].startswith(prefix) for prefix in ["$2b$", "$6$", "$5$"]):
|
||||||
|
log.debug("Detected pre-crypted password")
|
||||||
|
f.write(" --iscrypted")
|
||||||
|
else:
|
||||||
|
log.debug("Detected plaintext password")
|
||||||
|
f.write(" --plaintext")
|
||||||
|
|
||||||
|
f.write(" --password \"%s\"" % user["password"])
|
||||||
|
|
||||||
|
if "shell" in user:
|
||||||
|
f.write(" --shell %s" % user["shell"])
|
||||||
|
|
||||||
|
if "uid" in user:
|
||||||
|
f.write(" --uid %d" % int(user["uid"]))
|
||||||
|
|
||||||
|
if "gid" in user:
|
||||||
|
f.write(" --gid %d" % int(user["gid"]))
|
||||||
|
|
||||||
|
if "description" in user:
|
||||||
|
f.write(" --gecos \"%s\"" % user["description"])
|
||||||
|
|
||||||
|
if "groups" in user:
|
||||||
|
f.write(" --groups %s" % ",".join(user["groups"]))
|
||||||
|
|
||||||
|
f.write("\n")
|
||||||
|
|
||||||
|
|
||||||
|
def write_ks_group(f, group):
|
||||||
|
""" Write kickstart group entry
|
||||||
|
|
||||||
|
:param f: kickstart file object
|
||||||
|
:type f: open file object
|
||||||
|
:param group: A blueprint group dictionary
|
||||||
|
:type user: dict
|
||||||
|
|
||||||
|
gid is optional
|
||||||
|
"""
|
||||||
|
if "name" not in group:
|
||||||
|
raise RuntimeError("group entry requires a name")
|
||||||
|
|
||||||
|
f.write("group --name %s" % group["name"])
|
||||||
|
if "gid" in group:
|
||||||
|
f.write(" --gid %d" % int(group["gid"]))
|
||||||
|
|
||||||
|
f.write("\n")
|
||||||
|
|
||||||
|
|
||||||
def add_customizations(f, recipe):
|
def add_customizations(f, recipe):
|
||||||
""" Add customizations to the kickstart file
|
""" Add customizations to the kickstart file
|
||||||
|
|
||||||
@ -101,13 +173,23 @@ def add_customizations(f, recipe):
|
|||||||
if "hostname" in customizations:
|
if "hostname" in customizations:
|
||||||
f.write("network --hostname=%s\n" % customizations["hostname"])
|
f.write("network --hostname=%s\n" % customizations["hostname"])
|
||||||
|
|
||||||
|
# TODO - remove this, should use user section to define this
|
||||||
if "sshkey" in customizations:
|
if "sshkey" in customizations:
|
||||||
# This is a list of entries
|
# This is a list of entries
|
||||||
for sshkey in customizations["sshkey"]:
|
for sshkey in customizations["sshkey"]:
|
||||||
if "user" not in sshkey or "key" not in sshkey:
|
if "user" not in sshkey or "key" not in sshkey:
|
||||||
log.error("%s is incorrect, skipping", sshkey)
|
log.error("%s is incorrect, skipping", sshkey)
|
||||||
continue
|
continue
|
||||||
f.write('sshkey --user %s "%s"' % (sshkey["user"], sshkey["key"]))
|
f.write('sshkey --user %s "%s"\n' % (sshkey["user"], sshkey["key"]))
|
||||||
|
|
||||||
|
if "user" in customizations:
|
||||||
|
# only name is required, everything else is optional
|
||||||
|
for user in customizations["user"]:
|
||||||
|
write_ks_user(f, user)
|
||||||
|
|
||||||
|
if "group" in customizations:
|
||||||
|
for group in customizations["group"]:
|
||||||
|
write_ks_group(f, group)
|
||||||
|
|
||||||
|
|
||||||
def start_build(cfg, dnflock, gitlock, branch, recipe_name, compose_type, test_mode=0):
|
def start_build(cfg, dnflock, gitlock, branch, recipe_name, compose_type, test_mode=0):
|
||||||
@ -197,14 +279,14 @@ def start_build(cfg, dnflock, gitlock, branch, recipe_name, compose_type, test_m
|
|||||||
# Save a copy of the original kickstart
|
# Save a copy of the original kickstart
|
||||||
shutil.copy(ks_template_path, results_dir)
|
shutil.copy(ks_template_path, results_dir)
|
||||||
|
|
||||||
# Create the final kickstart with repos and package list
|
|
||||||
ks_path = joinpaths(results_dir, "final-kickstart.ks")
|
|
||||||
with open(ks_path, "w") as f:
|
|
||||||
with dnflock.lock:
|
with dnflock.lock:
|
||||||
repos = list(dnflock.dbo.repos.iter_enabled())
|
repos = list(dnflock.dbo.repos.iter_enabled())
|
||||||
if not repos:
|
if not repos:
|
||||||
raise RuntimeError("No enabled repos, canceling build.")
|
raise RuntimeError("No enabled repos, canceling build.")
|
||||||
|
|
||||||
|
# Create the final kickstart with repos and package list
|
||||||
|
ks_path = joinpaths(results_dir, "final-kickstart.ks")
|
||||||
|
with open(ks_path, "w") as f:
|
||||||
ks_url = repo_to_ks(repos[0], "url")
|
ks_url = repo_to_ks(repos[0], "url")
|
||||||
log.debug("url = %s", ks_url)
|
log.debug("url = %s", ks_url)
|
||||||
f.write('url %s\n' % ks_url)
|
f.write('url %s\n' % ks_url)
|
||||||
|
@ -12,3 +12,30 @@ hostname = "custombase"
|
|||||||
[[customizations.sshkey]]
|
[[customizations.sshkey]]
|
||||||
user = "root"
|
user = "root"
|
||||||
key = "A SSH KEY FOR ROOT"
|
key = "A SSH KEY FOR ROOT"
|
||||||
|
|
||||||
|
[[customizations.user]]
|
||||||
|
name = "widget"
|
||||||
|
description = "Widget process user account"
|
||||||
|
home = "/srv/widget/"
|
||||||
|
shell = "/usr/bin/false"
|
||||||
|
groups = ["dialout", "users"]
|
||||||
|
|
||||||
|
[[customizations.user]]
|
||||||
|
name = "admin"
|
||||||
|
description = "Widget admin account"
|
||||||
|
password = "$6$CHO2$3rN8eviE2t50lmVyBYihTgVRHcaecmeCk31LeOUleVK/R/aeWVHVZDi26zAH.o0ywBKH9Tc0/wm7sW/q39uyd1"
|
||||||
|
home = "/srv/widget/"
|
||||||
|
shell = "/usr/bin/bash"
|
||||||
|
groups = ["widget", "users"]
|
||||||
|
uid = 1200
|
||||||
|
|
||||||
|
[[customizations.user]]
|
||||||
|
name = "plain"
|
||||||
|
password = "simple plain password"
|
||||||
|
|
||||||
|
[[customizations.user]]
|
||||||
|
name = "bart"
|
||||||
|
key = "SSH KEY FOR BART"
|
||||||
|
|
||||||
|
[[customizations.group]]
|
||||||
|
name = "widget"
|
||||||
|
161
tests/pylorax/test_compose.py
Normal file
161
tests/pylorax/test_compose.py
Normal file
@ -0,0 +1,161 @@
|
|||||||
|
#
|
||||||
|
# Copyright (C) 2018 Red Hat, Inc.
|
||||||
|
#
|
||||||
|
# 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 2 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, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
from io import StringIO
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
from pylorax.api.compose import add_customizations
|
||||||
|
from pylorax.api.recipes import recipe_from_toml
|
||||||
|
|
||||||
|
BASE_RECIPE = """name = "test-cases"
|
||||||
|
description = "Used for testing"
|
||||||
|
version = "0.0.1"
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
HOSTNAME = BASE_RECIPE + """[customizations]
|
||||||
|
hostname = "testhostname"
|
||||||
|
"""
|
||||||
|
|
||||||
|
SSHKEY = BASE_RECIPE + """[[customizations.sshkey]]
|
||||||
|
user = "root"
|
||||||
|
key = "ROOT SSH KEY"
|
||||||
|
"""
|
||||||
|
|
||||||
|
USER = BASE_RECIPE + """[[customizations.user]]
|
||||||
|
name = "tester"
|
||||||
|
"""
|
||||||
|
|
||||||
|
USER_KEY = """
|
||||||
|
key = "A SSH KEY FOR TESTER"
|
||||||
|
"""
|
||||||
|
|
||||||
|
USER_DESC = """
|
||||||
|
description = "a test user account"
|
||||||
|
"""
|
||||||
|
|
||||||
|
USER_CRYPT = """
|
||||||
|
password = "$6$CHO2$3rN8eviE2t50lmVyBYihTgVRHcaecmeCk31LeOUleVK/R/aeWVHVZDi26zAH.o0ywBKH9Tc0/wm7sW/q39uyd1"
|
||||||
|
"""
|
||||||
|
|
||||||
|
USER_PLAIN = """
|
||||||
|
password = "plainpassword"
|
||||||
|
"""
|
||||||
|
|
||||||
|
USER_HOME = """
|
||||||
|
home = "/opt/users/tester/"
|
||||||
|
"""
|
||||||
|
|
||||||
|
USER_SHELL = """
|
||||||
|
shell = "/usr/bin/zsh"
|
||||||
|
"""
|
||||||
|
|
||||||
|
USER_UID = """
|
||||||
|
uid = 1013
|
||||||
|
"""
|
||||||
|
|
||||||
|
USER_GID = """
|
||||||
|
gid = 4242
|
||||||
|
"""
|
||||||
|
|
||||||
|
USER_GROUPS = """
|
||||||
|
groups = ["wheel", "users"]
|
||||||
|
"""
|
||||||
|
|
||||||
|
USER_ALL = USER + USER_KEY + USER_DESC + USER_CRYPT + USER_HOME + USER_SHELL + USER_UID + USER_GID
|
||||||
|
|
||||||
|
GROUP = BASE_RECIPE + """[[customizations.group]]
|
||||||
|
name = "testgroup"
|
||||||
|
"""
|
||||||
|
|
||||||
|
GROUP_GID = GROUP + """
|
||||||
|
gid = 1011
|
||||||
|
"""
|
||||||
|
|
||||||
|
KS_USER_ALL = '''sshkey --user tester "A SSH KEY FOR TESTER"
|
||||||
|
user --name tester --homedir /opt/users/tester/ --iscrypted --password "$6$CHO2$3rN8eviE2t50lmVyBYihTgVRHcaecmeCk31LeOUleVK/R/aeWVHVZDi26zAH.o0ywBKH9Tc0/wm7sW/q39uyd1" --shell /usr/bin/zsh --uid 1013 --gid 4242 --gecos "a test user account"
|
||||||
|
'''
|
||||||
|
|
||||||
|
class CustomizationsTestCase(unittest.TestCase):
|
||||||
|
def assertCustomization(self, test, result):
|
||||||
|
r = recipe_from_toml(test)
|
||||||
|
f = StringIO()
|
||||||
|
add_customizations(f, r)
|
||||||
|
self.assertTrue(result in f.getvalue(), f.getvalue())
|
||||||
|
|
||||||
|
def test_set_hostname(self):
|
||||||
|
"""Test setting the hostname"""
|
||||||
|
self.assertCustomization(HOSTNAME, "network --hostname=testhostname")
|
||||||
|
|
||||||
|
def test_set_sshkey(self):
|
||||||
|
"""Test setting sshkey without user"""
|
||||||
|
self.assertCustomization(SSHKEY, 'sshkey --user root "ROOT SSH KEY"')
|
||||||
|
|
||||||
|
def test_sshkey_only(self):
|
||||||
|
"""Test adding a sshkey to an existing user account"""
|
||||||
|
self.assertCustomization(USER + USER_KEY, 'sshkey --user tester "A SSH KEY FOR TESTER"')
|
||||||
|
|
||||||
|
def test_create_user(self):
|
||||||
|
"""Test creating a user with no options"""
|
||||||
|
self.assertCustomization(USER, "user --name tester")
|
||||||
|
|
||||||
|
def test_create_user_desc(self):
|
||||||
|
"""Test creating a user with a description"""
|
||||||
|
self.assertCustomization(USER + USER_DESC, '--gecos "a test user account"')
|
||||||
|
|
||||||
|
def test_create_user_crypt(self):
|
||||||
|
"""Test creating a user with a pre-crypted password"""
|
||||||
|
self.assertCustomization(USER + USER_CRYPT, '--password "$6$CHO2$3r')
|
||||||
|
|
||||||
|
def test_create_user_plain(self):
|
||||||
|
"""Test creating a user with a plaintext password"""
|
||||||
|
self.assertCustomization(USER + USER_PLAIN, '--password "plainpassword"')
|
||||||
|
|
||||||
|
def test_create_user_home(self):
|
||||||
|
"""Test creating user with a home directory"""
|
||||||
|
self.assertCustomization(USER + USER_HOME, "--homedir /opt/users/tester/")
|
||||||
|
|
||||||
|
def test_create_user_shell(self):
|
||||||
|
"""Test creating user with shell set"""
|
||||||
|
self.assertCustomization(USER + USER_SHELL, "--shell /usr/bin/zsh")
|
||||||
|
|
||||||
|
def test_create_user_uid(self):
|
||||||
|
"""Test creating user with uid set"""
|
||||||
|
self.assertCustomization(USER + USER_UID, "--uid 1013")
|
||||||
|
|
||||||
|
def test_create_user_gid(self):
|
||||||
|
"""Test creating user with gid set"""
|
||||||
|
self.assertCustomization(USER + USER_GID, "--gid 4242")
|
||||||
|
|
||||||
|
def test_create_user_groups(self):
|
||||||
|
"""Test creating user with group membership"""
|
||||||
|
self.assertCustomization(USER + USER_GROUPS, "--groups wheel,users")
|
||||||
|
|
||||||
|
def test_create_user_all(self):
|
||||||
|
"""Test creating user with all settings"""
|
||||||
|
r = recipe_from_toml(USER_ALL)
|
||||||
|
f = StringIO()
|
||||||
|
add_customizations(f, r)
|
||||||
|
self.assertEqual(KS_USER_ALL, f.getvalue())
|
||||||
|
|
||||||
|
def test_create_group(self):
|
||||||
|
"""Test creating group without gid set"""
|
||||||
|
self.assertCustomization(GROUP, "group --name testgroup")
|
||||||
|
|
||||||
|
def test_create_group_gid(self):
|
||||||
|
"""Test creating group with gid set"""
|
||||||
|
self.assertCustomization(GROUP_GID, "group --name testgroup --gid 1011")
|
||||||
|
|
Loading…
Reference in New Issue
Block a user