package pipeline import ( "fmt" "time" "tangled.org/core/api/tangled" "tangled.org/core/spindle/models" "tangled.org/core/workflow" ) const ( TriggerNSIDPush = "org.tangled.event.push" TriggerNSIDPullRequest = "org.tangled.event.pullRequest" TriggerNSIDManual = "org.tangled.ci.trigger.manual" ) func FromTangled(id models.PipelineId, createdAt time.Time, raw tangled.Pipeline) (*models.PipelineRecord, error) { metadata := raw.TriggerMetadata if metadata == nil { return nil, fmt.Errorf("pipeline %s has no trigger metadata", id) } repoDID := "" if metadata.Repo != nil { if metadata.Repo.RepoDid != nil && *metadata.Repo.RepoDid != "" { repoDID = *metadata.Repo.RepoDid } else { repoDID = metadata.Repo.Did } } record := &models.PipelineRecord{ ID: id, RepoDID: repoDID, SourceRepo: metadata.SourceRepo, CreatedAt: createdAt.Format(time.RFC3339), Trigger: models.PipelineTrigger{ Kind: metadata.Kind, }, Workflows: make([]*models.PipelineWorkflow, 0, len(raw.Workflows)), } switch workflow.TriggerKind(metadata.Kind) { case workflow.TriggerKindPush: if metadata.Push == nil { return nil, fmt.Errorf("pipeline %s has no push trigger", id) } record.Commit = metadata.Push.NewSha record.Trigger.Push = &models.PipelinePushTrigger{ Ref: metadata.Push.Ref, NewCommit: metadata.Push.NewSha, OldCommit: metadata.Push.OldSha, } case workflow.TriggerKindPullRequest: if metadata.PullRequest == nil { return nil, fmt.Errorf("pipeline %s has no pull request trigger", id) } record.Commit = metadata.PullRequest.SourceSha record.Trigger.PullRequest = &models.PipelinePullRequestTrigger{ Action: metadata.PullRequest.Action, SourceRepo: metadata.SourceRepo, SourceBranch: &metadata.PullRequest.SourceBranch, SourceCommit: metadata.PullRequest.SourceSha, TargetBranch: metadata.PullRequest.TargetBranch, Pull: metadata.PullRequest.Pull, } case workflow.TriggerKindManual: if metadata.Manual == nil { return nil, fmt.Errorf("pipeline %s has no manual trigger", id) } record.Commit = metadata.Manual.Sha record.Trigger.Manual = &models.PipelineManualTrigger{ Ref: metadata.Manual.Ref, SourceRepo: metadata.SourceRepo, Inputs: inputPairs(metadata.Manual.Inputs), } case workflow.TriggerKindSchedule: if metadata.Schedule == nil { return nil, fmt.Errorf("pipeline %s has no schedule trigger", id) } record.Commit = metadata.Schedule.Sha record.Trigger.Schedule = &models.PipelineScheduleTrigger{ Ref: metadata.Schedule.Ref, ScheduledAt: metadata.Schedule.ScheduledAt, Sha: metadata.Schedule.Sha, } default: return nil, fmt.Errorf("pipeline %s has unknown trigger kind %q", id, metadata.Kind) } sourceRepo := record.RepoDID if record.SourceRepo != nil && *record.SourceRepo != "" { sourceRepo = *record.SourceRepo } for _, rawWorkflow := range raw.Workflows { if rawWorkflow == nil { continue } definition, err := FileDefinition(rawWorkflow.Name, []byte(rawWorkflow.Raw), sourceRepo, record.Commit) if err != nil { return nil, fmt.Errorf("pipeline %s workflow %q: %w", id, rawWorkflow.Name, err) } record.Workflows = append(record.Workflows, &models.PipelineWorkflow{ ID: rawWorkflow.Name, Name: rawWorkflow.Name, Definition: definition, Status: string(models.StatusKindPending), }) } return record, nil } func FileDefinition(name string, contents []byte, repo, commit string) (*models.WorkflowDefinition, error) { parsed, err := workflow.FromFile(name, contents) if err != nil { return nil, err } return &models.WorkflowDefinition{ ID: name, Name: name, Source: models.WorkflowDefinitionSource{File: &models.WorkflowFileSource{ Repo: repo, Commit: commit, Path: workflow.WorkflowDir + "/" + name, }}, Triggers: declaredTriggerNSIDs(parsed), }, nil } func declaredTriggerNSIDs(definition workflow.Workflow) []string { out := make([]string, 0) for _, constraint := range definition.When { for _, event := range constraint.Event { switch event { case string(workflow.TriggerKindPush): event = TriggerNSIDPush case string(workflow.TriggerKindPullRequest): event = TriggerNSIDPullRequest case string(workflow.TriggerKindManual): event = TriggerNSIDManual } out = append(out, event) } } return out } func inputPairs(inputs []*tangled.Pipeline_Pair) []*models.PipelineInputPair { out := make([]*models.PipelineInputPair, 0, len(inputs)) for _, input := range inputs { if input != nil { out = append(out, &models.PipelineInputPair{Key: input.Key, Value: input.Value}) } } return out } func ToTangled(record *models.PipelineRecord) *tangled.CiPipeline { if record == nil { return nil } trigger := &tangled.CiPipeline_Trigger{} switch { case record.Trigger.Push != nil: push := record.Trigger.Push trigger.CiTrigger_Push = &tangled.CiTrigger_Push{ Ref: push.Ref, NewSha: push.NewCommit, OldSha: push.OldCommit, } case record.Trigger.PullRequest != nil: pullRequest := record.Trigger.PullRequest trigger.CiTrigger_PullRequest = &tangled.CiTrigger_PullRequest{ Action: pullRequest.Action, SourceRepo: pullRequest.SourceRepo, SourceBranch: pullRequest.SourceBranch, SourceSha: pullRequest.SourceCommit, TargetBranch: pullRequest.TargetBranch, Pull: pullRequest.Pull, } case record.Trigger.Manual != nil: manual := record.Trigger.Manual trigger.CiTrigger_Manual = &tangled.CiTrigger_Manual{ Ref: manual.Ref, Sha: record.Commit, SourceRepo: manual.SourceRepo, Inputs: tangledInputPairs(manual.Inputs), } case record.Trigger.Schedule != nil: schedule := record.Trigger.Schedule trigger.CiTrigger_Schedule = &tangled.CiTrigger_Schedule{ Ref: schedule.Ref, ScheduledAt: schedule.ScheduledAt, Sha: schedule.Sha, } } createdAt := record.CreatedAt workflows := make([]*tangled.CiPipeline_Workflow, 0, len(record.Workflows)) for _, workflow := range record.Workflows { if workflow == nil { continue } workflows = append(workflows, &tangled.CiPipeline_Workflow{ Id: workflow.ID, Name: workflow.Name, Status: workflow.Status, Error: workflow.Error, StartedAt: workflow.StartedAt, FinishedAt: workflow.FinishedAt, }) } return &tangled.CiPipeline{ Id: string(record.ID), Repo: record.RepoDID, SourceRepo: record.SourceRepo, Commit: record.Commit, CreatedAt: &createdAt, Trigger: trigger, Workflows: workflows, } } func tangledInputPairs(inputs []*models.PipelineInputPair) []*tangled.CiTrigger_Pair { out := make([]*tangled.CiTrigger_Pair, 0, len(inputs)) for _, input := range inputs { if input != nil { out = append(out, &tangled.CiTrigger_Pair{Key: input.Key, Value: input.Value}) } } return out }