diff --git a/go/models/deploy.go b/go/models/deploy.go index b9ad2336..8f82b160 100644 --- a/go/models/deploy.go +++ b/go/models/deploy.go @@ -81,6 +81,11 @@ type Deploy struct { // required Required []string `json:"required"` + // An array of code_shas for the edge-function bundles that need to be uploaded to + // complete the deploy. + // + RequiredEdgeFunctions []string `json:"required_edge_functions"` + // required functions RequiredFunctions []string `json:"required_functions"` diff --git a/go/models/deploy_files.go b/go/models/deploy_files.go index 9afba55f..d74bb09b 100644 --- a/go/models/deploy_files.go +++ b/go/models/deploy_files.go @@ -33,6 +33,11 @@ type DeployFiles struct { // draft Draft bool `json:"draft,omitempty"` + // A hash mapping edge-function bundle formats to the code_sha of each bundle. The + // response's required_edge_functions lists which of these still need to be uploaded. + // + EdgeFunctions interface{} `json:"edge_functions,omitempty"` + // A list of deploy-specific environment variable data. Data specified this way applies only // to this specific deploy and is merged into any existing environment variables set on the // account and site. diff --git a/go/plumbing/operations/operations_client.go b/go/plumbing/operations/operations_client.go index 742a19ff..d985cc5b 100644 --- a/go/plumbing/operations/operations_client.go +++ b/go/plumbing/operations/operations_client.go @@ -381,6 +381,8 @@ type ClientService interface { UpdateSplitTest(params *UpdateSplitTestParams, authInfo runtime.ClientAuthInfoWriter) (*UpdateSplitTestCreated, error) + UploadDeployEdgeFunction(params *UploadDeployEdgeFunctionParams, authInfo runtime.ClientAuthInfoWriter) (*UploadDeployEdgeFunctionOK, error) + UploadDeployFile(params *UploadDeployFileParams, authInfo runtime.ClientAuthInfoWriter) (*UploadDeployFileOK, error) UploadDeployFunction(params *UploadDeployFunctionParams, authInfo runtime.ClientAuthInfoWriter) (*UploadDeployFunctionOK, error) @@ -6434,6 +6436,40 @@ func (a *Client) UpdateSplitTest(params *UpdateSplitTestParams, authInfo runtime return nil, runtime.NewAPIError("unexpected success response: content available as default response in error", unexpectedSuccess, unexpectedSuccess.Code()) } +/* +UploadDeployEdgeFunction upload deploy edge function API +*/ +func (a *Client) UploadDeployEdgeFunction(params *UploadDeployEdgeFunctionParams, authInfo runtime.ClientAuthInfoWriter) (*UploadDeployEdgeFunctionOK, error) { + // TODO: Validate the params before sending + if params == nil { + params = NewUploadDeployEdgeFunctionParams() + } + + result, err := a.transport.Submit(&runtime.ClientOperation{ + ID: "uploadDeployEdgeFunction", + Method: "PUT", + PathPattern: "/deploys/{deploy_id}/edge_functions/{code_sha}", + ProducesMediaTypes: []string{"application/json"}, + ConsumesMediaTypes: []string{"application/octet-stream"}, + Schemes: []string{"https"}, + Params: params, + Reader: &UploadDeployEdgeFunctionReader{formats: a.formats}, + AuthInfo: authInfo, + Context: params.Context, + Client: params.HTTPClient, + }) + if err != nil { + return nil, err + } + success, ok := result.(*UploadDeployEdgeFunctionOK) + if ok { + return success, nil + } + // unexpected success response + unexpectedSuccess := result.(*UploadDeployEdgeFunctionDefault) + return nil, runtime.NewAPIError("unexpected success response: content available as default response in error", unexpectedSuccess, unexpectedSuccess.Code()) +} + /* UploadDeployFile upload deploy file API */ diff --git a/go/plumbing/operations/upload_deploy_edge_function_parameters.go b/go/plumbing/operations/upload_deploy_edge_function_parameters.go new file mode 100644 index 00000000..bdcd049f --- /dev/null +++ b/go/plumbing/operations/upload_deploy_edge_function_parameters.go @@ -0,0 +1,194 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package operations + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "context" + "io" + "net/http" + "time" + + "github.com/go-openapi/errors" + "github.com/go-openapi/runtime" + cr "github.com/go-openapi/runtime/client" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" +) + +// NewUploadDeployEdgeFunctionParams creates a new UploadDeployEdgeFunctionParams object +// with the default values initialized. +func NewUploadDeployEdgeFunctionParams() *UploadDeployEdgeFunctionParams { + var () + return &UploadDeployEdgeFunctionParams{ + + timeout: cr.DefaultTimeout, + } +} + +// NewUploadDeployEdgeFunctionParamsWithTimeout creates a new UploadDeployEdgeFunctionParams object +// with the default values initialized, and the ability to set a timeout on a request +func NewUploadDeployEdgeFunctionParamsWithTimeout(timeout time.Duration) *UploadDeployEdgeFunctionParams { + var () + return &UploadDeployEdgeFunctionParams{ + + timeout: timeout, + } +} + +// NewUploadDeployEdgeFunctionParamsWithContext creates a new UploadDeployEdgeFunctionParams object +// with the default values initialized, and the ability to set a context for a request +func NewUploadDeployEdgeFunctionParamsWithContext(ctx context.Context) *UploadDeployEdgeFunctionParams { + var () + return &UploadDeployEdgeFunctionParams{ + + Context: ctx, + } +} + +// NewUploadDeployEdgeFunctionParamsWithHTTPClient creates a new UploadDeployEdgeFunctionParams object +// with the default values initialized, and the ability to set a custom HTTPClient for a request +func NewUploadDeployEdgeFunctionParamsWithHTTPClient(client *http.Client) *UploadDeployEdgeFunctionParams { + var () + return &UploadDeployEdgeFunctionParams{ + HTTPClient: client, + } +} + +/* +UploadDeployEdgeFunctionParams contains all the parameters to send to the API endpoint +for the upload deploy edge function operation typically these are written to a http.Request +*/ +type UploadDeployEdgeFunctionParams struct { + + /*XNfRetryCount*/ + XNfRetryCount *int64 + /*CodeSha*/ + CodeSha string + /*DeployID*/ + DeployID string + /*FileBody*/ + FileBody io.ReadCloser + + timeout time.Duration + Context context.Context + HTTPClient *http.Client +} + +// WithTimeout adds the timeout to the upload deploy edge function params +func (o *UploadDeployEdgeFunctionParams) WithTimeout(timeout time.Duration) *UploadDeployEdgeFunctionParams { + o.SetTimeout(timeout) + return o +} + +// SetTimeout adds the timeout to the upload deploy edge function params +func (o *UploadDeployEdgeFunctionParams) SetTimeout(timeout time.Duration) { + o.timeout = timeout +} + +// WithContext adds the context to the upload deploy edge function params +func (o *UploadDeployEdgeFunctionParams) WithContext(ctx context.Context) *UploadDeployEdgeFunctionParams { + o.SetContext(ctx) + return o +} + +// SetContext adds the context to the upload deploy edge function params +func (o *UploadDeployEdgeFunctionParams) SetContext(ctx context.Context) { + o.Context = ctx +} + +// WithHTTPClient adds the HTTPClient to the upload deploy edge function params +func (o *UploadDeployEdgeFunctionParams) WithHTTPClient(client *http.Client) *UploadDeployEdgeFunctionParams { + o.SetHTTPClient(client) + return o +} + +// SetHTTPClient adds the HTTPClient to the upload deploy edge function params +func (o *UploadDeployEdgeFunctionParams) SetHTTPClient(client *http.Client) { + o.HTTPClient = client +} + +// WithXNfRetryCount adds the xNfRetryCount to the upload deploy edge function params +func (o *UploadDeployEdgeFunctionParams) WithXNfRetryCount(xNfRetryCount *int64) *UploadDeployEdgeFunctionParams { + o.SetXNfRetryCount(xNfRetryCount) + return o +} + +// SetXNfRetryCount adds the xNfRetryCount to the upload deploy edge function params +func (o *UploadDeployEdgeFunctionParams) SetXNfRetryCount(xNfRetryCount *int64) { + o.XNfRetryCount = xNfRetryCount +} + +// WithCodeSha adds the codeSha to the upload deploy edge function params +func (o *UploadDeployEdgeFunctionParams) WithCodeSha(codeSha string) *UploadDeployEdgeFunctionParams { + o.SetCodeSha(codeSha) + return o +} + +// SetCodeSha adds the codeSha to the upload deploy edge function params +func (o *UploadDeployEdgeFunctionParams) SetCodeSha(codeSha string) { + o.CodeSha = codeSha +} + +// WithDeployID adds the deployID to the upload deploy edge function params +func (o *UploadDeployEdgeFunctionParams) WithDeployID(deployID string) *UploadDeployEdgeFunctionParams { + o.SetDeployID(deployID) + return o +} + +// SetDeployID adds the deployId to the upload deploy edge function params +func (o *UploadDeployEdgeFunctionParams) SetDeployID(deployID string) { + o.DeployID = deployID +} + +// WithFileBody adds the fileBody to the upload deploy edge function params +func (o *UploadDeployEdgeFunctionParams) WithFileBody(fileBody io.ReadCloser) *UploadDeployEdgeFunctionParams { + o.SetFileBody(fileBody) + return o +} + +// SetFileBody adds the fileBody to the upload deploy edge function params +func (o *UploadDeployEdgeFunctionParams) SetFileBody(fileBody io.ReadCloser) { + o.FileBody = fileBody +} + +// WriteToRequest writes these params to a swagger request +func (o *UploadDeployEdgeFunctionParams) WriteToRequest(r runtime.ClientRequest, reg strfmt.Registry) error { + + if err := r.SetTimeout(o.timeout); err != nil { + return err + } + var res []error + + if o.XNfRetryCount != nil { + + // header param X-Nf-Retry-Count + if err := r.SetHeaderParam("X-Nf-Retry-Count", swag.FormatInt64(*o.XNfRetryCount)); err != nil { + return err + } + + } + + // path param code_sha + if err := r.SetPathParam("code_sha", o.CodeSha); err != nil { + return err + } + + // path param deploy_id + if err := r.SetPathParam("deploy_id", o.DeployID); err != nil { + return err + } + + if o.FileBody != nil { + if err := r.SetBodyParam(o.FileBody); err != nil { + return err + } + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} diff --git a/go/plumbing/operations/upload_deploy_edge_function_responses.go b/go/plumbing/operations/upload_deploy_edge_function_responses.go new file mode 100644 index 00000000..1e23196c --- /dev/null +++ b/go/plumbing/operations/upload_deploy_edge_function_responses.go @@ -0,0 +1,107 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package operations + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "fmt" + "io" + + "github.com/go-openapi/runtime" + "github.com/go-openapi/strfmt" + + "github.com/netlify/open-api/v2/go/models" +) + +// UploadDeployEdgeFunctionReader is a Reader for the UploadDeployEdgeFunction structure. +type UploadDeployEdgeFunctionReader struct { + formats strfmt.Registry +} + +// ReadResponse reads a server response into the received o. +func (o *UploadDeployEdgeFunctionReader) ReadResponse(response runtime.ClientResponse, consumer runtime.Consumer) (interface{}, error) { + switch response.Code() { + case 200: + result := NewUploadDeployEdgeFunctionOK() + if err := result.readResponse(response, consumer, o.formats); err != nil { + return nil, err + } + return result, nil + default: + result := NewUploadDeployEdgeFunctionDefault(response.Code()) + if err := result.readResponse(response, consumer, o.formats); err != nil { + return nil, err + } + if response.Code()/100 == 2 { + return result, nil + } + return nil, result + } +} + +// NewUploadDeployEdgeFunctionOK creates a UploadDeployEdgeFunctionOK with default headers values +func NewUploadDeployEdgeFunctionOK() *UploadDeployEdgeFunctionOK { + return &UploadDeployEdgeFunctionOK{} +} + +/* +UploadDeployEdgeFunctionOK handles this case with default header values. + +OK +*/ +type UploadDeployEdgeFunctionOK struct { +} + +func (o *UploadDeployEdgeFunctionOK) Error() string { + return fmt.Sprintf("[PUT /deploys/{deploy_id}/edge_functions/{code_sha}][%d] uploadDeployEdgeFunctionOK ", 200) +} + +func (o *UploadDeployEdgeFunctionOK) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { + + return nil +} + +// NewUploadDeployEdgeFunctionDefault creates a UploadDeployEdgeFunctionDefault with default headers values +func NewUploadDeployEdgeFunctionDefault(code int) *UploadDeployEdgeFunctionDefault { + return &UploadDeployEdgeFunctionDefault{ + _statusCode: code, + } +} + +/* +UploadDeployEdgeFunctionDefault handles this case with default header values. + +error +*/ +type UploadDeployEdgeFunctionDefault struct { + _statusCode int + + Payload *models.Error +} + +// Code gets the status code for the upload deploy edge function default response +func (o *UploadDeployEdgeFunctionDefault) Code() int { + return o._statusCode +} + +func (o *UploadDeployEdgeFunctionDefault) Error() string { + return fmt.Sprintf("[PUT /deploys/{deploy_id}/edge_functions/{code_sha}][%d] uploadDeployEdgeFunction default %+v", o._statusCode, o.Payload) +} + +func (o *UploadDeployEdgeFunctionDefault) GetPayload() *models.Error { + return o.Payload +} + +func (o *UploadDeployEdgeFunctionDefault) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { + + o.Payload = new(models.Error) + + // response payload + if err := consumer.Consume(response.Body(), o.Payload); err != nil && err != io.EOF { + return err + } + + return nil +} diff --git a/go/porcelain/deploy.go b/go/porcelain/deploy.go index ac0761be..038e488b 100644 --- a/go/porcelain/deploy.go +++ b/go/porcelain/deploy.go @@ -41,6 +41,7 @@ const ( fileUpload uploadType = iota functionUpload + edgeFunctionUpload lfsVersionString = "version https://git-lfs.github.com/spec/v1" @@ -105,6 +106,7 @@ type DeployOptions struct { files *deployFiles functions *deployFiles + edgeFunctions *deployFiles functionSchedules []*models.FunctionSchedule functionsConfig map[string]models.FunctionConfig } @@ -320,6 +322,15 @@ func (n *Netlify) DoDeploy(ctx context.Context, options *DeployOptions, deploy * options.functionSchedules = schedules options.functionsConfig = functionsConfig + edgeFunctions, err := bundleEdgeFunctions(ctx, options.EdgeFunctionsDir, options.Observer) + if err != nil { + if options.Observer != nil { + options.Observer.OnFailedWalk() + } + return nil, err + } + options.edgeFunctions = edgeFunctions + deployFiles := &models.DeployFiles{ Files: options.files.Sums, Draft: options.IsDraft, @@ -330,6 +341,9 @@ func (n *Netlify) DoDeploy(ctx context.Context, options *DeployOptions, deploy * if options.functions != nil { deployFiles.Functions = options.functions.Sums } + if options.edgeFunctions != nil { + deployFiles.EdgeFunctions = options.edgeFunctions.Sums + } if len(options.Environment) > 0 { deployFiles.Environment = options.Environment @@ -411,7 +425,7 @@ func (n *Netlify) DoDeploy(ctx context.Context, options *DeployOptions, deploy * } } - if len(deploy.Required) == 0 && len(deploy.RequiredFunctions) == 0 { + if len(deploy.Required) == 0 && len(deploy.RequiredFunctions) == 0 && len(deploy.RequiredEdgeFunctions) == 0 { return deploy, nil } @@ -427,6 +441,12 @@ func (n *Netlify) DoDeploy(ctx context.Context, options *DeployOptions, deploy * } } + if options.edgeFunctions != nil { + if err := n.uploadFiles(ctx, deploy, options.edgeFunctions, options.Observer, edgeFunctionUpload, options.UploadTimeout, skipRetry); err != nil { + return nil, err + } + } + return deploy, nil } @@ -490,6 +510,8 @@ func (n *Netlify) uploadFiles(ctx context.Context, d *models.Deploy, files *depl required = d.Required case functionUpload: required = d.RequiredFunctions + case edgeFunctionUpload: + required = d.RequiredEdgeFunctions } count := 0 @@ -618,6 +640,20 @@ func (n *Netlify) uploadFile(ctx context.Context, d *models.Deploy, f *FileBundl } _, operationError = n.Operations.UploadDeployFunction(params, authInfo) } + case edgeFunctionUpload: + var body io.ReadCloser + body, operationError = os.Open(f.Path) + if operationError == nil { + defer body.Close() + params := operations.NewUploadDeployEdgeFunctionParams().WithDeployID(d.ID).WithCodeSha(f.Sum).WithFileBody(body) + if retryCount > 0 { + params = params.WithXNfRetryCount(&retryCount) + } + if timeout != 0 { + params.SetTimeout(timeout) + } + _, operationError = n.Operations.UploadDeployEdgeFunction(params, authInfo) + } } if operationError != nil { @@ -1077,6 +1113,67 @@ func zipFunctionFile(filePath string, i os.FileInfo, runtime string, tmpDir *laz }, nil } +// bundleEdgeFunctions reads the edge-bundler manifest from edgeFunctionsDir and turns each bundle it +// lists into an uploadable FileBundle. The deploy declares these as its edge_functions map +// ({format => code_sha}); the server replies with the subset (required_edge_functions) not already +// stored, and only those are streamed up. A missing manifest means no edge functions to upload. +func bundleEdgeFunctions(ctx context.Context, edgeFunctionsDir string, observer DeployObserver) (*deployFiles, error) { + if edgeFunctionsDir == "" { + return nil, nil + } + + manifestBytes, err := os.ReadFile(filepath.Join(edgeFunctionsDir, "manifest.json")) + if os.IsNotExist(err) { + return nil, nil + } + if err != nil { + return nil, err + } + + context.GetLogger(ctx).Debug("Found edge functions manifest file") + + var manifest edgeFunctionsManifest + if err := json.Unmarshal(manifestBytes, &manifest); err != nil { + return nil, fmt.Errorf("malformed edge functions manifest file: %w", err) + } + + if len(manifest.Bundles) == 0 { + return nil, nil + } + + files := newDeployFiles() + for _, bundle := range manifest.Bundles { + file, err := newEdgeFunctionFile(edgeFunctionsDir, bundle) + if err != nil { + return nil, err + } + files.Add(file.Name, file) + + if observer != nil { + if err := observer.OnSuccessfulStep(file); err != nil { + return nil, err + } + } + } + + return files, nil +} + +func newEdgeFunctionFile(edgeFunctionsDir string, bundle edgeFunctionsManifestBundle) (*FileBundle, error) { + path := filepath.Join(edgeFunctionsDir, bundle.Asset) + + // code_sha is the dedup key in the deployer<->functions-origin contract, so we compute it from the + // bundle's bytes rather than trusting the edge-bundler's asset filename (which currently also happens + // to be the sha256, but that's a bundler implementation detail). createFileBundleWithHasher streams + // the bytes through the hasher, so the bundle is never held in memory. + file, err := createFileBundleWithHasher(bundle.Format, path, sha256.New()) + if err != nil { + return nil, fmt.Errorf("edge functions manifest specifies a bundle that cannot be read: %s: %w", bundle.Asset, err) + } + + return file, nil +} + func zipFile(i os.FileInfo) bool { return filepath.Ext(i.Name()) == ".zip" } diff --git a/go/porcelain/deploy_test.go b/go/porcelain/deploy_test.go index 869c68f2..7887cbe9 100644 --- a/go/porcelain/deploy_test.go +++ b/go/porcelain/deploy_test.go @@ -3,6 +3,8 @@ package porcelain import ( "bytes" gocontext "context" + "crypto/sha256" + "encoding/hex" "fmt" "io" "io/ioutil" @@ -783,6 +785,88 @@ func TestUploadFunctions_RetryCountHeader(t *testing.T) { require.NoError(t, client.uploadFiles(apiCtx, d, files, nil, functionUpload, time.Minute, false)) } +func TestBundleEdgeFunctions(t *testing.T) { + dir := t.TempDir() + require.NoError(t, os.WriteFile(filepath.Join(dir, "aaa111.eszip"), []byte("eszip-bundle"), 0644)) + require.NoError(t, os.WriteFile(filepath.Join(dir, "bbb222.tar.gz"), []byte("tar-bundle"), 0644)) + // A realistic edge-bundler manifest: an eszip plus a tar bundle (asset names are "", + // e.g. ".eszip" / ".tar.gz"). porcelain declares every bundle regardless of format; restricting to a + // format (today, tar) is bitballoon's job, not the client's. + manifest := `{ + "bundles": [ + { "asset": "aaa111.eszip", "format": "eszip2" }, + { "asset": "bbb222.tar.gz", "format": "tar" } + ] + }` + require.NoError(t, os.WriteFile(filepath.Join(dir, "manifest.json"), []byte(manifest), 0644)) + + files, err := bundleEdgeFunctions(gocontext.Background(), dir, mockObserver{}) + require.NoError(t, err) + + // The declared edge_functions map keys each bundle by its format, with the code_sha computed by the + // deployer from the bundle bytes (not parsed from the asset filename). + eszipSha := sha256.Sum256([]byte("eszip-bundle")) + tarSha := sha256.Sum256([]byte("tar-bundle")) + require.Equal(t, map[string]string{ + "eszip2": hex.EncodeToString(eszipSha[:]), + "tar": hex.EncodeToString(tarSha[:]), + }, files.Sums) + + // Bundles are tracked by Path and streamed at upload time; they are never buffered into memory. + require.Equal(t, filepath.Join(dir, "bbb222.tar.gz"), files.Files["tar"].Path) + require.Nil(t, files.Files["tar"].Buffer) +} + +func TestBundleEdgeFunctions_NoManifest(t *testing.T) { + // No edge functions dir configured. + files, err := bundleEdgeFunctions(gocontext.Background(), "", mockObserver{}) + require.NoError(t, err) + require.Nil(t, files) + + // A dir without a manifest.json yields no edge functions rather than an error. + files, err = bundleEdgeFunctions(gocontext.Background(), t.TempDir(), mockObserver{}) + require.NoError(t, err) + require.Nil(t, files) +} + +func TestUploadEdgeFunctions(t *testing.T) { + ctx, cancel := gocontext.WithCancel(gocontext.Background()) + t.Cleanup(cancel) + + bundleBody := []byte("baked-tar-bundle-bytes") + + var gotPath string + var gotBody []byte + server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + gotPath = req.URL.Path + gotBody, _ = io.ReadAll(req.Body) + rw.WriteHeader(http.StatusOK) + })) + defer server.Close() + + hu, _ := url.Parse(server.URL) + tr := apiClient.NewWithClient(hu.Host, "/api/v1", []string{"http"}, http.DefaultClient) + client := NewRetryable(tr, strfmt.Default, 1) + client.uploadLimit = 1 + apiCtx := context.WithAuthInfo(ctx, apiClient.BearerToken("token")) + + dir := t.TempDir() + require.NoError(t, os.WriteFile(filepath.Join(dir, "edgecodesha.tar.gz"), bundleBody, 0644)) + manifest := `{ "bundles": [ { "asset": "edgecodesha.tar.gz", "format": "tar" } ] }` + require.NoError(t, os.WriteFile(filepath.Join(dir, "manifest.json"), []byte(manifest), 0644)) + + files, err := bundleEdgeFunctions(gocontext.Background(), dir, mockObserver{}) + require.NoError(t, err) + + codeSha := files.Sums["tar"] + d := &models.Deploy{ID: "deploy-id", RequiredEdgeFunctions: []string{codeSha}} + + require.NoError(t, client.uploadFiles(apiCtx, d, files, nil, edgeFunctionUpload, time.Minute, false)) + + require.Equal(t, "/api/v1/deploys/deploy-id/edge_functions/"+codeSha, gotPath) + require.Equal(t, bundleBody, gotBody) +} + func TestBundle(t *testing.T) { functions, schedules, functionsConfig, err := bundle(gocontext.Background(), "../internal/data", &lazyTempDir{root: t.TempDir()}, mockObserver{}) diff --git a/go/porcelain/edge_functions_manifest.go b/go/porcelain/edge_functions_manifest.go new file mode 100644 index 00000000..0a6c48e4 --- /dev/null +++ b/go/porcelain/edge_functions_manifest.go @@ -0,0 +1,11 @@ +package porcelain + +// https://github.com/netlify/build/blob/main/packages/edge-bundler/node/manifest.ts +type edgeFunctionsManifest struct { + Bundles []edgeFunctionsManifestBundle `json:"bundles"` +} + +type edgeFunctionsManifestBundle struct { + Asset string `json:"asset"` + Format string `json:"format"` +} diff --git a/swagger.yml b/swagger.yml index 8971a2c9..7534acba 100644 --- a/swagger.yml +++ b/swagger.yml @@ -1665,6 +1665,33 @@ paths: $ref: '#/definitions/function' default: $ref: '#/responses/error' + /deploys/{deploy_id}/edge_functions/{code_sha}: + put: + operationId: uploadDeployEdgeFunction + tags: [edgeFunction] + consumes: + - application/octet-stream + parameters: + - name: deploy_id + type: string + in: path + required: true + - name: code_sha + type: string + in: path + required: true + - name: file_body + in: body + schema: + type: string + format: binary + required: true + - $ref: '#/parameters/retryCount' + responses: + '200': + description: OK + default: + $ref: '#/responses/error' /sites/{site_id}/plugins/{package}: put: x-internal: true @@ -4743,6 +4770,13 @@ definitions: type: array items: type: string + required_edge_functions: + type: array + items: + type: string + description: | + An array of code_shas for the edge-function bundles that need to be uploaded to + complete the deploy. error_message: type: string branch: @@ -4842,6 +4876,11 @@ definitions: type: boolean functions: type: object + edge_functions: + type: object + description: | + A hash mapping edge-function bundle formats to the code_sha of each bundle. The + response's required_edge_functions lists which of these still need to be uploaded. function_schedules: type: array items: