Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: deployment config for expected params and targets #1214

Merged
merged 7 commits into from
Dec 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 17 additions & 7 deletions api/build/compile_publish.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,14 @@ import (

// CompileAndPublishConfig is a struct that contains information for the CompileAndPublish function.
type CompileAndPublishConfig struct {
Build *types.Build
Metadata *internal.Metadata
BaseErr string
Source string
Comment string
Labels []string
Retries int
Build *types.Build
Deployment *types.Deployment
Metadata *internal.Metadata
BaseErr string
Source string
Comment string
Labels []string
Retries int
}

// CompileAndPublish is a helper function to generate the queue items for a build. It takes a form
Expand Down Expand Up @@ -307,6 +308,15 @@ func CompileAndPublish(
errors.New(skip)
}

// validate deployment config
if (b.GetEvent() == constants.EventDeploy) && cfg.Deployment != nil {
if err := p.Deployment.Validate(cfg.Deployment.GetTarget(), cfg.Deployment.GetPayload()); err != nil {
retErr := fmt.Errorf("%s: failed to validate deployment for %s: %w", baseErr, repo.GetFullName(), err)

return nil, nil, http.StatusBadRequest, retErr
}
}

// check if the pipeline did not already exist in the database
if pipeline == nil {
pipeline = compiled
Expand Down
2 changes: 1 addition & 1 deletion api/deployment/get.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ func GetDeployment(c *gin.Context) {
}

// send API call to database to capture the deployment
d, err := database.FromContext(c).GetDeployment(ctx, int64(number))
d, err := database.FromContext(c).GetDeploymentForRepo(ctx, r, int64(number))
if err != nil {
// send API call to SCM to capture the deployment
d, err = scm.FromContext(c).GetDeployment(ctx, u, r, int64(number))
Expand Down
128 changes: 128 additions & 0 deletions api/deployment/get_config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
// SPDX-License-Identifier: Apache-2.0

package deployment

import (
"errors"
"fmt"
"net/http"

"github.com/gin-gonic/gin"
"github.com/sirupsen/logrus"
"gorm.io/gorm"

"github.com/go-vela/server/compiler"
"github.com/go-vela/server/database"
"github.com/go-vela/server/router/middleware/repo"
"github.com/go-vela/server/router/middleware/user"
"github.com/go-vela/server/scm"
"github.com/go-vela/server/util"
)

// swagger:operation GET /api/v1/deployments/{org}/{repo}/config deployments GetDeploymentConfig
//
// Get a deployment config
//
// ---
// produces:
// - application/json
// parameters:
// - in: path
// name: org
// description: Name of the organization
// required: true
// type: string
// - in: path
// name: repo
// description: Name of the repository
// required: true
// type: string
// - in: query
// name: ref
// description: Ref to target for the deployment config
// type: string
// security:
// - ApiKeyAuth: []
// responses:
// '200':
// description: Successfully retrieved the deployment config
// schema:
// "$ref": "#/definitions/Deployment"
// '400':
// description: Invalid request payload or path
// schema:
// "$ref": "#/definitions/Error"
// '401':
// description: Unauthorized
// schema:
// "$ref": "#/definitions/Error"
// '404':
// description: Not found
// schema:
// "$ref": "#/definitions/Error"
// '500':
// description: Unexpected server error
// schema:
// "$ref": "#/definitions/Error"

// GetDeploymentConfig represents the API handler to get a deployment config at a given ref.
func GetDeploymentConfig(c *gin.Context) {
// capture middleware values
l := c.MustGet("logger").(*logrus.Entry)
r := repo.Retrieve(c)
u := user.Retrieve(c)

ctx := c.Request.Context()

// capture ref from parameters - use default branch if not provided
ref := util.QueryParameter(c, "ref", r.GetBranch())
ecrupper marked this conversation as resolved.
Show resolved Hide resolved

entry := fmt.Sprintf("%s@%s", r.GetFullName(), ref)

l.Debugf("reading deployment config %s", entry)

var config []byte

// check if the pipeline exists in the database
p, err := database.FromContext(c).GetPipelineForRepo(ctx, ref, r)
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
l.Debugf("pipeline %s not found in database, fetching from scm", entry)

config, err = scm.FromContext(c).ConfigBackoff(ctx, u, r, ref)
if err != nil {
retErr := fmt.Errorf("unable to get pipeline configuration for %s: %w", entry, err)

util.HandleError(c, http.StatusNotFound, retErr)

return
}
} else {
// some other error
retErr := fmt.Errorf("unable to get pipeline for %s: %w", entry, err)

util.HandleError(c, http.StatusInternalServerError, retErr)

return
}
} else {
l.Debugf("pipeline %s found in database", entry)

config = p.GetData()
}

// set up compiler
compiler := compiler.FromContext(c).Duplicate().WithCommit(ref).WithRepo(r).WithUser(u)

// compile the pipeline
pipeline, _, err := compiler.CompileLite(ctx, config, nil, true)
if err != nil {
retErr := fmt.Errorf("unable to compile pipeline %s: %w", entry, err)

util.HandleError(c, http.StatusBadRequest, retErr)

return
}

c.JSON(http.StatusOK, pipeline.Deployment)
}
15 changes: 8 additions & 7 deletions api/webhook/post.go
Original file line number Diff line number Diff line change
Expand Up @@ -378,13 +378,14 @@ func PostWebhook(c *gin.Context) {

// construct CompileAndPublishConfig
config := build.CompileAndPublishConfig{
Build: b,
Metadata: m,
BaseErr: baseErr,
Source: "webhook",
Comment: prComment,
Labels: prLabels,
Retries: 3,
Build: b,
Deployment: webhook.Deployment,
Metadata: m,
BaseErr: baseErr,
Source: "webhook",
Comment: prComment,
Labels: prLabels,
Retries: 3,
}

// generate the queue item
Expand Down
12 changes: 12 additions & 0 deletions compiler/native/compile.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,12 @@ func (c *client) CompileLite(ctx context.Context, v interface{}, ruleData *pipel
// create map of templates for easy lookup
templates := mapFromTemplates(p.Templates)

// expand deployment config
p, err = c.ExpandDeployment(ctx, p, templates)
if err != nil {
return nil, _pipeline, err
}

switch {
case len(p.Stages) > 0:
// inject the templates into the steps
Expand Down Expand Up @@ -330,6 +336,12 @@ func (c *client) compileSteps(ctx context.Context, p *yaml.Build, _pipeline *api
return nil, _pipeline, err
}

// inject the template for deploy config if exists
p, err = c.ExpandDeployment(ctx, p, tmpls)
if err != nil {
return nil, _pipeline, err
}

// inject the templates into the steps
p, err = c.ExpandSteps(ctx, p, tmpls, r, c.GetTemplateDepth())
if err != nil {
Expand Down
2 changes: 1 addition & 1 deletion compiler/native/compile_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -637,7 +637,7 @@ func TestNative_Compile_StepsPipeline(t *testing.T) {
t.Errorf("Compile returned err: %v", err)
}

if diff := cmp.Diff(got, want); diff != "" {
if diff := cmp.Diff(want, got); diff != "" {
t.Errorf("Compile mismatch (-want +got):\n%s", diff)
}
}
Expand Down
51 changes: 51 additions & 0 deletions compiler/native/expand.go
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,43 @@
return s, nil
}

// ExpandDeployment injects the template for a
// templated deployment config in a yaml configuration.
func (c *client) ExpandDeployment(ctx context.Context, b *yaml.Build, tmpls map[string]*yaml.Template) (*yaml.Build, error) {
if len(tmpls) == 0 {
return b, nil
}

if len(b.Deployment.Template.Name) == 0 {
return b, nil
}

// lookup step template name
tmpl, ok := tmpls[b.Deployment.Template.Name]
if !ok {
return b, fmt.Errorf("missing template source for template %s in pipeline for deployment config", b.Deployment.Template.Name)
}

bytes, err := c.getTemplate(ctx, tmpl, b.Deployment.Template.Name)
if err != nil {
return b, err
}

// initialize variable map if not parsed from config
if len(b.Deployment.Template.Variables) == 0 {
b.Deployment.Template.Variables = make(map[string]interface{})
}

tmplBuild, err := c.mergeDeployTemplate(bytes, tmpl, &b.Deployment)
if err != nil {
return b, err
}

b.Deployment = tmplBuild.Deployment

return b, nil
}

func (c *client) getTemplate(ctx context.Context, tmpl *yaml.Template, name string) ([]byte, error) {
var (
bytes []byte
Expand Down Expand Up @@ -356,7 +393,7 @@
//nolint:lll // ignore long line length due to input arguments
func (c *client) mergeTemplate(bytes []byte, tmpl *yaml.Template, step *yaml.Step) (*yaml.Build, error) {
switch tmpl.Format {
case constants.PipelineTypeGo, "golang", "":

Check failure on line 396 in compiler/native/expand.go

View workflow job for this annotation

GitHub Actions / full-review

string `golang` has 3 occurrences, make it a constant (goconst)

Check failure on line 396 in compiler/native/expand.go

View workflow job for this annotation

GitHub Actions / full-review

string `golang` has 3 occurrences, make it a constant (goconst)

Check failure on line 396 in compiler/native/expand.go

View workflow job for this annotation

GitHub Actions / diff-review

string `golang` has 3 occurrences, make it a constant (goconst)

Check failure on line 396 in compiler/native/expand.go

View workflow job for this annotation

GitHub Actions / golangci

[golangci] compiler/native/expand.go#L396

string `golang` has 3 occurrences, make it a constant (goconst)
Raw output
compiler/native/expand.go:396:33: string `golang` has 3 occurrences, make it a constant (goconst)
	case constants.PipelineTypeGo, "golang", "":
	                               ^
//nolint:lll // ignore long line length due to return
return native.Render(string(bytes), step.Name, step.Template.Name, step.Environment, step.Template.Variables)
case constants.PipelineTypeStarlark:
Expand All @@ -368,6 +405,20 @@
}
}

func (c *client) mergeDeployTemplate(bytes []byte, tmpl *yaml.Template, d *yaml.Deployment) (*yaml.Build, error) {
switch tmpl.Format {
case constants.PipelineTypeGo, "golang", "":
//nolint:lll // ignore long line length due to return
return native.Render(string(bytes), "", d.Template.Name, make(raw.StringSliceMap), d.Template.Variables)
case constants.PipelineTypeStarlark:
//nolint:lll // ignore long line length due to return
return starlark.Render(string(bytes), "", d.Template.Name, make(raw.StringSliceMap), d.Template.Variables, c.GetStarlarkExecLimit())
default:
//nolint:lll // ignore long line length due to return
return &yaml.Build{}, fmt.Errorf("format of %s is unsupported", tmpl.Format)
}
}

// helper function that creates a map of templates from a yaml configuration.
func mapFromTemplates(templates []*yaml.Template) map[string]*yaml.Template {
m := make(map[string]*yaml.Template)
Expand Down
Loading
Loading