Skip to content

Commit

Permalink
Create new API endpoint fbc-operations
Browse files Browse the repository at this point in the history
[CLOUDDST-16721]
  • Loading branch information
lipoja committed Feb 1, 2023
1 parent 568b6f1 commit 22bb77a
Show file tree
Hide file tree
Showing 6 changed files with 354 additions and 2 deletions.
55 changes: 55 additions & 0 deletions iib/web/api_v1.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
Operator,
Request,
RequestAdd,
RequestFbcOperations,
RequestMergeIndexImage,
RequestRecursiveRelatedBundles,
RequestRegenerateBundle,
Expand Down Expand Up @@ -51,6 +52,7 @@
AddRequestPayload,
AddRmBatchPayload,
CreateEmptyIndexPayload,
FbcOperationRequestPayload,
MergeIndexImagesPayload,
PayloadTypesUnion,
RecursiveRelatedBundlesRequestPayload,
Expand Down Expand Up @@ -1096,3 +1098,56 @@ def get_nested_bundles(request_id: int) -> flask.Response:

with open(related_bundles_file_path) as f:
return flask.Response(f.read(), mimetype='application/json')


@api_v1.route('/builds/fbc-operations', methods=['POST'])
@login_required
def fbc_operations() -> Tuple[flask.Response, int]:
"""
Submit a request to run supported fbc operation on an FBC index image.
:rtype: flask.Response
:raise ValidationError: if required parameters are not supplied
"""
payload: FbcOperationRequestPayload = flask.request.get_json()
if not isinstance(payload, dict):
raise ValidationError('The input data must be a JSON object')

request = RequestFbcOperations.from_json(payload)
db.session.add(request)
db.session.commit()
messaging.send_message_for_state_change(request, new_batch_msg=True)

overwrite_from_index = payload.get('overwrite_from_index', False)
# TODO - remove # noqa when worker implementation is done
celery_queue = _get_user_queue(serial=overwrite_from_index) # noqa

# TODO - update order based on worker implementation
args = [
request.id,
payload['fbc_fragment'],
payload['from_index'],
payload.get('binary_image'),
payload.get('distribution_scope'),
payload.get('overwrite_from_index'),
payload.get('overwrite_from_index_token'),
payload.get('build_tags'),
payload.get('add_arches'),
]
# TODO - remove no-qa when worker implementation is done
safe_args = _get_safe_args(args, payload) # noqa
error_callback = failed_request_callback.s(request.id) # noqa
try:
# TODO - remove this line once worker is ready
raise NotImplementedError(
"Worker part handle_fbc_operation_request is not implemented yet."
)
# TODO - update call of worker handle function once it is ready
# handle_fbc_operation_request.apply_async(
# args=args, link_error=error_callback, argsrepr=repr(safe_args), queue=celery_queue
# )
except kombu.exceptions.OperationalError:
handle_broker_error(request)

flask.current_app.logger.debug('Successfully scheduled request %d', request.id)
return flask.jsonify(request.to_json()), 201
25 changes: 25 additions & 0 deletions iib/web/iib_static_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ class RelatedBundlesMetadata(TypedDict):
'AddRmBatchPayload',
'MergeIndexImagesPayload',
'CreateEmptyIndexPayload',
'FbcOperationRequestPayload',
]


Expand Down Expand Up @@ -109,6 +110,21 @@ class RmRequestPayload(TypedDict):
overwrite_from_index_token: Optional[str]


class FbcOperationRequestPayload(TypedDict):
"""Datastructure of the request to /builds/fbc-operation API point."""

fbc_fragment: str
from_index: str
binary_image: NotRequired[str]
build_tags: NotRequired[List[str]]
add_arches: NotRequired[List[str]]
overwrite_from_index: NotRequired[bool]
overwrite_from_index_token: NotRequired[str]
batch: NotRequired[str]
distribution_scope: NotRequired[str]
user: NotRequired[str]


class RegenerateBundlePayload(TypedDict):
"""Datastructure of the request to /builds/regenerate-bundle API point."""

Expand Down Expand Up @@ -183,6 +199,7 @@ class RequestPayload(TypedDict):
cnr_token: NotRequired[str]
deprecation_list: NotRequired[List[str]]
distribution_scope: NotRequired[str]
fbc_fragment: NotRequired[bool]
force_backport: NotRequired[bool]
from_bundle_image: NotRequired[str]
from_index: NotRequired[str]
Expand All @@ -204,6 +221,7 @@ class RequestPayload(TypedDict):
PayloadTypesUnion = Union[
AddRequestPayload,
CreateEmptyIndexPayload,
FbcOperationRequestPayload,
MergeIndexImagesPayload,
RecursiveRelatedBundlesRequestPayload,
RegenerateBundlePayload,
Expand Down Expand Up @@ -396,4 +414,11 @@ class BaseClassRequestResponse(APIPartImageBuildRequestResponse, CommonIndexImag
"""Datastructure representing data returned by Request class to_json method."""


class FbcOperationRequestResponse(BaseClassRequestResponse):
"""Datastructure of the response to request from /builds/fbc-operations API point."""

fbc_fragment: str
fbc_fragment_resolved: Optional[str]


# End of the RequestResponses Part
84 changes: 84 additions & 0 deletions iib/web/migrations/versions/8d50f82f0be9_fbc_operations_api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
"""
Add fbc-operations api.
Revision ID: 8d50f82f0be9
Revises: a0eadb516360
Create Date: 2023-01-04 10:39:49.366511
"""
from alembic import op
import sqlalchemy as sa


# revision identifiers, used by Alembic.
revision = '8d50f82f0be9'
down_revision = 'a0eadb516360'
branch_labels = None
depends_on = None


def upgrade():
op.create_table(
'request_fbc_operations',
sa.Column('id', sa.Integer(), autoincrement=False, nullable=False),
sa.Column('fbc_fragment_id', sa.Integer(), nullable=True),
sa.Column('fbc_fragment_resolved_id', sa.Integer(), nullable=True),
sa.Column('binary_image_id', sa.Integer(), nullable=True),
sa.Column('binary_image_resolved_id', sa.Integer(), nullable=True),
sa.Column('from_index_id', sa.Integer(), nullable=True),
sa.Column('from_index_resolved_id', sa.Integer(), nullable=True),
sa.Column('index_image_id', sa.Integer(), nullable=True),
sa.Column('index_image_resolved_id', sa.Integer(), nullable=True),
sa.Column('internal_index_image_copy_id', sa.Integer(), nullable=True),
sa.Column('internal_index_image_copy_resolved_id', sa.Integer(), nullable=True),
sa.Column('distribution_scope', sa.String(), nullable=True),
sa.ForeignKeyConstraint(
['binary_image_id'],
['image.id'],
),
sa.ForeignKeyConstraint(
['binary_image_resolved_id'],
['image.id'],
),
sa.ForeignKeyConstraint(
['fbc_fragment_id'],
['image.id'],
),
sa.ForeignKeyConstraint(
['fbc_fragment_resolved_id'],
['image.id'],
),
sa.ForeignKeyConstraint(
['from_index_id'],
['image.id'],
),
sa.ForeignKeyConstraint(
['from_index_resolved_id'],
['image.id'],
),
sa.ForeignKeyConstraint(
['id'],
['request.id'],
),
sa.ForeignKeyConstraint(
['index_image_id'],
['image.id'],
),
sa.ForeignKeyConstraint(
['index_image_resolved_id'],
['image.id'],
),
sa.ForeignKeyConstraint(
['internal_index_image_copy_id'],
['image.id'],
),
sa.ForeignKeyConstraint(
['internal_index_image_copy_resolved_id'],
['image.id'],
),
sa.PrimaryKeyConstraint('id'),
)


def downgrade():
op.drop_table('request_fbc_operations')
101 changes: 101 additions & 0 deletions iib/web/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@
RegenerateBundlePayload,
RegenerateBundleRequestResponse,
RmRequestPayload,
FbcOperationRequestPayload,
FbcOperationRequestResponse,
)


Expand Down Expand Up @@ -102,6 +104,7 @@ class RequestTypeMapping(BaseEnum):
merge_index_image: int = 4
create_empty_index: int = 5
recursive_related_bundles: int = 6
fbc_operations: int = 7

@classmethod
def pretty(cls, num: int) -> str:
Expand Down Expand Up @@ -490,6 +493,7 @@ def to_json(
MergeIndexImageRequestResponse,
RecursiveRelatedBundlesRequestResponse,
RegenerateBundleRequestResponse,
FbcOperationRequestResponse,
]:
"""
Provide the basic JSON representation of a build request.
Expand Down Expand Up @@ -751,6 +755,8 @@ def get_request_query_options(verbose: Optional[bool] = False) -> List[_UnboundL
joinedload(RequestRm.operators),
joinedload(RequestRm.build_tags),
joinedload(RequestMergeIndexImage.build_tags),
joinedload(RequestFbcOperations.fbc_fragment),
joinedload(RequestFbcOperations.fbc_fragment_resolved),
]
if verbose:
query_options.append(joinedload(Request.states))
Expand Down Expand Up @@ -1943,3 +1949,98 @@ def get_mutable_keys(self) -> Set[str]:
rv = super().get_mutable_keys()
rv.add('parent_bundle_image_resolved')
return rv


class RequestFbcOperations(Request, RequestIndexImageMixin):
"""FBC operation build request."""

__tablename__ = 'request_fbc_operations'

id = db.Column(db.Integer, db.ForeignKey('request.id'), autoincrement=False, primary_key=True)

fbc_fragment_id = db.Column(db.Integer, db.ForeignKey('image.id'))
fbc_fragment_resolved_id = db.Column(db.Integer, db.ForeignKey('image.id'))
fbc_fragment = db.relationship('Image', foreign_keys=[fbc_fragment_id], uselist=False)
fbc_fragment_resolved = db.relationship(
'Image', foreign_keys=[fbc_fragment_resolved_id], uselist=False
)
build_tags = None

__mapper_args__ = {
'polymorphic_identity': RequestTypeMapping.__members__['fbc_operations'].value
}

@classmethod
def from_json( # type: ignore[override] # noqa: F821
cls,
kwargs: FbcOperationRequestPayload,
):
"""
Handle JSON requests for the fbc-operations API endpoint.
:param dict kwargs: the JSON payload of the request.
"""
request_kwargs = deepcopy(kwargs)

validate_request_params(
request_kwargs,
required_params={'fbc_fragment', 'from_index'},
optional_params={
'add_arches',
'binary_image',
'build_tags',
'overwrite_from_index',
'overwrite_from_index_token',
},
)

# Validate parent_bundle_image is correctly provided
fbc_fragment = request_kwargs.get('fbc_fragment')
if not isinstance(fbc_fragment, str):
raise ValidationError('The "fbc_fragment" must be a string')
request_kwargs['fbc_fragment'] = Image.get_or_create(pull_specification=fbc_fragment)

# cast to more wider type, see _from_json method
cls._from_json(
cast(RequestPayload, request_kwargs),
additional_optional_params=[
'bundles',
'fbc_fragment',
'from_index',
'organization',
],
)

request = cls(**request_kwargs)
request.add_state('in_progress', 'The request was initiated')
return request

def to_json(self, verbose: Optional[bool] = True) -> FbcOperationRequestResponse:
"""
Provide the JSON representation of a "fbc-operation" build request.
:param bool verbose: determines if the JSON output should be verbose
:return: a dictionary representing the JSON of the build request
:rtype: dict
"""
# cast to result type, super-type returns Union
rv = cast(FbcOperationRequestResponse, super().to_json(verbose=verbose))
rv.update(self.get_common_index_image_json()) # type: ignore
rv['fbc_fragment'] = self.fbc_fragment.pull_specification
rv['fbc_fragment_resolved'] = getattr(
self.fbc_fragment_resolved, 'pull_specification', None
)

return rv

def get_mutable_keys(self) -> Set[str]:
"""
Return the set of keys representing the attributes that can be modified.
:return: a set of key names
:rtype: set
"""
rv = super().get_mutable_keys()
rv.update(self.get_index_image_mutable_keys())
rv.add('fbc_fragment_resolved')
return rv
Loading

0 comments on commit 22bb77a

Please sign in to comment.