Skip to content

Commit

Permalink
feat (api): add release builtin action (#1019)
Browse files Browse the repository at this point in the history
  • Loading branch information
sguiheux authored and yesnault committed Sep 5, 2017
1 parent a048d81 commit b3c57c0
Show file tree
Hide file tree
Showing 26 changed files with 498 additions and 49 deletions.
31 changes: 31 additions & 0 deletions engine/api/action/builtin.go
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,37 @@ Tag the current branch and push it.`
return err
}

// ----------------------------------- Git Release -----------------------
gitrelease := sdk.NewAction(sdk.ReleaseAction)
gitrelease.Type = sdk.BuiltinAction
gitrelease.Description = `CDS Builtin Action. Make a release using repository manager.`

gitrelease.Parameter(sdk.Parameter{
Name: "tag",
Description: "Tag name.",
Value: "{{.cds.release.version}}",
Type: sdk.StringParameter,
})
gitrelease.Parameter(sdk.Parameter{
Name: "title",
Value: "",
Description: "Set a title for the release",
Type: sdk.StringParameter,
})
gitrelease.Parameter(sdk.Parameter{
Name: "releaseNote",
Description: "Set a release note for the release",
Type: sdk.TextParameter,
})
gitrelease.Parameter(sdk.Parameter{
Name: "artifacts",
Description: "Set a list of artifacts, separate by , . You can also use regexp.",
Type: sdk.StringParameter,
})
if err := checkBuiltinAction(db, gitrelease); err != nil {
return err
}

return nil
}

Expand Down
1 change: 1 addition & 0 deletions engine/api/main_routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ func (router *Router) init() {
router.Handle("/project/{permProjectKey}/workflows/{workflowName}/artifact/{artifactId}", GET(getDownloadArtifactHandler))
router.Handle("/project/{permProjectKey}/workflows/{workflowName}/node/{nodeID}/triggers/condition", GET(getWorkflowTriggerConditionHandler))
router.Handle("/project/{permProjectKey}/workflows/{workflowName}/join/{joinID}/triggers/condition", GET(getWorkflowTriggerJoinConditionHandler))
router.Handle("/project/{permProjectKey}/workflows/{workflowName}/runs/{number}/nodes/{id}/release", POST(releaseApplicationWorkflowHandler))

// DEPRECATED
router.Handle("/project/{key}/pipeline/{permPipelineKey}/action/{jobID}", PUT(updatePipelineActionHandler, DEPRECATED), DELETE(deleteJobHandler))
Expand Down
72 changes: 72 additions & 0 deletions engine/api/repositoriesmanager/repogithub/client.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package repogithub

import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"strconv"
Expand All @@ -22,6 +24,76 @@ type GithubClient struct {
DisableStatusURL bool
}

// ReleaseRequest Request sent to Github to create a release
type ReleaseRequest struct {
TagName string `json:"tag_name"`
Name string `json:"name"`
Body string `json:"body"`
}

// ReleaseResponse Response return by Github after release creation
type ReleaseResponse struct {
ID int64 `json:"id"`
UploadURL string `json:"upload_url"`
}

// Release Create a release Github
func (g *GithubClient) Release(fullname string, tagName string, title string, releaseNote string) (*sdk.VCSRelease, error) {
var url = "/repos/" + fullname + "/releases"

req := ReleaseRequest{
TagName: tagName,
Name: title,
Body: releaseNote,
}
b, err := json.Marshal(req)
if err != nil {
return nil, sdk.WrapError(err, "github.Release > Cannot marshal body %+v", req)
}

res, err := g.post(url, "application/json", bytes.NewBuffer(b), false)
if err != nil {
return nil, sdk.WrapError(err, "github.Release > Cannot create release on github")
}

defer res.Body.Close()

body, err := ioutil.ReadAll(res.Body)
if err != nil {
return nil, sdk.WrapError(err, "github.Release > Cannot read release response")
}

if res.StatusCode != 201 {
return nil, sdk.WrapError(fmt.Errorf("github.Release >Unable to create release on github. Url : %s Status code : %d - Body: %s", url, res.StatusCode, body), "")
}

var response ReleaseResponse
if err := json.Unmarshal(body, &response); err != nil {
return nil, sdk.WrapError(err, "github.Release> Cannot unmarshal response: %s", string(body))
}

release := &sdk.VCSRelease{
ID: response.ID,
UploadURL: response.UploadURL,
}

return release, nil
}

// UploadReleaseFile Attach a file into the release
func (g *GithubClient) UploadReleaseFile(repo string, release *sdk.VCSRelease, runArtifact sdk.WorkflowNodeRunArtifact, buf *bytes.Buffer) error {
var url = strings.Split(release.UploadURL, "{")[0] + "?name=" + runArtifact.Name
res, err := g.post(url, "application/octet-stream", buf, true)
if err != nil {
return err
}
defer res.Body.Close()
if res.StatusCode != 201 {
return sdk.WrapError(fmt.Errorf("github.Release >Unable to upload file on release. Url : %s - Status code : %d", url, res.StatusCode), "")
}
return nil
}

// Repos list repositories that are accessible to the authenticated user
// https://developer.github.com/v3/repos/#list-your-repositories
func (g *GithubClient) Repos() ([]sdk.VCSRepo, error) {
Expand Down
2 changes: 1 addition & 1 deletion engine/api/repositoriesmanager/repogithub/client_event.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ func (g *GithubClient) SetStatus(event sdk.Event) error {
}
buf := bytes.NewBuffer(b)

res, err := g.post(path, "application/json", buf)
res, err := g.post(path, "application/json", buf, false)
if err != nil {
return err
}
Expand Down
5 changes: 3 additions & 2 deletions engine/api/repositoriesmanager/repogithub/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,8 +117,8 @@ func withETag(c *GithubClient, req *http.Request, path string) {
}
func withoutETag(c *GithubClient, req *http.Request, path string) {}

func (c *GithubClient) post(path string, bodyType string, body io.Reader) (*http.Response, error) {
if !strings.HasPrefix(path, APIURL) {
func (c *GithubClient) post(path string, bodyType string, body io.Reader, skipDefaultBaseURL bool) (*http.Response, error) {
if !skipDefaultBaseURL && !strings.HasPrefix(path, APIURL) {
path = APIURL + path
}

Expand All @@ -127,6 +127,7 @@ func (c *GithubClient) post(path string, bodyType string, body io.Reader) (*http
return nil, err
}

req.Header.Set("Content-Type", bodyType)
req.Header.Set("User-Agent", "CDS-gh_client_id="+c.ClientID)
req.Header.Add("Accept", "application/json")
req.Header.Add("Authorization", fmt.Sprintf("token %s", c.OAuthToken))
Expand Down
11 changes: 11 additions & 0 deletions engine/api/repositoriesmanager/repostash/client.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package repostash

import (
"bytes"
"fmt"
"net/http"
"net/url"
Expand Down Expand Up @@ -34,6 +35,16 @@ type StashClient struct {
disableSetStatus bool
}

// Release not implemented on bitbucket
func (s *StashClient) Release(repo string, tagName string, title string, releaseNote string) (*sdk.VCSRelease, error) {
return nil, fmt.Errorf("Stash do not provide release system")
}

// UploadReleaseFile not implemented on bitbucket
func (s *StashClient) UploadReleaseFile(repo string, release *sdk.VCSRelease, runArtifact sdk.WorkflowNodeRunArtifact, buf *bytes.Buffer) error {
return fmt.Errorf("Stash do not provide an upload file system")
}

//Repos returns the list of accessible repositories
func (s *StashClient) Repos() ([]sdk.VCSRepo, error) {
repos := []sdk.VCSRepo{}
Expand Down
2 changes: 1 addition & 1 deletion engine/api/workflow/dao_node.go
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,7 @@ func loadNodeContext(db gorp.SqlExecutor, wn *sdk.WorkflowNode, u *sdk.User) (*s

//Load the application in the context
if ctx.ApplicationID != 0 {
app, err := application.LoadByID(db, ctx.ApplicationID, u)
app, err := application.LoadByID(db, ctx.ApplicationID, u, application.LoadOptions.WithRepositoryManager)
if err != nil {
return nil, sdk.WrapError(err, "loadNodeContext> Unable to load application %d", ctx.ApplicationID)
}
Expand Down
4 changes: 4 additions & 0 deletions engine/api/workflow/execute_node_job_run_log.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package workflow

import (
"database/sql"
"time"

"github.com/go-gorp/gorp"
Expand All @@ -17,6 +18,9 @@ func LoadStepLogs(db gorp.SqlExecutor, id int64, order int64) (*sdk.Log, error)
logs := &sdk.Log{}
var s, m, d time.Time
if err := db.QueryRow(query, id, order).Scan(&logs.Id, &logs.PipelineBuildJobID, &logs.PipelineBuildID, &s, &m, &d, &logs.StepOrder, &logs.Val); err != nil {
if err == sql.ErrNoRows {
return nil, nil
}
return nil, err
}
var err error
Expand Down
13 changes: 10 additions & 3 deletions engine/api/workflow/process.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ func processWorkflowRun(db gorp.SqlExecutor, w *sdk.WorkflowRun, hookEvent *sdk.

//Check conditions
var params = nodeRun.BuildParameters
//Define specific desitination parameters
//Define specific destination parameters
sdk.AddParameter(&params, "cds.dest.pipeline", sdk.StringParameter, t.WorkflowDestNode.Pipeline.Name)
if t.WorkflowDestNode.Context.Application != nil {
sdk.AddParameter(&params, "cds.dest.application", sdk.StringParameter, t.WorkflowDestNode.Context.Application.Name)
Expand Down Expand Up @@ -298,8 +298,7 @@ func processWorkflowNodeRun(db gorp.SqlExecutor, w *sdk.WorkflowRun, n *sdk.Work
run.PipelineParameters = m.PipelineParameters
}

//Process parameters for the jobs
//TODO inherit parameter from parent job
// Process parameters for the jobs
jobParams, errParam := getNodeRunBuildParameters(db, run)
if errParam != nil {
AddWorkflowRunInfo(w, sdk.SpawnMsg{
Expand All @@ -310,6 +309,14 @@ func processWorkflowNodeRun(db gorp.SqlExecutor, w *sdk.WorkflowRun, n *sdk.Work
}
run.BuildParameters = jobParams

// Inherit parameter from parent job
if len(sourceNodeRuns) > 0 {
parentsParams, errPP := getParentParameters(db, run, sourceNodeRuns)
if errPP != nil {
return sdk.WrapError(errPP, "processWorkflowNodeRun> getParentParameters failed")
}
run.BuildParameters = append(run.BuildParameters, parentsParams...)
}
for _, p := range jobParams {
switch p.Name {
case "git.hash", "git.branch", "git.tag", "git.author":
Expand Down
76 changes: 56 additions & 20 deletions engine/api/workflow/process_parameters.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package workflow

import (
"fmt"
"strings"

"github.com/fsamin/go-dump"
"github.com/go-gorp/gorp"
Expand All @@ -12,11 +13,7 @@ import (
)

func getNodeJobRunParameters(db gorp.SqlExecutor, j sdk.Job, run *sdk.WorkflowNodeRun, stage *sdk.Stage) ([]sdk.Parameter, error) {
params, err := getNodeRunBuildParameters(db, run)
if err != nil {
return nil, err
}

params := run.BuildParameters
tmp := map[string]string{}

tmp["cds.stage"] = stage.Name
Expand All @@ -35,15 +32,14 @@ func getNodeJobRunParameters(db gorp.SqlExecutor, j sdk.Job, run *sdk.WorkflowNo
if errm.IsEmpty() {
return params, nil
}

return params, errm
}

// GetNodeBuildParameters returns build parameters with default values for cds.version, cds.run, cds.run.number, cds.run.subnumber
func GetNodeBuildParameters(proj *sdk.Project, w *sdk.Workflow, n *sdk.WorkflowNode, pipelineParameters []sdk.Parameter, payload interface{}) ([]sdk.Parameter, error) {
vars := map[string]string{}
tmp := sdk.ParametersFromProjectVariables(proj)
for k, v := range tmp {
tmpProj := sdk.ParametersFromProjectVariables(proj)
for k, v := range tmpProj {
vars[k] = v
}

Expand All @@ -66,8 +62,8 @@ func GetNodeBuildParameters(proj *sdk.Project, w *sdk.Workflow, n *sdk.WorkflowN
}

// compute pipeline parameters
tmp = sdk.ParametersFromPipelineParameters(pipelineParameters)
for k, v := range tmp {
tmpPip := sdk.ParametersFromPipelineParameters(pipelineParameters)
for k, v := range tmpPip {
vars[k] = v
}

Expand All @@ -80,20 +76,22 @@ func GetNodeBuildParameters(proj *sdk.Project, w *sdk.Workflow, n *sdk.WorkflowN
errm.Append(errdump)
}
for k, v := range payloadMap {
tmp[k] = v
vars[k] = v
}

tmp["cds.project"] = w.ProjectKey
tmp["cds.workflow"] = w.Name
tmp["cds.pipeline"] = n.Pipeline.Name
tmp["cds.version"] = fmt.Sprintf("%d.%d", 1, 0)
tmp["cds.run"] = fmt.Sprintf("%d.%d", 1, 0)
tmp["cds.run.number"] = fmt.Sprintf("%d", 1)
tmp["cds.run.subnumber"] = fmt.Sprintf("%d", 0)
// TODO Update suggest.go with new variable

vars["cds.project"] = w.ProjectKey
vars["cds.workflow"] = w.Name
vars["cds.pipeline"] = n.Pipeline.Name
vars["cds.version"] = fmt.Sprintf("%d.%d", 1, 0)
vars["cds.run"] = fmt.Sprintf("%d.%d", 1, 0)
vars["cds.run.number"] = fmt.Sprintf("%d", 1)
vars["cds.run.subnumber"] = fmt.Sprintf("%d", 0)

params := []sdk.Parameter{}
for k, v := range tmp {
s, err := sdk.Interpolate(v, tmp)
for k, v := range vars {
s, err := sdk.Interpolate(v, vars)
if err != nil {
errm.Append(err)
continue
Expand All @@ -108,6 +106,44 @@ func GetNodeBuildParameters(proj *sdk.Project, w *sdk.Workflow, n *sdk.WorkflowN
return params, errm
}

func getParentParameters(db gorp.SqlExecutor, run *sdk.WorkflowNodeRun, nodeRunIds []int64) ([]sdk.Parameter, error) {
//Load workflow run
w, err := LoadRunByID(db, run.WorkflowRunID)
if err != nil {
return nil, sdk.WrapError(err, "getParentParameters> Unable to load workflow run")
}

params := []sdk.Parameter{}
for _, nodeRunID := range nodeRunIds {
parentNodeRun, errNR := LoadNodeRunByID(db, nodeRunID)
if errNR != nil {
return nil, sdk.WrapError(errNR, "getParentParameters> Cannot get parent node run")
}

node := w.Workflow.GetNode(parentNodeRun.WorkflowNodeID)
if node == nil {
return nil, sdk.WrapError(fmt.Errorf("Unable to find node %d in workflow", parentNodeRun.WorkflowNodeID), "getParentParameters>")
}

for i := range parentNodeRun.BuildParameters {
p := &parentNodeRun.BuildParameters[i]

if p.Name == "cds.semver" || p.Name == "cds.release.version" || strings.HasPrefix(p.Name, "cds.proj") || strings.HasPrefix(p.Name, "workflow.") {
continue
}

prefix := "workflow." + node.Name + "."
if strings.HasPrefix(p.Name, "cds.") {
p.Name = strings.Replace(p.Name, "cds.", prefix, 1)
} else {
p.Name = prefix + p.Name
}
}
params = append(params, parentNodeRun.BuildParameters...)
}
return params, nil
}

func getNodeRunBuildParameters(db gorp.SqlExecutor, run *sdk.WorkflowNodeRun) ([]sdk.Parameter, error) {
//Load workflow run
w, err := LoadRunByID(db, run.WorkflowRunID)
Expand Down
Loading

0 comments on commit b3c57c0

Please sign in to comment.