diff --git a/README.md b/README.md index b134a98..a5f611b 100644 --- a/README.md +++ b/README.md @@ -313,6 +313,11 @@ includes the following: ## Change Log +### v3.2.2 + +- The `kubectl` command now creates the `kubeconfig` file with restricted + permissions (600) to prevent potential security issues. + ### v3.2.1 - Add optional `pre_hook_with_context` method to the `Command` base class, diff --git a/docs/acctload.html b/docs/acctload.html index f648b5c..2d92681 100644 --- a/docs/acctload.html +++ b/docs/acctload.html @@ -3,13 +3,13 @@ - + awsrun.acctload API documentation - + @@ -1177,7 +1177,6 @@

Overview

no_verify=False, cache_path=None, ): - session = requests.Session() session.mount("file://", FileAdapter()) @@ -1248,7 +1247,7 @@

Overview

def __repr__(self): pairs = (f"{k}={repr(v)}" for k, v in self._attrs.items()) - return f'Account({", ".join(pairs)})' + return f"Account({', '.join(pairs)})" def __str__(self): if not self._str_template: @@ -1267,7 +1266,7 @@

Overview

def __init__(self, missing_acct_ids): self.missing_acct_ids = missing_acct_ids - super().__init__(f'Account IDs not found: {", ".join(missing_acct_ids)}') + super().__init__(f"Account IDs not found: {', '.join(missing_acct_ids)}") class InvalidFormatTemplateError(Exception): @@ -1386,12 +1385,28 @@

Methods

Returns the account ID as a string associated with the acct object.

+
+ +Expand source code + +
def acct_id(self, acct):
+    """Returns the account ID as a string associated with the `acct` object."""
+    raise NotImplementedError
+
def attributes(self)

Returns a dict of all metadata attribute names and values.

+
+ +Expand source code + +
def attributes(self):
+    """Returns a dict of all metadata attribute names and values."""
+    raise NotImplementedError
+
def accounts(self, acct_ids=None, include=None, exclude=None) @@ -1403,6 +1418,21 @@

Methods

Duplicate account IDs do not result in duplicate account objects. To filter the list based on account metadata, include and exclude dicts can be specified identifying the keys and matching values.

+
+ +Expand source code + +
def accounts(self, acct_ids=None, include=None, exclude=None):
+    """Returns a list of account objects representing accounts and metadata.
+
+    Without any arguments, all accounts are returned. A list of `acct_ids`
+    may be provided to limit the list to accounts matching the account IDs.
+    Duplicate account IDs do not result in duplicate account objects. To
+    filter the list based on account metadata, `include` and `exclude` dicts
+    can be specified identifying the keys and matching values.
+    """
+    raise NotImplementedError
+
@@ -1496,6 +1526,18 @@

Methods

Returns the account ID associated with the acct object.

This is an identity function as the account ID of an account object loaded by this class is the account object itself, thus acct returned.

+
+ +Expand source code + +
def acct_id(self, acct):
+    """Returns the account ID associated with the `acct` object.
+
+    This is an identity function as the account ID of an account object
+    loaded by this class is the account object itself, thus `acct` returned.
+    """
+    return acct
+
def attributes(self) @@ -1505,6 +1547,19 @@

Methods

IdentityAccountLoader builds account objects that are simply strings representing the ID of an account. Because there is no other metadata associated with these objects, this method returns an empty dict.

+
+ +Expand source code + +
def attributes(self):
+    """Returns a dict of all metadata attribute names and values.
+
+    `IdentityAccountLoader` builds account objects that are simply strings
+    representing the ID of an account. Because there is no other metadata
+    associated with these objects, this method returns an empty dict.
+    """
+    return {}
+
def accounts(self, acct_ids=None, include=None, exclude=None) @@ -1517,6 +1572,28 @@

Methods

objects without duplicates. It is an identity function. Because there are no loaded accounts, use of include and exclude parameters will raise an AttributeError.

+
+ +Expand source code + +
def accounts(self, acct_ids=None, include=None, exclude=None):
+    """Returns a list of account objects.
+
+    Without any arguments, no accounts are returned because this class does
+    not load a list of accounts from an external data source. Instead, this
+    method returns the same list of `acct_ids` as the list of account
+    objects without duplicates. It is an identity function. Because there
+    are no loaded accounts, use of `include` and `exclude` parameters will
+    raise an AttributeError.
+    """
+    if include or exclude:
+        raise AttributeError("Cannot use filters as no attributes are defined")
+
+    if acct_ids is None:
+        return []
+
+    return list(set(acct_ids))
+
@@ -2143,6 +2220,20 @@

Methods

instance of this MetaAccountLoader via MetaAccountLoader.accounts(). This method returns the string representing the account ID of the account object.

+
+ +Expand source code + +
def acct_id(self, acct):
+    """Returns the account ID associated with the `acct` object.
+
+    The `acct` parameter must be an account object that was created by an
+    instance of this `MetaAccountLoader` via `MetaAccountLoader.accounts`.
+    This method returns the string representing the account ID of the
+    account object.
+    """
+    return getattr(acct, self.id_attr)
+
def attributes(self) @@ -2165,6 +2256,36 @@

Methods

assert attrs['status'] == {'active', 'suspended'} assert attrs['id'] == {'100200300400', '200300400100', '300400100200'}
+
+ +Expand source code + +
def attributes(self):
+    """Returns a dict of all metadata attribute names and values.
+
+    This method returns the metadata associated with the account objects
+    created by this instance of `MetaAccountLoader`. The keys of the
+    returned dict are the attribute names attached to the account objects.
+    The value of each key is a set representing all of the possible values
+    assigned to that attribute. For example:
+
+        accts = [
+            {'id': '100200300400', 'env': 'prod', 'status': 'active'},
+            {'id': '200300400100', 'env': 'prod', 'status': 'suspended'},
+            {'id': '300400100200', 'env': 'dev', 'status': 'active'},
+        ]
+        loader = acctload.MetaAccountLoader(accts)
+        attrs = loader.attributes()
+        assert attrs['env'] == {'prod', 'dev'}
+        assert attrs['status'] == {'active', 'suspended'}
+        assert attrs['id'] == {'100200300400', '200300400100', '300400100200'}
+    """
+    d = defaultdict(set)
+    for acct in self.accts:
+        for attr, value in acct.items():
+            d[attr].add(value)
+    return d
+
def accounts(self, acct_ids=None, include=None, exclude=None) @@ -2209,6 +2330,84 @@

Methods

to an invalid attribute name, AttributeError is raised. Finally, if include is not set, then all accounts are matched. Likewise, if exclude is not set, then no accounts are excluded.

+
+ +Expand source code + +
def accounts(self, acct_ids=None, include=None, exclude=None):
+    """Returns a list of account objects.
+
+    Without any arguments, account objects for all accounts are returned. A
+    list of `acct_ids` may be provided to limit the list to accounts
+    matching the account IDs. If one or more specified account IDs is
+    missing, `AccountsNotFoundError` is raised.
+
+    The returned list of accounts can be filtered further by providing dicts
+    for `include` and `exclude` parameters that specify attributes and a
+    list of values for those attributes that must match. For example,
+    assuming the following accounts:
+
+        accts = [
+            {'id': '100200300400', 'env': 'prod', 'status': 'active'},
+            {'id': '200300400100', 'env': 'prod', 'status': 'suspended'},
+            {'id': '300400100200', 'env': 'dev', 'status': 'active'},
+        ]
+        loader = acctload.MetaAccountLoader(accts)
+
+    To filter active accounts, use the following:
+
+        include = {'status': ['active']}
+
+    To filter active *and* production accounts:
+
+        include = {'status': ['active'], 'env': ['prod']}
+
+    To filter production accounts, but not suspended:
+
+        include = {'env': ['prod']}
+        exclude = {'status': ['suspended']}
+
+    To filter active *or* suspended accounts, but not dev accounts:
+
+        include = {'status': ['active', 'suspended']}
+        exclude = {'env': ['dev']}
+
+    Both `include` and `exclude` filters are applied after the initial
+    account list has been determined, which is all accounts unless it was
+    first limited by `acct_ids`. Then, the `include` filter is applied,
+    followed by the `exclude` filter. If a filter dict has multiple keys,
+    then *each* key must match. A key matches if at least *one* of the
+    values matches as illustrated in the examples above. If a filter refers
+    to an invalid attribute name, `AttributeError` is raised. Finally, if
+    `include` is not set, then all accounts are matched. Likewise, if
+    `exclude` is not set, then no accounts are excluded.
+    """
+    accts = self.accts
+    acct_ids = [] if acct_ids is None else acct_ids
+    include = {} if include is None else include
+    exclude = {} if exclude is None else exclude
+
+    # Limit our account list to the requested IDs
+    if acct_ids:
+        requested = set(acct_ids)
+        all_ids = (a[self.id_attr] for a in self.accts)
+
+        missing_acct_ids = requested.difference(all_ids)
+        if missing_acct_ids:
+            raise AccountsNotFoundError(list(missing_acct_ids))
+
+        accts = (a for a in self.accts if a[self.id_attr] in requested)
+
+    # Make sure the filters contain valid attribute names
+    for attr in itertools.chain(include.keys(), exclude.keys()):
+        if attr not in self.attrs:
+            raise AttributeError(f"Invalid attribute '{attr}' in filter")
+
+    # Limit our account list by the user-supplied filters
+    return [
+        self.CustomAccount(a) for a in accts if self._filter(a, include, exclude)
+    ]
+
@@ -2956,7 +3155,6 @@

Ancestors

no_verify=False, cache_path=None, ): - session = requests.Session() session.mount("file://", FileAdapter()) @@ -3075,7 +3273,7 @@

Inherited members

def __repr__(self): pairs = (f"{k}={repr(v)}" for k, v in self._attrs.items()) - return f'Account({", ".join(pairs)})' + return f"Account({', '.join(pairs)})" def __str__(self): if not self._str_template: @@ -3106,7 +3304,7 @@

Inherited members

def __init__(self, missing_acct_ids): self.missing_acct_ids = missing_acct_ids - super().__init__(f'Account IDs not found: {", ".join(missing_acct_ids)}')
+ super().__init__(f"Account IDs not found: {', '.join(missing_acct_ids)}")

Ancestors

+
+ +Expand source code + +
def all(self):
+    return self.events
+
def users(self)
+
+ +Expand source code + +
def users(self):
+    return self.events_by_user.keys()
+
def by_user(self, user)
+
+ +Expand source code + +
def by_user(self, user):
+    return self.events_by_user.get(user, [])
+
def by_id(self, event_id)
+
+ +Expand source code + +
def by_id(self, event_id) -> Optional[_UserIdentityType]:
+    return self.events_by_key.get(event_id)
+
def filter(self, expr)
+
+ +Expand source code + +
def filter(self, expr):
+    if not expr:  # empty string?
+        events = self.unfiltered_events
+    else:
+        events = _filter_events(self.unfiltered_events, expr)
+
+    self.filter_expr = expr
+    self._load_events(events)
+    return len(self.events)
+
@@ -1768,7 +1810,7 @@

Class variables

var DEFAULT_CSS
-
+

The type of the None singleton.

var Changed
@@ -1780,11 +1822,11 @@

Class variables

var can_focus
-
+

The type of the None singleton.

var can_focus_children
-
+

The type of the None singleton.

Methods

@@ -1803,18 +1845,44 @@

Example

) yield Footer()
+
+ +Expand source code + +
def compose(self) -> ComposeResult:
+    yield Static(Markdown(self.help_md))
+    with Horizontal():
+        yield Input(placeholder=self.prompt)
+        yield Button("Close", variant="primary")
+
def on_input_submitted(self, event)
+
+ +Expand source code + +
def on_input_submitted(self, event: Input.Submitted) -> None:
+    event.stop()
+    self.post_message(self.Changed(event.value))
+
def on_button_pressed(self, event)
+
+ +Expand source code + +
def on_button_pressed(self, event: Button.Pressed) -> None:
+    event.stop()
+    self.post_message(self.Closed())
+
@@ -1861,23 +1929,15 @@

Ancestors

  • textual.dom.DOMNode
  • textual.message_pump.MessagePump
  • -

    Class variables

    -
    -
    var can_focus
    -
    -
    -
    -
    var can_focus_children
    -
    -
    -
    -

    Inherited members

    -

    Class variables

    -
    -
    var can_focus
    -
    -
    -
    -
    var can_focus_children
    -
    -
    -
    -

    Inherited members

    -

    Class variables

    -
    -
    var DEFAULT_CSS
    -
    -
    -
    -
    var can_focus
    -
    -
    -
    -
    var can_focus_children
    -
    -
    -
    -

    Methods

    @@ -2225,19 +2296,43 @@

    Methods

    +
    + +Expand source code + +
    def on_mount(self):
    +    dt = self.query_one(DataTable)
    +    dt.show_header = False
    +    dt.border_title = "Users"
    +
    def watch_contents(self, users)
    +
    + +Expand source code + +
    def watch_contents(self, users):
    +    dt = self.query_one(DataTable)
    +    dt.clear()
    +    for user in users:
    +        dt.add_row(user, key=user)
    +    dt.scroll_home()
    +    dt.border_subtitle = f"0 of {len(users)}"
    +

    Inherited members