def from_response(cls, details: Dict) -> ContainerInstance: return cls( arn=query("containerInstanceArn", details), status=query("status", details), ec2id=query("ec2InstanceId", details), registered_at=query("registeredAt", details), agent_connected=query("agentConnected", details), details=details, )
def terminate(self) -> Tuple[str, str]: instance = self.ec2_instance response = instance.terminate() raise_for_status(response) state = query("TerminatingInstances.0.CurrentState.Name", response) logger.info(f"terminated {instance.id}: state={state}") return instance.id, state
def instances(self) -> List[ContainerInstance]: response = self.client.describe_container_instances( cluster=self.cluster_name, containerInstances=self._container_instance_arns) raise_for_status(response) instances = query("containerInstances", response) or [] return [ContainerInstance.from_response(ins) for ins in instances]
def ipv4(self) -> str: return reduce( # type: ignore [ x["value"] for x in query("attachments.0.details", self._details) if x["name"] == "privateIPv4Address" ])
def raise_for_status(response: dict, ok_codes: Union[int, List[int]] = 200): status_code = query("ResponseMetadata.HTTPStatusCode", response) if status_code is None: raise ValueError( f"Couldn't determine status_code from response: {response=}") if status_code not in ensure_list(ok_codes): raise HTTPResponseError( f"Request returned invalid response code: {status_code}")
def __init__(self, cluster: str, client: boto3.client = None): """ Get details about and issue various commands to an ECS cluster Arguments: cluster {str} -- cluster name or arn Keyword Arguments: client {boto3.client} -- instance of boto3.client('ecs') (default: None) """ self.cluster_name = cluster.split("/")[-1] if "/" else cluster self.client = client or boto3.client("ecs") details = self._cluster_details self.arn = query("clusterArn", details) self.instance_count = query("registeredContainerInstancesCount", details) self.services = query("activeServicesCount", details) self.tasks = query("runningTasksCount", details)
def account_id(self) -> str: if self._account_id is None: payload = sts.get_caller_identity() sts_response_code = query("ResponseMetadata.HTTPStatusCode", data=payload) if sts_response_code != 200: raise ValueError(f"({self.iam_username}) failed to fetch account id") self._account_id = payload["Account"] return self._account_id
def delete_key(self, access_key_id: str): payload = iam.delete_access_key( UserName=self.iam_username, AccessKeyId=access_key_id ) response_code = query("ResponseMetadata.HTTPStatusCode", data=payload) if response_code == 200: logger.info(f"({self.iam_username}) deleted old access key") else: raise ValueError(f"({self.iam_username}) failed to delete old access key")
def create_new_credentials(self, is_retry: bool = False) -> Dict[str, str]: if not self.new: try: payload = iam.create_access_key(UserName=self.iam_username) except iam.exceptions.LimitExceededException as e: logger.warning(f"({self.iam_username}) -- {e}") oldest = self.oldest_key if oldest: self.delete_key(oldest) logger.warning( f"({self.iam_username}) retrying creating new credentials" ) if not is_retry: # only retry once return self.create_new_credentials(is_retry=True) response_code = query("ResponseMetadata.HTTPStatusCode", data=payload) if response_code == 200: logger.info(f"({self.iam_username}) created new access key") else: raise ValueError( f"({self.iam_username}) failed to create new access key" ) access_key = payload["AccessKey"] self._next_key_id = access_key["AccessKeyId"] self.new = { "AWS_ACCOUNT_ID": self.account_id, "AWS_ACCESS_KEY_ID": access_key["AccessKeyId"], "AWS_SECRET_ACCESS_KEY": access_key["SecretAccessKey"], "AWS_IAM_ROLE": access_key["UserName"], "LAST_ROTATED": str(date.today()), } return self.new else: raise ValueError(f"credentials have already been generated")
def _container_instance_arns(self) -> List[str]: response = self.client.list_container_instances( cluster=self.cluster_name) raise_for_status(response) return query("containerInstanceArns", response) or []
def _cluster_details(self) -> Dict: response = self.client.describe_clusters(clusters=[self.cluster_name]) raise_for_status(response) cluster: Dict = reduce(query("clusters", response)) # type: ignore return cluster
def test_query_nested_collection(queryable): expected = "example_full" assert it.query("slim.tasks.1.name", data=queryable) == expected
def _fetch_access_key(self, oldest: bool = False): payload = iam.list_access_keys(UserName=self.iam_username) index = -1 if oldest else 0 return query(f"AccessKeyMetadata.{index}.AccessKeyId", data=payload)