def fetch( self, fields: Optional[List[str]] = None, update: bool = True, fetch_aa_data: bool = False, ) -> None: if self._dirty: raise IntegrityError( "role object has unsaved modifications, fetching may overwrite changes" ) if self.role_id: stored_role_data = get_role_by_id(self.role_id, fields=fields) elif self.arn: stored_role_data = get_role_by_arn(self.arn, fields=fields) else: # TODO: we can pull role_name and account from an ARN, support that too raise ModelError( "missing role_id or role_name and account on Role instance") if update: self.update(stored_role_data, store=False, dirty=False) self._updated_fields - set(stored_role_data.keys()) if fetch_aa_data: self.fetch_aa_data()
def fetch(self, fields: Optional[List[str]] = None, update: bool = True) -> None: if self._dirty: raise IntegrityError( "role object has unsaved modifications, fetching may overwrite changes" ) if self.role_id: stored_role_data = get_role_by_id(self.role_id, fields=fields) elif self.role_name and self.account: stored_role_data = get_role_by_name(self.account, self.role_name, fields=fields) else: raise ModelError( "missing role_id or role_name and account on Role instance") if update: self.update(stored_role_data, store=False) self._updated_fields - set(stored_role_data.keys())
def store(self, fields: Optional[List[str]] = None) -> None: create = False try: remote_role_data = Role(role_id=self.role_id, arn=self.arn) remote_role_data.fetch(fields=["LastUpdated"]) if (remote_role_data.last_updated and self.last_updated and remote_role_data.last_updated > self.last_updated): raise IntegrityError( "stored role has been updated since last fetch") except RoleNotFoundError: create = True self.last_updated = datetime.datetime.now() if create: # TODO: handle this case in set_role_data() to simplify logic here create_dynamodb_entry( self.dict(by_alias=True, exclude={"config", "_dirty", "_updated_fields"})) self._updated_fields = set() else: if fields: include_fields = set(fields) include_fields.add("last_updated") set_role_data( self.role_id, self.dict( include=include_fields, by_alias=True, exclude=self._default_exclude, ), ) self._updated_fields - set(fields) else: set_role_data( self.role_id, self.dict(by_alias=True, exclude=self._default_exclude), ) self._updated_fields = set() self._dirty = False
def store(self, fields: Optional[List[str]] = None) -> None: create = False try: remote_role_data = Role(role_id=self.role_id, arn=self.arn) remote_role_data.fetch(fields=["LastUpdated"]) if (remote_role_data.last_updated and self.last_updated and remote_role_data.last_updated > self.last_updated): # Fetch the rest of the role data for debugging remote_role_data.fetch() logger.warning( "role has been updated since last fetch: stored %s, local %s", remote_role_data.last_updated, self.last_updated, extra={ "stored_role": remote_role_data.dict(), "local_role": self.dict(), }, ) raise IntegrityError( "stored role has been updated since last fetch") except RoleNotFoundError: create = True self.last_updated = datetime.datetime.now() set_role_data_args: Dict[str, Any] = { "by_alias": True, } # If fields are specified, we need to add last_updated to make sure it gets set if fields: include_fields = set(fields) include_fields.add("last_updated") set_role_data_args["include"] = include_fields # Exclude key fields unless this is a newly-created item. Key fields cannot be included # in DynamoDB update calls. exclude_fields = self._meta if not create: exclude_fields.update(self._keys) set_role_data_args["exclude"] = exclude_fields attempts = 0 max_retries = 3 while attempts < max_retries: try: set_role_data( self.role_id, self.dict(**set_role_data_args), create=create, ) self._updated_fields = (self._updated_fields - set(fields) if fields else set()) # model is still dirty if we haven't stored all updated fields self._dirty = len(self._updated_fields) > 0 return except DynamoDBMaxItemSizeError: logger.info( "role %s too big for DynamoDB, removing oldest policy version", self.role_name, ) self._remove_oldest_policy_version() attempts += 1 continue except DynamoDBError: logger.info( "failed attempt %d to store role %s in DynamoDB", attempts, self.role_name, exc_info=True, ) attempts += 1 continue # If we've made it this far, the role was not stored raise RoleStoreError(f"failed to store {self.arn} in DynamoDB")