From 1894a9dcb2d98ff94332ea553dede1505d065b34 Mon Sep 17 00:00:00 2001 From: lucasgarfield Date: Tue, 25 Jul 2023 19:35:24 +0200 Subject: [PATCH] API: Add programatically generated Image Builder API slice definitions These definitions were generated using the RTKQ code generation tool and will replace the existing definitions. --- api/config/image-builder.ts | 13 + api/schema/image-builder.json | 1 + package.json | 1 + .../CreateImageWizard/CreateImageWizard.js | 6 +- .../formComponents/FileSystemConfigButtons.js | 6 +- .../formComponents/Packages.js | 4 +- src/store/apiSlice.ts | 23 +- src/store/emptyImageBuilderApi.ts | 9 + src/store/imageBuilderApi.ts | 290 ++++++++++++++++++ src/store/index.js | 7 +- 10 files changed, 329 insertions(+), 31 deletions(-) create mode 100644 api/config/image-builder.ts create mode 100644 api/schema/image-builder.json create mode 100644 src/store/emptyImageBuilderApi.ts create mode 100644 src/store/imageBuilderApi.ts diff --git a/api/config/image-builder.ts b/api/config/image-builder.ts new file mode 100644 index 00000000..dd143508 --- /dev/null +++ b/api/config/image-builder.ts @@ -0,0 +1,13 @@ +import type { ConfigFile } from '@rtk-query/codegen-openapi' + +const config: ConfigFile = { + schemaFile: '../schema/image-builder.json', + apiFile: '../../src/store/emptyImageBuilderApi.ts', + apiImport: 'emptyImageBuilderApi', + outputFile: '../../src/store/imageBuilderApi.ts', + exportName: 'imageBuilderApi', + hooks: true, + filterEndpoints: ['getComposes', 'getComposeStatus', 'getComposeClones', 'getCloneStatus', 'getArchitectures'], +} + +export default config diff --git a/api/schema/image-builder.json b/api/schema/image-builder.json new file mode 100644 index 00000000..8fae6283 --- /dev/null +++ b/api/schema/image-builder.json @@ -0,0 +1 @@ +{"components":{"schemas":{"AWSEC2Clone":{"properties":{"region":{"description":"A region as described in\nhttps://docs.aws.amazon.com/AWSEC2/latest/UserGuide/using-regions-availability-zones.html#concepts-regions\n","type":"string"},"share_with_accounts":{"description":"An array of AWS account IDs as described in\nhttps://docs.aws.amazon.com/IAM/latest/UserGuide/console_account-alias.html\n","example":["123456789012"],"items":{"pattern":"^[0-9]{12}$","type":"string"},"maxItems":100,"type":"array"},"share_with_sources":{"example":["12345"],"items":{"type":"string"},"type":"array","uniqueItems":true}},"required":["region"],"type":"object"},"AWSS3UploadRequestOptions":{"type":"object"},"AWSS3UploadStatus":{"properties":{"url":{"type":"string"}},"required":["url"],"type":"object"},"AWSUploadRequestOptions":{"properties":{"share_with_accounts":{"example":["123456789012"],"items":{"type":"string"},"type":"array","uniqueItems":true},"share_with_sources":{"example":["12345"],"items":{"type":"string"},"type":"array","uniqueItems":true}},"type":"object"},"AWSUploadStatus":{"properties":{"ami":{"example":"ami-0c830793775595d4b","type":"string"},"region":{"example":"eu-west-1","type":"string"}},"required":["ami","region"],"type":"object"},"ArchitectureItem":{"properties":{"arch":{"example":"x86_64","type":"string"},"image_types":{"items":{"example":"qcow2","type":"string"},"type":"array"},"repositories":{"description":"Base repositories for the given distribution and architecture.","items":{"$ref":"#/components/schemas/Repository"},"type":"array"}},"required":["arch","image_types","repositories"],"type":"object"},"Architectures":{"items":{"$ref":"#/components/schemas/ArchitectureItem"},"type":"array"},"AzureUploadRequestOptions":{"properties":{"image_name":{"description":"Name of the created image.\nMust begin with a letter or number, end with a letter, number or underscore, and may contain only letters, numbers, underscores, periods, or hyphens.\nThe total length is limited to 60 characters.\n","example":"LinuxImage","maxLength":60,"minLength":1,"pattern":"(^[a-zA-Z0-9]$)|(^[a-zA-Z0-9][a-zA-Z0-9_\\.-]*[a-zA-Z0-9_]$)","type":"string"},"resource_group":{"description":"Name of the resource group where the image should be uploaded.\n","example":"ToucanResourceGroup","type":"string"},"source_id":{"description":"ID of the source that will be used to resolve the tenant and subscription IDs.\nDo not provide a tenant_id or subscription_id when providing a source_id.\n","example":"12345","type":"string"},"subscription_id":{"description":"ID of subscription where the image should be uploaded.\nWhen providing a subscription_id, also be sure to provide a tenant_id and do not include a source_id.\n","example":"4e5d8b2c-ab24-4413-90c5-612306e809e2","type":"string"},"tenant_id":{"description":"ID of the tenant where the image should be uploaded. This link explains how\nto find it in the Azure Portal:\nhttps://docs.microsoft.com/en-us/azure/active-directory/fundamentals/active-directory-how-to-find-tenant\nWhen providing a tenant_id, also be sure to provide a subscription_id and do not include a source_id.\n","example":"5c7ef5b6-1c3f-4da0-a622-0b060239d7d7","type":"string"}},"required":["resource_group"],"type":"object"},"AzureUploadStatus":{"properties":{"image_name":{"example":"my-image","type":"string"}},"required":["image_name"],"type":"object"},"CloneRequest":{"oneOf":[{"$ref":"#/components/schemas/AWSEC2Clone"}]},"CloneResponse":{"properties":{"id":{"example":"123e4567-e89b-12d3-a456-426655440000","format":"uuid","type":"string"}},"required":["id"]},"ClonesResponse":{"properties":{"data":{"items":{"$ref":"#/components/schemas/ClonesResponseItem"},"type":"array"},"links":{"properties":{"first":{"example":"/api/image-builder/v1/composes?limit=10\u0026offset=0","type":"string"},"last":{"example":"/api/image-builder/v1/composes?limit=10\u0026offset=10","type":"string"}},"required":["first","last"],"type":"object"},"meta":{"properties":{"count":{"type":"integer"}},"required":["count"],"type":"object"}},"required":["meta","links","data"]},"ClonesResponseItem":{"properties":{"created_at":{"type":"string"},"id":{"format":"uuid","type":"string"},"request":{}},"required":["id","request","created_at"]},"ComposeMetadata":{"properties":{"ostree_commit":{"description":"ID (hash) of the built commit","type":"string"},"packages":{"description":"Package list including NEVRA","items":{"$ref":"#/components/schemas/PackageMetadata"},"type":"array"}},"type":"object"},"ComposeRequest":{"additionalProperties":false,"properties":{"customizations":{"$ref":"#/components/schemas/Customizations"},"distribution":{"$ref":"#/components/schemas/Distributions"},"image_name":{"example":"MyImageName","maxLength":100,"type":"string"},"image_requests":{"description":"Array of exactly one image request. Having more image requests in one compose is currently not supported.\n","items":{"$ref":"#/components/schemas/ImageRequest"},"maxItems":1,"minItems":1,"type":"array","uniqueItems":true}},"required":["distribution","image_requests"],"type":"object"},"ComposeResponse":{"properties":{"id":{"format":"uuid","type":"string"}},"required":["id"]},"ComposeStatus":{"properties":{"image_status":{"$ref":"#/components/schemas/ImageStatus"},"request":{"$ref":"#/components/schemas/ComposeRequest"}},"required":["image_status","request"]},"ComposeStatusError":{"properties":{"details":{},"id":{"type":"integer"},"reason":{"type":"string"}},"required":["id","reason"]},"ComposesResponse":{"properties":{"data":{"items":{"$ref":"#/components/schemas/ComposesResponseItem"},"type":"array"},"links":{"properties":{"first":{"example":"/api/image-builder/v1/composes?limit=10\u0026offset=0","type":"string"},"last":{"example":"/api/image-builder/v1/composes?limit=10\u0026offset=10","type":"string"}},"required":["first","last"],"type":"object"},"meta":{"properties":{"count":{"type":"integer"}},"required":["count"],"type":"object"}},"required":["meta","links","data"]},"ComposesResponseItem":{"properties":{"created_at":{"type":"string"},"id":{"format":"uuid","type":"string"},"image_name":{"type":"string"},"request":{}},"required":["id","request","created_at"]},"CustomRepository":{"description":"Repository configuration for custom repositories.\nAt least one of the 'baseurl', 'mirrorlist', 'metalink' properties must\nbe specified. If more of them are specified, the order of precedence is\nthe same as listed above. Id is required.\n","properties":{"baseurl":{"example":["https://cdn.redhat.com/content/dist/rhel8/8/x86_64/baseos/os/"],"items":{"format":"uri","type":"string"},"type":"array"},"check_gpg":{"type":"boolean"},"check_repo_gpg":{"type":"boolean"},"enabled":{"type":"boolean"},"filename":{"type":"string"},"gpgkey":{"description":"GPG key used to sign packages in this repository. Can be a gpg key or a URL","example":["-----BEGIN PGP PUBLIC KEY BLOCK-----\n\nmQINBGAcScoBEADLf8YHkezJ6adlMYw7aGGIlJalt8Jj2x/B2K+hIfIuxGtpVj7e\nLRgDU76jaT5pVD5mFMJ3pkeneR/cTmqqQkNyQshX2oQXwEzUSb1CNMCfCGgkX8Q2\nzZkrIcCrF0Q2wrKblaudhU+iVanADsm18YEqsb5AU37dtUrM3QYdWg9R+XiPfV8R\nKBjT03vVBOdMSsY39LaCn6Ip1Ovp8IEo/IeEVY1qmCOPAaK0bJH3ufg4Cueks+TS\nwQWTeCLxuZL6OMXoOPKwvMQfxbg1XD8vuZ0Ktj/cNH2xau0xmsAu9HJpekvOPRxl\nyqtjyZfroVieFypwZgvQwtnnM8/gSEu/JVTrY052mEUT7Ccb74kcHFTFfMklnkG/\n0fU4ARa504H3xj0ktbe3vKcPXoPOuKBVsHSv00UGYAyPeuy+87cU/YEhM7k3SVKj\n6eIZgyiMO0wl1YGDRKculwks9A+ulkg1oTb4s3zmZvP07GoTxW42jaK5WS+NhZee\n860XoVhbc1KpS+jfZojsrEtZ8PbUZ+YvF8RprdWArjHbJk2JpRKAxThxsQAsBhG1\n0Lux2WaMB0g2I5PcMdJ/cqjo08ccrjBXuixWri5iu9MXp8qT/fSzNmsdIgn8/qZK\ni8Qulfu77uqhW/wt2btnitgRsqjhxMujYU4Zb4hktF8hKU/XX742qhL5KwARAQAB\ntDFGZWRvcmEgKDM1KSA8ZmVkb3JhLTM1LXByaW1hcnlAZmVkb3JhcHJvamVjdC5v\ncmc+iQJOBBMBCAA4FiEEeH6mrhFH7uVsQLMM20Y5cZhnxY8FAmAcScoCGw8FCwkI\nBwIGFQoJCAsCBBYCAwECHgECF4AACgkQ20Y5cZhnxY+NYA/7BYpglySAZYHhjyKh\n/+f6zPfVvbH20Eq3kI7OFBN0nLX+BU1muvS+qTuS3WLrB3m3GultpKREJKLtm5ED\n1rGzXAoT1yp9YI8LADdMCCOyjAjsoWU87YUuC+/bnjrTeR2LROCfyPC76W985iOV\nm5S+bsQDw7C2LrldAM4MDuoyZ1SitGaZ4KQLVt+TEa14isYSGCjzo7PY8V3JOk50\ngqWg82N/bm2EzS7T83WEDb1lvj4IlvxgIqKeg11zXYxmrYSZJJCfvzf+lNS6uxgH\njx/J0ylZ2LibGr6GAAyO9UWrAZSwSM0EcjT8wECnxkSDuyqmWwVvNBXuEIV8Oe3Y\nMiU1fJN8sd7DpsFx5M+XdnMnQS+HrjTPKD3mWrlAdnEThdYV8jZkpWhDys3/99eO\nhk0rLny0jNwkauf/iU8Oc6XvMkjLRMJg5U9VKyJuWWtzwXnjMN5WRFBqK4sZomMM\nftbTH1+5ybRW/A3vBbaxRW2t7UzNjczekSZEiaLN9L/HcJCIR1QF8682DdAlEF9d\nk2gQiYSQAaaJ0JJAzHvRkRJLLgK2YQYiHNVy2t3JyFfsram5wSCWOfhPeIyLBTZJ\nvrpNlPbefsT957Tf2BNIugzZrC5VxDSKkZgRh1VGvSIQnCyzkQy6EU2qPpiW59G/\nhPIXZrKocK3KLS9/izJQTRltjMA=\n=PfT7\n-----END PGP PUBLIC KEY BLOCK-----\n"],"items":{"type":"string"},"type":"array"},"id":{"type":"string"},"metalink":{"example":"https://mirrors.fedoraproject.org/metalink?repo=fedora-32\u0026arch=x86_64","format":"uri","type":"string"},"mirrorlist":{"example":"http://mirrorlist.centos.org/?release=8-stream\u0026arch=aarch64\u0026repo=BaseOS","format":"uri","type":"string"},"name":{"type":"string"},"priority":{"type":"integer"},"ssl_verify":{"type":"boolean"}},"required":["id"],"type":"object"},"Customizations":{"properties":{"custom_repositories":{"items":{"$ref":"#/components/schemas/CustomRepository"},"type":"array"},"filesystem":{"items":{"$ref":"#/components/schemas/Filesystem"},"maxItems":128,"type":"array"},"openscap":{"$ref":"#/components/schemas/OpenSCAP"},"packages":{"example":["postgresql"],"items":{"type":"string"},"maxItems":10000,"type":"array"},"payload_repositories":{"items":{"$ref":"#/components/schemas/Repository"},"type":"array"},"subscription":{"$ref":"#/components/schemas/Subscription"},"users":{"description":"list of users that a customer can add, also specifying their respective groups and SSH keys","items":{"$ref":"#/components/schemas/User"},"type":"array"}},"type":"object"},"DistributionItem":{"properties":{"description":{"example":"Red Hat Enterprise Linux (RHEL) 8.4","type":"string"},"name":{"example":"rhel-84","type":"string"}},"required":["name","description"],"type":"object"},"Distributions":{"enum":["rhel-8","rhel-8-nightly","rhel-84","rhel-85","rhel-86","rhel-87","rhel-88","rhel-9","rhel-9-nightly","rhel-90","rhel-91","rhel-92","centos-8","centos-9","fedora-37","fedora-38","fedora-39"],"type":"string"},"DistributionsResponse":{"items":{"$ref":"#/components/schemas/DistributionItem"},"type":"array"},"Filesystem":{"properties":{"min_size":{"example":1024,"x-go-type":"uint64"},"mountpoint":{"example":"/var","type":"string"}},"required":["mountpoint","min_size"],"type":"object"},"GCPUploadRequestOptions":{"properties":{"share_with_accounts":{"description":"List of valid Google accounts to share the imported Compute Node image with.\nEach string must contain a specifier of the account type. Valid formats are:\n - 'user:{emailid}': An email address that represents a specific\n Google account. For example, 'alice@example.com'.\n - 'serviceAccount:{emailid}': An email address that represents a\n service account. For example, 'my-other-app@appspot.gserviceaccount.com'.\n - 'group:{emailid}': An email address that represents a Google group.\n For example, 'admins@example.com'.\n - 'domain:{domain}': The G Suite domain (primary) that represents all\n the users of that domain. For example, 'google.com' or 'example.com'.\n If not specified, the imported Compute Node image is not shared with any\n account.\n","example":["user:alice@example.com","serviceAccount:my-other-app@appspot.gserviceaccount.com","group:admins@example.com","domain:example.com"],"items":{"type":"string"},"type":"array","uniqueItems":true}},"required":["share_with_accounts"],"type":"object"},"GCPUploadStatus":{"properties":{"image_name":{"example":"my-image","type":"string"},"project_id":{"example":"ascendant-braid-303513","type":"string"}},"required":["project_id","image_name"],"type":"object"},"HTTPError":{"properties":{"detail":{"type":"string"},"title":{"type":"string"}},"required":["title","detail"]},"HTTPErrorList":{"properties":{"errors":{"items":{"$ref":"#/components/schemas/HTTPError"},"type":"array"}},"required":["errors"]},"ImageRequest":{"additionalProperties":false,"properties":{"architecture":{"description":"CPU architecture of the image, x86_64 and aarch64 are currently supported.\n","enum":["x86_64","aarch64"],"type":"string"},"image_type":{"$ref":"#/components/schemas/ImageTypes"},"ostree":{"$ref":"#/components/schemas/OSTree"},"upload_request":{"$ref":"#/components/schemas/UploadRequest"}},"required":["architecture","image_type","upload_request"],"type":"object"},"ImageStatus":{"properties":{"error":{"$ref":"#/components/schemas/ComposeStatusError"},"status":{"enum":["success","failure","pending","building","uploading","registering"],"example":"success","type":"string"},"upload_status":{"$ref":"#/components/schemas/UploadStatus"}},"required":["status"]},"ImageTypes":{"enum":["aws","azure","edge-commit","edge-installer","gcp","guest-image","image-installer","vsphere","vsphere-ova","ami","rhel-edge-commit","rhel-edge-installer","vhd"],"type":"string"},"OSTree":{"properties":{"contenturl":{"description":"A URL which, if set, is used for fetching content. Implies that `url` is set as well,\nwhich will be used for metadata only.\n","type":"string"},"parent":{"description":"Can be either a commit (example: 02604b2da6e954bd34b8b82a835e5a77d2b60ffa), or a branch-like reference (example: rhel/8/x86_64/edge)\n","example":"rhel/8/x86_64/edge","type":"string"},"ref":{"example":"rhel/8/x86_64/edge","type":"string"},"rhsm":{"description":"Determines whether a valid subscription manager (candlepin) identity is required to\naccess this repository. Consumer certificates will be used as client certificates when\nfetching metadata and content.\n","type":"boolean"},"url":{"type":"string"}},"type":"object"},"OpenSCAP":{"properties":{"profile_id":{"example":"xccdf_org.ssgproject.content_profile_cis","type":"string"}},"required":["profile_id"],"type":"object"},"Package":{"properties":{"name":{"type":"string"},"summary":{"type":"string"}},"required":["name","summary"]},"PackageMetadata":{"properties":{"arch":{"type":"string"},"epoch":{"type":"string"},"name":{"type":"string"},"release":{"type":"string"},"sigmd5":{"type":"string"},"signature":{"type":"string"},"type":{"type":"string"},"version":{"type":"string"}},"required":["type","name","version","release","arch","sigmd5"]},"PackagesResponse":{"properties":{"data":{"items":{"$ref":"#/components/schemas/Package"},"type":"array"},"links":{"properties":{"first":{"example":"/api/image-builder/v1/packages?limit=10\u0026offset=0\u0026distribution....","type":"string"},"last":{"example":"/api/image-builder/v1/packages?limit=10\u0026offset=10\u0026distribution....","type":"string"}},"required":["first","last"],"type":"object"},"meta":{"properties":{"count":{"type":"integer"}},"required":["count"],"type":"object"}},"required":["meta","links","data"],"type":"object"},"Readiness":{"properties":{"readiness":{"type":"string"}},"required":["readiness"],"type":"object"},"Repository":{"properties":{"baseurl":{"example":"https://cdn.redhat.com/content/dist/rhel8/8/x86_64/baseos/os/","format":"uri","type":"string"},"check_gpg":{"type":"boolean"},"check_repo_gpg":{"default":false,"description":"Enables gpg verification of the repository metadata\n","type":"boolean"},"gpgkey":{"type":"string"},"ignore_ssl":{"type":"boolean"},"metalink":{"example":"https://mirrors.fedoraproject.org/metalink?repo=fedora-32\u0026arch=x86_64","format":"uri","type":"string"},"mirrorlist":{"example":"http://mirrorlist.centos.org/?release=8-stream\u0026arch=aarch64\u0026repo=BaseOS","format":"uri","type":"string"},"rhsm":{"type":"boolean"}},"required":["rhsm"],"type":"object"},"Subscription":{"properties":{"activation-key":{"example":"my-secret-key","format":"password","type":"string"},"base-url":{"example":"http://cdn.redhat.com/","type":"string"},"insights":{"example":true,"type":"boolean"},"organization":{"example":2040324,"type":"integer"},"rhc":{"default":false,"description":"Optional flag to use rhc to register the system, which also always enables Insights.\n","example":true,"type":"boolean"},"server-url":{"example":"subscription.rhsm.redhat.com","type":"string"}},"required":["organization","activation-key","server-url","base-url","insights"],"type":"object"},"UploadRequest":{"properties":{"options":{"anyOf":[{"$ref":"#/components/schemas/AWSUploadRequestOptions"},{"$ref":"#/components/schemas/AWSS3UploadRequestOptions"},{"$ref":"#/components/schemas/GCPUploadRequestOptions"},{"$ref":"#/components/schemas/AzureUploadRequestOptions"}]},"type":{"$ref":"#/components/schemas/UploadTypes"}},"required":["type","options"],"type":"object"},"UploadStatus":{"properties":{"options":{"oneOf":[{"$ref":"#/components/schemas/AWSUploadStatus"},{"$ref":"#/components/schemas/AWSS3UploadStatus"},{"$ref":"#/components/schemas/GCPUploadStatus"},{"$ref":"#/components/schemas/AzureUploadStatus"}]},"status":{"enum":["success","failure","pending","running"],"type":"string"},"type":{"$ref":"#/components/schemas/UploadTypes"}},"required":["status","type","options"]},"UploadTypes":{"enum":["aws","gcp","azure","aws.s3"],"type":"string"},"User":{"properties":{"name":{"example":"user1","type":"string"},"ssh_key":{"example":"ssh-rsa AAAAB3NzaC1","type":"string"}},"required":["name","ssh_key"],"type":"object"},"Version":{"properties":{"version":{"type":"string"}},"required":["version"]}}},"info":{"description":"Service that relays image build requests","license":{"name":"Apache 2.0","url":"https://www.apache.org/licenses/LICENSE-2.0.html"},"title":"Image-builder service","version":"1.0"},"openapi":"3.0.1","paths":{"/architectures/{distribution}":{"get":{"operationId":"GetArchitectures","parameters":[{"description":"distribution for which to look up available architectures","example":"rhel-84","in":"path","name":"distribution","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Architectures"}}},"description":"a list of available architectures and their associated image types"}},"summary":"get the architectures and their image types available for a given distribution"}},"/clones/{id}":{"get":{"description":"status of a clone","operationId":"GetCloneStatus","parameters":[{"description":"Id of clone status to get","in":"path","name":"id","required":true,"schema":{"example":"123e4567-e89b-12d3-a456-426655440000","format":"uuid","type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UploadStatus"}}},"description":"clone status"}},"summary":"get status of a compose clone"}},"/compose":{"post":{"description":"compose image","operationId":"ComposeImage","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ComposeRequest"}}},"description":"details of image to be composed","required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ComposeResponse"}}},"description":"compose has started"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPErrorList"}}},"description":"the compose request is malformed"}},"summary":"compose image"}},"/composes":{"get":{"operationId":"GetComposes","parameters":[{"description":"max amount of composes, default 100","in":"query","name":"limit","schema":{"default":100,"maximum":100,"minimum":1,"type":"integer"}},{"description":"composes page offset, default 0","in":"query","name":"offset","schema":{"default":0,"minimum":0,"type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ComposesResponse"}}},"description":"a list of composes"}},"summary":"get a collection of previous compose requests for the logged in user"}},"/composes/{composeId}":{"delete":{"description":"Deletes a compose, the compose will still count towards quota.\n","operationId":"DeleteCompose","responses":{"200":{"description":"OK"}},"summary":"delete a compose"},"get":{"description":"status of an image compose","operationId":"GetComposeStatus","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ComposeStatus"}}},"description":"compose status"}},"summary":"get status of an image compose"},"parameters":[{"description":"Id of compose","in":"path","name":"composeId","required":true,"schema":{"example":"123e4567-e89b-12d3-a456-426655440000","format":"uuid","type":"string"}}]},"/composes/{composeId}/clone":{"post":{"description":"Clones a compose. Only composes with the 'aws' image type currently support cloning.\n","operationId":"CloneCompose","parameters":[{"description":"Id of compose to clone","in":"path","name":"composeId","required":true,"schema":{"example":"123e4567-e89b-12d3-a456-426655440000","format":"uuid","type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CloneRequest"}}},"description":"details of the new clone","required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CloneResponse"}}},"description":"cloning has started"}},"summary":"clone a compose"}},"/composes/{composeId}/clones":{"get":{"description":"Returns a list of all the clones which were started for a compose\n","operationId":"GetComposeClones","parameters":[{"description":"Id of compose to get the clones of","in":"path","name":"composeId","required":true,"schema":{"example":"123e4567-e89b-12d3-a456-426655440000","format":"uuid","type":"string"}},{"description":"max amount of clones, default 100","in":"query","name":"limit","schema":{"default":100,"maximum":100,"minimum":1,"type":"integer"}},{"description":"clones page offset, default 0","in":"query","name":"offset","schema":{"default":0,"minimum":0,"type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClonesResponse"}}},"description":"compose clones"}},"summary":"get clones of a compose"}},"/composes/{composeId}/metadata":{"get":{"description":"metadata for an image compose","operationId":"GetComposeMetadata","parameters":[{"description":"Id of compose metadata to get","in":"path","name":"composeId","required":true,"schema":{"example":"123e4567-e89b-12d3-a456-426655440000","format":"uuid","type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ComposeMetadata"}}},"description":"compose metadata"}},"summary":"get metadata of an image compose"}},"/distributions":{"get":{"operationId":"GetDistributions","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DistributionsResponse"}}},"description":"a list of available distributions"}},"summary":"get the available distributions"}},"/openapi.json":{"get":{"operationId":"GetOpenapiJson","responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"returns this document"}},"summary":"get the openapi json specification","tags":["meta"]}},"/packages":{"get":{"operationId":"GetPackages","parameters":[{"description":"distribution to look up packages for","in":"query","name":"distribution","required":true,"schema":{"$ref":"#/components/schemas/Distributions"}},{"description":"architecture to look up packages for","in":"query","name":"architecture","required":true,"schema":{"enum":["x86_64","aarch64"],"type":"string"}},{"description":"packages to look for","in":"query","name":"search","required":true,"schema":{"type":"string"}},{"description":"max amount of packages, default 100","in":"query","name":"limit","schema":{"default":100,"maximum":100,"minimum":1,"type":"integer"}},{"description":"packages page offset, default 0","in":"query","name":"offset","schema":{"default":0,"minimum":0,"type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PackagesResponse"}}},"description":"a list of packages"}}}},"/ready":{"get":{"operationId":"GetReadiness","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Readiness"}}},"description":"readiness"}},"summary":"return the readiness"}},"/version":{"get":{"description":"get the service version","operationId":"GetVersion","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Version"}}},"description":"a service version"}},"summary":"get the service version"}}},"servers":[{"url":"/api/image-builder/v1"},{"url":"/api/image-builder/v1.0"},{"url":"/api/image-builder/v1.0"}]} diff --git a/package.json b/package.json index 9d2b64c6..62325803 100644 --- a/package.json +++ b/package.json @@ -112,6 +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", "verify": "npm-run-all build lint test" }, "insights": { diff --git a/src/Components/CreateImageWizard/CreateImageWizard.js b/src/Components/CreateImageWizard/CreateImageWizard.js index eade6660..19f5168b 100644 --- a/src/Components/CreateImageWizard/CreateImageWizard.js +++ b/src/Components/CreateImageWizard/CreateImageWizard.js @@ -29,8 +29,8 @@ import { import './CreateImageWizard.scss'; import api from '../../api'; import { UNIT_GIB, UNIT_KIB, UNIT_MIB } from '../../constants'; -import { useGetArchitecturesByDistributionQuery } from '../../store/apiSlice'; import { composeAdded } from '../../store/composesSlice'; +import { useGetArchitecturesQuery } from '../../store/imageBuilderApi'; import isRhel from '../../Utilities/isRhel'; import { resolveRelPath } from '../../Utilities/path'; import { useGetEnvironment } from '../../Utilities/useGetEnvironment'; @@ -502,8 +502,8 @@ const CreateImageWizard = () => { const composeRequest = compose?.request; // TODO: This causes an annoying re-render when using Recreate image - const { data: distroInfo } = useGetArchitecturesByDistributionQuery( - composeRequest?.distribution, + const { data: distroInfo } = useGetArchitecturesQuery( + { distribution: composeRequest?.distribution }, { // distroInfo is only needed when recreating an image, skip otherwise skip: composeId ? false : true, diff --git a/src/Components/CreateImageWizard/formComponents/FileSystemConfigButtons.js b/src/Components/CreateImageWizard/formComponents/FileSystemConfigButtons.js index d390f9e9..ded6bd37 100644 --- a/src/Components/CreateImageWizard/formComponents/FileSystemConfigButtons.js +++ b/src/Components/CreateImageWizard/formComponents/FileSystemConfigButtons.js @@ -5,7 +5,7 @@ import WizardContext from '@data-driven-forms/react-form-renderer/wizard-context import { Button } from '@patternfly/react-core'; import PropTypes from 'prop-types'; -import { usePrefetch } from '../../../store/apiSlice'; +import { imageBuilderApi } from '../../../store/imageBuilderApi'; // FileSystemconfigButtons are defined separately to display errors inside of the button footer const FileSystemConfigButtons = ({ handleNext, handlePrev, nextStep }) => { @@ -15,7 +15,7 @@ const FileSystemConfigButtons = ({ handleNext, handlePrev, nextStep }) => { getState()?.errors?.['file-system-configuration'] ? true : false ); const [nextHasBeenClicked, setNextHasBeenClicked] = useState(false); - const prefetchArchitectures = usePrefetch('getArchitecturesByDistribution'); + const prefetchArchitectures = imageBuilderApi.usePrefetch('getArchitectures'); useEffect(() => { const errors = getState()?.errors?.['file-system-configuration']; @@ -38,7 +38,7 @@ const FileSystemConfigButtons = ({ handleNext, handlePrev, nextStep }) => { const handleMouseEnter = () => { const distribution = getState()?.values?.release; - prefetchArchitectures(distribution); + prefetchArchitectures({ distribution }); }; return ( diff --git a/src/Components/CreateImageWizard/formComponents/Packages.js b/src/Components/CreateImageWizard/formComponents/Packages.js index b38fa434..cc87d419 100644 --- a/src/Components/CreateImageWizard/formComponents/Packages.js +++ b/src/Components/CreateImageWizard/formComponents/Packages.js @@ -30,7 +30,7 @@ import { import PropTypes from 'prop-types'; import api from '../../../api'; -import { useGetArchitecturesByDistributionQuery } from '../../../store/apiSlice'; +import { useGetArchitecturesQuery } from '../../../store/imageBuilderApi'; const ExactMatch = ({ pkgList, @@ -61,7 +61,7 @@ export const RedHatPackages = ({ defaultArch }) => { const { getState } = useFormApi(); const distribution = getState()?.values?.release; const { data: distributionInformation, isSuccess: isSuccessDistroInfo } = - useGetArchitecturesByDistributionQuery(distribution); + useGetArchitecturesQuery({ distribution }); const getAllPackages = async (packagesSearchName) => { // if the env is stage beta then use content-sources api diff --git a/src/store/apiSlice.ts b/src/store/apiSlice.ts index 29ac65ef..4c37d790 100644 --- a/src/store/apiSlice.ts +++ b/src/store/apiSlice.ts @@ -1,6 +1,6 @@ import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'; -import { ActivationKeys, Architectures, ClonesResponse, ComposeStatus, ComposesResponse, RepositoryCollectionResponse, SourceResponse, SourceUploadInfoResponse, UploadStatus, } from '../../types'; +import { ActivationKeys, RepositoryCollectionResponse, SourceResponse, SourceUploadInfoResponse } from '../../types'; type GetRepositoriesArgs = { available_for_arch: string, available_for_version: string, limit: number, offset: number } @@ -11,7 +11,6 @@ enum Provider { import { CONTENT_SOURCES, - IMAGE_BUILDER_API, PROVISIONING_SOURCES_ENDPOINT, RHSM_API, } from '../constants'; @@ -27,10 +26,6 @@ export const apiSlice = createApi({ query: (sourceId) => `${PROVISIONING_SOURCES_ENDPOINT}/sources/${sourceId}/upload_info` }), - getArchitecturesByDistribution: builder.query({ - query: (distribution) => - `${IMAGE_BUILDER_API}/architectures/${distribution}`, - }), getActivationKeys: builder.query({ query: () => `${RHSM_API}/activation_keys`, }), @@ -40,29 +35,13 @@ export const apiSlice = createApi({ getRepositories: builder.query({ query: ({available_for_arch, available_for_version, limit, offset}) => `${CONTENT_SOURCES}/repositories/?available_for_arch=${available_for_arch}&available_for_version=${available_for_version}&limit=${limit}&offset=${offset}`, }), - getClones: builder.query({ - query: (composeId) => `${IMAGE_BUILDER_API}/composes/${composeId}/clones`, - }), - getCloneStatus: builder.query({ - query: (cloneId) => `${IMAGE_BUILDER_API}/clones/${cloneId}`, - }), - getComposes: builder.query({ - query: () => `${IMAGE_BUILDER_API}/composes`, - }), - getComposeStatus: builder.query({ - query: (composeId) => `${IMAGE_BUILDER_API}/composes/${composeId}`, - }), }), }); export const { - useGetArchitecturesByDistributionQuery, useGetActivationKeysQuery, useGetActivationKeyInformationQuery, useGetRepositoriesQuery, - useGetClonesQuery, - useGetCloneStatusQuery, - useGetComposeStatusQuery, useGetSourcesQuery, useGetSourceDetailQuery, usePrefetch, diff --git a/src/store/emptyImageBuilderApi.ts b/src/store/emptyImageBuilderApi.ts new file mode 100644 index 00000000..7e94f83e --- /dev/null +++ b/src/store/emptyImageBuilderApi.ts @@ -0,0 +1,9 @@ +import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react' +import {IMAGE_BUILDER_API} from '../constants' + +// initialize an empty api service that we'll inject endpoints into later as needed +export const emptyImageBuilderApi = createApi({ + reducerPath: 'imageBuilderApi', + baseQuery: fetchBaseQuery({ baseUrl: IMAGE_BUILDER_API }), + endpoints: () => ({}), +}) diff --git a/src/store/imageBuilderApi.ts b/src/store/imageBuilderApi.ts new file mode 100644 index 00000000..382b18aa --- /dev/null +++ b/src/store/imageBuilderApi.ts @@ -0,0 +1,290 @@ +import { emptyImageBuilderApi as api } from "./emptyImageBuilderApi"; +const injectedRtkApi = api.injectEndpoints({ + endpoints: (build) => ({ + getArchitectures: build.query< + GetArchitecturesApiResponse, + GetArchitecturesApiArg + >({ + query: (queryArg) => ({ url: `/architectures/${queryArg.distribution}` }), + }), + getCloneStatus: build.query< + GetCloneStatusApiResponse, + GetCloneStatusApiArg + >({ + query: (queryArg) => ({ url: `/clones/${queryArg.id}` }), + }), + getComposes: build.query({ + query: (queryArg) => ({ + url: `/composes`, + params: { limit: queryArg.limit, offset: queryArg.offset }, + }), + }), + getComposeStatus: build.query< + GetComposeStatusApiResponse, + GetComposeStatusApiArg + >({ + query: (queryArg) => ({ url: `/composes/${queryArg.composeId}` }), + }), + getComposeClones: build.query< + GetComposeClonesApiResponse, + GetComposeClonesApiArg + >({ + query: (queryArg) => ({ + url: `/composes/${queryArg.composeId}/clones`, + params: { limit: queryArg.limit, offset: queryArg.offset }, + }), + }), + }), + overrideExisting: false, +}); +export { injectedRtkApi as imageBuilderApi }; +export type GetArchitecturesApiResponse = + /** status 200 a list of available architectures and their associated image types */ Architectures; +export type GetArchitecturesApiArg = { + /** distribution for which to look up available architectures */ + distribution: string; +}; +export type GetCloneStatusApiResponse = + /** status 200 clone status */ UploadStatus; +export type GetCloneStatusApiArg = { + /** Id of clone status to get */ + id: string; +}; +export type GetComposesApiResponse = + /** status 200 a list of composes */ ComposesResponse; +export type GetComposesApiArg = { + /** max amount of composes, default 100 */ + limit?: number; + /** composes page offset, default 0 */ + offset?: number; +}; +export type GetComposeStatusApiResponse = + /** status 200 compose status */ ComposeStatus; +export type GetComposeStatusApiArg = { + /** Id of compose */ + composeId: string; +}; +export type GetComposeClonesApiResponse = + /** status 200 compose clones */ ClonesResponse; +export type GetComposeClonesApiArg = { + /** Id of compose to get the clones of */ + composeId: string; + /** max amount of clones, default 100 */ + limit?: number; + /** clones page offset, default 0 */ + offset?: number; +}; +export type Repository = { + baseurl?: string; + check_gpg?: boolean; + check_repo_gpg?: boolean; + gpgkey?: string; + ignore_ssl?: boolean; + metalink?: string; + mirrorlist?: string; + rhsm: boolean; +}; +export type ArchitectureItem = { + arch: string; + image_types: string[]; + repositories: Repository[]; +}; +export type Architectures = ArchitectureItem[]; +export type AwsUploadStatus = { + ami: string; + region: string; +}; +export type Awss3UploadStatus = { + url: string; +}; +export type GcpUploadStatus = { + image_name: string; + project_id: string; +}; +export type AzureUploadStatus = { + image_name: string; +}; +export type UploadTypes = "aws" | "gcp" | "azure" | "aws.s3"; +export type UploadStatus = { + options: + | AwsUploadStatus + | Awss3UploadStatus + | GcpUploadStatus + | AzureUploadStatus; + status: "success" | "failure" | "pending" | "running"; + type: UploadTypes; +}; +export type ComposesResponseItem = { + created_at: string; + id: string; + image_name?: string; + request: any; +}; +export type ComposesResponse = { + data: ComposesResponseItem[]; + links: { + first: string; + last: string; + }; + meta: { + count: number; + }; +}; +export type ComposeStatusError = { + details?: any; + id: number; + reason: string; +}; +export type ImageStatus = { + error?: ComposeStatusError; + status: + | "success" + | "failure" + | "pending" + | "building" + | "uploading" + | "registering"; + upload_status?: UploadStatus; +}; +export type CustomRepository = { + baseurl?: string[]; + check_gpg?: boolean; + check_repo_gpg?: boolean; + enabled?: boolean; + filename?: string; + gpgkey?: string[]; + id: string; + metalink?: string; + mirrorlist?: string; + name?: string; + priority?: number; + ssl_verify?: boolean; +}; +export type Filesystem = { + min_size: any; + mountpoint: string; +}; +export type OpenScap = { + profile_id: string; +}; +export type Subscription = { + "activation-key": string; + "base-url": string; + insights: boolean; + organization: number; + rhc?: boolean; + "server-url": string; +}; +export type User = { + name: string; + ssh_key: string; +}; +export type Customizations = { + custom_repositories?: CustomRepository[]; + filesystem?: Filesystem[]; + openscap?: OpenScap; + packages?: string[]; + payload_repositories?: Repository[]; + subscription?: Subscription; + users?: User[]; +}; +export type Distributions = + | "rhel-8" + | "rhel-8-nightly" + | "rhel-84" + | "rhel-85" + | "rhel-86" + | "rhel-87" + | "rhel-88" + | "rhel-9" + | "rhel-9-nightly" + | "rhel-90" + | "rhel-91" + | "rhel-92" + | "centos-8" + | "centos-9" + | "fedora-37" + | "fedora-38" + | "fedora-39"; +export type ImageTypes = + | "aws" + | "azure" + | "edge-commit" + | "edge-installer" + | "gcp" + | "guest-image" + | "image-installer" + | "vsphere" + | "vsphere-ova" + | "ami" + | "rhel-edge-commit" + | "rhel-edge-installer" + | "vhd"; +export type OsTree = { + contenturl?: string; + parent?: string; + ref?: string; + rhsm?: boolean; + url?: string; +}; +export type AwsUploadRequestOptions = { + share_with_accounts?: string[]; + share_with_sources?: string[]; +}; +export type Awss3UploadRequestOptions = object; +export type GcpUploadRequestOptions = { + share_with_accounts: string[]; +}; +export type AzureUploadRequestOptions = { + image_name?: string; + resource_group: string; + source_id?: string; + subscription_id?: string; + tenant_id?: string; +}; +export type UploadRequest = { + options: + | AwsUploadRequestOptions + | Awss3UploadRequestOptions + | GcpUploadRequestOptions + | AzureUploadRequestOptions; + type: UploadTypes; +}; +export type ImageRequest = { + architecture: "x86_64" | "aarch64"; + image_type: ImageTypes; + ostree?: OsTree; + upload_request: UploadRequest; +}; +export type ComposeRequest = { + customizations?: Customizations; + distribution: Distributions; + image_name?: string; + image_requests: ImageRequest[]; +}; +export type ComposeStatus = { + image_status: ImageStatus; + request: ComposeRequest; +}; +export type ClonesResponseItem = { + created_at: string; + id: string; + request: any; +}; +export type ClonesResponse = { + data: ClonesResponseItem[]; + links: { + first: string; + last: string; + }; + meta: { + count: number; + }; +}; +export const { + useGetArchitecturesQuery, + useGetCloneStatusQuery, + useGetComposesQuery, + useGetComposeStatusQuery, + useGetComposeClonesQuery, +} = injectedRtkApi; diff --git a/src/store/index.js b/src/store/index.js index 0ae61335..25474ce8 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -5,15 +5,20 @@ import promiseMiddleware from 'redux-promise-middleware'; import { apiSlice } from './apiSlice'; import clonesSlice from './clonesSlice'; import composesSlice from './composesSlice'; +import { imageBuilderApi } from './imageBuilderApi'; export const reducer = { [apiSlice.reducerPath]: apiSlice.reducer, clones: clonesSlice, composes: composesSlice, + [imageBuilderApi.reducerPath]: imageBuilderApi.reducer, notifications: notificationsReducer, }; export const middleware = (getDefaultMiddleware) => - getDefaultMiddleware().concat(promiseMiddleware).concat(apiSlice.middleware); + getDefaultMiddleware() + .concat(promiseMiddleware) + .concat(apiSlice.middleware) + .concat(imageBuilderApi.middleware); export const store = configureStore({ reducer, middleware });