Skip to content

Commit

Permalink
feat: transform to workflow as code (#3749)
Browse files Browse the repository at this point in the history
  • Loading branch information
sguiheux authored and fsamin committed Jan 8, 2019
1 parent ff4d21d commit 477a319
Show file tree
Hide file tree
Showing 56 changed files with 1,929 additions and 257 deletions.
1 change: 1 addition & 0 deletions cli/cdsctl/workflow.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ func workflow() *cobra.Command {
cli.NewCommand(workflowPullCmd, workflowPullRun, nil, withAllCommandModifiers()...),
cli.NewCommand(workflowPushCmd, workflowPushRun, nil, withAllCommandModifiers()...),
cli.NewCommand(workflowFavoriteCmd, workflowFavoriteRun, nil, withAllCommandModifiers()...),
cli.NewCommand(workflowTransformAsCodeCmd, workflowTransformAsCodeRun, nil, withAllCommandModifiers()...),
workflowArtifact(),
workflowLog(),
workflowAdvanced(),
Expand Down
52 changes: 52 additions & 0 deletions cli/cdsctl/workflow_transform_as_code.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package main

import (
"fmt"
"time"

"github.com/ovh/cds/cli"
"github.com/ovh/cds/sdk"
)

var workflowTransformAsCodeCmd = cli.Command{
Name: "ascode",
Short: "Transform an existing workflow to an as code workflow",
Ctx: []cli.Arg{
{Name: _ProjectKey},
{Name: _WorkflowName},
},
}

func workflowTransformAsCodeRun(v cli.Values) error {
w, err := client.WorkflowGet(v.GetString(_ProjectKey), v.GetString(_WorkflowName))
if err != nil {
return err
}
if w.FromRepository != "" {
fmt.Println("Workflow is already as code.")
return nil
}

ope, err := client.WorkflowTransformAsCode(v.GetString(_ProjectKey), v.GetString(_WorkflowName))
if err != nil {
return err
}

fmt.Printf("CDS is pushing files on your repository. A pull request will be created, please wait...\n")
for {
if err := client.WorkflowTransformAsCodeFollow(v.GetString(_ProjectKey), v.GetString(_WorkflowName), ope); err != nil {
return err
}
if ope.Status > sdk.OperationStatusProcessing {
break
}
time.Sleep(1 * time.Second)
}
switch ope.Status {
case sdk.OperationStatusDone:
fmt.Println(cli.Blue(ope.Setup.Push.PRLink))
case sdk.OperationStatusError:
return fmt.Errorf("cannot perform operation: %s", ope.Error)
}
return nil
}
2 changes: 2 additions & 0 deletions engine/api/api_routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,8 @@ func (api *API) InitRouter() {

r.Handle("/project/{permProjectKey}/workflows", r.POST(api.postWorkflowHandler, EnableTracing()), r.GET(api.getWorkflowsHandler, AllowProvider(true), EnableTracing()))
r.Handle("/project/{key}/workflows/{permWorkflowName}", r.GET(api.getWorkflowHandler, AllowProvider(true), EnableTracing()), r.PUT(api.putWorkflowHandler, EnableTracing()), r.DELETE(api.deleteWorkflowHandler))
r.Handle("/project/{key}/workflows/{permWorkflowName}/ascode/{uuid}", r.GET(api.getWorkflowAsCodeHandler))
r.Handle("/project/{key}/workflows/{permWorkflowName}/ascode", r.POST(api.postWorkflowAsCodeHandler, EnableTracing()))
r.Handle("/project/{key}/workflows/{permWorkflowName}/label", r.POST(api.postWorkflowLabelHandler))
r.Handle("/project/{key}/workflows/{permWorkflowName}/label/{labelID}", r.DELETE(api.deleteWorkflowLabelHandler))
r.Handle("/project/{key}/workflows/{permWorkflowName}/rollback/{auditID}", r.POST(api.postWorkflowRollbackHandler))
Expand Down
6 changes: 3 additions & 3 deletions engine/api/ascode.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ func (api *API) postImportAsCodeHandler() service.Handler {
}
}

if err := workflow.PostRepositoryOperation(ctx, api.mustDB(), api.Cache, *p, ope); err != nil {
if err := workflow.PostRepositoryOperation(ctx, api.mustDB(), *p, ope, nil); err != nil {
return sdk.WrapError(err, "Cannot create repository operation")
}
ope.RepositoryStrategy.SSHKeyContent = ""
Expand All @@ -90,7 +90,7 @@ func (api *API) getImportAsCodeHandler() service.Handler {

var ope = new(sdk.Operation)
ope.UUID = uuid
if err := workflow.GetRepositoryOperation(ctx, api.mustDB(), api.Cache, ope); err != nil {
if err := workflow.GetRepositoryOperation(ctx, api.mustDB(), ope); err != nil {
return sdk.WrapError(err, "Cannot get repository operation status")
}
return service.WriteJSON(w, ope, http.StatusOK)
Expand Down Expand Up @@ -128,7 +128,7 @@ func (api *API) postPerformImportAsCodeHandler() service.Handler {
var ope = new(sdk.Operation)
ope.UUID = uuid

if err := workflow.GetRepositoryOperation(ctx, api.mustDB(), api.Cache, ope); err != nil {
if err := workflow.GetRepositoryOperation(ctx, api.mustDB(), ope); err != nil {
return sdk.WrapError(err, "Unable to get repository operation")
}

Expand Down
20 changes: 20 additions & 0 deletions engine/api/repositoriesmanager/repositories_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,18 @@ func (c *vcsClient) Commit(ctx context.Context, fullname, hash string) (sdk.VCSC
return commit, nil
}

func (c *vcsClient) PullRequest(ctx context.Context, fullname string, ID int) (sdk.VCSPullRequest, error) {
pr := sdk.VCSPullRequest{}
path := fmt.Sprintf("/vcs/%s/repos/%s/pullrequests/%d", c.name, fullname, ID)
if code, err := c.doJSONRequest(ctx, "GET", path, nil, &pr); err != nil {
if code != http.StatusNotFound {
return pr, err
}
return pr, sdk.ErrNotFound
}
return pr, nil
}

func (c *vcsClient) PullRequests(ctx context.Context, fullname string) ([]sdk.VCSPullRequest, error) {
prs := []sdk.VCSPullRequest{}
path := fmt.Sprintf("/vcs/%s/repos/%s/pullrequests", c.name, fullname)
Expand All @@ -367,6 +379,14 @@ func (c *vcsClient) PullRequestComment(ctx context.Context, fullname string, id
return nil
}

func (c *vcsClient) PullRequestCreate(ctx context.Context, fullname string, pr sdk.VCSPullRequest) (sdk.VCSPullRequest, error) {
path := fmt.Sprintf("/vcs/%s/repos/%s/pullrequests", c.name, fullname)
if _, err := c.doJSONRequest(ctx, "POST", path, pr, &pr); err != nil {
return pr, err
}
return pr, nil
}

func (c *vcsClient) CreateHook(ctx context.Context, fullname string, hook *sdk.VCSHook) error {
path := fmt.Sprintf("/vcs/%s/repos/%s/hooks", c.name, fullname)
_, err := c.doJSONRequest(ctx, "POST", path, hook, hook)
Expand Down
69 changes: 69 additions & 0 deletions engine/api/services/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@ import (
"encoding/base64"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"mime/multipart"
"net/http"
"net/textproto"
"net/url"
"time"

Expand All @@ -17,9 +19,76 @@ import (
"github.com/ovh/cds/sdk/tracingutils"
)

// MultiPartData represents the data to send
type MultiPartData struct {
Reader io.Reader
ContentType string
}

// HTTPClient will be set to a default httpclient if not set
var HTTPClient sdk.HTTPClient

// DoMultiPartRequest performs an http request on a service with multipart tar file + json field
func DoMultiPartRequest(ctx context.Context, srvs []sdk.Service, method, path string, multiPartData *MultiPartData, in interface{}, out interface{}, mods ...sdk.RequestModifier) (int, error) {

body := &bytes.Buffer{}
writer := multipart.NewWriter(body)

// Create tar part
dataFileHeader := make(textproto.MIMEHeader)
dataFileHeader.Set("Content-Type", multiPartData.ContentType)
dataFileHeader.Set("Content-Disposition", "form-data; name=\"dataFiles\"; filename=\"data\"")
dataPart, err := writer.CreatePart(dataFileHeader)
if err != nil {
return 0, sdk.WrapError(err, "unable to create data part")
}
if _, err := io.Copy(dataPart, multiPartData.Reader); err != nil {
return 0, sdk.WrapError(err, "unable to write into data part")
}

jsonData, errM := json.Marshal(in)
if errM != nil {
return 0, sdk.WrapError(errM, "unable to marshal data")
}
if err := writer.WriteField("dataJSON", string(jsonData)); err != nil {
return 0, sdk.WrapError(err, "unable to add field dataJSON")
}

// Close writer
if err := writer.Close(); err != nil {
return 0, sdk.WrapError(err, "unable to close writer")
}

mods = append(mods, sdk.SetHeader("Content-Type", writer.FormDataContentType()))
var lastErr error
var lastCode int
var attempt int
for {
attempt++
for i := range srvs {
srv := &srvs[i]
res, code, err := doRequest(ctx, srv.HTTPURL, srv.Hash, method, path, body.Bytes(), mods...)
if err != nil {
return code, sdk.WrapError(err, "Unable to perform request on service %s (%s)", srv.Name, srv.Type)
}
if out != nil {
if err := json.Unmarshal(res, out); err != nil {
return code, sdk.WrapError(err, "Unable to marshal output")
}
}
if err == nil {
return code, nil
}
lastErr = err
lastCode = code
}
if lastErr != nil || attempt > 5 {
break
}
}
return lastCode, lastErr
}

// DoJSONRequest performs an http request on a service
func DoJSONRequest(ctx context.Context, srvs []sdk.Service, method, path string, in interface{}, out interface{}, mods ...sdk.RequestModifier) (int, error) {
var lastErr error
Expand Down
10 changes: 6 additions & 4 deletions engine/api/workflow.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,17 +53,19 @@ func (api *API) getWorkflowHandler() service.Handler {
withLabels := FormBool(r, "withLabels")
withDeepPipelines := FormBool(r, "withDeepPipelines")
withTemplate := FormBool(r, "withTemplate")
withAsCodeEvents := FormBool(r, "withAsCodeEvents")

proj, err := project.Load(api.mustDB(), api.Cache, key, getUser(ctx), project.LoadOptions.WithPlatforms)
if err != nil {
return sdk.WrapError(err, "unable to load projet")
}

opts := workflow.LoadOptions{
WithFavorites: true,
DeepPipeline: withDeepPipelines,
WithIcon: true,
WithLabels: withLabels,
WithFavorites: true,
DeepPipeline: withDeepPipelines,
WithIcon: true,
WithLabels: withLabels,
WithAsCodeUpdateEvent: withAsCodeEvents,
}
w1, err := workflow.Load(ctx, api.mustDB(), api.Cache, proj, name, getUser(ctx), opts)
if err != nil {
Expand Down
Loading

0 comments on commit 477a319

Please sign in to comment.