def __init__(self, **kwargs): # Get the specified AT instance logger.debug(f"AnsibleTower instantiated with {kwargs=}") instance_name = kwargs.pop("AnsibleTower", None) # Validate the AnsibleTower-specific settings self._validate_settings(instance_name) # get our instance settings self.url = settings.ANSIBLETOWER.base_url self.uname = settings.ANSIBLETOWER.get("username") self.pword = settings.ANSIBLETOWER.get("password") self.token = settings.ANSIBLETOWER.get("token") self._inventory = ( kwargs.get("tower_inventory") or settings.ANSIBLETOWER.inventory ) # Init the class itself self._construct_params = [] config = kwargs.get("config", awxkit.config) config.base_url = self.url root = kwargs.get("root") if root is None: root = awxkit.api.Api() # support mock stub for unit tests # Prefer token if its set, otherwise use username/password # auth paths for the API taken from: # https://github.com/ansible/awx/blob/ddb6c5d0cce60779be279b702a15a2fddfcd0724/awxkit/awxkit/cli/client.py#L85-L94 # unit test mock structure means the root API instance can't be loaded on the same line if self.token: helpers.emit(auth_type="token") logger.info("Using token authentication") config.token = self.token try: root.connection.login( username=None, password=None, token=self.token, auth_type="Bearer" ) except awxkit.exceptions.Unauthorized as err: raise exceptions.AuthenticationError(err.args[0]) versions = root.get().available_versions try: # lookup the user that authenticated with the token # If a username was specified in config, use that instead my_username = ( self.uname or versions.v2.get().me.get().results[0].username ) except (IndexError, AttributeError): # lookup failed for whatever reason raise exceptions.ProviderError( provider="AnsibleTower", message="Failed to lookup a username for the given token, please check credentials", ) else: # dynaconf validators should have checked that either token or password was provided helpers.emit(auth_type="password") logger.info("Using username and password authentication") config.credentials = { "default": {"username": self.uname, "password": self.pword} } config.use_sessions = True root.load_session().get() versions = root.available_versions my_username = self.uname self.v2 = versions.v2.get() self.username = my_username
def __call__(self, *args, **kwargs): try: return self.main(*args, **kwargs) except Exception as err: if not isinstance(err, exceptions.BrokerError): err = exceptions.BrokerError(err) helpers.emit(return_code=err.error_code, error_message=str(err.message)) sys.exit(err.error_code) helpers.emit(return_code=0)
def test_emitter(tmp_file): assert not tmp_file.exists() helpers.emit.set_file(tmp_file) assert tmp_file.exists() helpers.emit(test="value", another=5) written = json.loads(tmp_file.read_text()) assert written == {"test": "value", "another": 5} helpers.emit({"thing": 13}) written = json.loads(tmp_file.read_text()) assert written == {"test": "value", "another": 5, "thing": 13}
def checkout(self): """checkout one or more VMs :return: Host obj or list of Host objects """ hosts = self._checkout() helpers.emit(hosts=[host.to_dict() for host in hosts]) self._hosts.extend(hosts) helpers.update_inventory([host.to_dict() for host in hosts]) return hosts if not len(hosts) == 1 else hosts[0]
def test_emitter(tmp_path): out_file = tmp_path / "output.json" assert not out_file.exists() helpers.emit.set_file(out_file) assert out_file.exists() helpers.emit(test="value", another=5) written = json.loads(out_file.read_text()) assert written == {"test": "value", "another": 5} helpers.emit({"thing": 13}) written = json.loads(out_file.read_text()) assert written == {"test": "value", "another": 5, "thing": 13}
def checkout(self, connect=False): """checkout one or more VMs :param connect: Boolean whether to establish host ssh connection :return: Host obj or list of Host objects """ hosts = self._checkout() helpers.emit(hosts=[host.to_dict() for host in hosts]) if connect: for host in hosts: host.connect() self._hosts.extend(hosts) helpers.update_inventory([host.to_dict() for host in hosts]) return hosts if not len(hosts) == 1 else hosts[0]
def _act(self, provider, method, checkout=False): """Perform a general action against a provider's method""" provider_inst = provider(**self._kwargs) helpers.emit({ "provider": provider_inst.__class__.__name__, "action": method, "arguments": self._kwargs }) result = getattr(provider_inst, method)(**self._kwargs) logger.debug(result) if result and checkout: return provider_inst.construct_host( provider_params=result, host_classes=self.host_classes, **self._kwargs ) else: return result
def execute(ctx, background, nick, output_format, artifacts, args_file, **kwargs): """Execute an arbitrary provider action COMMAND: broker execute --workflow "workflow-name" --workflow-arg1 something or COMMAND: broker execute --nick "nickname" :param ctx: clicks context object :param background: run a new broker subprocess to carry out command :param nick: shortcut for arguments saved in settings.yaml, passed in as a string :param output_format: change the format of the output to one of the choice options :param artifacts: AnsibleTower provider specific option for choosing what to return :param args_file: this broker argument wil be replaced with the contents of the file passed in """ broker_args = helpers.clean_dict(kwargs) if nick: broker_args["nick"] = nick if artifacts: broker_args["artifacts"] = artifacts if args_file: broker_args["args_file"] = args_file # if additional arguments were passed, include them in the broker args # strip leading -- characters broker_args.update( { (key[2:] if key.startswith("--") else key): val for key, val in zip(ctx.args[::2], ctx.args[1::2]) } ) broker_args = helpers.resolve_file_args(broker_args) if background: helpers.fork_broker() broker_inst = VMBroker(**broker_args) result = broker_inst.execute() helpers.emit({"output": result}) if output_format == "raw": print(result) elif output_format == "log": logger.info(result) elif output_format == "yaml": print(helpers.yaml_format(result))
def inventory(details, sync, filter): """Get a list of all VMs you've checked out showing hostname and local id hostname pulled from list of dictionaries """ if sync: VMBroker.sync_inventory(provider=sync) logger.info("Pulling local inventory") inventory = helpers.load_inventory(filter=filter) emit_data = [] for num, host in enumerate(inventory): emit_data.append(host) if details: logger.info( f"{num}: {host['hostname'] or host['name']}, Details: {helpers.yaml_format(host)}" ) else: logger.info(f"{num}: {host['hostname'] or host['name']}") helpers.emit({"inventory": emit_data})
def checkout(self): """checkout one or more VMs :return: Host obj or list of Host objects """ hosts = self._checkout() err, to_emit = None, [] for host in hosts: if not isinstance(host, exceptions.ProviderError): to_emit.append(host.to_dict()) else: err = host hosts.remove(host) helpers.emit(hosts=[host.to_dict() for host in hosts]) self._hosts.extend(hosts) helpers.update_inventory([host.to_dict() for host in hosts]) if err: raise err return hosts if not len(hosts) == 1 else hosts[0]
) payload = {"extra_vars": str(kwargs)} if self.inventory: payload["inventory"] = self.inventory else: logger.info( "No inventory specified, Ansible Tower will use a default.") logger.debug( f"Launching {subject}: {url_parser.urljoin(self.url, str(target.url))}\n" f"{payload=}") job = target.launch(payload=payload) job_number = job.url.rstrip("/").split("/")[-1] job_api_url = url_parser.urljoin(self.url, str(job.url)) job_ui_url = url_parser.urljoin(self.url, f"/#/{subject}s/{job_number}") helpers.emit(api_url=job_api_url, ui_url=job_ui_url) logger.info("Waiting for job: \n" f"API: {job_api_url}\n" f"UI: {job_ui_url}") job.wait_until_completed( timeout=settings.ANSIBLETOWER.workflow_timeout) if not job.status == "successful": message_data = { f"{subject.capitalize()} Status": job.status, "Reason(s)": self._get_failure_messages(job), "URL": job_ui_url } helpers.emit(message_data) raise exceptions.ProviderError(provider="AnsibleTower", message=message_data["Reason(s)"]) if (artifacts := kwargs.get("artifacts")):