Skip to content
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
76 changes: 76 additions & 0 deletions docs/components/Semaphore.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { CardGrid, LinkCard } from "@astrojs/starlight/components";
## Actions

<CardGrid>
<LinkCard title="Get Pipeline" href="#get-pipeline" description="Fetch a Semaphore pipeline by ID" />
<LinkCard title="Run Workflow" href="#run-workflow" description="Run Semaphore workflow" />
</CardGrid>

Expand Down Expand Up @@ -127,6 +128,81 @@ This trigger automatically sets up a Semaphore webhook when configured. The webh
}
```

<a id="get-pipeline"></a>

## Get Pipeline

The Get Pipeline component fetches a Semaphore pipeline by ID and returns its current state, result, workflow ID, and metadata.

### Use Cases

- **Pipeline status checks**: After Run Workflow starts a pipeline, fetch its status to decide when to proceed or notify
- **Result verification**: Look up the result of a specific pipeline to get full details or confirm state
- **Conditional deployments**: Verify a pipeline passed before triggering a dependent action (e.g. deploy only if build pipeline passed)

### Configuration

- **Pipeline ID**: The Semaphore pipeline ID (e.g. from Run Workflow output or On Pipeline Done event data). Accepts expressions.

### Output

Returns the pipeline object including:
- **name**: Pipeline name
- **ppl_id**: Pipeline ID
- **wf_id**: Workflow ID
- **state**: Pipeline state (e.g. done, running)
- **result**: Pipeline result (e.g. passed, failed, stopped)
- **result_reason**: Reason for the result
- **created_at**: When the pipeline was created
- **done_at**: When the pipeline finished
- **running_at**: When the pipeline started running
- **yaml_file_name**: Pipeline YAML file name
- **working_directory**: Pipeline working directory

### Example Output

```json
{
"data": {
"created_at": {
"nanos": 0,
"seconds": 1737559967
},
"done_at": {
"nanos": 0,
"seconds": 1737559975
},
"error_description": "",
"name": "Initial Pipeline",
"pending_at": {
"nanos": 0,
"seconds": 1737559968
},
"ppl_id": "00000000-0000-0000-0000-000000000000",
"queuing_at": {
"nanos": 0,
"seconds": 1737559968
},
"result": "passed",
"result_reason": "test",
"running_at": {
"nanos": 0,
"seconds": 1737559968
},
"state": "done",
"stopping_at": {
"nanos": 0,
"seconds": 0
},
"wf_id": "00000000-0000-0000-0000-000000000000",
"working_directory": ".semaphore",
"yaml_file_name": "semaphore.yml"
},
"timestamp": "2026-01-22T15:32:56.061430218Z",
"type": "semaphore.pipeline"
}
```

<a id="run-workflow"></a>

## Run Workflow
Expand Down
21 changes: 21 additions & 0 deletions pkg/integrations/semaphore/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,27 @@ func (c *Client) GetPipeline(id string) (*Pipeline, error) {
return pipelineResponse.Pipeline, nil
}

func (c *Client) GetPipelineRaw(id string) (map[string]any, error) {
URL := fmt.Sprintf("%s/api/v1alpha/pipelines/%s", c.OrgURL, id)
responseBody, err := c.execRequest(http.MethodGet, URL, nil)
if err != nil {
return nil, err
}

var response map[string]any
err = json.Unmarshal(responseBody, &response)
if err != nil {
return nil, fmt.Errorf("error unmarshaling response: %v", err)
}

pipeline, ok := response["pipeline"].(map[string]any)
if !ok {
return nil, fmt.Errorf("pipeline data missing from response")
}

return pipeline, nil
}

func (c *Client) ListPipelines(projectID string) ([]any, error) {
URL := fmt.Sprintf("%s/api/v1alpha/pipelines?project_id=%s", c.OrgURL, projectID)
response, err := c.execRequest(http.MethodGet, URL, nil)
Expand Down
10 changes: 10 additions & 0 deletions pkg/integrations/semaphore/example.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,26 @@ var exampleOutputRunWorkflowBytes []byte
//go:embed example_data_on_pipeline_done.json
var exampleDataOnPipelineDoneBytes []byte

//go:embed example_output_get_pipeline.json
var exampleOutputGetPipelineBytes []byte

var exampleOutputOnce sync.Once
var exampleOutput map[string]any

var exampleDataOnce sync.Once
var exampleData map[string]any

var exampleOutputGetPipelineOnce sync.Once
var exampleOutputGetPipeline map[string]any

func (c *RunWorkflow) ExampleOutput() map[string]any {
return utils.UnmarshalEmbeddedJSON(&exampleOutputOnce, exampleOutputRunWorkflowBytes, &exampleOutput)
}

func (t *OnPipelineDone) ExampleData() map[string]any {
return utils.UnmarshalEmbeddedJSON(&exampleDataOnce, exampleDataOnPipelineDoneBytes, &exampleData)
}

func (c *GetPipeline) ExampleOutput() map[string]any {
return utils.UnmarshalEmbeddedJSON(&exampleOutputGetPipelineOnce, exampleOutputGetPipelineBytes, &exampleOutputGetPipeline)
}
39 changes: 39 additions & 0 deletions pkg/integrations/semaphore/example_output_get_pipeline.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
{
"data": {
"created_at": {
"nanos": 0,
"seconds": 1737559967
},
"done_at": {
"nanos": 0,
"seconds": 1737559975
},
"error_description": "",
"name": "Initial Pipeline",
"pending_at": {
"nanos": 0,
"seconds": 1737559968
},
"ppl_id": "00000000-0000-0000-0000-000000000000",
"queuing_at": {
"nanos": 0,
"seconds": 1737559968
},
"result": "passed",
"result_reason": "test",
"running_at": {
"nanos": 0,
"seconds": 1737559968
},
"state": "done",
"stopping_at": {
"nanos": 0,
"seconds": 0
},
"wf_id": "00000000-0000-0000-0000-000000000000",
"working_directory": ".semaphore",
"yaml_file_name": "semaphore.yml"
},
"timestamp": "2026-01-22T15:32:56.061430218Z",
"type": "semaphore.pipeline"
}
147 changes: 147 additions & 0 deletions pkg/integrations/semaphore/get_pipeline.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
package semaphore

import (
"errors"
"fmt"
"net/http"

"github.com/google/uuid"
"github.com/mitchellh/mapstructure"
"github.com/superplanehq/superplane/pkg/configuration"
"github.com/superplanehq/superplane/pkg/core"
)

const GetPipelinePayloadType = "semaphore.pipeline"

type GetPipeline struct{}

type GetPipelineSpec struct {
PipelineID string `json:"pipelineId" mapstructure:"pipelineId"`
}

func (c *GetPipeline) Name() string {
return "semaphore.getPipeline"
}

func (c *GetPipeline) Label() string {
return "Get Pipeline"
}

func (c *GetPipeline) Description() string {
return "Fetch a Semaphore pipeline by ID"
}

func (c *GetPipeline) Documentation() string {
return `The Get Pipeline component fetches a Semaphore pipeline by ID and returns its current state, result, workflow ID, and metadata.

## Use Cases

- **Pipeline status checks**: After Run Workflow starts a pipeline, fetch its status to decide when to proceed or notify
- **Result verification**: Look up the result of a specific pipeline to get full details or confirm state
- **Conditional deployments**: Verify a pipeline passed before triggering a dependent action (e.g. deploy only if build pipeline passed)

## Configuration

- **Pipeline ID**: The Semaphore pipeline ID (e.g. from Run Workflow output or On Pipeline Done event data). Accepts expressions.

## Output

Returns the pipeline object including:
- **name**: Pipeline name
- **ppl_id**: Pipeline ID
- **wf_id**: Workflow ID
- **state**: Pipeline state (e.g. done, running)
- **result**: Pipeline result (e.g. passed, failed, stopped)
- **result_reason**: Reason for the result
- **created_at**: When the pipeline was created
- **done_at**: When the pipeline finished
- **running_at**: When the pipeline started running
- **yaml_file_name**: Pipeline YAML file name
- **working_directory**: Pipeline working directory`
}

func (c *GetPipeline) Icon() string {
return "workflow"
}

func (c *GetPipeline) Color() string {
return "gray"
}

func (c *GetPipeline) OutputChannels(configuration any) []core.OutputChannel {
return []core.OutputChannel{core.DefaultOutputChannel}
}

func (c *GetPipeline) Configuration() []configuration.Field {
return []configuration.Field{
{
Name: "pipelineId",
Label: "Pipeline ID",
Type: configuration.FieldTypeString,
Required: true,
Description: "The Semaphore pipeline ID to fetch",
},
}
}

func (c *GetPipeline) Setup(ctx core.SetupContext) error {
spec := GetPipelineSpec{}
err := mapstructure.Decode(ctx.Configuration, &spec)
if err != nil {
return fmt.Errorf("failed to decode configuration: %w", err)
}

if spec.PipelineID == "" {
return errors.New("pipelineId is required")
}

return nil
}

func (c *GetPipeline) Execute(ctx core.ExecutionContext) error {
spec := GetPipelineSpec{}
err := mapstructure.Decode(ctx.Configuration, &spec)
if err != nil {
return fmt.Errorf("error decoding configuration: %v", err)
}

client, err := NewClient(ctx.HTTP, ctx.Integration)
if err != nil {
return fmt.Errorf("error creating client: %v", err)
}

pipeline, err := client.GetPipelineRaw(spec.PipelineID)
if err != nil {
return fmt.Errorf("failed to get pipeline: %v", err)
}

return ctx.ExecutionState.Emit(
core.DefaultOutputChannel.Name,
GetPipelinePayloadType,
[]any{pipeline},
)
}

func (c *GetPipeline) Cancel(ctx core.ExecutionContext) error {
return nil
}

func (c *GetPipeline) ProcessQueueItem(ctx core.ProcessQueueContext) (*uuid.UUID, error) {
return ctx.DefaultProcessing()
}

func (c *GetPipeline) Actions() []core.Action {
return []core.Action{}
}

func (c *GetPipeline) HandleAction(ctx core.ActionContext) error {
return nil
}

func (c *GetPipeline) HandleWebhook(ctx core.WebhookRequestContext) (int, error) {
return http.StatusOK, nil
}

func (c *GetPipeline) Cleanup(ctx core.SetupContext) error {
return nil
}
Loading