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
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ For example:

## Getting started

> [!TIP]
> The [**Plugin Development Book**](./book/) provides a step-by-step walkthrough for building your first plugin.

### Prerequisites

- [Go 1.24 or later](https://go.dev/)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
---
title: "Architecture"
linkTitle: "Architecture"
weight: 10
description: >
The relationship between Piped and Plugins.
---

To build a plugin, you first need to understand how it fits into the PipeCD ecosystem.

## The gRPC Bridge

In PipeCD v1, the `piped` agent does not contain the logic for every possible deployment platform. Instead, it acts as an orchestrator. When a deployment starts, `piped` looks at the application configuration to see which plugin is responsible for the work.

`piped` then communicates with the plugin over **gRPC**.

```mermaid
graph LR
subgraph "PipeCD Control Plane"
UI[Web UI]
API[API Server]
end

subgraph "Your Environment"
Piped[Piped Agent]
Plugin[Your Plugin]
end

UI --> API
API --> Piped
Piped -- "gRPC (Client)" --> Plugin
Plugin -- "SDK / API" --> Target[Target Platform]
```

### Key Responsibilities

| Component | Responsibility |
|-----------|----------------|
| **Piped** | Watches Git, triggers deployments, manages plugin lifecycles, and provides shared utilities (secret management, etc.). |
| **Plugin** | Implements specific logic for planning deployments, syncing resources, and calculating drift. |
| **SDK** | Simplifies gRPC implementation, provides log persistence, and abstracts common tasks. |

## Plugin Interfaces

The PipeCD Go SDK defines three main interfaces that a plugin can implement:

1. **Deployment**: Mandatory for any plugin that wants to execute stages. It handles planning and execution.
2. **LiveState**: Optional. Used to fetch the current state of resources from the live environment.
3. **Drift**: Optional. Used to compare the desired state (Git) with the live state.

In this book, we will focus on the **Deployment** interface.

## Lifecycle of a Plugin

1. **Startup**: `piped` starts the plugin binary as a sidecar or managed process.
2. **Registration**: The plugin starts a gRPC server and listens on a port.
3. **Handshake**: `piped` connects to the plugin and verifies compatibility.
4. **Execution**: When a deployment reaches a relevant stage, `piped` calls `ExecuteStage` on the plugin.
5. **Logging**: The plugin sends logs back to `piped` in real-time via the SDK.
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
---
title: "Your First Plugin"
linkTitle: "Your First Plugin"
weight: 20
description: >
Setting up and building a minimal stage plugin.
---

Let's build a simple "Hello World" plugin that executes a custom stage.

## 1. Initialize the Project

Create a new directory for your plugin and initialize the Go module:

```bash
mkdir my-plugin
cd my-plugin
go mod init github.com/your-username/my-plugin
```

Add the PipeCD Plugin SDK as a dependency:

```bash
go get github.com/pipe-cd/piped-plugin-sdk-go
```

## 2. The Main Entry Point

Create a `main.go` file. This file will use the SDK to start a gRPC server and register your plugin implementation.

```go
package main

import (
"log"
sdk "github.com/pipe-cd/piped-plugin-sdk-go"
)

func main() {
// Create a new plugin instance and register our implementation
p, err := sdk.NewPlugin("0.1.0", sdk.WithStagePlugin(&myPlugin{}))
if err != nil {
log.Fatalln(err)
}

// Run the gRPC server
if err := p.Run(); err != nil {
log.Fatalln(err)
}
}
```

## 3. Implementing the Interface

Now, create `plugin.go` to implement the required methods for a `StagePlugin`.

```go
package main

import (
"context"
"fmt"
sdk "github.com/pipe-cd/piped-plugin-sdk-go"
)

type myPlugin struct{}

// FetchDefinedStages returns the names of stages this plugin handles.
func (p *myPlugin) FetchDefinedStages() []string {
return []string{"HELLO_WORLD"}
}

// BuildPipelineSyncStages prepares the stages for execution.
func (p *myPlugin) BuildPipelineSyncStages(ctx context.Context, _ sdk.ConfigNone, input *sdk.BuildPipelineSyncStagesInput) (*sdk.BuildPipelineSyncStagesResponse, error) {
stages := make([]sdk.PipelineStage, 0, len(input.Request.Stages))
for _, rs := range input.Request.Stages {
stages = append(stages, sdk.PipelineStage{
Index: rs.Index,
Name: rs.Name,
})
}
return &sdk.BuildPipelineSyncStagesResponse{Stages: stages}, nil
}

// ExecuteStage is where the actual work happens.
func (p *myPlugin) ExecuteStage(ctx context.Context, _ sdk.ConfigNone, _ sdk.DeployTargetsNone, input *sdk.ExecuteStageInput[struct{}]) (*sdk.ExecuteStageResponse, error) {
// Access the log persister to send logs back to PipeCD
lp := input.LogPersister
lp.Infof("Hello from the plugin! Executing stage: %s", input.Stage.Name)

return &sdk.ExecuteStageResponse{
Status: sdk.StageStatusSuccess,
}, nil
}
```

## 4. Understanding the methods

- **`FetchDefinedStages`**: This tells `piped` which stage names in the YAML should be routed to this plugin.
- **`BuildPipelineSyncStages`**: This is called when planning the deployment. It allows you to transform raw stage requests into structured `PipelineStage` objects.
- **`ExecuteStage`**: The core execution logic. You receive the stage data and can use the `LogPersister` to provide feedback to the user via the Web UI.

In the next chapter, we'll see how to add configuration options to our plugin.
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
---
title: "Configuration"
linkTitle: "Configuration"
weight: 30
description: >
Defining and parsing plugin configuration.
---

Most plugins need input from the user. For example, a "Wait" plugin needs to know how long to wait.

In PipeCD, configuration is passed through the `app.pipecd.yaml` file in the Git repository.

## 1. Define the Options Struct

In your plugin, define a Go struct that matches the YAML structure of your stage configuration. Use JSON tags for mapping.

```go
package main

import (
"encoding/json"
"fmt"
)

// MyStageOptions defines the fields allowed in the pipeline stage.
type MyStageOptions struct {
Message string `json:"message"`
Repeat int `json:"repeat"`
}

// Helper function to decode raw JSON from Piped
func decodeOptions(data []byte) (MyStageOptions, error) {
var opts MyStageOptions
if err := json.Unmarshal(data, &opts); err != nil {
return opts, err
}
return opts, nil
}
```

## 2. Access Configuration in `ExecuteStage`

Inside the `ExecuteStage` method, you can access the raw configuration from the input request and decode it.

```go
func (p *myPlugin) ExecuteStage(ctx context.Context, _ sdk.ConfigNone, _ sdk.DeployTargetsNone, input *sdk.ExecuteStageInput[struct{}]) (*sdk.ExecuteStageResponse, error) {
lp := input.LogPersister

// Decode the configuration
opts, err := decodeOptions(input.Request.StageConfig)
if err != nil {
lp.Errorf("Failed to decode config: %v", err)
return &sdk.ExecuteStageResponse{Status: sdk.StageStatusFailure}, nil
}

// Use the options
for i := 0; i < opts.Repeat; i++ {
lp.Infof("Message %d: %s", i+1, opts.Message)
}

return &sdk.ExecuteStageResponse{Status: sdk.StageStatusSuccess}, nil
}
```

## 3. Usage in `app.pipecd.yaml`

Once your plugin is registered (which we'll cover in the next chapter), users can use it in their application configuration like this:

```yaml
apiVersion: pipecd.dev/v1beta1
kind: KubernetesApp
spec:
pipeline:
stages:
- name: MY_CUSTOM_STAGE
with:
message: "Hello from Git!"
repeat: 3
```

The fields under `with` are passed as `StageConfig` to your plugin.

---

> [!NOTE]
> You can also define **Plugin-wide configuration** (set in the `piped` config) and **Deploy Target configuration**. These are passed as the second and third arguments to `ExecuteStage` respectively.
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
---
title: "Testing and Debugging"
linkTitle: "Testing and Debugging"
weight: 40
description: >
Running and verifying your plugin locally.
---

Testing a plugin involves building it and then telling a `piped` agent to use it.

## 1. Build your Plugin

Since plugins are standalone binaries, you can build them using standard Go commands:

```bash
go build -o my-plugin .
```

If you are contributing to the official PipeCD repository, you can use the provided Makefile:

```bash
make build/plugin
```

## 2. Configure Piped to use your Plugin

To test your plugin locally, you need a `piped-config.yaml` that registers your plugin's address.

```yaml
apiVersion: pipecd.dev/v1beta1
kind: Piped
spec:
plugins:
- name: my-custom-plugin
url: localhost:7001 # Address where your plugin will listen
```

## 3. Run Piped and your Plugin

First, start your plugin. By default, the SDK listens on a port (you can configure this via flags or environment variables, usually `--port`).

```bash
./my-plugin --port=7001
```

In another terminal, run `piped` with your local configuration:

```bash
# Using the make command from the pipecd repo
make run/piped CONFIG_FILE=piped-config.yaml EXPERIMENTAL=true INSECURE=true
```

## 4. Verify in the Web UI

1. Create an application in PipeCD that uses your custom stage.
2. Trigger a deployment.
3. Open the deployment details in the Web UI.
4. You should see your custom stage executing. Click on it to see the logs sent via `LogPersister`.

## 5. Debugging Tips

- **Check Piped Logs**: `piped` will log any gRPC connection errors or handshake failures.
- **Verbose Logging**: Use `lp.Debugf` in your plugin for detailed logs that only appear if the deployment is in debug mode.
- **Restarting**: If you change your plugin code, you must rebuild and restart the plugin binary. `piped` will automatically try to reconnect.

---

Congratulations! You've built and tested your first PipeCD plugin. For more complex examples, explore the [official plugins](https://github.com/pipe-cd/pipecd/tree/master/pkg/app/pipedv1/plugin) in the repository.
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
---
title: "Plugin Development Book"
linkTitle: "Plugin Development Book"
weight: 1
description: >
A hands-on guide to building your first PipeCD plugin.
---

Welcome to the Plugin Development Book! This guide is designed to take you from a basic understanding of PipeCD to building and testing your own custom plugin.

PipeCD v1 is built with extensibility in mind. By creating a plugin, you can add support for new platforms, custom deployment strategies, or specialized automation tasks.

## What we will build

In this book, we will walk through the creation of a simple **Stage Plugin**. This type of plugin is used to execute specific steps (stages) within a deployment pipeline.

By the end of this guide, you will have a working plugin that can:
1. Be registered with a Piped agent.
2. Execute a custom stage defined in an application's `app.pipecd.yaml`.
3. Report logs and status back to PipeCD.

## Chapters

1. **[Architecture](./01-architecture/)**: Understand how Piped and Plugins communicate via gRPC.
2. **[Your First Plugin](./02-first-stage-plugin/)**: Set up the project structure and implement the basic interface.
3. **[Configuration](./03-config/)**: Learn how to pass parameters from Git to your plugin.
4. **[Testing and Debugging](./04-testing/)**: Run your plugin locally and verify its behavior.

---

> [!TIP]
> This book focuses on the Go SDK. While plugins can be written in any language that supports gRPC, using the official [piped-plugin-sdk-go](https://github.com/pipe-cd/piped-plugin-sdk-go) is highly recommended as it handles most of the boilerplate for you.
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,8 @@ description: >
Links and short notes for developing PipeCD plugins.
---

> **Note:**
> This section is still a work in progress. A full tutorial and an in-docs translation of the Zenn book are planned over time.
>
> For a hands-on walkthrough, read [**Build and learn PipeCD plugins**](https://zenn.dev/warashi/books/try-and-learn-pipecd-plugin) (Zenn; Japanese title *作って学ぶ PipeCD プラグイン*). The same book is linked again in [Links](#links), together with other references. Use your browser's translate feature to read this in English. Verify commands and field names against this documentation and the [`pipecd`](https://github.com/pipe-cd/pipecd) repository.
> [!TIP]
> New to plugin development? Start with our [**Plugin Development Book**](./book/), a hands-on guide to building and testing your first plugin.

Use this page together with [Contribute to PipeCD plugins](../), which covers layout, `make` targets, and how to open a pull request.

Expand Down Expand Up @@ -80,6 +78,7 @@ After rebuilding a plugin, `piped` may still use files under **`~/.piped`** (inc

| Resource | Notes |
|----------|-------|
| [**Plugin Development Book**](./book/) | Hands-on English tutorial (In-docs) |
| [**Build and learn PipeCD plugins** (Zenn)](https://zenn.dev/warashi/books/try-and-learn-pipecd-plugin) | Japanese tutorial book (*作って学ぶ PipeCD プラグイン*) |
| [DeepWiki (pipecd)](https://deepwiki.com/pipe-cd/pipecd) | Unofficial wiki-style overview of the repository. |
| [Plugin Architecture RFC](https://github.com/pipe-cd/pipecd/blob/master/docs/rfcs/0015-pipecd-plugin-arch-meta.md) | Design (RFC) |
Expand Down
Loading