diff --git a/api/config/provisioning.ts b/api/config/provisioning.ts new file mode 100644 index 00000000..ca36231f --- /dev/null +++ b/api/config/provisioning.ts @@ -0,0 +1,13 @@ +import type { ConfigFile } from '@rtk-query/codegen-openapi' + +const config: ConfigFile = { + schemaFile: '../schema/provisioning.json', + apiFile: '../../src/store/emptyProvisioningApi.ts', + apiImport: 'emptyProvisioningApi', + outputFile: '../../src/store/provisioningApi.ts', + exportName: 'provisioningApi', + hooks: true, + filterEndpoints: ['getSourceList', 'getSourceUploadInfo'], +} + +export default config diff --git a/api/schema/provisioning.json b/api/schema/provisioning.json new file mode 100644 index 00000000..e46ff0e3 --- /dev/null +++ b/api/schema/provisioning.json @@ -0,0 +1,1870 @@ +{ + "components": { + "examples": { + "v1.AvailabilityStatusRequest": { + "value": { + "source_id": "463243" + } + }, + "v1.AwsReservationRequestPayloadExample": { + "value": { + "amount": 1, + "image_id": "ami-7846387643232", + "instance_type": "t3.small", + "launch_template_id": "", + "name": "my-instance", + "poweroff": false, + "pubkey_id": 42, + "region": "us-east-1", + "source_id": "654321" + } + }, + "v1.AwsReservationResponsePayloadDoneExample": { + "value": { + "amount": 1, + "aws_reservation_id": "r-3743243324231", + "image_id": "ami-7846387643232", + "instance_type": "t3.small", + "instances": [ + { + "detail": { + "publicdns": "", + "publicipv4": "10.0.0.88" + }, + "instance_id": "i-2324343212" + } + ], + "launch_template_id": "", + "name": "my-instance", + "poweroff": false, + "pubkey_id": 42, + "region": "us-east-1", + "reservation_id": 1305, + "source_id": "654321" + } + }, + "v1.AwsReservationResponsePayloadPendingExample": { + "value": { + "amount": 1, + "aws_reservation_id": "", + "image_id": "ami-7846387643232", + "instance_type": "t3.small", + "instances": [], + "launch_template_id": "", + "name": "my-instance", + "poweroff": false, + "pubkey_id": 42, + "region": "us-east-1", + "reservation_id": 0, + "source_id": "654321" + } + }, + "v1.AzureReservationRequestPayloadExample": { + "value": { + "amount": 1, + "image_id": "composer-api-081fc867-838f-44a5-af03-8b8def808431", + "instance_size": "Basic_A0", + "location": "useast", + "name": "my-instance", + "poweroff": false, + "pubkey_id": 42, + "source_id": "654321" + } + }, + "v1.AzureReservationResponsePayloadDoneExample": { + "value": { + "amount": 1, + "image_id": "composer-api-081fc867-838f-44a5-af03-8b8def808431", + "instance_size": "Basic_A0", + "instances": [ + { + "detail": { + "publicdns": "", + "publicipv4": "10.0.0.88" + }, + "instance_id": "/subscriptions/4b9d213f-712f-4d17-a483-8a10bbe9df3a/resourceGroups/redhat-deployed/providers/Microsoft.Compute/images/composer-api-92ea98f8-7697-472e-80b1-7454fa0e7fa7" + } + ], + "location": "useast", + "name": "my-instance", + "poweroff": false, + "pubkey_id": 42, + "reservation_id": 1310, + "source_id": "654321" + } + }, + "v1.AzureReservationResponsePayloadPendingExample": { + "value": { + "amount": 1, + "image_id": "composer-api-081fc867-838f-44a5-af03-8b8def808431", + "instance_size": "Basic_A0", + "instances": [], + "location": "useast", + "name": "my-instance", + "poweroff": false, + "pubkey_id": 42, + "reservation_id": 1310, + "source_id": "654321" + } + }, + "v1.GCPReservationRequestPayloadExample": { + "value": { + "amount": 1, + "image_id": "08a48fed-de87-40ab-a571-f64e30bd0aa8", + "launch_template_name": "", + "machine_type": "e2-micro", + "name_pattern": "my-instance", + "poweroff": false, + "pubkey_id": 42, + "source_id": "654321", + "zone": "us-east-4" + } + }, + "v1.GCPReservationResponsePayloadDoneExample": { + "value": { + "amount": 1, + "gcp_operation_name": "operation-1686646674436-5fdff07e43209-66146b7e-f3f65ec5", + "image_id": "08a48fed-de87-40ab-a571-f64e30bd0aa8", + "instances": [ + { + "detail": { + "publicdns": "", + "publicipv4": "10.0.0.88" + }, + "instance_id": "3003942005876582747" + } + ], + "launch_template_name": "template-1", + "machine_type": "e2-micro", + "name_pattern": "my-instance", + "poweroff": false, + "pubkey_id": 42, + "reservation_id": 1305, + "source_id": "654321", + "zone": "us-east-4" + } + }, + "v1.GCPReservationResponsePayloadPendingExample": { + "value": { + "amount": 1, + "gcp_operation_name": "operation-1686646674436-5fdff07e43209-66146b7e-f3f65ec5", + "image_id": "08a48fed-de87-40ab-a571-f64e30bd0aa8", + "instances": [], + "launch_template_name": "template-1", + "machine_type": "e2-micro", + "name_pattern": "my-instance", + "poweroff": false, + "pubkey_id": 42, + "reservation_id": 1305, + "source_id": "654321", + "zone": "us-east-4" + } + }, + "v1.GenericReservationResponsePayloadFailureExample": { + "value": { + "created_at": "2013-05-13T19:20:15Z", + "error": "cannot launch ec2 instance: VPCIdNotSpecified: No default VPC for this user. GroupName is only supported for EC2-Classic and default VPC", + "finished_at": "2013-05-13T19:20:25Z", + "id": 1313, + "provider": 1, + "status": "Finished Launch instance(s)", + "step": 2, + "step_titles": [ + "Ensure public key", + "Launch instance(s)", + "Fetch instance(s) description" + ], + "steps": 3, + "success": false + } + }, + "v1.GenericReservationResponsePayloadListExample": { + "value": [ + { + "created_at": "2013-05-13T19:20:15Z", + "error": "", + "finished_at": null, + "id": 1310, + "provider": 1, + "status": "Started Ensure public key", + "step": 1, + "step_titles": [ + "Ensure public key", + "Launch instance(s)", + "Fetch instance(s) description" + ], + "steps": 3, + "success": null + }, + { + "created_at": "2013-05-13T19:20:15Z", + "error": "", + "finished_at": "2013-05-13T19:20:25Z", + "id": 1305, + "provider": 1, + "status": "Finished Fetch instance(s) description", + "step": 3, + "step_titles": [ + "Ensure public key", + "Launch instance(s)", + "Fetch instance(s) description" + ], + "steps": 3, + "success": true + }, + { + "created_at": "2013-05-13T19:20:15Z", + "error": "cannot launch ec2 instance: VPCIdNotSpecified: No default VPC for this user. GroupName is only supported for EC2-Classic and default VPC", + "finished_at": "2013-05-13T19:20:25Z", + "id": 1313, + "provider": 1, + "status": "Finished Launch instance(s)", + "step": 2, + "step_titles": [ + "Ensure public key", + "Launch instance(s)", + "Fetch instance(s) description" + ], + "steps": 3, + "success": false + } + ] + }, + "v1.GenericReservationResponsePayloadPendingExample": { + "value": { + "created_at": "2013-05-13T19:20:15Z", + "error": "", + "finished_at": null, + "id": 1310, + "provider": 1, + "status": "Started Ensure public key", + "step": 1, + "step_titles": [ + "Ensure public key", + "Launch instance(s)", + "Fetch instance(s) description" + ], + "steps": 3, + "success": null + } + }, + "v1.GenericReservationResponsePayloadSuccessExample": { + "value": { + "created_at": "2013-05-13T19:20:15Z", + "error": "", + "finished_at": "2013-05-13T19:20:25Z", + "id": 1305, + "provider": 1, + "status": "Finished Fetch instance(s) description", + "step": 3, + "step_titles": [ + "Ensure public key", + "Launch instance(s)", + "Fetch instance(s) description" + ], + "steps": 3, + "success": true + } + }, + "v1.InstanceTypesAWSResponse": { + "value": [ + { + "arch": "x86_64", + "cores": 16, + "memory_mib": 65536, + "name": "c5a.8xlarge", + "storage_gb": 0, + "supported": true, + "vcpus": 32 + } + ] + }, + "v1.InstanceTypesAzureResponse": { + "value": [ + { + "arch": "x86_64", + "azure": { + "gen_v1": true, + "gen_v2": true + }, + "cores": 64, + "memory_mib": 2000000, + "name": "Standard_M128s", + "storage_gb": 4096, + "supported": true, + "vcpus": 128 + } + ] + }, + "v1.InstanceTypesGCPResponse": { + "value": [ + { + "arch": "x86_64", + "cores": 0, + "memory_mib": 15623, + "name": "e2-highcpu-16", + "storage_gb": 0, + "supported": true, + "vcpus": 16 + } + ] + }, + "v1.LaunchTemplateListResponse": { + "value": [ + { + "id": "lt-9843797432897342", + "name": "XXL large backend API" + } + ] + }, + "v1.NoopReservationResponsePayloadExample": { + "value": { + "reservation_id": 1310 + } + }, + "v1.PubkeyListResponseExample": { + "value": [ + { + "body": "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEhnn80ZywmjeBFFOGm+cm+5HUwm62qTVnjKlOdYFLHN lzap", + "fingerprint": "gL/y6MvNmJ8jDXtsL/oMmK8jUuIefN39BBuvYw/Rndk=", + "fingerprint_legacy": "ee:f1:d4:62:99:ab:17:d9:3b:00:66:62:32:b2:55:9e", + "id": 1, + "name": "My key", + "type": "ssh-ed25519" + } + ] + }, + "v1.PubkeyRequestExample": { + "value": { + "body": "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEhnn80ZywmjeBFFOGm+cm+5HUwm62qTVnjKlOdYFLHN lzap", + "name": "My key" + } + }, + "v1.PubkeyResponseExample": { + "value": { + "body": "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEhnn80ZywmjeBFFOGm+cm+5HUwm62qTVnjKlOdYFLHN lzap", + "fingerprint": "gL/y6MvNmJ8jDXtsL/oMmK8jUuIefN39BBuvYw/Rndk=", + "fingerprint_legacy": "ee:f1:d4:62:99:ab:17:d9:3b:00:66:62:32:b2:55:9e", + "id": 1, + "name": "My key", + "type": "ssh-ed25519" + } + }, + "v1.SourceListResponseExample": { + "value": [ + { + "id": "654321", + "name": "My AWS account", + "source_type_id": "", + "uid": "" + }, + { + "id": "543621", + "name": "My other AWS account", + "source_type_id": "", + "uid": "" + } + ] + }, + "v1.SourceUploadInfoAWSResponse": { + "value": { + "aws": { + "account_id": "78462784632" + }, + "azure": null, + "gcp": null, + "provider": "aws" + } + }, + "v1.SourceUploadInfoAzureResponse": { + "value": { + "aws": null, + "azure": { + "resourcegroups": [ + "MyGroup 1", + "MyGroup 42" + ], + "subscriptionid": "617807e1-e4e0-4855-983c-1e3ce1e49674", + "tenantid": "617807e1-e4e0-481c-983c-be3ce1e49253" + }, + "gcp": null, + "provider": "azure" + } + } + }, + "responses": { + "BadRequest": { + "content": { + "application/json": { + "examples": { + "error": { + "value": { + "build_time": "2023-04-14_17:15:02", + "edge_id": "", + "environment": "", + "error": "error: bad request: details can be long", + "trace_id": "b57f7b78c", + "version": "df8a489" + } + } + }, + "schema": { + "$ref": "#/components/schemas/v1.ResponseError" + } + } + }, + "description": "The request's parameters are not valid" + }, + "InternalError": { + "content": { + "application/json": { + "examples": { + "error": { + "value": { + "build_time": "2023-04-14_17:15:02", + "edge_id": "", + "environment": "", + "error": "error: this can be pretty long string", + "trace_id": "b57f7b78c", + "version": "df8a489" + } + } + }, + "schema": { + "$ref": "#/components/schemas/v1.ResponseError" + } + } + }, + "description": "The server encountered an internal error" + }, + "NotFound": { + "content": { + "application/json": { + "examples": { + "error": { + "value": { + "build_time": "2023-04-14_17:15:02", + "edge_id": "", + "environment": "", + "error": "error: resource not found: details can be long", + "trace_id": "b57f7b78c", + "version": "df8a489" + } + } + }, + "schema": { + "$ref": "#/components/schemas/v1.ResponseError" + } + } + }, + "description": "The requested resource was not found" + } + }, + "schemas": { + "v1.AWSReservationRequest": { + "properties": { + "amount": { + "format": "int32", + "type": "integer" + }, + "image_id": { + "type": "string" + }, + "instance_type": { + "type": "string" + }, + "launch_template_id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "poweroff": { + "type": "boolean" + }, + "pubkey_id": { + "format": "int64", + "type": "integer" + }, + "region": { + "type": "string" + }, + "source_id": { + "type": "string" + } + }, + "type": "object" + }, + "v1.AWSReservationResponse": { + "properties": { + "amount": { + "format": "int32", + "type": "integer" + }, + "aws_reservation_id": { + "type": "string" + }, + "image_id": { + "type": "string" + }, + "instance_type": { + "type": "string" + }, + "instances": { + "items": { + "properties": { + "detail": { + "properties": { + "public_dns": { + "type": "string" + }, + "public_ipv4": { + "type": "string" + } + }, + "type": "object" + }, + "instance_id": { + "type": "string" + } + }, + "type": "object" + }, + "type": "array" + }, + "launch_template_id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "poweroff": { + "type": "boolean" + }, + "pubkey_id": { + "format": "int64", + "type": "integer" + }, + "region": { + "type": "string" + }, + "reservation_id": { + "format": "int64", + "type": "integer" + }, + "source_id": { + "type": "string" + } + }, + "type": "object" + }, + "v1.AccountIDTypeResponse": { + "properties": { + "aws": { + "properties": { + "account_id": { + "type": "string" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "v1.AvailabilityStatusRequest": { + "properties": { + "source_id": { + "type": "string" + } + }, + "type": "object" + }, + "v1.AzureReservationRequest": { + "properties": { + "amount": { + "format": "int64", + "type": "integer" + }, + "image_id": { + "type": "string" + }, + "instance_size": { + "type": "string" + }, + "location": { + "type": "string" + }, + "name": { + "type": "string" + }, + "poweroff": { + "type": "boolean" + }, + "pubkey_id": { + "format": "int64", + "type": "integer" + }, + "source_id": { + "type": "string" + } + }, + "type": "object" + }, + "v1.AzureReservationResponse": { + "properties": { + "amount": { + "format": "int64", + "type": "integer" + }, + "image_id": { + "type": "string" + }, + "instance_size": { + "type": "string" + }, + "instances": { + "items": { + "properties": { + "detail": { + "properties": { + "public_dns": { + "type": "string" + }, + "public_ipv4": { + "type": "string" + } + }, + "type": "object" + }, + "instance_id": { + "type": "string" + } + }, + "type": "object" + }, + "type": "array" + }, + "location": { + "type": "string" + }, + "name": { + "type": "string" + }, + "poweroff": { + "type": "boolean" + }, + "pubkey_id": { + "format": "int64", + "type": "integer" + }, + "reservation_id": { + "format": "int64", + "type": "integer" + }, + "source_id": { + "type": "string" + } + }, + "type": "object" + }, + "v1.GCPReservationRequest": { + "properties": { + "amount": { + "format": "int64", + "type": "integer" + }, + "image_id": { + "type": "string" + }, + "launch_template_name": { + "type": "string" + }, + "machine_type": { + "type": "string" + }, + "name_pattern": { + "type": "string" + }, + "poweroff": { + "type": "boolean" + }, + "pubkey_id": { + "format": "int64", + "type": "integer" + }, + "source_id": { + "type": "string" + }, + "zone": { + "type": "string" + } + }, + "type": "object" + }, + "v1.GCPReservationResponse": { + "properties": { + "amount": { + "format": "int64", + "type": "integer" + }, + "gcp_operation_name": { + "type": "string" + }, + "image_id": { + "type": "string" + }, + "instances": { + "items": { + "properties": { + "detail": { + "properties": { + "public_dns": { + "type": "string" + }, + "public_ipv4": { + "type": "string" + } + }, + "type": "object" + }, + "instance_id": { + "type": "string" + } + }, + "type": "object" + }, + "type": "array" + }, + "launch_template_name": { + "type": "string" + }, + "machine_type": { + "type": "string" + }, + "name_pattern": { + "type": "string" + }, + "poweroff": { + "type": "boolean" + }, + "pubkey_id": { + "format": "int64", + "type": "integer" + }, + "reservation_id": { + "format": "int64", + "type": "integer" + }, + "source_id": { + "type": "string" + }, + "zone": { + "type": "string" + } + }, + "type": "object" + }, + "v1.GenericReservationResponsePayload": { + "properties": { + "created_at": { + "format": "date-time", + "type": "string" + }, + "error": { + "type": "string" + }, + "finished_at": { + "format": "date-time", + "nullable": true, + "type": "string" + }, + "id": { + "format": "int64", + "type": "integer" + }, + "provider": { + "type": "integer" + }, + "status": { + "type": "string" + }, + "step": { + "format": "int32", + "type": "integer" + }, + "step_titles": { + "items": { + "type": "string" + }, + "type": "array" + }, + "steps": { + "format": "int32", + "type": "integer" + }, + "success": { + "nullable": true, + "type": "boolean" + } + }, + "type": "object" + }, + "v1.InstanceTypeResponse": { + "properties": { + "architecture": { + "type": "string" + }, + "azure": { + "properties": { + "gen_v1": { + "type": "boolean" + }, + "gen_v2": { + "type": "boolean" + } + }, + "type": "object" + }, + "cores": { + "format": "int32", + "type": "integer" + }, + "memory_mib": { + "format": "int64", + "type": "integer" + }, + "name": { + "type": "string" + }, + "storage_gb": { + "format": "int64", + "type": "integer" + }, + "supported": { + "type": "boolean" + }, + "vcpus": { + "format": "int32", + "type": "integer" + } + }, + "type": "object" + }, + "v1.LaunchTemplatesResponse": { + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "type": "object" + }, + "v1.NoopReservationResponse": { + "properties": { + "reservation_id": { + "format": "int64", + "type": "integer" + } + }, + "type": "object" + }, + "v1.PubkeyRequest": { + "properties": { + "body": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "type": "object" + }, + "v1.PubkeyResponse": { + "properties": { + "body": { + "type": "string" + }, + "fingerprint": { + "type": "string" + }, + "fingerprint_legacy": { + "type": "string" + }, + "id": { + "format": "int64", + "type": "integer" + }, + "name": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "type": "object" + }, + "v1.ResponseError": { + "properties": { + "build_time": { + "type": "string" + }, + "edge_id": { + "type": "string" + }, + "environment": { + "type": "string" + }, + "error": { + "type": "string" + }, + "msg": { + "type": "string" + }, + "trace_id": { + "type": "string" + }, + "version": { + "type": "string" + } + }, + "type": "object" + }, + "v1.SourceResponse": { + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "source_type_id": { + "type": "string" + }, + "uid": { + "type": "string" + } + }, + "type": "object" + }, + "v1.SourceUploadInfoResponse": { + "properties": { + "aws": { + "nullable": true, + "properties": { + "account_id": { + "type": "string" + } + }, + "type": "object" + }, + "azure": { + "nullable": true, + "properties": { + "resource_groups": { + "items": { + "type": "string" + }, + "type": "array" + }, + "subscription_id": { + "type": "string" + }, + "tenant_id": { + "type": "string" + } + }, + "type": "object" + }, + "gcp": { + "nullable": true + }, + "provider": { + "type": "string" + } + }, + "type": "object" + } + } + }, + "info": { + "description": "Provisioning service API", + "license": { + "name": "GPL-3.0" + }, + "title": "provisioning-api", + "version": "1.2.0" + }, + "openapi": "3.0.0", + "paths": { + "/availability_status/sources": { + "post": { + "description": "Schedules a background operation of Sources availability check. These checks are are performed in separate process at it's own pace. Results are sent via Kafka to Sources. There is no output from this REST operation available, no tracking of jobs is possible.\n", + "operationId": "availabilityStatus", + "requestBody": { + "content": { + "application/json": { + "examples": { + "example": { + "$ref": "#/components/examples/v1.AvailabilityStatusRequest" + } + }, + "schema": { + "$ref": "#/components/schemas/v1.AvailabilityStatusRequest" + } + } + }, + "description": "availability status request with source id", + "required": true + }, + "responses": { + "200": { + "description": "Returned on success, empty response." + }, + "500": { + "$ref": "#/components/responses/InternalError" + } + }, + "tags": [ + "AvailabilityStatus" + ] + } + }, + "/instance_types/{PROVIDER}": { + "get": { + "description": "Return a list of instance types for particular provider. A region must be provided. A zone must be provided for Azure.\n", + "operationId": "getInstanceTypeListAll", + "parameters": [ + { + "description": "Cloud provider: aws, azure", + "in": "path", + "name": "PROVIDER", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Region to list instance types within. This is required.", + "in": "query", + "name": "region", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Availability zone (or location) to list instance types within. Not applicable for AWS EC2 as all zones within a region are the same (will lead to an error when used). Required for Azure.", + "in": "query", + "name": "zone", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "examples": { + "aws": { + "$ref": "#/components/examples/v1.InstanceTypesAWSResponse" + }, + "azure": { + "$ref": "#/components/examples/v1.InstanceTypesAzureResponse" + } + }, + "schema": { + "items": { + "$ref": "#/components/schemas/v1.InstanceTypeResponse" + }, + "type": "array" + } + } + }, + "description": "Return on success. Instance types have a field \"supported\" that indicates whether that particular type is supported by Red Hat. Typically, instances with less than 1.5 GiB RAM are not supported, but other rules may apply.\n" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "500": { + "$ref": "#/components/responses/InternalError" + } + }, + "tags": [ + "InstanceType" + ] + } + }, + "/pubkeys": { + "get": { + "description": "A pubkey represents an SSH public portion of a key pair with name and body. This operation returns list of all pubkeys for particular account.\n", + "operationId": "getPubkeyList", + "responses": { + "200": { + "content": { + "application/json": { + "examples": { + "example": { + "$ref": "#/components/examples/v1.PubkeyListResponseExample" + } + }, + "schema": { + "items": { + "$ref": "#/components/schemas/v1.PubkeyResponse" + }, + "type": "array" + } + } + }, + "description": "Returned on success." + }, + "500": { + "$ref": "#/components/responses/InternalError" + } + }, + "tags": [ + "Pubkey" + ] + }, + "post": { + "description": "A pubkey represents an SSH public portion of a key pair with name and body. When pubkey is created, it is stored in the Provisioning database. Pubkeys are uploaded to clouds when an instance is launched. Some fields (e.g. type or fingerprint) are read only.\n", + "operationId": "createPubkey", + "requestBody": { + "content": { + "application/json": { + "examples": { + "example": { + "$ref": "#/components/examples/v1.PubkeyRequestExample" + } + }, + "schema": { + "$ref": "#/components/schemas/v1.PubkeyRequest" + } + } + }, + "description": "request body", + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "examples": { + "example": { + "$ref": "#/components/examples/v1.PubkeyRequestExample" + } + }, + "schema": { + "$ref": "#/components/schemas/v1.PubkeyResponse" + } + } + }, + "description": "Returned on success." + }, + "500": { + "$ref": "#/components/responses/InternalError" + } + }, + "tags": [ + "Pubkey" + ] + } + }, + "/pubkeys/{ID}": { + "delete": { + "description": "A pubkey represents an SSH public portion of a key pair with name and body. If a pubkey was uploaded to one or more clouds, the deletion request will attempt to delete those SSH keys from all clouds. This means in order to delete a pubkey the account must have valid credentials to all cloud accounts the pubkey was uploaded to, otherwise the delete operation will fail and the pubkey will not be deleted from Provisioning database. This operation returns no body.\n", + "operationId": "removePubkeyById", + "parameters": [ + { + "description": "Database ID of resource.", + "in": "path", + "name": "ID", + "required": true, + "schema": { + "format": "int64", + "type": "integer" + } + } + ], + "responses": { + "204": { + "description": "The Pubkey was deleted successfully." + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "500": { + "$ref": "#/components/responses/InternalError" + } + }, + "tags": [ + "Pubkey" + ] + }, + "get": { + "description": "A pubkey represents an SSH public portion of a key pair with name and body. Pubkeys must have unique name and body (SSH public key fingerprint) per each account. Pubkey type is detected during create operation as well as fingerprints. Currently two types are supported: RSA and ssh-ed25519. Also, two fingerprint types are calculated: standard SHA fingerprint and legacy MD5 fingerprint available under fingerprint_legacy field. Fingerprints are used to check uniqueness of key.\n", + "operationId": "getPubkeyById", + "parameters": [ + { + "description": "Database ID to search for", + "in": "path", + "name": "ID", + "required": true, + "schema": { + "format": "int64", + "type": "integer" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "examples": { + "example": { + "$ref": "#/components/examples/v1.PubkeyResponseExample" + } + }, + "schema": { + "$ref": "#/components/schemas/v1.PubkeyResponse" + } + } + }, + "description": "Returned on success" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "500": { + "$ref": "#/components/responses/InternalError" + } + }, + "tags": [ + "Pubkey" + ] + } + }, + "/reservations": { + "get": { + "description": "A reservation is a way to activate a job, keeps all data needed for a job to start. This operation returns list of all reservations for particular account. To get a reservation with common fields, use /reservations/ID. To get a detailed reservation with all fields which are different per provider, use /reservations/aws/ID. Reservation can be in three states: pending, success, failed. This can be recognized by the success field (null for pending, true for success, false for failure). See the examples.\n", + "operationId": "getReservationsList", + "responses": { + "200": { + "content": { + "application/json": { + "examples": { + "example": { + "$ref": "#/components/examples/v1.GenericReservationResponsePayloadListExample" + } + }, + "schema": { + "items": { + "$ref": "#/components/schemas/v1.GenericReservationResponsePayload" + }, + "type": "array" + } + } + }, + "description": "Returned on success." + }, + "500": { + "$ref": "#/components/responses/InternalError" + } + }, + "tags": [ + "Reservation" + ] + } + }, + "/reservations/aws": { + "post": { + "description": "A reservation is a way to activate a job, keeps all data needed for a job to start. An AWS reservation is a reservation created for an AWS job. Image Builder UUID image is required, the service will also launch any AMI image prefixed with \"ami-\". Optionally, AWS EC2 launch template ID can be provided. All flags set through this endpoint override template values. Public key must exist prior calling this endpoint and ID must be provided, even when AWS EC2 launch template provides ssh-keys. Public key will be always be overwritten. A single account can create maximum of 2 reservations per second.\n", + "operationId": "createAwsReservation", + "requestBody": { + "content": { + "application/json": { + "examples": { + "example": { + "$ref": "#/components/examples/v1.AwsReservationRequestPayloadExample" + } + }, + "schema": { + "$ref": "#/components/schemas/v1.AWSReservationRequest" + } + } + }, + "description": "aws request body", + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/v1.AWSReservationResponse" + } + } + }, + "description": "Returned on success." + }, + "500": { + "$ref": "#/components/responses/InternalError" + } + }, + "tags": [ + "Reservation" + ] + } + }, + "/reservations/aws/{ID}": { + "get": { + "description": "Return an AWS reservation with details by id", + "operationId": "getAWSReservationByID", + "parameters": [ + { + "description": "Reservation ID, must be an AWS reservation otherwise 404 is returned", + "in": "path", + "name": "ID", + "required": true, + "schema": { + "format": "int64", + "type": "integer" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "examples": { + "done": { + "$ref": "#/components/examples/v1.AwsReservationResponsePayloadDoneExample" + }, + "pending": { + "$ref": "#/components/examples/v1.AwsReservationResponsePayloadPendingExample" + } + }, + "schema": { + "$ref": "#/components/schemas/v1.AWSReservationResponse" + } + } + }, + "description": "Returns detailed reservation information for an AWS reservation." + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "500": { + "$ref": "#/components/responses/InternalError" + } + }, + "tags": [ + "Reservation" + ] + } + }, + "/reservations/azure": { + "post": { + "description": "A reservation is a way to activate a job, keeps all data needed for a job to start. An Azure reservation is a reservation created for an Azure job. Image Builder UUID image is required and needs to be stored under same account as provided by SourceID. A single account can create maximum of 2 reservations per second.\n", + "operationId": "createAzureReservation", + "requestBody": { + "content": { + "application/json": { + "examples": { + "example": { + "$ref": "#/components/examples/v1.AzureReservationRequestPayloadExample" + } + }, + "schema": { + "$ref": "#/components/schemas/v1.AzureReservationRequest" + } + } + }, + "description": "azure request body", + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/v1.AzureReservationResponse" + } + } + }, + "description": "Returned on success." + }, + "500": { + "$ref": "#/components/responses/InternalError" + } + }, + "tags": [ + "Reservation" + ] + } + }, + "/reservations/azure/{ID}": { + "get": { + "description": "Return an Azure reservation with details by id", + "operationId": "getAzureReservationByID", + "parameters": [ + { + "description": "Reservation ID, must be an Azure reservation otherwise 404 is returned", + "in": "path", + "name": "ID", + "required": true, + "schema": { + "format": "int64", + "type": "integer" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "examples": { + "done": { + "$ref": "#/components/examples/v1.AzureReservationResponsePayloadDoneExample" + }, + "pending": { + "$ref": "#/components/examples/v1.AzureReservationResponsePayloadPendingExample" + } + }, + "schema": { + "$ref": "#/components/schemas/v1.AzureReservationResponse" + } + } + }, + "description": "Returns detailed reservation information for an Azure reservation." + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "500": { + "$ref": "#/components/responses/InternalError" + } + }, + "tags": [ + "Reservation" + ] + } + }, + "/reservations/gcp": { + "post": { + "description": "A reservation is a way to activate a job, keeps all data needed for a job to start. A GCP reservation is a reservation created for a GCP job. Image Builder UUID image is required and needs to be shared with the service account. Furthermore, by specifying the name pattern for example as \"instance\", instances names will be created in the format: \"instance-#####\". A single account can create maximum of 2 reservations per second.\n", + "operationId": "createGCPReservation", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/v1.GCPReservationRequest" + } + } + }, + "description": "gcp request body", + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/v1.GCPReservationResponse" + } + } + }, + "description": "Returned on success." + }, + "500": { + "$ref": "#/components/responses/InternalError" + } + }, + "tags": [ + "Reservation" + ] + } + }, + "/reservations/gcp/{ID}": { + "get": { + "description": "Return an GCP reservation with details by id", + "operationId": "getGCPReservationByID", + "parameters": [ + { + "description": "Reservation ID, must be an GCP reservation otherwise 404 is returned", + "in": "path", + "name": "ID", + "required": true, + "schema": { + "format": "int64", + "type": "integer" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/v1.GCPReservationResponse" + } + } + }, + "description": "Returns detailed reservation information for an GCP reservation." + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "500": { + "$ref": "#/components/responses/InternalError" + } + }, + "tags": [ + "Reservation" + ] + } + }, + "/reservations/noop": { + "post": { + "description": "A reservation is a way to activate a job, keeps all data needed for a job to start. A Noop reservation actually does nothing and immediately finish background job. This reservation has no input payload\n", + "operationId": "createNoopReservation", + "responses": { + "200": { + "content": { + "application/json": { + "examples": { + "example": { + "$ref": "#/components/examples/v1.NoopReservationResponsePayloadExample" + } + }, + "schema": { + "$ref": "#/components/schemas/v1.NoopReservationResponse" + } + } + }, + "description": "Returned on success." + }, + "500": { + "$ref": "#/components/responses/InternalError" + } + }, + "tags": [ + "Reservation" + ] + } + }, + "/reservations/{ID}": { + "get": { + "description": "Return a generic reservation by id", + "operationId": "getReservationByID", + "parameters": [ + { + "description": "Reservation ID", + "in": "path", + "name": "ID", + "required": true, + "schema": { + "format": "int64", + "type": "integer" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "examples": { + "failure": { + "$ref": "#/components/examples/v1.GenericReservationResponsePayloadFailureExample" + }, + "pending": { + "$ref": "#/components/examples/v1.GenericReservationResponsePayloadPendingExample" + }, + "success": { + "$ref": "#/components/examples/v1.GenericReservationResponsePayloadSuccessExample" + } + }, + "schema": { + "$ref": "#/components/schemas/v1.GenericReservationResponsePayload" + } + } + }, + "description": "Returns generic reservation information like status or creation time." + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "500": { + "$ref": "#/components/responses/InternalError" + } + }, + "tags": [ + "Reservation" + ] + } + }, + "/sources": { + "get": { + "description": "Cloud credentials are kept in the sources application. This endpoint lists available sources for the particular account per individual type (AWS, Azure, ...). All the fields in the response are optional and can be omitted if Sources application also omits them.\n", + "operationId": "getSourceList", + "parameters": [ + { + "in": "query", + "name": "provider", + "schema": { + "enum": [ + "aws", + "azure", + "gcp" + ], + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "examples": { + "example": { + "$ref": "#/components/examples/v1.SourceListResponseExample" + } + }, + "schema": { + "items": { + "$ref": "#/components/schemas/v1.SourceResponse" + }, + "type": "array" + } + } + }, + "description": "Returned on success." + }, + "500": { + "$ref": "#/components/responses/InternalError" + } + }, + "tags": [ + "Source" + ] + } + }, + "/sources/{ID}/account_identity": { + "get": { + "deprecated": true, + "description": "This endpoint is deprecated. Please use upload_info instead", + "operationId": "getSourceAccountIdentity", + "parameters": [ + { + "description": "Source ID from Sources Database", + "in": "path", + "name": "ID", + "required": true, + "schema": { + "format": "int64", + "type": "integer" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/v1.AccountIDTypeResponse" + } + } + }, + "description": "Return on success." + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "500": { + "$ref": "#/components/responses/InternalError" + } + }, + "tags": [ + "Source" + ] + } + }, + "/sources/{ID}/instance_types": { + "get": { + "deprecated": true, + "description": "Deprecated endpoint, use /instance_types instead.", + "operationId": "getInstanceTypeList", + "parameters": [ + { + "description": "Source ID from Sources Database", + "in": "path", + "name": "ID", + "required": true, + "schema": { + "format": "int64", + "type": "integer" + } + }, + { + "description": "Hyperscaler region", + "in": "query", + "name": "region", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "items": { + "$ref": "#/components/schemas/v1.InstanceTypeResponse" + }, + "type": "array" + } + } + }, + "description": "Return on success." + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "500": { + "$ref": "#/components/responses/InternalError" + } + }, + "tags": [ + "Source" + ] + } + }, + "/sources/{ID}/launch_templates": { + "get": { + "description": "Return a list of launch templates.\nA launch template is a configuration set with a name that is available through hyperscaler API. When creating reservations, launch template can be provided in order to set additional configuration for instances.\nCurrently only AWS Launch Templates are supported.\n", + "operationId": "getLaunchTemplatesList", + "parameters": [ + { + "description": "Source ID from Sources Database", + "in": "path", + "name": "ID", + "required": true, + "schema": { + "format": "int64", + "type": "integer" + } + }, + { + "description": "Hyperscaler region", + "in": "query", + "name": "region", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "examples": { + "example": { + "$ref": "#/components/examples/v1.LaunchTemplateListResponse" + } + }, + "schema": { + "items": { + "$ref": "#/components/schemas/v1.LaunchTemplatesResponse" + }, + "type": "array" + } + } + }, + "description": "Return on success." + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "500": { + "$ref": "#/components/responses/InternalError" + } + }, + "tags": [ + "Source" + ] + } + }, + "/sources/{ID}/upload_info": { + "get": { + "description": "Provides all necessary information to upload an image for given Source. Typically, this is account number, subscription ID but some hyperscaler types also provide additional data.\nThe response contains \"provider\" field which can be one of aws, azure or gcp and then exactly one field named \"aws\", \"azure\" or \"gcp\". Enum is not used due to limitation of the language (Go).\nSome types may perform more than one calls (e.g. Azure) so latency might be increased. Caching of static information is performed to improve latency of consequent calls.\n", + "operationId": "getSourceUploadInfo", + "parameters": [ + { + "description": "Source ID from Sources Database", + "in": "path", + "name": "ID", + "required": true, + "schema": { + "format": "int64", + "type": "integer" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "examples": { + "aws": { + "$ref": "#/components/examples/v1.SourceUploadInfoAWSResponse" + }, + "azure": { + "$ref": "#/components/examples/v1.SourceUploadInfoAzureResponse" + } + }, + "schema": { + "$ref": "#/components/schemas/v1.SourceUploadInfoResponse" + } + } + }, + "description": "Return on success." + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "500": { + "$ref": "#/components/responses/InternalError" + } + }, + "tags": [ + "Source" + ] + } + } + }, + "servers": [ + { + "description": "Local development", + "url": "http://0.0.0.0:{port}/api/{applicationName}", + "variables": { + "applicationName": { + "default": "provisioning" + }, + "port": { + "default": "8000" + } + } + } + ], + "tags": [ + { + "description": "Public SSH keys operations", + "name": "Pubkey" + } + ] +} \ No newline at end of file diff --git a/package.json b/package.json index 8426744d..3ec1f2b4 100644 --- a/package.json +++ b/package.json @@ -112,7 +112,7 @@ "test": "TZ=UTC jest --verbose --no-cache", "test:single": "jest --verbose -w 1", "build": "webpack --config config/prod.webpack.config.js", - "api": "npx @rtk-query/codegen-openapi ./api/config/image-builder.ts & npx @rtk-query/codegen-openapi ./api/config/rhsm.ts & npx @rtk-query/codegen-openapi ./api/config/contentSources.ts", + "api": "npx @rtk-query/codegen-openapi ./api/config/image-builder.ts & npx @rtk-query/codegen-openapi ./api/config/rhsm.ts & npx @rtk-query/codegen-openapi ./api/config/contentSources.ts & npx @rtk-query/codegen-openapi ./api/config/provisioning.ts", "verify": "npm-run-all build lint test" }, "insights": { diff --git a/src/Components/CreateImageWizard/formComponents/AWSAccountId.tsx b/src/Components/CreateImageWizard/formComponents/AWSAccountId.tsx deleted file mode 100644 index 9df729c7..00000000 --- a/src/Components/CreateImageWizard/formComponents/AWSAccountId.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import {useGetSourceDetailQuery} from "../../../store/apiSlice"; - -type AWSAccountIdProps = { - sourceId: string -} - -export const AWSAccountId = ({ sourceId }: AWSAccountIdProps) => { - const { data } = useGetSourceDetailQuery(sourceId); - return <>{data?.aws?.account_id}; -}; diff --git a/src/Components/CreateImageWizard/formComponents/AWSSourcesSelect.js b/src/Components/CreateImageWizard/formComponents/AWSSourcesSelect.js index d297e75c..5810ed88 100644 --- a/src/Components/CreateImageWizard/formComponents/AWSSourcesSelect.js +++ b/src/Components/CreateImageWizard/formComponents/AWSSourcesSelect.js @@ -14,9 +14,9 @@ import { import PropTypes from 'prop-types'; import { - useGetSourceDetailQuery, - useGetSourcesQuery, -} from '../../../store/apiSlice'; + useGetSourceListQuery, + useGetSourceUploadInfoQuery, +} from '../../../store/provisioningApi'; export const AWSSourcesSelect = ({ label, @@ -37,16 +37,19 @@ export const AWSSourcesSelect = ({ isSuccess, isError, refetch, - } = useGetSourcesQuery('aws'); + } = useGetSourceListQuery({ provider: 'aws' }); const { data: sourceDetails, isFetching: isFetchingDetails, isSuccess: isSuccessDetails, isError: isErrorDetails, - } = useGetSourceDetailQuery(selectedSourceId, { - skip: !selectedSourceId, - }); + } = useGetSourceUploadInfoQuery( + { id: selectedSourceId }, + { + skip: !selectedSourceId, + } + ); useEffect(() => { if (isFetchingDetails || !isSuccessDetails) return; diff --git a/src/Components/CreateImageWizard/formComponents/AwsAccountId.tsx b/src/Components/CreateImageWizard/formComponents/AwsAccountId.tsx new file mode 100644 index 00000000..da34c01f --- /dev/null +++ b/src/Components/CreateImageWizard/formComponents/AwsAccountId.tsx @@ -0,0 +1,12 @@ +import React from 'react'; + +import {useGetSourceUploadInfoQuery} from "../../../store/provisioningApi"; + +type AwsAccountIdProps = { + sourceId: number +} + +export const AwsAccountId = ({ sourceId }: AwsAccountIdProps) => { + const { data } = useGetSourceUploadInfoQuery({ id: sourceId }); + return <>{data?.aws?.account_id}; +}; diff --git a/src/Components/CreateImageWizard/formComponents/AzureResourceGroups.js b/src/Components/CreateImageWizard/formComponents/AzureResourceGroups.js index 86c941d4..f96667a1 100644 --- a/src/Components/CreateImageWizard/formComponents/AzureResourceGroups.js +++ b/src/Components/CreateImageWizard/formComponents/AzureResourceGroups.js @@ -12,7 +12,7 @@ import { } from '@patternfly/react-core'; import PropTypes from 'prop-types'; -import { useGetSourceDetailQuery } from '../../../store/apiSlice'; +import { useGetSourceUploadInfoQuery } from '../../../store/provisioningApi'; const AzureResourceGroups = ({ label, isRequired, className, ...props }) => { const { change, getState } = useFormApi(); @@ -25,8 +25,8 @@ const AzureResourceGroups = ({ label, isRequired, className, ...props }) => { setSourceId(values['azure-sources-select']); }; - const { data: sourceDetails, isFetching } = useGetSourceDetailQuery( - sourceId, + const { data: sourceDetails, isFetching } = useGetSourceUploadInfoQuery( + { id: sourceId }, { skip: !sourceId, } diff --git a/src/Components/CreateImageWizard/formComponents/AzureSourcesSelect.js b/src/Components/CreateImageWizard/formComponents/AzureSourcesSelect.js index 9b516db6..7a83ddc7 100644 --- a/src/Components/CreateImageWizard/formComponents/AzureSourcesSelect.js +++ b/src/Components/CreateImageWizard/formComponents/AzureSourcesSelect.js @@ -14,9 +14,9 @@ import { import PropTypes from 'prop-types'; import { - useGetSourcesQuery, - useGetSourceDetailQuery, -} from '../../../store/apiSlice'; + useGetSourceListQuery, + useGetSourceUploadInfoQuery, +} from '../../../store/provisioningApi'; const AzureSourcesSelect = ({ label, isRequired, className, ...props }) => { const { change } = useFormApi(); @@ -30,16 +30,19 @@ const AzureSourcesSelect = ({ label, isRequired, className, ...props }) => { isSuccess, isError, refetch, - } = useGetSourcesQuery('azure'); + } = useGetSourceListQuery({ provider: 'azure' }); const { data: sourceDetails, isFetching: isFetchingDetails, isSuccess: isSuccessDetails, isError: isErrorDetails, - } = useGetSourceDetailQuery(selectedSourceId, { - skip: !selectedSourceId, - }); + } = useGetSourceUploadInfoQuery( + { id: selectedSourceId }, + { + skip: !selectedSourceId, + } + ); useEffect(() => { if (isFetchingDetails || !isSuccessDetails) return; diff --git a/src/Components/CreateImageWizard/formComponents/ReviewStepTextLists.js b/src/Components/CreateImageWizard/formComponents/ReviewStepTextLists.js index 67c3c79a..604c5d52 100644 --- a/src/Components/CreateImageWizard/formComponents/ReviewStepTextLists.js +++ b/src/Components/CreateImageWizard/formComponents/ReviewStepTextLists.js @@ -18,7 +18,7 @@ import { ExclamationTriangleIcon, HelpIcon } from '@patternfly/react-icons'; import PropTypes from 'prop-types'; import ActivationKeyInformation from './ActivationKeyInformation'; -import { AWSAccountId } from './AWSAccountId'; +import { AwsAccountId } from './AwsAccountId'; import { FSReviewTable, PackagesTable, @@ -26,7 +26,7 @@ import { } from './ReviewStepTables'; import { RELEASES, UNIT_GIB } from '../../../constants'; -import { useGetSourcesQuery } from '../../../store/apiSlice'; +import { useGetSourceListQuery } from '../../../store/provisioningApi'; import { useShowActivationKeyQuery } from '../../../store/rhsmApi'; import { useGetEnvironment } from '../../../Utilities/useGetEnvironment'; import { googleAccType } from '../steps/googleCloud'; @@ -64,7 +64,9 @@ export const ImageOutputList = () => { }; export const TargetEnvAWSList = () => { - const { data: awsSources, isSuccess } = useGetSourcesQuery(); + const { data: awsSources, isSuccess } = useGetSourceListQuery({ + provider: 'aws', + }); const { isBeta } = useGetEnvironment(); const { getState } = useFormApi(); @@ -92,7 +94,7 @@ export const TargetEnvAWSList = () => { getState()?.values?.['aws-target-type'] === 'aws-target-type-source' && isSuccess && ( - )} @@ -169,7 +171,7 @@ export const TargetEnvGCPList = () => { export const TargetEnvAzureList = () => { const { getState } = useFormApi(); const { data: azureSources, isSuccess: isSuccessAzureSources } = - useGetSourcesQuery('azure'); + useGetSourceListQuery({ provider: 'azure' }); return ( Microsoft Azure diff --git a/src/Components/CreateImageWizard/formComponents/TargetEnvironment.js b/src/Components/CreateImageWizard/formComponents/TargetEnvironment.js index aa35e397..60bb995c 100644 --- a/src/Components/CreateImageWizard/formComponents/TargetEnvironment.js +++ b/src/Components/CreateImageWizard/formComponents/TargetEnvironment.js @@ -15,7 +15,7 @@ import { import { HelpIcon } from '@patternfly/react-icons'; import PropTypes from 'prop-types'; -import { usePrefetch } from '../../../store/apiSlice'; +import { provisioningApi } from '../../../store/provisioningApi'; const TargetEnvironment = ({ label, isRequired, ...props }) => { const { getState, change } = useFormApi(); @@ -29,7 +29,7 @@ const TargetEnvironment = ({ label, isRequired, ...props }) => { 'guest-image': false, 'image-installer': false, }); - const prefetchSources = usePrefetch('getSources'); + const prefetchSources = provisioningApi.usePrefetch('getSourceList'); useEffect(() => { if (getState()?.values?.[input.name]) { @@ -76,7 +76,7 @@ const TargetEnvironment = ({ label, isRequired, ...props }) => { } onClick={() => handleSetEnvironment('aws', !environment.aws)} onKeyDown={(e) => handleKeyDown(e, 'aws', !environment.aws)} - onMouseEnter={() => prefetchSources('aws')} + onMouseEnter={() => prefetchSources({ provider: 'aws' })} isSelected={environment.aws} isStacked isDisplayLarge @@ -96,6 +96,7 @@ const TargetEnvironment = ({ label, isRequired, ...props }) => { onClick={() => handleSetEnvironment('gcp', !environment.gcp)} isSelected={environment.gcp} onKeyDown={(e) => handleKeyDown(e, 'gcp', !environment.gcp)} + onMouseEnter={() => prefetchSources({ provider: 'gcp' })} isStacked isDisplayLarge /> @@ -113,7 +114,7 @@ const TargetEnvironment = ({ label, isRequired, ...props }) => { } onClick={() => handleSetEnvironment('azure', !environment.azure)} onKeyDown={(e) => handleKeyDown(e, 'azure', !environment.azure)} - onMouseEnter={() => prefetchSources('azure')} + onMouseEnter={() => prefetchSources({ provider: 'azure' })} isSelected={environment.azure} isStacked isDisplayLarge diff --git a/src/Components/ImagesTable/ImageDetails.js b/src/Components/ImagesTable/ImageDetails.js index 025e4e59..ca99c234 100644 --- a/src/Components/ImagesTable/ImageDetails.js +++ b/src/Components/ImagesTable/ImageDetails.js @@ -17,7 +17,7 @@ import { useSelector } from 'react-redux'; import ClonesTable from './ClonesTable'; -import { useGetSourcesQuery } from '../../store/apiSlice'; +import { useGetSourceListQuery } from '../../store/provisioningApi'; const sourceNotFoundPopover = () => { return ( @@ -59,7 +59,9 @@ const sourceNotFoundPopover = () => { }; const getAzureSourceName = (id) => { - const { data: sources, isSuccess } = useGetSourcesQuery('azure'); + const { data: sources, isSuccess } = useGetSourceListQuery({ + provider: 'azure', + }); if (isSuccess) { const sourcename = sources.find((source) => source.id === id); @@ -74,7 +76,9 @@ const getAzureSourceName = (id) => { }; const getAWSSourceName = (id) => { - const { data: sources, isSuccess } = useGetSourcesQuery('aws'); + const { data: sources, isSuccess } = useGetSourceListQuery({ + provider: 'aws', + }); if (isSuccess) { const sourcename = sources.find((source) => source.id === id); diff --git a/src/constants.js b/src/constants.js index 587d2a88..1f1380f7 100644 --- a/src/constants.js +++ b/src/constants.js @@ -1,7 +1,7 @@ export const IMAGE_BUILDER_API = '/api/image-builder/v1'; export const RHSM_API = '/api/rhsm/v2'; export const CONTENT_SOURCES_API = '/api/content-sources/v1'; -export const PROVISIONING_SOURCES_ENDPOINT = '/api/provisioning/v1'; +export const PROVISIONING_API = '/api/provisioning/v1'; export const RHEL_8 = 'rhel-88'; export const RHEL_9 = 'rhel-92'; export const CENTOS_8 = 'centos-8'; diff --git a/src/store/apiSlice.ts b/src/store/apiSlice.ts deleted file mode 100644 index 51218430..00000000 --- a/src/store/apiSlice.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'; - -import { SourceResponse, SourceUploadInfoResponse } from '../../types'; - -enum Provider { - 'azure', - 'aws' -} - -import { - PROVISIONING_SOURCES_ENDPOINT, -} from '../constants'; - -export const apiSlice = createApi({ - reducerPath: 'api', - baseQuery: fetchBaseQuery({ baseUrl: '' }), - endpoints: (builder) => ({ - getSources: builder.query({ - query: (provider) => `${PROVISIONING_SOURCES_ENDPOINT}/sources?provider=${provider}`, - }), - getSourceDetail: builder.query({ - query: (sourceId) => - `${PROVISIONING_SOURCES_ENDPOINT}/sources/${sourceId}/upload_info` - }), - }), -}); - -export const { - useGetSourcesQuery, - useGetSourceDetailQuery, - usePrefetch, -} = apiSlice; diff --git a/src/store/emptyProvisioningApi.ts b/src/store/emptyProvisioningApi.ts new file mode 100644 index 00000000..25c2cd0c --- /dev/null +++ b/src/store/emptyProvisioningApi.ts @@ -0,0 +1,9 @@ +import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react' +import {PROVISIONING_API} from '../constants' + +// initialize an empty api service that we'll inject endpoints into later as needed +export const emptyProvisioningApi = createApi({ + reducerPath: 'provisioningApi', + baseQuery: fetchBaseQuery({ baseUrl: PROVISIONING_API }), + endpoints: () => ({}), +}) diff --git a/src/store/index.js b/src/store/index.js index 87780e92..4be7ac46 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -2,29 +2,29 @@ import { notificationsReducer } from '@redhat-cloud-services/frontend-components import { configureStore } from '@reduxjs/toolkit'; import promiseMiddleware from 'redux-promise-middleware'; -import { apiSlice } from './apiSlice'; import clonesSlice from './clonesSlice'; import composesSlice from './composesSlice'; import { contentSourcesApi } from './contentSourcesApi'; import { imageBuilderApi } from './imageBuilderApi'; +import { provisioningApi } from './provisioningApi'; import { rhsmApi } from './rhsmApi'; export const reducer = { - [apiSlice.reducerPath]: apiSlice.reducer, clones: clonesSlice, composes: composesSlice, [contentSourcesApi.reducerPath]: contentSourcesApi.reducer, [imageBuilderApi.reducerPath]: imageBuilderApi.reducer, [rhsmApi.reducerPath]: rhsmApi.reducer, + [provisioningApi.reducerPath]: provisioningApi.reducer, notifications: notificationsReducer, }; export const middleware = (getDefaultMiddleware) => getDefaultMiddleware() .concat(promiseMiddleware) - .concat(apiSlice.middleware) .concat(contentSourcesApi.middleware) .concat(imageBuilderApi.middleware) - .concat(rhsmApi.middleware); + .concat(rhsmApi.middleware) + .concat(provisioningApi.middleware); export const store = configureStore({ reducer, middleware }); diff --git a/src/store/provisioningApi.ts b/src/store/provisioningApi.ts new file mode 100644 index 00000000..77f61524 --- /dev/null +++ b/src/store/provisioningApi.ts @@ -0,0 +1,59 @@ +import { emptyProvisioningApi as api } from "./emptyProvisioningApi"; +const injectedRtkApi = api.injectEndpoints({ + endpoints: (build) => ({ + getSourceList: build.query({ + query: (queryArg) => ({ + url: `/sources`, + params: { provider: queryArg.provider }, + }), + }), + getSourceUploadInfo: build.query< + GetSourceUploadInfoApiResponse, + GetSourceUploadInfoApiArg + >({ + query: (queryArg) => ({ url: `/sources/${queryArg.id}/upload_info` }), + }), + }), + overrideExisting: false, +}); +export { injectedRtkApi as provisioningApi }; +export type GetSourceListApiResponse = + /** status 200 Returned on success. */ V1SourceResponse[]; +export type GetSourceListApiArg = { + provider?: "aws" | "azure" | "gcp"; +}; +export type GetSourceUploadInfoApiResponse = + /** status 200 Return on success. */ V1SourceUploadInfoResponse; +export type GetSourceUploadInfoApiArg = { + /** Source ID from Sources Database */ + id: number; +}; +export type V1SourceResponse = { + id?: string; + name?: string; + source_type_id?: string; + uid?: string; +}; +export type V1ResponseError = { + build_time?: string; + edge_id?: string; + environment?: string; + error?: string; + msg?: string; + trace_id?: string; + version?: string; +}; +export type V1SourceUploadInfoResponse = { + aws?: { + account_id?: string; + } | null; + azure?: { + resource_groups?: string[]; + subscription_id?: string; + tenant_id?: string; + } | null; + gcp?: any | null; + provider?: string; +}; +export const { useGetSourceListQuery, useGetSourceUploadInfoQuery } = + injectedRtkApi; diff --git a/src/test/Components/CreateImageWizard/CreateImageWizard.azure.beta.test.js b/src/test/Components/CreateImageWizard/CreateImageWizard.azure.beta.test.js index e8cd2c22..9496c856 100644 --- a/src/test/Components/CreateImageWizard/CreateImageWizard.azure.beta.test.js +++ b/src/test/Components/CreateImageWizard/CreateImageWizard.azure.beta.test.js @@ -4,7 +4,7 @@ import { screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { rest } from 'msw'; -import { PROVISIONING_SOURCES_ENDPOINT } from '../../../constants.js'; +import { PROVISIONING_API } from '../../../constants.js'; import { server } from '../../mocks/server.js'; import { renderWithReduxRouter } from '../../testUtils'; @@ -161,7 +161,7 @@ describe('Step Upload to Azure', () => { test('component renders error state correctly', async () => { setUp(); server.use( - rest.get(`${PROVISIONING_SOURCES_ENDPOINT}/sources`, (req, res, ctx) => + rest.get(`${PROVISIONING_API}/sources`, (req, res, ctx) => res(ctx.status(500)) ) ); diff --git a/src/test/Components/CreateImageWizard/CreateImageWizard.beta.test.js b/src/test/Components/CreateImageWizard/CreateImageWizard.beta.test.js index 97cc8102..5d81a846 100644 --- a/src/test/Components/CreateImageWizard/CreateImageWizard.beta.test.js +++ b/src/test/Components/CreateImageWizard/CreateImageWizard.beta.test.js @@ -11,11 +11,7 @@ import userEvent from '@testing-library/user-event'; import { rest } from 'msw'; import api from '../../../api.js'; -import { - RHEL_8, - RHEL_9, - PROVISIONING_SOURCES_ENDPOINT, -} from '../../../constants.js'; +import { RHEL_8, RHEL_9, PROVISIONING_API } from '../../../constants.js'; import { mockComposesEmpty } from '../../fixtures/composes.js'; import { customizations, ids } from '../../fixtures/customizations.js'; import { mockPkgResultAlphaContentSources } from '../../fixtures/packages.js'; @@ -127,7 +123,7 @@ describe('Step Upload to AWS', () => { test('component renders error state correctly', async () => { setUp(); server.use( - rest.get(`${PROVISIONING_SOURCES_ENDPOINT}/sources`, (req, res, ctx) => + rest.get(`${PROVISIONING_API}/sources`, (req, res, ctx) => res(ctx.status(500)) ) ); diff --git a/src/test/mocks/handlers.js b/src/test/mocks/handlers.js index 2ed524bc..56873c6a 100644 --- a/src/test/mocks/handlers.js +++ b/src/test/mocks/handlers.js @@ -3,7 +3,7 @@ import { rest } from 'msw'; import { CONTENT_SOURCES_API, IMAGE_BUILDER_API, - PROVISIONING_SOURCES_ENDPOINT, + PROVISIONING_API, RHSM_API, } from '../../constants'; import { @@ -25,12 +25,12 @@ import { mockRepositoryResults } from '../fixtures/repositories'; import { mockSourcesByProvider, mockUploadInfo } from '../fixtures/sources'; export const handlers = [ - rest.get(`${PROVISIONING_SOURCES_ENDPOINT}/sources`, (req, res, ctx) => { + rest.get(`${PROVISIONING_API}/sources`, (req, res, ctx) => { const provider = req.url.searchParams.get('provider'); return res(ctx.status(200), ctx.json(mockSourcesByProvider(provider))); }), rest.get( - `${PROVISIONING_SOURCES_ENDPOINT}/sources/:sourceId/upload_info`, + `${PROVISIONING_API}/sources/:sourceId/upload_info`, (req, res, ctx) => { const { sourceId } = req.params; if (sourceId === '666' || sourceId === '667' || sourceId === '123') { diff --git a/tsconfig.json b/tsconfig.json index 6196ad4b..8b5eee31 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -7,6 +7,7 @@ "jsx": "react-jsx", "allowJs": true, "moduleResolution": "node", - "strictNullChecks": true + "strictNullChecks": true, + "allowSyntheticDefaultImports": true } }