api/cloud: drop v1 API
It's deprecated and not used anywhere, let's just drop it. Signed-off-by: Ondřej Budai <ondrej@budai.cz>
This commit is contained in:
parent
8d81da7d7b
commit
d967790ea5
6 changed files with 0 additions and 2277 deletions
|
|
@ -247,7 +247,6 @@ func (c *Composer) Start() error {
|
|||
|
||||
if c.apiListener != nil {
|
||||
go func() {
|
||||
const apiRoute = "/api/composer/v1"
|
||||
const apiRouteV2 = "/api/image-builder-composer/v2"
|
||||
const kojiRoute = "/api/composer-koji/v1"
|
||||
|
||||
|
|
@ -256,7 +255,6 @@ func (c *Composer) Start() error {
|
|||
// Add a "/" here, because http.ServeMux expects the
|
||||
// trailing slash for rooted subtrees, whereas the
|
||||
// handler functions don't.
|
||||
mux.Handle(apiRoute+"/", c.api.V1(apiRoute))
|
||||
mux.Handle(apiRouteV2+"/", c.api.V2(apiRouteV2))
|
||||
mux.Handle(kojiRoute+"/", c.koji.Handler(kojiRoute))
|
||||
|
||||
|
|
|
|||
|
|
@ -7,27 +7,20 @@ import (
|
|||
"github.com/osbuild/osbuild-composer/internal/rpmmd"
|
||||
"github.com/osbuild/osbuild-composer/internal/worker"
|
||||
|
||||
v1 "github.com/osbuild/osbuild-composer/internal/cloudapi/v1"
|
||||
v2 "github.com/osbuild/osbuild-composer/internal/cloudapi/v2"
|
||||
)
|
||||
|
||||
type Server struct {
|
||||
v1 *v1.Server
|
||||
v2 *v2.Server
|
||||
}
|
||||
|
||||
func NewServer(workers *worker.Server, rpmMetadata rpmmd.RPMMD, distros *distroregistry.Registry, awsBucket string) *Server {
|
||||
server := &Server{
|
||||
v1: v1.NewServer(workers, rpmMetadata, distros),
|
||||
v2: v2.NewServer(workers, rpmMetadata, distros, awsBucket),
|
||||
}
|
||||
return server
|
||||
}
|
||||
|
||||
func (server *Server) V1(path string) http.Handler {
|
||||
return server.v1.Handler(path)
|
||||
}
|
||||
|
||||
func (server *Server) V2(path string) http.Handler {
|
||||
return server.v2.Handler(path)
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -1,539 +0,0 @@
|
|||
---
|
||||
openapi: 3.0.1
|
||||
info:
|
||||
version: '1'
|
||||
title: OSBuild Composer cloud api
|
||||
description: Service to build and install images.
|
||||
license:
|
||||
name: Apache 2.0
|
||||
url: https://www.apache.org/licenses/LICENSE-2.0.html
|
||||
|
||||
paths:
|
||||
/version:
|
||||
get:
|
||||
summary: get the service version
|
||||
description: "get the service version"
|
||||
operationId: getVersion
|
||||
responses:
|
||||
'200':
|
||||
description: a service version
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Version'
|
||||
/openapi.json:
|
||||
get:
|
||||
summary: get the openapi json specification
|
||||
operationId: getOpenapiJson
|
||||
tags:
|
||||
- meta
|
||||
responses:
|
||||
'200':
|
||||
description: returns this document
|
||||
/compose/{id}:
|
||||
get:
|
||||
summary: The status of a compose
|
||||
parameters:
|
||||
- in: path
|
||||
name: id
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
example: '123e4567-e89b-12d3-a456-426655440000'
|
||||
required: true
|
||||
description: ID of compose status to get
|
||||
description: Get the status of a running or completed compose. This includes whether or not it succeeded, and also meta information about the result.
|
||||
operationId: compose_status
|
||||
responses:
|
||||
'200':
|
||||
description: compose status
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ComposeStatus'
|
||||
'400':
|
||||
description: Invalid compose id
|
||||
content:
|
||||
text/plain:
|
||||
schema:
|
||||
type: string
|
||||
'404':
|
||||
description: Unknown compose id
|
||||
content:
|
||||
text/plain:
|
||||
schema:
|
||||
type: string
|
||||
/compose/{id}/metadata:
|
||||
get:
|
||||
summary: Get the metadata for a compose.
|
||||
operationId: compose_metadata
|
||||
parameters:
|
||||
- in: path
|
||||
name: id
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
example: 123e4567-e89b-12d3-a456-426655440000
|
||||
required: true
|
||||
description: ID of compose status to get
|
||||
description: 'Get the metadata of a finished compose. The exact information returned depends on the requested image type.'
|
||||
responses:
|
||||
'200':
|
||||
description: The metadata for the given compose.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ComposeMetadata'
|
||||
'400':
|
||||
description: Invalid compose id
|
||||
content:
|
||||
text/plain:
|
||||
schema:
|
||||
type: string
|
||||
'404':
|
||||
description: Unknown compose id
|
||||
content:
|
||||
text/plain:
|
||||
schema:
|
||||
type: string
|
||||
/compose:
|
||||
post:
|
||||
summary: Create compose
|
||||
description: Create a new compose, potentially consisting of several images and upload each to their destinations.
|
||||
operationId: compose
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ComposeRequest'
|
||||
responses:
|
||||
'201':
|
||||
description: Compose has started
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ComposeResult'
|
||||
|
||||
components:
|
||||
schemas:
|
||||
Version:
|
||||
required:
|
||||
- version
|
||||
properties:
|
||||
version:
|
||||
type: string
|
||||
ComposeStatus:
|
||||
required:
|
||||
- image_status
|
||||
properties:
|
||||
image_status:
|
||||
$ref: '#/components/schemas/ImageStatus'
|
||||
ImageStatus:
|
||||
required:
|
||||
- status
|
||||
properties:
|
||||
status:
|
||||
$ref: '#/components/schemas/ImageStatusValue'
|
||||
upload_status:
|
||||
$ref: '#/components/schemas/UploadStatus'
|
||||
ImageStatusValue:
|
||||
type: string
|
||||
enum: ['success', 'failure', 'pending', 'building', 'uploading', 'registering']
|
||||
UploadStatus:
|
||||
required:
|
||||
- status
|
||||
- type
|
||||
- options
|
||||
properties:
|
||||
status:
|
||||
type: string
|
||||
enum: ['success', 'failure', 'pending', 'running']
|
||||
type:
|
||||
$ref: '#/components/schemas/UploadTypes'
|
||||
options:
|
||||
oneOf:
|
||||
- $ref: '#/components/schemas/AWSUploadStatus'
|
||||
- $ref: '#/components/schemas/AWSS3UploadStatus'
|
||||
- $ref: '#/components/schemas/GCPUploadStatus'
|
||||
- $ref: '#/components/schemas/AzureUploadStatus'
|
||||
AWSUploadStatus:
|
||||
type: object
|
||||
required:
|
||||
- ami
|
||||
- region
|
||||
properties:
|
||||
ami:
|
||||
type: string
|
||||
example: 'ami-0c830793775595d4b'
|
||||
region:
|
||||
type: string
|
||||
example: 'eu-west-1'
|
||||
AWSS3UploadStatus:
|
||||
type: object
|
||||
required:
|
||||
- url
|
||||
properties:
|
||||
url:
|
||||
type: string
|
||||
GCPUploadStatus:
|
||||
type: object
|
||||
required:
|
||||
- project_id
|
||||
- image_name
|
||||
properties:
|
||||
project_id:
|
||||
type: string
|
||||
example: 'ascendant-braid-303513'
|
||||
image_name:
|
||||
type: string
|
||||
example: 'my-image'
|
||||
AzureUploadStatus:
|
||||
type: object
|
||||
required:
|
||||
- image_name
|
||||
properties:
|
||||
image_name:
|
||||
type: string
|
||||
example: 'my-image'
|
||||
ComposeMetadata:
|
||||
type: object
|
||||
properties:
|
||||
packages:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/PackageMetadata'
|
||||
description: 'Package list including NEVRA'
|
||||
ostree_commit:
|
||||
type: string
|
||||
description: 'ID (hash) of the built commit'
|
||||
ComposeRequest:
|
||||
type: object
|
||||
required:
|
||||
- distribution
|
||||
- image_requests
|
||||
properties:
|
||||
distribution:
|
||||
type: string
|
||||
example: 'rhel-8'
|
||||
image_requests:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/ImageRequest'
|
||||
customizations:
|
||||
$ref: '#/components/schemas/Customizations'
|
||||
ImageRequest:
|
||||
required:
|
||||
- architecture
|
||||
- image_type
|
||||
- repositories
|
||||
- upload_request
|
||||
properties:
|
||||
architecture:
|
||||
type: string
|
||||
example: 'x86_64'
|
||||
image_type:
|
||||
type: string
|
||||
example: 'ami'
|
||||
repositories:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Repository'
|
||||
ostree:
|
||||
$ref: '#/components/schemas/OSTree'
|
||||
upload_request:
|
||||
$ref: '#/components/schemas/UploadRequest'
|
||||
Repository:
|
||||
type: object
|
||||
required:
|
||||
- rhsm
|
||||
properties:
|
||||
rhsm:
|
||||
type: boolean
|
||||
baseurl:
|
||||
type: string
|
||||
format: url
|
||||
example: 'https://cdn.redhat.com/content/dist/rhel8/8/x86_64/baseos/os/'
|
||||
mirrorlist:
|
||||
type: string
|
||||
format: url
|
||||
example: 'https://mirrors.fedoraproject.org/mirrorlist?repo=fedora-33&arch=x86_64'
|
||||
metalink:
|
||||
type: string
|
||||
format: url
|
||||
example: 'https://mirrors.fedoraproject.org/metalink?repo=fedora-32&arch=x86_64'
|
||||
UploadRequest:
|
||||
type: object
|
||||
required:
|
||||
- type
|
||||
- options
|
||||
properties:
|
||||
type:
|
||||
$ref: '#/components/schemas/UploadTypes'
|
||||
options:
|
||||
oneOf:
|
||||
- $ref: '#/components/schemas/AWSUploadRequestOptions'
|
||||
- $ref: '#/components/schemas/AWSS3UploadRequestOptions'
|
||||
- $ref: '#/components/schemas/GCPUploadRequestOptions'
|
||||
- $ref: '#/components/schemas/AzureUploadRequestOptions'
|
||||
UploadTypes:
|
||||
type: string
|
||||
enum: ['aws', 'aws.s3', 'gcp', 'azure']
|
||||
AWSUploadRequestOptions:
|
||||
type: object
|
||||
required:
|
||||
- region
|
||||
- s3
|
||||
- ec2
|
||||
properties:
|
||||
region:
|
||||
type: string
|
||||
example: 'eu-west-1'
|
||||
s3:
|
||||
$ref: '#/components/schemas/AWSUploadRequestOptionsS3'
|
||||
ec2:
|
||||
$ref: '#/components/schemas/AWSUploadRequestOptionsEc2'
|
||||
AWSS3UploadRequestOptions:
|
||||
type: object
|
||||
required:
|
||||
- region
|
||||
- s3
|
||||
properties:
|
||||
region:
|
||||
type: string
|
||||
example: 'eu-west-1'
|
||||
s3:
|
||||
$ref: '#/components/schemas/AWSUploadRequestOptionsS3'
|
||||
AWSUploadRequestOptionsS3:
|
||||
type: object
|
||||
required:
|
||||
- access_key_id
|
||||
- secret_access_key
|
||||
- bucket
|
||||
properties:
|
||||
access_key_id:
|
||||
type: string
|
||||
example: 'AKIAIOSFODNN7EXAMPLE'
|
||||
secret_access_key:
|
||||
type: string
|
||||
format: password
|
||||
example: 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY'
|
||||
session_token:
|
||||
type: string
|
||||
example: 'AQoEXAMPLEH4aoAH0gNCAPyJxz4BlCFFxWNE1OPTgk5TthT+FvwqnKwRcOIfrRh3c/LTo6UDdyJwOOvEVPvLXCrrrUtdnniCEXAMPLE/IvU1dYUg2RVAJBanLiHb4IgRmpRV3zrkuWJOgQs8IZZaIv2BXIa2R4OlgkBN9bkUDNCJiBeb/AXlzBBko7b15fjrBs2+cTQtpZ3CYWFXG8C5zqx37wnOE49mRl/+OtkIKGO7fAE'
|
||||
bucket:
|
||||
type: string
|
||||
example: 'my-bucket'
|
||||
AWSUploadRequestOptionsEc2:
|
||||
type: object
|
||||
required:
|
||||
- access_key_id
|
||||
- secret_access_key
|
||||
properties:
|
||||
access_key_id:
|
||||
type: string
|
||||
example: 'AKIAIOSFODNN7EXAMPLE'
|
||||
secret_access_key:
|
||||
type: string
|
||||
format: password
|
||||
example: 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY'
|
||||
session_token:
|
||||
type: string
|
||||
example: 'AQoEXAMPLEH4aoAH0gNCAPyJxz4BlCFFxWNE1OPTgk5TthT+FvwqnKwRcOIfrRh3c/LTo6UDdyJwOOvEVPvLXCrrrUtdnniCEXAMPLE/IvU1dYUg2RVAJBanLiHb4IgRmpRV3zrkuWJOgQs8IZZaIv2BXIa2R4OlgkBN9bkUDNCJiBeb/AXlzBBko7b15fjrBs2+cTQtpZ3CYWFXG8C5zqx37wnOE49mRl/+OtkIKGO7fAE'
|
||||
snapshot_name:
|
||||
type: string
|
||||
example: 'my-snapshot'
|
||||
share_with_accounts:
|
||||
type: array
|
||||
example: ['123456789012']
|
||||
items:
|
||||
type: string
|
||||
GCPUploadRequestOptions:
|
||||
type: object
|
||||
required:
|
||||
- bucket
|
||||
properties:
|
||||
region:
|
||||
type: string
|
||||
example: 'eu'
|
||||
description: |
|
||||
The GCP region where the OS image will be imported to and shared from.
|
||||
The value must be a valid GCP location. See https://cloud.google.com/storage/docs/locations.
|
||||
If not specified, the multi-region location closest to the source
|
||||
(source Storage Bucket location) is chosen automatically.
|
||||
bucket:
|
||||
type: string
|
||||
example: 'my-example-bucket'
|
||||
description: 'Name of an existing STANDARD Storage class Bucket.'
|
||||
# don't expose the os type for now
|
||||
# os:
|
||||
# type: string
|
||||
# example: 'rhel-8-byol'
|
||||
# description: 'OS of the disk image being imported needed for installation of GCP guest tools.'
|
||||
image_name:
|
||||
type: string
|
||||
example: 'my-image'
|
||||
description: |
|
||||
The name to use for the imported and shared Compute Engine image.
|
||||
The image name must be unique within the GCP project, which is used
|
||||
for the OS image upload and import. If not specified a random
|
||||
'composer-api-<uuid>' string is used as the image name.
|
||||
share_with_accounts:
|
||||
type: array
|
||||
example: [
|
||||
'user:alice@example.com',
|
||||
'serviceAccount:my-other-app@appspot.gserviceaccount.com',
|
||||
'group:admins@example.com',
|
||||
'domain:example.com'
|
||||
]
|
||||
description: |
|
||||
List of valid Google accounts to share the imported Compute Engine image with.
|
||||
Each string must contain a specifier of the account type. Valid formats are:
|
||||
- 'user:{emailid}': An email address that represents a specific
|
||||
Google account. For example, 'alice@example.com'.
|
||||
- 'serviceAccount:{emailid}': An email address that represents a
|
||||
service account. For example, 'my-other-app@appspot.gserviceaccount.com'.
|
||||
- 'group:{emailid}': An email address that represents a Google group.
|
||||
For example, 'admins@example.com'.
|
||||
- 'domain:{domain}': The G Suite domain (primary) that represents all
|
||||
the users of that domain. For example, 'google.com' or 'example.com'.
|
||||
If not specified, the imported Compute Engine image is not shared with any
|
||||
account.
|
||||
items:
|
||||
type: string
|
||||
AzureUploadRequestOptions:
|
||||
type: object
|
||||
required:
|
||||
- tenant_id
|
||||
- subscription_id
|
||||
- resource_group
|
||||
- location
|
||||
properties:
|
||||
tenant_id:
|
||||
type: string
|
||||
example: '5c7ef5b6-1c3f-4da0-a622-0b060239d7d7'
|
||||
description: |
|
||||
ID of the tenant where the image should be uploaded. This link explains how
|
||||
to find it in the Azure Portal:
|
||||
https://docs.microsoft.com/en-us/azure/active-directory/fundamentals/active-directory-how-to-find-tenant
|
||||
subscription_id:
|
||||
type: string
|
||||
example: '4e5d8b2c-ab24-4413-90c5-612306e809e2'
|
||||
description: |
|
||||
ID of subscription where the image should be uploaded.
|
||||
resource_group:
|
||||
type: string
|
||||
example: 'ToucanResourceGroup'
|
||||
description: |
|
||||
Name of the resource group where the image should be uploaded.
|
||||
location:
|
||||
type: string
|
||||
example: 'westeurope'
|
||||
description: |
|
||||
Location where the image should be uploaded and registered. This link explain
|
||||
how to list all locations:
|
||||
https://docs.microsoft.com/en-us/cli/azure/account?view=azure-cli-latest#az_account_list_locations'
|
||||
image_name:
|
||||
type: string
|
||||
example: 'my-image'
|
||||
description: |
|
||||
Name of the uploaded image. It must be unique in the given resource group.
|
||||
If name is omitted from the request, a random one based on a UUID is
|
||||
generated.
|
||||
Customizations:
|
||||
type: object
|
||||
properties:
|
||||
subscription:
|
||||
$ref: '#/components/schemas/Subscription'
|
||||
packages:
|
||||
type: array
|
||||
example: ['postgres']
|
||||
items:
|
||||
type: string
|
||||
users:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/User'
|
||||
OSTree:
|
||||
type: object
|
||||
properties:
|
||||
url:
|
||||
type: string
|
||||
ref:
|
||||
type: string
|
||||
example: ['rhel/8/x86_64/edge']
|
||||
Subscription:
|
||||
type: object
|
||||
required:
|
||||
- organization
|
||||
- activation-key
|
||||
- server-url
|
||||
- base-url
|
||||
- insights
|
||||
properties:
|
||||
organization:
|
||||
type: integer
|
||||
example: 2040324
|
||||
activation-key:
|
||||
type: string
|
||||
format: password
|
||||
example: 'my-secret-key'
|
||||
server-url:
|
||||
type: string
|
||||
example: 'subscription.rhsm.redhat.com'
|
||||
base-url:
|
||||
type: string
|
||||
format: url
|
||||
example: http://cdn.redhat.com/
|
||||
insights:
|
||||
type: boolean
|
||||
example: true
|
||||
ComposeResult:
|
||||
required:
|
||||
- id
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
format: uuid
|
||||
example: '123e4567-e89b-12d3-a456-426655440000'
|
||||
PackageMetadata:
|
||||
required:
|
||||
- type
|
||||
- name
|
||||
- version
|
||||
- release
|
||||
- arch
|
||||
- sigmd5
|
||||
properties:
|
||||
type:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
version:
|
||||
type: string
|
||||
release:
|
||||
type: string
|
||||
epoch:
|
||||
type: string
|
||||
arch:
|
||||
type: string
|
||||
sigmd5:
|
||||
type: string
|
||||
signature:
|
||||
type: string
|
||||
User:
|
||||
type: object
|
||||
required:
|
||||
- name
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
example: "user1"
|
||||
groups:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
example: "group1"
|
||||
key:
|
||||
type: string
|
||||
example: "public ssh key"
|
||||
|
|
@ -1,588 +0,0 @@
|
|||
//go:generate go run github.com/deepmap/oapi-codegen/cmd/oapi-codegen --package=v1 --generate types,spec,client,server -o openapi.v1.gen.go openapi.v1.yml
|
||||
|
||||
package v1
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math"
|
||||
"math/big"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/labstack/echo/v4"
|
||||
|
||||
"github.com/osbuild/osbuild-composer/internal/blueprint"
|
||||
"github.com/osbuild/osbuild-composer/internal/distro"
|
||||
"github.com/osbuild/osbuild-composer/internal/distroregistry"
|
||||
osbuild "github.com/osbuild/osbuild-composer/internal/osbuild2"
|
||||
"github.com/osbuild/osbuild-composer/internal/ostree"
|
||||
"github.com/osbuild/osbuild-composer/internal/prometheus"
|
||||
"github.com/osbuild/osbuild-composer/internal/rpmmd"
|
||||
"github.com/osbuild/osbuild-composer/internal/target"
|
||||
"github.com/osbuild/osbuild-composer/internal/worker"
|
||||
)
|
||||
|
||||
// Server represents the state of the cloud Server
|
||||
type Server struct {
|
||||
workers *worker.Server
|
||||
rpmMetadata rpmmd.RPMMD
|
||||
distros *distroregistry.Registry
|
||||
}
|
||||
|
||||
type apiHandlers struct {
|
||||
server *Server
|
||||
}
|
||||
|
||||
type binder struct{}
|
||||
|
||||
// NewServer creates a new cloud server
|
||||
func NewServer(workers *worker.Server, rpmMetadata rpmmd.RPMMD, distros *distroregistry.Registry) *Server {
|
||||
server := &Server{
|
||||
workers: workers,
|
||||
rpmMetadata: rpmMetadata,
|
||||
distros: distros,
|
||||
}
|
||||
return server
|
||||
}
|
||||
|
||||
// Create an http.Handler() for this server, that provides the composer API at
|
||||
// the given path.
|
||||
func (server *Server) Handler(path string) http.Handler {
|
||||
e := echo.New()
|
||||
e.Binder = binder{}
|
||||
|
||||
handler := apiHandlers{
|
||||
server: server,
|
||||
}
|
||||
RegisterHandlers(e.Group(path, prometheus.MetricsMiddleware), &handler)
|
||||
return e
|
||||
}
|
||||
|
||||
func (b binder) Bind(i interface{}, ctx echo.Context) error {
|
||||
contentType := ctx.Request().Header["Content-Type"]
|
||||
if len(contentType) != 1 || contentType[0] != "application/json" {
|
||||
return echo.NewHTTPError(http.StatusUnsupportedMediaType, "Only 'application/json' content type is supported")
|
||||
}
|
||||
|
||||
err := json.NewDecoder(ctx.Request().Body).Decode(i)
|
||||
if err != nil {
|
||||
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Cannot parse request body: %v", err))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Compose handles a new /compose POST request
|
||||
func (h *apiHandlers) Compose(ctx echo.Context) error {
|
||||
contentType := ctx.Request().Header["Content-Type"]
|
||||
if len(contentType) != 1 || contentType[0] != "application/json" {
|
||||
return echo.NewHTTPError(http.StatusUnsupportedMediaType, "Only 'application/json' content type is supported")
|
||||
}
|
||||
|
||||
var request ComposeRequest
|
||||
err := ctx.Bind(&request)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
distribution := h.server.distros.GetDistro(request.Distribution)
|
||||
if distribution == nil {
|
||||
return echo.NewHTTPError(http.StatusBadRequest, "Unsupported distribution: %s", request.Distribution)
|
||||
}
|
||||
|
||||
var bp = blueprint.Blueprint{}
|
||||
err = bp.Initialize()
|
||||
if err != nil {
|
||||
return echo.NewHTTPError(http.StatusInternalServerError, "Unable to initialize blueprint")
|
||||
}
|
||||
if request.Customizations != nil && request.Customizations.Packages != nil {
|
||||
for _, p := range *request.Customizations.Packages {
|
||||
bp.Packages = append(bp.Packages, blueprint.Package{
|
||||
Name: p,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// imagerequest
|
||||
type imageRequest struct {
|
||||
manifest distro.Manifest
|
||||
arch string
|
||||
exports []string
|
||||
pipelineNames worker.PipelineNames
|
||||
}
|
||||
imageRequests := make([]imageRequest, len(request.ImageRequests))
|
||||
var targets []*target.Target
|
||||
|
||||
// use the same seed for all images so we get the same IDs
|
||||
bigSeed, err := rand.Int(rand.Reader, big.NewInt(math.MaxInt64))
|
||||
if err != nil {
|
||||
panic("cannot generate a manifest seed: " + err.Error())
|
||||
}
|
||||
manifestSeed := bigSeed.Int64()
|
||||
|
||||
for i, ir := range request.ImageRequests {
|
||||
arch, err := distribution.GetArch(ir.Architecture)
|
||||
if err != nil {
|
||||
return echo.NewHTTPError(http.StatusBadRequest, "Unsupported architecture '%s' for distribution '%s'", ir.Architecture, request.Distribution)
|
||||
}
|
||||
imageType, err := arch.GetImageType(ir.ImageType)
|
||||
if err != nil {
|
||||
return echo.NewHTTPError(http.StatusBadRequest, "Unsupported image type '%s' for %s/%s", ir.ImageType, ir.Architecture, request.Distribution)
|
||||
}
|
||||
repositories := make([]rpmmd.RepoConfig, len(ir.Repositories))
|
||||
for j, repo := range ir.Repositories {
|
||||
repositories[j].RHSM = repo.Rhsm
|
||||
|
||||
if repo.Baseurl != nil {
|
||||
repositories[j].BaseURL = *repo.Baseurl
|
||||
} else if repo.Mirrorlist != nil {
|
||||
repositories[j].MirrorList = *repo.Mirrorlist
|
||||
} else if repo.Metalink != nil {
|
||||
repositories[j].Metalink = *repo.Metalink
|
||||
} else {
|
||||
return echo.NewHTTPError(http.StatusBadRequest, "Must specify baseurl, mirrorlist, or metalink")
|
||||
}
|
||||
}
|
||||
|
||||
packageSets := imageType.PackageSets(bp)
|
||||
depsolveJobID, err := h.server.workers.EnqueueDepsolve(&worker.DepsolveJob{
|
||||
PackageSets: packageSets,
|
||||
Repos: repositories,
|
||||
ModulePlatformID: distribution.ModulePlatformID(),
|
||||
Arch: arch.Name(),
|
||||
Releasever: distribution.Releasever(),
|
||||
})
|
||||
if err != nil {
|
||||
return echo.NewHTTPError(http.StatusInternalServerError, "Unable to enqueue depsolve job")
|
||||
}
|
||||
|
||||
var depsolveResults worker.DepsolveJobResult
|
||||
for {
|
||||
status, _, err := h.server.workers.JobStatus(depsolveJobID, &depsolveResults)
|
||||
if err != nil {
|
||||
return echo.NewHTTPError(http.StatusInternalServerError, "Unable to get depsolve results")
|
||||
}
|
||||
if status.Canceled {
|
||||
return echo.NewHTTPError(http.StatusInternalServerError, "Depsolving job canceled unexpectedly")
|
||||
}
|
||||
if !status.Finished.IsZero() {
|
||||
break
|
||||
}
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
}
|
||||
|
||||
if depsolveResults.Error != "" {
|
||||
if depsolveResults.ErrorType == worker.DepsolveErrorType {
|
||||
return echo.NewHTTPError(http.StatusBadRequest, "Failed to depsolve requested package set: %s", depsolveResults.Error)
|
||||
}
|
||||
return echo.NewHTTPError(http.StatusInternalServerError, "Error while depsolving: %s", depsolveResults.Error)
|
||||
}
|
||||
pkgSpecSets := depsolveResults.PackageSpecs
|
||||
|
||||
imageOptions := distro.ImageOptions{Size: imageType.Size(0)}
|
||||
if request.Customizations != nil && request.Customizations.Subscription != nil {
|
||||
imageOptions.Subscription = &distro.SubscriptionImageOptions{
|
||||
Organization: fmt.Sprintf("%d", request.Customizations.Subscription.Organization),
|
||||
ActivationKey: request.Customizations.Subscription.ActivationKey,
|
||||
ServerUrl: request.Customizations.Subscription.ServerUrl,
|
||||
BaseUrl: request.Customizations.Subscription.BaseUrl,
|
||||
Insights: request.Customizations.Subscription.Insights,
|
||||
}
|
||||
}
|
||||
|
||||
// set default ostree ref, if one not provided
|
||||
ostreeOptions := ir.Ostree
|
||||
if ostreeOptions == nil || ostreeOptions.Ref == nil {
|
||||
imageOptions.OSTree = distro.OSTreeImageOptions{Ref: imageType.OSTreeRef()}
|
||||
} else if !ostree.VerifyRef(*ostreeOptions.Ref) {
|
||||
return echo.NewHTTPError(http.StatusBadRequest, "Invalid OSTree ref: %s", *ostreeOptions.Ref)
|
||||
} else {
|
||||
imageOptions.OSTree = distro.OSTreeImageOptions{Ref: *ostreeOptions.Ref}
|
||||
}
|
||||
|
||||
var parent string
|
||||
if ostreeOptions != nil && ostreeOptions.Url != nil {
|
||||
imageOptions.OSTree.URL = *ostreeOptions.Url
|
||||
parent, err = ostree.ResolveRef(imageOptions.OSTree.URL, imageOptions.OSTree.Ref)
|
||||
if err != nil {
|
||||
return echo.NewHTTPError(http.StatusBadRequest, "Error resolving OSTree repo %s: %s", imageOptions.OSTree.URL, err)
|
||||
}
|
||||
imageOptions.OSTree.Parent = parent
|
||||
}
|
||||
|
||||
// Set the blueprint customisation to take care of the user
|
||||
var blueprintCustoms *blueprint.Customizations
|
||||
if request.Customizations != nil && request.Customizations.Users != nil {
|
||||
var userCustomizations []blueprint.UserCustomization
|
||||
for _, user := range *request.Customizations.Users {
|
||||
var groups []string
|
||||
if user.Groups != nil {
|
||||
groups = *user.Groups
|
||||
} else {
|
||||
groups = nil
|
||||
}
|
||||
userCustomizations = append(userCustomizations,
|
||||
blueprint.UserCustomization{
|
||||
Name: user.Name,
|
||||
Key: user.Key,
|
||||
Groups: groups,
|
||||
},
|
||||
)
|
||||
}
|
||||
blueprintCustoms = &blueprint.Customizations{
|
||||
User: userCustomizations,
|
||||
}
|
||||
}
|
||||
|
||||
manifest, err := imageType.Manifest(blueprintCustoms, imageOptions, repositories, pkgSpecSets, manifestSeed)
|
||||
if err != nil {
|
||||
return echo.NewHTTPError(http.StatusBadRequest, "Failed to get manifest for for %s/%s/%s: %s", ir.ImageType, ir.Architecture, request.Distribution, err)
|
||||
}
|
||||
|
||||
imageRequests[i].manifest = manifest
|
||||
imageRequests[i].arch = arch.Name()
|
||||
imageRequests[i].exports = imageType.Exports()
|
||||
imageRequests[i].pipelineNames = worker.PipelineNames{
|
||||
Build: imageType.BuildPipelines(),
|
||||
Payload: imageType.PayloadPipelines(),
|
||||
}
|
||||
|
||||
uploadRequest := ir.UploadRequest
|
||||
/* oneOf is not supported by the openapi generator so marshal and unmarshal the uploadrequest based on the type */
|
||||
if uploadRequest.Type == UploadTypes_aws {
|
||||
var awsUploadOptions AWSUploadRequestOptions
|
||||
jsonUploadOptions, err := json.Marshal(uploadRequest.Options)
|
||||
if err != nil {
|
||||
return echo.NewHTTPError(http.StatusInternalServerError, "Unable to marshal aws upload request")
|
||||
}
|
||||
err = json.Unmarshal(jsonUploadOptions, &awsUploadOptions)
|
||||
if err != nil {
|
||||
return echo.NewHTTPError(http.StatusInternalServerError, "Unable to unmarshal aws upload request")
|
||||
}
|
||||
|
||||
var share []string
|
||||
if awsUploadOptions.Ec2.ShareWithAccounts != nil {
|
||||
share = *awsUploadOptions.Ec2.ShareWithAccounts
|
||||
}
|
||||
key := fmt.Sprintf("composer-api-%s", uuid.New().String())
|
||||
t := target.NewAWSTarget(&target.AWSTargetOptions{
|
||||
Filename: imageType.Filename(),
|
||||
Region: awsUploadOptions.Region,
|
||||
AccessKeyID: awsUploadOptions.S3.AccessKeyId,
|
||||
SecretAccessKey: awsUploadOptions.S3.SecretAccessKey,
|
||||
Bucket: awsUploadOptions.S3.Bucket,
|
||||
Key: key,
|
||||
ShareWithAccounts: share,
|
||||
})
|
||||
if awsUploadOptions.Ec2.SnapshotName != nil {
|
||||
t.ImageName = *awsUploadOptions.Ec2.SnapshotName
|
||||
} else {
|
||||
t.ImageName = key
|
||||
}
|
||||
|
||||
targets = append(targets, t)
|
||||
} else if uploadRequest.Type == UploadTypes_aws_s3 {
|
||||
var awsS3UploadOptions AWSS3UploadRequestOptions
|
||||
jsonUploadOptions, err := json.Marshal(uploadRequest.Options)
|
||||
if err != nil {
|
||||
return echo.NewHTTPError(http.StatusInternalServerError, "Unable to unmarshal aws upload request")
|
||||
}
|
||||
err = json.Unmarshal(jsonUploadOptions, &awsS3UploadOptions)
|
||||
if err != nil {
|
||||
return echo.NewHTTPError(http.StatusInternalServerError, "Unable to unmarshal aws upload request")
|
||||
}
|
||||
|
||||
key := fmt.Sprintf("composer-api-%s", uuid.New().String())
|
||||
t := target.NewAWSS3Target(&target.AWSS3TargetOptions{
|
||||
Filename: imageType.Filename(),
|
||||
Region: awsS3UploadOptions.Region,
|
||||
AccessKeyID: awsS3UploadOptions.S3.AccessKeyId,
|
||||
SecretAccessKey: awsS3UploadOptions.S3.SecretAccessKey,
|
||||
Bucket: awsS3UploadOptions.S3.Bucket,
|
||||
Key: key,
|
||||
})
|
||||
t.ImageName = key
|
||||
|
||||
targets = append(targets, t)
|
||||
} else if uploadRequest.Type == UploadTypes_gcp {
|
||||
var gcpUploadOptions GCPUploadRequestOptions
|
||||
jsonUploadOptions, err := json.Marshal(uploadRequest.Options)
|
||||
if err != nil {
|
||||
return echo.NewHTTPError(http.StatusInternalServerError, "Unable to marshal gcp upload request")
|
||||
}
|
||||
err = json.Unmarshal(jsonUploadOptions, &gcpUploadOptions)
|
||||
if err != nil {
|
||||
return echo.NewHTTPError(http.StatusInternalServerError, "Unable to unmarshal gcp upload request")
|
||||
}
|
||||
|
||||
var share []string
|
||||
if gcpUploadOptions.ShareWithAccounts != nil {
|
||||
share = *gcpUploadOptions.ShareWithAccounts
|
||||
}
|
||||
var region string
|
||||
if gcpUploadOptions.Region != nil {
|
||||
region = *gcpUploadOptions.Region
|
||||
}
|
||||
object := fmt.Sprintf("composer-api-%s", uuid.New().String())
|
||||
t := target.NewGCPTarget(&target.GCPTargetOptions{
|
||||
Filename: imageType.Filename(),
|
||||
Region: region,
|
||||
Os: "", // not exposed in cloudapi for now
|
||||
Bucket: gcpUploadOptions.Bucket,
|
||||
Object: object,
|
||||
ShareWithAccounts: share,
|
||||
})
|
||||
// Import will fail if an image with this name already exists
|
||||
if gcpUploadOptions.ImageName != nil {
|
||||
t.ImageName = *gcpUploadOptions.ImageName
|
||||
} else {
|
||||
t.ImageName = object
|
||||
}
|
||||
|
||||
targets = append(targets, t)
|
||||
} else if uploadRequest.Type == UploadTypes_azure {
|
||||
var azureUploadOptions AzureUploadRequestOptions
|
||||
jsonUploadOptions, err := json.Marshal(uploadRequest.Options)
|
||||
if err != nil {
|
||||
return echo.NewHTTPError(http.StatusInternalServerError, "Unable to marshal azure upload request")
|
||||
}
|
||||
err = json.Unmarshal(jsonUploadOptions, &azureUploadOptions)
|
||||
if err != nil {
|
||||
return echo.NewHTTPError(http.StatusInternalServerError, "Unable to unmarshal azure upload request")
|
||||
}
|
||||
t := target.NewAzureImageTarget(&target.AzureImageTargetOptions{
|
||||
Filename: imageType.Filename(),
|
||||
TenantID: azureUploadOptions.TenantId,
|
||||
Location: azureUploadOptions.Location,
|
||||
SubscriptionID: azureUploadOptions.SubscriptionId,
|
||||
ResourceGroup: azureUploadOptions.ResourceGroup,
|
||||
})
|
||||
|
||||
if azureUploadOptions.ImageName != nil {
|
||||
t.ImageName = *azureUploadOptions.ImageName
|
||||
} else {
|
||||
// if ImageName wasn't given, generate a random one
|
||||
t.ImageName = fmt.Sprintf("composer-api-%s", uuid.New().String())
|
||||
}
|
||||
|
||||
targets = append(targets, t)
|
||||
} else {
|
||||
return echo.NewHTTPError(http.StatusBadRequest, "Unknown upload request type, only 'aws', 'azure' and 'gcp' are supported")
|
||||
}
|
||||
}
|
||||
|
||||
var ir imageRequest
|
||||
if len(imageRequests) == 1 {
|
||||
// NOTE: the store currently does not support multi-image composes
|
||||
ir = imageRequests[0]
|
||||
} else {
|
||||
return echo.NewHTTPError(http.StatusBadRequest, "Only single-image composes are currently supported")
|
||||
}
|
||||
|
||||
id, err := h.server.workers.EnqueueOSBuild(ir.arch, &worker.OSBuildJob{
|
||||
Manifest: ir.manifest,
|
||||
Targets: targets,
|
||||
Exports: ir.exports,
|
||||
PipelineNames: &ir.pipelineNames,
|
||||
})
|
||||
if err != nil {
|
||||
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to enqueue manifest")
|
||||
}
|
||||
|
||||
var response ComposeResult
|
||||
response.Id = id.String()
|
||||
|
||||
return ctx.JSON(http.StatusCreated, response)
|
||||
}
|
||||
|
||||
// ComposeStatus handles a /compose/{id} GET request
|
||||
func (h *apiHandlers) ComposeStatus(ctx echo.Context, id string) error {
|
||||
jobId, err := uuid.Parse(id)
|
||||
if err != nil {
|
||||
return echo.NewHTTPError(http.StatusBadRequest, "Invalid format for parameter id: %s", err)
|
||||
}
|
||||
|
||||
var result worker.OSBuildJobResult
|
||||
status, _, err := h.server.workers.JobStatus(jobId, &result)
|
||||
if err != nil {
|
||||
return echo.NewHTTPError(http.StatusNotFound, "Job %s not found: %s", id, err)
|
||||
}
|
||||
|
||||
var us *UploadStatus
|
||||
if result.TargetResults != nil {
|
||||
// Only single upload target is allowed, therefore only a single upload target result is allowed as well
|
||||
if len(result.TargetResults) != 1 {
|
||||
return echo.NewHTTPError(http.StatusInternalServerError, "Job %s returned more upload target results than allowed", id)
|
||||
}
|
||||
tr := *result.TargetResults[0]
|
||||
|
||||
var uploadType UploadTypes
|
||||
var uploadOptions interface{}
|
||||
|
||||
switch tr.Name {
|
||||
case "org.osbuild.aws":
|
||||
uploadType = UploadTypes_aws
|
||||
awsOptions := tr.Options.(*target.AWSTargetResultOptions)
|
||||
uploadOptions = AWSUploadStatus{
|
||||
Ami: awsOptions.Ami,
|
||||
Region: awsOptions.Region,
|
||||
}
|
||||
case "org.osbuild.aws.s3":
|
||||
uploadType = UploadTypes_aws_s3
|
||||
awsOptions := tr.Options.(*target.AWSS3TargetResultOptions)
|
||||
uploadOptions = AWSS3UploadStatus{
|
||||
Url: awsOptions.URL,
|
||||
}
|
||||
case "org.osbuild.gcp":
|
||||
uploadType = UploadTypes_gcp
|
||||
gcpOptions := tr.Options.(*target.GCPTargetResultOptions)
|
||||
uploadOptions = GCPUploadStatus{
|
||||
ImageName: gcpOptions.ImageName,
|
||||
ProjectId: gcpOptions.ProjectID,
|
||||
}
|
||||
case "org.osbuild.azure.image":
|
||||
uploadType = UploadTypes_azure
|
||||
gcpOptions := tr.Options.(*target.AzureImageTargetResultOptions)
|
||||
uploadOptions = AzureUploadStatus{
|
||||
ImageName: gcpOptions.ImageName,
|
||||
}
|
||||
default:
|
||||
return echo.NewHTTPError(http.StatusInternalServerError, "Job %s returned unknown upload target results %s", id, tr.Name)
|
||||
}
|
||||
|
||||
us = &UploadStatus{
|
||||
Status: result.UploadStatus,
|
||||
Type: uploadType,
|
||||
Options: uploadOptions,
|
||||
}
|
||||
}
|
||||
|
||||
response := ComposeStatus{
|
||||
ImageStatus: ImageStatus{
|
||||
Status: composeStatusFromJobStatus(status, &result),
|
||||
UploadStatus: us,
|
||||
},
|
||||
}
|
||||
return ctx.JSON(http.StatusOK, response)
|
||||
}
|
||||
|
||||
func composeStatusFromJobStatus(js *worker.JobStatus, result *worker.OSBuildJobResult) ImageStatusValue {
|
||||
if js.Canceled {
|
||||
return ImageStatusValue_failure
|
||||
}
|
||||
|
||||
if js.Started.IsZero() {
|
||||
return ImageStatusValue_pending
|
||||
}
|
||||
|
||||
if js.Finished.IsZero() {
|
||||
// TODO: handle also ImageStatusValue_uploading
|
||||
// TODO: handle also ImageStatusValue_registering
|
||||
return ImageStatusValue_building
|
||||
}
|
||||
|
||||
if result.Success {
|
||||
return ImageStatusValue_success
|
||||
}
|
||||
|
||||
return ImageStatusValue_failure
|
||||
}
|
||||
|
||||
// GetOpenapiJson handles a /openapi.json GET request
|
||||
func (h *apiHandlers) GetOpenapiJson(ctx echo.Context) error {
|
||||
spec, err := GetSwagger()
|
||||
if err != nil {
|
||||
return echo.NewHTTPError(http.StatusInternalServerError, "Could not load openapi spec")
|
||||
}
|
||||
return ctx.JSON(http.StatusOK, spec)
|
||||
}
|
||||
|
||||
// GetVersion handles a /version GET request
|
||||
func (h *apiHandlers) GetVersion(ctx echo.Context) error {
|
||||
spec, err := GetSwagger()
|
||||
if err != nil {
|
||||
return echo.NewHTTPError(http.StatusInternalServerError, "Could not load version")
|
||||
}
|
||||
version := Version{spec.Info.Version}
|
||||
return ctx.JSON(http.StatusOK, version)
|
||||
}
|
||||
|
||||
// ComposeMetadata handles a /compose/{id}/metadata GET request
|
||||
func (h *apiHandlers) ComposeMetadata(ctx echo.Context, id string) error {
|
||||
jobId, err := uuid.Parse(id)
|
||||
if err != nil {
|
||||
return echo.NewHTTPError(http.StatusBadRequest, "Invalid format for parameter id: %s", err)
|
||||
}
|
||||
|
||||
var result worker.OSBuildJobResult
|
||||
status, _, err := h.server.workers.JobStatus(jobId, &result)
|
||||
if err != nil {
|
||||
return echo.NewHTTPError(http.StatusNotFound, "Job %s not found: %s", id, err)
|
||||
}
|
||||
|
||||
if result.OSBuildOutput == nil || len(result.OSBuildOutput.Log) == 0 {
|
||||
// no data to work with; parse error
|
||||
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to read metadata for job %s", id)
|
||||
}
|
||||
|
||||
var job worker.OSBuildJob
|
||||
if _, _, _, err = h.server.workers.Job(jobId, &job); err != nil {
|
||||
return echo.NewHTTPError(http.StatusNotFound, "Job %s not found: %s", id, err)
|
||||
}
|
||||
|
||||
if status.Finished.IsZero() {
|
||||
// job still running: empty response
|
||||
return ctx.JSON(200, ComposeMetadata{})
|
||||
}
|
||||
|
||||
var ostreeCommitMetadata *osbuild.OSTreeCommitStageMetadata
|
||||
var rpmStagesMd []osbuild.RPMStageMetadata // collect rpm stage metadata from payload pipelines
|
||||
for _, plName := range job.PipelineNames.Payload {
|
||||
plMd, hasMd := result.OSBuildOutput.Metadata[plName]
|
||||
if !hasMd {
|
||||
continue
|
||||
}
|
||||
for _, stageMd := range plMd {
|
||||
switch md := stageMd.(type) {
|
||||
case *osbuild.RPMStageMetadata:
|
||||
rpmStagesMd = append(rpmStagesMd, *md)
|
||||
case *osbuild.OSTreeCommitStageMetadata:
|
||||
ostreeCommitMetadata = md
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
packages := stagesToPackageMetadata(rpmStagesMd)
|
||||
|
||||
resp := new(ComposeMetadata)
|
||||
resp.Packages = &packages
|
||||
|
||||
if ostreeCommitMetadata != nil {
|
||||
resp.OstreeCommit = &ostreeCommitMetadata.Compose.OSTreeCommit
|
||||
}
|
||||
|
||||
return ctx.JSON(200, resp)
|
||||
}
|
||||
|
||||
func stagesToPackageMetadata(stages []osbuild.RPMStageMetadata) []PackageMetadata {
|
||||
packages := make([]PackageMetadata, 0)
|
||||
for _, md := range stages {
|
||||
for _, rpm := range md.Packages {
|
||||
packages = append(packages,
|
||||
PackageMetadata{
|
||||
Type: "rpm",
|
||||
Name: rpm.Name,
|
||||
Version: rpm.Version,
|
||||
Release: rpm.Release,
|
||||
Epoch: rpm.Epoch,
|
||||
Arch: rpm.Arch,
|
||||
Sigmd5: rpm.SigMD5,
|
||||
Signature: rpmmd.PackageMetadataToSignature(rpm),
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
return packages
|
||||
}
|
||||
|
|
@ -107,7 +107,6 @@ Obsoletes: osbuild-composer-koji <= 23
|
|||
# generated code compatible by applying some sed magic.
|
||||
#
|
||||
# Remove when F33 is EOL
|
||||
sed -i "s/openapi3.Swagger/openapi3.T/;s/openapi3.NewSwaggerLoader().LoadSwaggerFromData/openapi3.NewLoader().LoadFromData/" internal/cloudapi/v1/openapi.v1.gen.go
|
||||
sed -i "s/openapi3.Swagger/openapi3.T/;s/openapi3.NewSwaggerLoader().LoadSwaggerFromData/openapi3.NewLoader().LoadFromData/" internal/cloudapi/v2/openapi.v2.gen.go
|
||||
sed -i "s/openapi3.Swagger/openapi3.T/;s/openapi3.NewSwaggerLoader().LoadSwaggerFromData/openapi3.NewLoader().LoadFromData/" internal/worker/api/api.gen.go
|
||||
%endif
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue