コード例 #1
0
    def run(self, params, args):
        (cmd, group) = self.fillParams([('command', None, True),
                                        ('group', None, True)])

        groupid = None
        try:
            groupid = int(group)
        except ValueError:
            pass

        if groupid is None:
            try:
                groupid = grp.getgrnam(group).gr_gid
            except KeyError:
                raise CommandError(self, 'cannot find group %s' % group)

        self.db.execute('insert into access(command, groupid) values (%s, %s)',
                        (cmd, groupid))
コード例 #2
0
    def deleteRule(self, table, rulename, extrasql=None):

        assert table
        assert rulename

        query = 'select * from %s where name="%s"' % (table, rulename)
        if extrasql:
            query = "%s and %s" % (query, extrasql)
        rows = self.db.execute(query)

        if rows == 0:
            raise CommandError(
                self, 'Could not find rule %s in %s' % (rulename, table))

        query = 'delete from %s where name="%s"' % (table, rulename)
        if extrasql:
            query = "%s and %s" % (query, extrasql)
        self.db.execute(query)
コード例 #3
0
    def run(self, params, args):
        (attr, doc) = self.fillParams([
            ('attr', None, True),
            ('doc', None, True),
        ])

        if self.db.count('(id) FROM attributes WHERE name = %s',
                         (attr, )) == 0:
            raise CommandError(
                self, 'Cannot set documentation for a non-existant attribute')

        self.db.execute('delete from attributes_doc where attr=%s', (attr, ))

        if doc:
            self.db.execute(
                """
				insert into attributes_doc(attr, doc) values (%s, %s)
			""", (attr, doc))
コード例 #4
0
ファイル: __init__.py プロジェクト: wwfeng990/stacki
	def run(self, params, args):

		if len(args) == 0:
			raise ArgRequired(self, 'environment')
		if len(args) != 1:
			raise ArgUnique(self, 'environment')
		environment = args[0]

		if self.db.count(
			'(ID) from environments where name=%s',
			(environment,)
		) > 0:
			raise CommandError(self, 'environment "%s" already exists' % environment)

		self.db.execute(
			'insert into environments(name) values (%s)',
			(environment,)
		)
コード例 #5
0
	def run(self, params, args):

		(attr, doc) = self.fillParams([
			('attr', None, True),
			('doc',  None, True),
			])

		rows = self.db.select('attr from attributes where attr=%s', (attr,))

		if not rows:
			raise CommandError(self, 'Cannot set documentation for a non-existant attribute')

		self.db.execute('delete from attributes_doc where attr=%s', (attr,))

		if doc:
			self.db.execute("""
				insert into attributes_doc(attr, doc) values (%s, %s)
			""", (attr, doc))
コード例 #6
0
ファイル: __init__.py プロジェクト: toomanytims/stacki
	def run(self, params, args):

		(environment, ) = self.fillParams([
			('environment', None, True)
			])
		
		if not len(args):
			raise ArgRequired(self, 'host')

		if environment and environment not in self.getEnvironmentNames():
			raise CommandError(self, 'environment parameter not valid')

		for host in self.getHostnames(args):
			self.db.execute("""
				update nodes set environment=
				(select id from environments where name='%s')
				where name='%s'
				""" % (environment, host))
コード例 #7
0
    def run(self, params, args):
        if len(args) < 1:
            raise ArgRequired(self, 'host')

        me = self.db.getHostname()
        hosts = self.getHostnames(args)

        # Don't allow the user to remove the host the command
        # is running on.  Right now that means cannot remove
        # the Frontend, but checked this way will allow for
        # future multiple Frontend's where you may still want
        # to remove some but not all of them.
        if me in hosts:
            raise CommandError(self, 'cannot remove "%s"' % me)
        # Don't run unless we actually got hosts back
        if hosts:
            self.runPlugins(hosts)
            self.command('sync.config')
コード例 #8
0
	def run(self, params, args):
		(b_action, b_type, b_os) = self.getBootActionTypeOS(params, args)

		if not self.actionExists(b_action, b_type, b_os):
			raise CommandError(self, 'action/type/os "%s/%s/%s" does not exists' % (b_action, b_type, b_os))

		if not b_os:
			self.db.execute("""
				delete from bootactions
				where os is NULL
				and bootname=(select id from bootnames where name=%s and type=%s)
			""", (b_action, b_type))
		else:
			self.db.execute("""
				delete from bootactions
				where os=(select id from oses where name=%s)
				and bootname=(select id from bootnames where name=%s and type=%s)
			""", (b_os, b_action, b_type))
コード例 #9
0
ファイル: __init__.py プロジェクト: tabitabe/stacki
    def run(self, params, args):
        filename, processor = self.fillParams([('file', None, True),
                                               ('processor', 'default')])

        if not os.path.exists(filename):
            raise CommandError(self, 'file "%s" does not exist' % filename)

        #
        # implementations can't return values
        #
        self.attrs = {}
        self.runImplementation('load_%s' % processor, (filename, ))
        self.runPlugins(self.attrs)
        self.command('sync.config')

        # Only sync the host config for the hosts in the
        # imported spreadsheet.

        hosts = self.getHostnames()
        for host in self.attrs.keys():
            if host in hosts:
                self.call('sync.host.config', [host])

        #
        # checkin the attribute spreadsheet
        #
        sheetsdir = '/export/stack/spreadsheets'
        if not os.path.exists(sheetsdir):
            os.makedirs(sheetsdir)

        RCSdir = '%s/RCS' % sheetsdir
        if not os.path.exists(RCSdir):
            os.makedirs(RCSdir)

        sheetsfile = '%s/%s' % (sheetsdir, os.path.basename(filename))
        if not os.path.exists(sheetsfile) or not \
         os.path.samefile(filename, sheetsfile):
            shutil.copyfile(filename, '%s' % sheetsfile)

        cmd = 'date | /opt/stack/bin/ci "%s"' % sheetsfile
        os.system(cmd)

        cmd = '/opt/stack/bin/co -f -l "%s"' % sheetsfile
        os.system(cmd)
コード例 #10
0
ファイル: __init__.py プロジェクト: toomanytims/stacki
    def run(self, params, args):
        if len(args) < 1:
            raise ArgRequired(self, 'pallet')

        (arch, box) = self.fillParams([('arch', self.arch),
                                       ('box', 'default')])

        rows = self.db.execute("""
			select * from boxes where name='%s'
			""" % box)
        if not rows:
            raise CommandError(self, 'unknown box "%s"' % box)

        for (roll, version, release) in self.getRollNames(args, params):
            if release:
                rel = "rel='%s'" % release
            else:
                rel = 'rel=""'

            rows = self.db.execute("""
				select b.name from
				stacks s, rolls r, boxes b where
				r.name = '%s' and
				r.version = '%s' and %s and
				r.arch = '%s' and
				b.name = '%s' and
				s.box = b.id and s.roll=r.id
				""" % (roll, version, rel, arch, box))

            if not rows:
                self.db.execute("""
					insert into stacks(box, roll)
					values (
					(select id from boxes where name='%s'),
					(select id from rolls where name='%s'
					and version='%s' and %s and arch='%s')
					)""" % (box, roll, version, rel, arch))

        # Regenerate stacki.repo
        os.system("""
			/opt/stack/bin/stack report host repo localhost | 
			/opt/stack/bin/stack report script | 
			/bin/sh
			""")
コード例 #11
0
ファイル: __init__.py プロジェクト: sukhbir148/stacki
    def run(self, params, args):

        (service, network, outnetwork, chain, action, protocol, flags, comment,
         table, rulename) = self.doParams()

        # Make sure we have a new rule
        if self.db.count('(*) from global_firewall where name=%s',
                         (rulename, )) > 0:
            raise CommandError(
                self, f'Rule with rulename "{rulename}" already exists')

        # Now let's add them
        self.db.execute(
            """insert into global_firewall
			(insubnet, outsubnet, service, protocol,
			action, chain, flags, comment, tabletype, name)
			values (%s, %s, %s, %s, %s, %s, %s, %s,%s, %s)""",
            (network, outnetwork, service, protocol, action, chain, flags,
             comment, table, rulename))
コード例 #12
0
    def run(self, params, args):

        (networks, address) = self.fillSetNetworkParams(args, 'address')
        if len(networks) > 1:
            raise ArgUnique(self, 'network')

        network = networks[0]

        # Make sure this is a valid network
        mask = self.db.select('mask from subnets where name=%s',
                              (network, ))[0][0]
        try:
            ipaddress.IPv4Network(f'{address}/{mask}')
        except:
            msg = '%s/%s is not a valid network address and subnet mask combination'
            raise CommandError(self, msg % (address, mask))

        self.db.execute('update subnets set address=%s where name=%s',
                        (address, network))
コード例 #13
0
ファイル: __init__.py プロジェクト: sukhbir148/stacki
    def run(self, params, args):
        (b_action, b_type, b_os) = self.getBootActionTypeOS(params, args)

        (b_kernel, b_ramdisk, b_args,
         force) = self.fillParams([('kernel', '', True), ('ramdisk', ''),
                                   ('args', ''), ('force', True)])

        force = self.str2bool(force)

        # If we don't force the update error out (AKA: the add command)
        existing = self.actionExists(b_action, b_type, b_os)
        if existing and not force:
            raise CommandError(self, 'action "%s" exists' % b_action)

        if not self.bootNameExists(b_action, b_type):
            self.db.execute(
                'insert into bootnames(name, type) values (%s, %s)',
                (b_action, b_type))

        if not existing:
            if b_os:
                self.db.execute(
                    """
					insert into bootactions(bootname, os) values (
						(select id from bootnames where name=%s and type=%s),
						(select id from oses where name=%s)
					)
				""", (b_action, b_type, b_os))
            else:
                self.db.execute(
                    """
					insert into bootactions(bootname) values (
						(select id from bootnames where name=%s and type=%s)
					)
				""", (b_action, b_type))

        for flag, command in [(b_kernel, 'kernel'), (b_args, 'args'),
                              (b_ramdisk, 'ramdisk')]:
            if flag:
                self.command(f'set.bootaction.{command}', [
                    b_action, f'type={b_type}', f'os={b_os}',
                    f'{command}={flag}'
                ])
コード例 #14
0
	def run(self, params, args):
		if not len(args):
			raise ArgRequired(self, 'switch')

		name, enforce_sm = self.fillParams([
			('name', None),
			('enforce_sm', False),
		])

		if name:
			name = name.lower()
		if name == 'default':
			name = 'Default'
		elif name != None:
			try:
				name = '0x{0:04x}'.format(int(name, 16))
			except ValueError:
				raise ParamValue(self, 'name', 'a hex value between 0x0001 and 0x7ffe, or "default"')

		switches = self.getSwitchNames(args)
		switch_attrs = self.getHostAttrDict(switches)
		for switch in switches:
			if switch_attrs[switch].get('switch_type') != 'infiniband':
				raise CommandError(self, f'{switch} does not have a switch_type of "infiniband"')

		if self.str2bool(enforce_sm):
			enforce_subnet_manager(self, switches)

		ids_sql = 'name, id FROM nodes WHERE name IN (%s)' % ','.join(['%s'] * len(switches))
		sw_ids = dict((row[0], row[1]) for row in self.db.select(ids_sql, tuple(switches)))

		format_str = ','.join(['%s'] * len(switches))
		delete_stmt = '''
			DELETE from ib_partitions
			WHERE switch IN (%s)''' % format_str
							   
		vals = list(sw_ids.values())

		if name:
			delete_stmt += ' AND ib_partitions.part_name=%s'
			vals.append(name)

		self.db.execute(delete_stmt, vals)
コード例 #15
0
    def _load(self, text):
        parser = JsonComment(json)  # standard JSON is stupid
        try:
            data = parser.loads(text)
        except ValueError as e:
            # parse the error message and split the input at the
            # syntax error
            i = int(re.search(r'char (.*?)\)', str(e)).group(1))
            b = text[:i]
            a = text[i:]

            # find the line with the error and report it
            # 'blen' in honor of our intern

            line1 = (b[b.rfind('\n'):] + a[:a.find('\n')]).strip()
            blen = len((b[b.rfind('\n'):]).strip())
            line2 = ' ' * blen + '^'
            raise CommandError(self, f'syntax error\n{line1}\n{line2}\n{e}')
        return data
コード例 #16
0
ファイル: plugin_basic.py プロジェクト: wwfeng990/stacki
	def run(self, args):
		# remove any duplicates
		args = tuple(unique_everseen(lowered(args)))
		# The imps must exist
		self.owner.ensure_imps_exist(imps = args)

		# remove the implementations
		try:
			self.owner.db.execute("DELETE FROM firmware_imp WHERE name IN %s", (args,))
		except IntegrityError:
			raise CommandError(
				cmd = self.owner,
				msg = (
					"Failed to remove all implementations because some are still in use."
					" Please run 'stack list firmware model expanded=true' to list the"
					" models still using the implementation and 'stack remove firmware model'"
					" to remove them."
				)
			)
コード例 #17
0
ファイル: __init__.py プロジェクト: toomanytims/stacki
    def run(self, params, args):
        (b_action, b_type, b_os) = self.getBootActionTypeOS(params, args)

        (b_kernel, b_ramdisk, b_args,
         force) = self.fillParams([('kernel', '', True), ('ramdisk', ''),
                                   ('args', ''), ('force', False)])

        force = self.str2bool(force)

        if self.actionExists(b_action, b_type, b_os):
            raise CommandError(self, 'action "%s" exists' % b_action)

        if not self.bootNameExists(b_action, b_type):
            self.db.execute("""
				insert into bootnames
				(name, type)
				values
				('%s', '%s')""" % (b_action, b_type))
        if b_os:
            self.db.execute("""
				insert into bootactions
				(bootname, os)
				values
				(
				(select id from bootnames where name='%s' and type='%s'),
				(select id from oses where name='%s')
				)""" % (b_action, b_type, b_os))
        else:
            self.db.execute("""
				insert into bootactions
				(bootname)
				values
				(
				(select id from bootnames where name='%s' and type='%s')
				)""" % (b_action, b_type))

        for (flag, command) in [(b_kernel, 'kernel'), (b_args, 'args'),
                                (b_ramdisk, 'ramdisk')]:
            if flag:
                self.command('set.bootaction.%s' % command,
                             (b_action, 'type=%s' % b_type, 'os=%s' % b_os,
                              '%s=%s' % (command, flag)))
コード例 #18
0
    def test_run_error(
        self,
        mock_unique_everseen,
        mock_lowered,
        basic_plugin,
    ):
        """Test that run fails when argument validation fails and does not touch the database."""
        expected_makes = ("foo", "bar", "baz")
        mock_args = [*expected_makes]
        mock_unique_everseen.return_value = (arg for arg in mock_args)
        basic_plugin.owner.ensure_unique_makes.side_effect = CommandError(
            cmd=basic_plugin.owner,
            msg="Test error",
        )

        with pytest.raises(CommandError):
            basic_plugin.run(args=mock_args)

        # Expect the database to be untouched
        basic_plugin.owner.db.execute.assert_not_called()
コード例 #19
0
ファイル: __init__.py プロジェクト: tabitabe/stacki
    def run(self, params, args):

        if len(args) == 0:
            raise ArgRequired(self, 'environment')
        if len(args) != 1:
            raise ArgUnique(self, 'environment')
        environment = args[0]

        dup = False
        for row in self.db.select("""
			* from environments where name='%s'
			""" % environment):
            dup = True
        if dup:
            raise CommandError(self,
                               'environment "%s" already exists' % environment)

        self.db.execute("""
			insert into environments (name) values ('%s')
			""" % environment)
コード例 #20
0
ファイル: __init__.py プロジェクト: sukhbir148/stacki
    def run(self, params, args):
        if not len(args):
            raise ArgRequired(self, 'network')

        networks = self.getNetworkNames(args)

        # Get a list of networks currently attached to host interfaces
        in_use = {
            interface['network']
            for interface in self.call('list.host.interface')
        }

        # See if any are in use
        for network in networks:
            if network in in_use:
                raise CommandError(self, f'network "{network}" in use')

        # Safe to delete them
        for network in networks:
            self.db.execute('delete from subnets where name=%s', (network, ))
コード例 #21
0
	def run(self, params, args):

		(profile, chapter) = self.fillParams([
			('profile', 'native'),
			('chapter', None) ])

		xmlinput  = ''
		osname    = None

		# If the command is not on a TTY, then try to read XML input.

		if not sys.stdin.isatty():
			for line in sys.stdin.readlines():
				if line.find('<stack:profile stack:os="') == 0:
					osname = line.split()[1][9:].strip('"')
				xmlinput += line
		if xmlinput and not osname:
			raise CommandError(self, "OS name not specified in profile")

		self.beginOutput()

		# If there's no XML input, either we have TTY, or we're running
		# in an environment where TTY cannot be created (ie. apache)

		if not xmlinput:
			hosts = self.getHostnames(args)
			if len(hosts) != 1:
				raise ArgUnique(self, 'host')
			host = hosts[0]

			osname	 = self.db.getHostOS(host)
			xmlinput = self.command('list.host.xml', [ host ])

			self.runImplementation(osname, (xmlinput, profile, chapter))

		# If we DO have XML input, simply parse it.

		else:
			self.runImplementation(osname, (xmlinput, profile, chapter))

		self.endOutput(padChar='')
コード例 #22
0
    def run(self, params, args):

        hosts = self.getHostnames(args)

        (address, gateway, netmask) = self.fillParams([('address', None, True),
                                                       ('gateway', None, True),
                                                       ('netmask',
                                                        '255.255.255.255')])

        #
        # determine if this is a subnet identifier
        #
        subnet = 0
        rows = self.db.execute("""select id from subnets where
			name = '%s' """ % gateway)

        if rows == 1:
            subnet, = self.db.fetchone()
            gateway = "''"
        else:
            subnet = 'NULL'
            gateway = "'%s'" % gateway

        # Verify the route doesn't already exist.  If it does
        # for any of the hosts raise a CommandError.

        for host in hosts:
            rows = self.db.execute("""select * from 
				node_routes r, nodes n where
				r.node=n.id and r.network='%s' 
				and n.name='%s'""" % (address, host))
            if rows:
                raise CommandError(self, 'route exists')

        # Now that we know things will work insert the route for
        # all the hosts

        for host in hosts:
            self.db.execute("""insert into node_routes values 
				((select id from nodes where name='%s'),
				'%s', '%s', %s, %s)""" % (host, address, netmask, gateway, subnet))
コード例 #23
0
ファイル: __init__.py プロジェクト: sukhbir148/stacki
    def run(self, params, args):

        apps = self.getApplianceNames(args)

        (address, gateway, netmask, interface) = self.fillParams([
            ('address', None, True),
            ('gateway', None, True),
            ('netmask', '255.255.255.255'),
            ('interface', None),
        ])

        if len(args) == 0:
            raise ParamRequired(self, 'appliance')

        # determine if this is a subnet identifier
        rows = self.db.select('id from subnets where name = %s', (gateway, ))

        if len(rows) == 1:
            subnet = rows[0][0]
            gateway = ''
        else:
            subnet = None

        # Verify the route doesn't already exist.  If it does
        # for any of the appliances raise a CommandError.
        for app in apps:
            if self.db.count(
                    """(*) from 
				appliance_routes r, appliances a where
				r.appliance=a.id and r.network=%s
				and a.name=%s""", (address, app)) > 0:
                raise CommandError(self, 'route exists')

        # Now that we know things will work insert the route for
        # all the appliances
        for app in apps:
            self.db.execute(
                """insert into appliance_routes values 
				((select id from appliances where name=%s),
				%s, %s, %s, %s, %s)""",
                (app, address, netmask, gateway, subnet, interface))
    def test_run_missing_args(self, mock_unique_everseen, mock_lowered,
                              failure_mock, basic_plugin):
        """Test that run fails when any of the exist* functions fail."""
        mock_args = ["foo", "bar", "baz"]
        mock_params = {"make": "mock_make", "version_regex": "mock_name"}
        mock_unique_everseen.return_value = (arg for arg in mock_args)
        mock_lowered.return_value = mock_params.values()
        mock_validation_functions = {
            "ensure_models_exist":
            basic_plugin.owner.ensure_models_exist,
            "ensure_version_regex_exists":
            basic_plugin.owner.ensure_version_regex_exists,
        }
        mock_validation_functions[failure_mock].side_effect = CommandError(
            cmd=basic_plugin.owner, msg="Test error")

        with pytest.raises(CommandError):
            basic_plugin.run(args=(mock_params, mock_args))

        # model sure the DB is not modified with bad arguments.
        basic_plugin.owner.db.execute.assert_not_called()
コード例 #25
0
ファイル: plugin_version.py プロジェクト: wwfeng990/stacki
    def check_errors(self, results):
        """Checks for any errors in the results of run_implementations_parallel. If there are errors,
		this will aggregate them all into one CommandError and raise it.
		"""
        # drop any results that didn't have any errors and aggregate the rest into one exception
        error_messages = []
        for error in (value.exception for value in results.values()
                      if value is not None and value.exception is not None):
            # if this looks like a stacki exception type, grab the message from it.
            if hasattr(error, 'message') and callable(getattr(
                    error, 'message')):
                error_messages.append(error.message())
            else:
                error_messages.append(f'{error}')

        if error_messages:
            error_message = '\n'.join(error_messages)
            raise CommandError(
                cmd=self.owner,
                msg=f"Errors occurred while listing firmware:\n{error_message}"
            )
コード例 #26
0
    def get_model_id(self, make, model):
        """Get the ID of the model with the provided name related to the provided make.

		This will raise a CommandError if the make + model combo doesn't exist.
		"""
        row = self.db.select(
            """
			firmware_model.id
			FROM firmware_model
				INNER JOIN firmware_make
					ON firmware_model.make_id=firmware_make.id
			WHERE firmware_make.name=%s AND firmware_model.name=%s
			""",
            (make, model),
        )
        if not row:
            raise CommandError(
                cmd=self,
                msg=f"Firmware model {model} doesn't exist for make {make}.")

        return row[0][0]
コード例 #27
0
	def run(self, params, args):

		host, port, interface, = self.fillParams([
			('host', None, True),
			('port', None, True),
			('interface', None, False),
			])
		switches = self.getSwitchNames(args)
		if len(switches) > 1:
			raise ArgUnique(self, 'switch')

		# Check if host exists
		hosts = self.getHostnames([host])

		for switch in switches:
			# Make sure switch has an interface
			if self.getSwitchNetwork(switch):
				self.addSwitchHost(switch, port, host, interface)
			else:
				raise CommandError(self,
					"switch '%s' doesn't have a management interface" % switch)
コード例 #28
0
ファイル: __init__.py プロジェクト: wwfeng990/stacki
    def copy(self, stacki_pallet_root, pallet_info, clean):
        '''
		Copy a pallet to the local filesystem

		Specifically, rsync from `pallet_info.pallet_root` to
		`stacki_pallet_root`/name/version/release/os/arch/
		'''
        pallet_dir = pallet_info.pallet_root
        destdir = pathlib.Path(stacki_pallet_root).joinpath(
            *info_getter(pallet_info))

        if destdir.exists() and clean:
            print(
                f'Cleaning {"-".join(info_getter(pallet_info))} from pallets directory'
            )
            shutil.rmtree(destdir)

        print(f'Copying {"-".join(info_getter(pallet_info))} ...')

        if not destdir.exists():
            destdir.mkdir(parents=True, exist_ok=True)

        # use rsync to perform the copy
        # archive implies
        # --recursive,
        # --links - copy symlinks as symlinks
        # --perms - preserve permissions
        # --times - preserve mtimes
        # --group - preserve group
        # --owner - preserve owner
        # --devices - preserve device files
        # --specials - preserve special files
        # we then overwrite the permissions to make apache happy.
        cmd = f'rsync --archive --chmod=D755 --chmod=F644 --exclude "TRANS.TBL" {pallet_dir}/ {destdir}/'
        result = self._exec(cmd, shlexsplit=True)
        if result.returncode != 0:
            raise CommandError(self,
                               f'Unable to copy pallet:\n{result.stderr}')

        return destdir
コード例 #29
0
ファイル: __init__.py プロジェクト: tabitabe/stacki
    def run(self, params, args):

        (b_kernel, ) = self.fillParams([('kernel', '', True)])
        (b_action, b_type, b_os) = self.getBootActionTypeOS(params, args)

        if not self.actionExists(b_action, b_type, b_os):
            raise CommandError(self, 'action "%s" does not exist' % b_action)

        if b_os:
            self.db.execute("""
				update bootactions 
				set kernel = '%s' where
				os = (select id from oses where name = '%s') and
				bootname = (select id from bootnames where name = '%s' and type = '%s')
				""" % (b_kernel, b_os, b_action, b_type))
        else:
            self.db.execute("""
				update bootactions 
				set kernel = '%s' where
				os is NULL and 
				bootname = (select id from bootnames where name = '%s' and type = '%s')
				""" % (b_kernel, b_action, b_type))
コード例 #30
0
	def run(self, params, args):
		host = self.getSingleHost(args)

		key, = self.fillParams([ ('key', None, True) ])

		# See if the key is a file name
		if os.path.exists(key):
			with open(key, 'r') as f:
				key = f.read()

		# Check if the key already exists
		if self.db.count("""(ID) from public_keys where
			node = (select id from nodes where name = %s) and
			public_key = %s """, (host, key)
		) != 0:
			raise CommandError(self, f'the public key already exists for host {host}')

		# Add the key
		self.db.execute("""insert into public_keys(node, public_key)
			values ((select id from nodes where name = %s), %s)""",
			(host, key)
		)