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

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
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
124 changes: 124 additions & 0 deletions api/deployment/get_config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
// 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
//
// ---
// 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
// 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())

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 @@ -189,7 +189,7 @@

defer func() {
// send API call to update the webhook
_, err = database.FromContext(c).UpdateHook(ctx, h)

Check failure on line 192 in api/webhook/post.go

View workflow job for this annotation

GitHub Actions / golangci

[golangci] api/webhook/post.go#L192

Non-inherited new context, use function like `context.WithXXX` instead (contextcheck)
Raw output
api/webhook/post.go:192:32: Non-inherited new context, use function like `context.WithXXX` instead (contextcheck)
		_, err = database.FromContext(c).UpdateHook(ctx, h)
		                             ^
if err != nil {
l.Errorf("unable to update webhook %s/%d: %v", r.GetFullName(), h.GetNumber(), err)
}
Expand Down Expand Up @@ -374,13 +374,14 @@

// 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 Expand Up @@ -658,7 +659,7 @@
case "archived", "unarchived", constants.ActionEdited:
l.Debugf("repository action %s for %s", h.GetEventAction(), r.GetFullName())
// send call to get repository from database
dbRepo, err := database.FromContext(c).GetRepoForOrg(ctx, r.GetOrg(), r.GetName())

Check failure on line 662 in api/webhook/post.go

View workflow job for this annotation

GitHub Actions / golangci

[golangci] api/webhook/post.go#L662

Non-inherited new context, use function like `context.WithXXX` instead (contextcheck)
Raw output
api/webhook/post.go:662:38: Non-inherited new context, use function like `context.WithXXX` instead (contextcheck)
		dbRepo, err := database.FromContext(c).GetRepoForOrg(ctx, r.GetOrg(), r.GetName())
		                                   ^
if err != nil {
retErr := fmt.Errorf("%s: failed to get repo %s: %w", baseErr, r.GetFullName(), err)

Expand All @@ -669,7 +670,7 @@
}

// send API call to capture the last hook for the repo
lastHook, err := database.FromContext(c).LastHookForRepo(ctx, dbRepo)

Check failure on line 673 in api/webhook/post.go

View workflow job for this annotation

GitHub Actions / golangci

[golangci] api/webhook/post.go#L673

Non-inherited new context, use function like `context.WithXXX` instead (contextcheck)
Raw output
api/webhook/post.go:673:40: Non-inherited new context, use function like `context.WithXXX` instead (contextcheck)
		lastHook, err := database.FromContext(c).LastHookForRepo(ctx, dbRepo)
		                                     ^
if err != nil {
retErr := fmt.Errorf("unable to get last hook for repo %s: %w", r.GetFullName(), err)

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 @@
// 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 @@ -322,6 +328,12 @@
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 All @@ -330,7 +342,7 @@

if c.ModificationService.Endpoint != "" {
// send config to external endpoint for modification
p, err = c.modifyConfig(p, c.build, c.repo)

Check failure on line 345 in compiler/native/compile.go

View workflow job for this annotation

GitHub Actions / golangci

[golangci] compiler/native/compile.go#L345

Function `modifyConfig` should pass the context parameter (contextcheck)
Raw output
compiler/native/compile.go:345:26: Function `modifyConfig` should pass the context parameter (contextcheck)
		p, err = c.modifyConfig(p, c.build, c.repo)
		                       ^
if err != nil {
return nil, _pipeline, err
}
Expand Down Expand Up @@ -425,7 +437,7 @@

if c.ModificationService.Endpoint != "" {
// send config to external endpoint for modification
p, err = c.modifyConfig(p, c.build, c.repo)

Check failure on line 440 in compiler/native/compile.go

View workflow job for this annotation

GitHub Actions / golangci

[golangci] compiler/native/compile.go#L440

Function `modifyConfig` should pass the context parameter (contextcheck)
Raw output
compiler/native/compile.go:440:26: Function `modifyConfig` should pass the context parameter (contextcheck)
		p, err = c.modifyConfig(p, c.build, c.repo)
		                       ^
if err != nil {
return nil, _pipeline, err
}
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 @@ -213,6 +213,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 @@ -347,7 +384,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 387 in compiler/native/expand.go

View workflow job for this annotation

GitHub Actions / golangci

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

string `golang` has 3 occurrences, make it a constant (goconst)
Raw output
compiler/native/expand.go:387: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 @@ -359,6 +396,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