diff --git a/docs/content/en/docs-v1.0.x/contribution-guidelines/contributing-plugins/_index.md b/docs/content/en/docs-v1.0.x/contribution-guidelines/contributing-plugins/_index.md index dc9ddf3a41..2593d7da72 100644 --- a/docs/content/en/docs-v1.0.x/contribution-guidelines/contributing-plugins/_index.md +++ b/docs/content/en/docs-v1.0.x/contribution-guidelines/contributing-plugins/_index.md @@ -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/) diff --git a/docs/content/en/docs-v1.0.x/contribution-guidelines/contributing-plugins/book/01-architecture.md b/docs/content/en/docs-v1.0.x/contribution-guidelines/contributing-plugins/book/01-architecture.md new file mode 100644 index 0000000000..e5c14cadaf --- /dev/null +++ b/docs/content/en/docs-v1.0.x/contribution-guidelines/contributing-plugins/book/01-architecture.md @@ -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. diff --git a/docs/content/en/docs-v1.0.x/contribution-guidelines/contributing-plugins/book/02-first-stage-plugin.md b/docs/content/en/docs-v1.0.x/contribution-guidelines/contributing-plugins/book/02-first-stage-plugin.md new file mode 100644 index 0000000000..69bb414840 --- /dev/null +++ b/docs/content/en/docs-v1.0.x/contribution-guidelines/contributing-plugins/book/02-first-stage-plugin.md @@ -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. diff --git a/docs/content/en/docs-v1.0.x/contribution-guidelines/contributing-plugins/book/03-config.md b/docs/content/en/docs-v1.0.x/contribution-guidelines/contributing-plugins/book/03-config.md new file mode 100644 index 0000000000..e0d5fdbb6b --- /dev/null +++ b/docs/content/en/docs-v1.0.x/contribution-guidelines/contributing-plugins/book/03-config.md @@ -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. diff --git a/docs/content/en/docs-v1.0.x/contribution-guidelines/contributing-plugins/book/04-testing.md b/docs/content/en/docs-v1.0.x/contribution-guidelines/contributing-plugins/book/04-testing.md new file mode 100644 index 0000000000..c9a0e5d60a --- /dev/null +++ b/docs/content/en/docs-v1.0.x/contribution-guidelines/contributing-plugins/book/04-testing.md @@ -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. diff --git a/docs/content/en/docs-v1.0.x/contribution-guidelines/contributing-plugins/book/_index.md b/docs/content/en/docs-v1.0.x/contribution-guidelines/contributing-plugins/book/_index.md new file mode 100644 index 0000000000..8bd8dd4b3f --- /dev/null +++ b/docs/content/en/docs-v1.0.x/contribution-guidelines/contributing-plugins/book/_index.md @@ -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. diff --git a/docs/content/en/docs-v1.0.x/contribution-guidelines/contributing-plugins/plugin-development-resources.md b/docs/content/en/docs-v1.0.x/contribution-guidelines/contributing-plugins/plugin-development-resources.md index 342ec88ce1..13d49c3972 100644 --- a/docs/content/en/docs-v1.0.x/contribution-guidelines/contributing-plugins/plugin-development-resources.md +++ b/docs/content/en/docs-v1.0.x/contribution-guidelines/contributing-plugins/plugin-development-resources.md @@ -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. @@ -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) |