diff --git a/engine/api/v2_workflow_run_craft.go b/engine/api/v2_workflow_run_craft.go index 786817242c..3b5e932be9 100644 --- a/engine/api/v2_workflow_run_craft.go +++ b/engine/api/v2_workflow_run_craft.go @@ -688,6 +688,15 @@ func checkJobTemplate(ctx context.Context, db *gorp.DbMap, store cache.Store, wr } tmpWorkflow.Name = e.Name + // If no stage on template and stage on parent job, apply stage on templated jobs + if tmpWorkflow.Stages == nil && j.Stage != "" { + for k := range tmpWorkflow.Jobs { + templatedJob := tmpWorkflow.Jobs[k] + templatedJob.Stage = j.Stage + tmpWorkflow.Jobs[k] = templatedJob + } + } + // Add stage from parent workflow to lint jobs if tmpWorkflow.Stages == nil { tmpWorkflow.Stages = make(map[string]sdk.WorkflowStage) @@ -800,7 +809,7 @@ loop: for _, id := range rootJobs { jobDef := newJobs[id] - // If new root templated job has a diffent stage from the old one + // If new root templated job has a different stage from the old one if jobDef.Stage == j.Stage { jobDef.Needs = append(jobDef.Needs, j.Needs...) newJobs[id] = jobDef diff --git a/engine/api/v2_workflow_run_craft_test.go b/engine/api/v2_workflow_run_craft_test.go index bdc155b326..48082b5c53 100644 --- a/engine/api/v2_workflow_run_craft_test.go +++ b/engine/api/v2_workflow_run_craft_test.go @@ -2919,6 +2919,184 @@ spec: |- } } +func TestComputeJobFromTemplate_NoStageInTemplate(t *testing.T) { + api, db, _ := newTestAPI(t) + ctx := context.TODO() + + db.Exec("DELETE FROM rbac") + db.Exec("DELETE FROM region") + + reg := sdk.Region{Name: "build"} + require.NoError(t, region.Insert(ctx, db, ®)) + + reg1 := sdk.Region{Name: "myregion"} + require.NoError(t, region.Insert(ctx, db, ®1)) + + proj := assets.InsertTestProject(t, db, api.Cache, sdk.RandomString(10), sdk.RandomString(10)) + admin, _ := assets.InsertAdminUser(t, db) + assets.InsertRBAcProject(t, db, sdk.ProjectRoleRead, proj.Key, *admin) + + vcsProject := assets.InsertTestVCSProject(t, db, proj.ID, "github", "github") + repo := assets.InsertTestProjectRepository(t, db, proj.Key, vcsProject.ID, "my/repo") + + model := sdk.IntegrationModel{Name: sdk.RandomString(10), Event: true, DefaultConfig: sdk.IntegrationConfig{ + "myparam": { + Value: "myregion", + Type: sdk.IntegrationConfigTypeRegion, + }, + }} + require.NoError(t, integration.InsertModel(db, &model)) + projInt := sdk.ProjectIntegration{ + Config: sdk.IntegrationConfig{ + "test": sdk.IntegrationConfigValue{ + Description: "here is a test", + Type: sdk.IntegrationConfigTypeString, + Value: "test", + }, + "myparam": model.DefaultConfig["myparam"], + }, + Name: sdk.RandomString(10), + ProjectID: proj.ID, + Model: model, + IntegrationModelID: model.ID, + } + require.NoError(t, integration.InsertIntegration(db, &projInt)) + + // Create template + e := sdk.Entity{ + ProjectKey: proj.Key, + Type: sdk.EntityTypeWorkflowTemplate, + FilePath: ".cds/workflow-templates/mytmpl.yml", + Name: "myTemplate", + Commit: "123456789", + Ref: "refs/heads/master", + ProjectRepositoryID: repo.ID, + UserID: &admin.ID, + Data: `name: mytemplate +spec: |- + jobs: + build: + test:`, + } + require.NoError(t, entity.Insert(ctx, db, &e)) + + s, _ := assets.InsertService(t, db, t.Name()+"_VCS", sdk.TypeVCS) + ctrl := gomock.NewController(t) + defer ctrl.Finish() + servicesClients := mock_services.NewMockClient(ctrl) + services.NewClient = func(_ []sdk.Service) services.Client { + return servicesClients + } + t.Cleanup(func() { + _ = services.Delete(db, s) + services.NewClient = services.NewDefaultClient + }) + servicesClients.EXPECT(). + DoJSONRequest(gomock.Any(), "GET", "/vcs/github/repos/my/repo", gomock.Any(), gomock.Any(), gomock.Any()). + DoAndReturn( + func(ctx context.Context, method, path string, in interface{}, out interface{}, _ interface{}) (http.Header, int, error) { + b := &sdk.VCSRepo{} + *(out.(*sdk.VCSRepo)) = *b + return nil, 200, nil + }, + ).Times(1) + + wkName := sdk.RandomString(10) + wr := sdk.V2WorkflowRun{ + DeprecatedUserID: admin.ID, + ProjectKey: proj.Key, + Status: sdk.V2WorkflowRunStatusCrafting, + VCSServerID: vcsProject.ID, + RepositoryID: repo.ID, + RunNumber: 0, + RunAttempt: 0, + WorkflowRef: "refs/heads/master", + WorkflowSha: "123456789", + WorkflowName: wkName, + WorkflowData: sdk.V2WorkflowRunData{ + Workflow: sdk.V2Workflow{ + Stages: map[string]sdk.WorkflowStage{ + "stage1": {}, + "stage2": { + Needs: []string{"stage1"}, + }, + "stage3": { + Needs: []string{"stage2"}, + }, + }, + Name: wkName, + Jobs: map[string]sdk.V2Job{ + "root": { + Stage: "stage1", + }, + "two": { + From: fmt.Sprintf("%s/%s/%s/%s", proj.Key, vcsProject.Name, repo.Name, "myTemplate"), + Needs: []string{"root"}, + Stage: "stage2", + }, + "three": { + Needs: []string{"two"}, + Stage: "stage3", + }, + }, + }, + }, + RunEvent: sdk.V2WorkflowRunEvent{ + HookType: sdk.WorkflowHookTypeRepository, + Payload: nil, + Ref: "refs/heads/main", + Sha: "123456789", + EventName: sdk.WorkflowHookEventNamePush, + }, + } + require.NoError(t, workflow_v2.InsertRun(ctx, db, &wr)) + + myWMEnt := sdk.Entity{ + ProjectKey: proj.Key, + ProjectRepositoryID: repo.ID, + Type: sdk.EntityTypeWorkerModel, + FilePath: ".cds/worker-models/myworker-model.yml", + Name: "myworker-model", + Ref: "refs/heads/master", + Commit: "123456789", + LastUpdate: time.Time{}, + Data: "name: myworkermodel", + } + require.NoError(t, entity.Insert(ctx, db, &myWMEnt)) + + // Create hatchery + hatch := sdk.Hatchery{Name: sdk.RandomString(10), ModelType: ""} + require.NoError(t, hatchery.Insert(ctx, db, &hatch)) + + perm := sdk.RBAC{ + Name: sdk.RandomString(10), + Hatcheries: []sdk.RBACHatchery{ + { + RegionID: reg1.ID, + HatcheryID: hatch.ID, + Role: sdk.HatcheryRoleSpawn, + }, + }, + } + require.NoError(t, rbac.Insert(ctx, db, &perm)) + + require.NoError(t, api.craftWorkflowRunV2(ctx, wr.ID)) + + wrDB, err := workflow_v2.LoadRunByID(ctx, db, wr.ID) + require.NoError(t, err) + + require.Equal(t, sdk.V2WorkflowRunStatusFail, wrDB.Status) + + buildJob, has := wrDB.WorkflowData.Workflow.Jobs["build"] + require.True(t, has) + + testJob, has := wrDB.WorkflowData.Workflow.Jobs["test"] + require.True(t, has) + + require.Equal(t, "stage2", buildJob.Stage) + require.Equal(t, "stage2", testJob.Stage) +} + func TestComputeConcurrency(t *testing.T) { api, db, _ := newTestAPI(t) ctx := context.TODO() @@ -3848,10 +4026,10 @@ func TestInsertRunWithEmptyLine(t *testing.T) { runs-on: .cds/worker-models/mymodel.yml steps: - run: |- - - + + echo toto - + echo toto2 name: aa on: [push]