Guide

Build with the Component SDK

Create collectors, tools, utilities, and remote adapters.

Prerequisites

  • Go 1.21+
  • epack CLI installed

Create a component

epack sdk new collector myservice

This creates a ready-to-build component project:

epack-collector-myservice/
├── main.go                           # Implementation
├── go.mod
├── README.md
├── .gitignore
├── docs/                             # Registry documentation
│   ├── overview.md
│   ├── configuration.md
│   └── examples.md
├── .slsa-goreleaser/                 # Multi-platform build configs
│   └── *.yml
└── .github/workflows/
    └── release.yaml                  # SLSA Level 3 + Sigstore signing

Component types

epack sdk new collector myservice    # Gathers evidence from APIs
epack sdk new tool analyzer          # Processes packs
epack sdk new utility viewer         # Standalone CLI tool
epack sdk new remote mycloud         # Push/pull adapter

Develop with watch mode

Run your component with automatic rebuild on file changes:

cd epack-collector-myservice
epack sdk run --watch .

Pass arguments after --:

epack sdk run --watch . -- --capabilities

Generate test data

Create sample inputs for testing:

# Generate test fixtures
epack sdk mock tool              # Creates sample-evidence.epack
epack sdk mock collector         # Creates sample config and env vars
epack sdk mock tool -o testdata  # Output to testdata/

Run conformance tests

Verify your component implements the protocol correctly:

# Build and test
epack sdk test .

# Test a binary directly
epack sdk test ./epack-collector-myservice

# Verbose output
epack sdk test --verbose .

Implement a collector

Edit main.go to fetch from your API:

main.go
func run(ctx componentsdk.CollectorContext) error {
    // Get config from epack.yaml
    org := ctx.Config()["organization"].(string)

    // Get secrets from environment
    token := ctx.Secret("MYSERVICE_API_TOKEN")

    // Call your API
    data, err := fetchFromAPI(org, token)
    if err != nil {
        return componentsdk.NewNetworkError("API call failed: %v", err)
    }

    // Emit collected artifacts
    return ctx.Emit([]componentsdk.CollectedArtifact{{
        Data: map[string]any{
            "collected_at": time.Now().UTC().Format(time.RFC3339),
            "organization": org,
            "mfa_enabled":   data.MFAEnabled,
            "user_count":    data.UserCount,
        },
    }})
}

Error types

componentsdk.NewConfigError("missing field: %s", field)   # Exit 2
componentsdk.NewAuthError("invalid token")                 # Exit 3
componentsdk.NewNetworkError("request failed: %v", err)    # Exit 4

Implement a tool

main.go
func run(ctx componentsdk.ToolContext) error {
    // Access the pack
    pack := ctx.Pack()

    // Read an artifact
    data, err := pack.ReadArtifact("artifacts/github-repos.json")
    if err != nil {
        return err
    }

    // Analyze
    result := analyze(data)

    // Write output
    return ctx.WriteOutput("analysis.json", result)
}

Pack access

pack.Manifest()                                    # Get manifest
pack.ReadArtifact("artifacts/report.json")         # Read artifact
ctx.Warn("CODE", "message", "path")                  # Add warning
ctx.Error("CODE", "message", "path")                 # Add error

Implement a utility

main.go
func run(args []string) error {
    if len(args) == 0 {
        return fmt.Errorf("no pack file specified")
    }

    pack, err := componentsdk.OpenPack(args[0])
    if err != nil {
        return err
    }
    defer pack.Close()

    fmt.Printf("Stream: %s\n", pack.Manifest().Stream)
    return nil
}

Implement a remote adapter

main.go
type handler struct{}

func (h *handler) PushPrepare(req componentsdk.PushPrepareRequest) (*componentsdk.PushPrepareResponse, error) {
    url, token := generateUploadURL(req.Target.Workspace, req.Pack.Digest)
    return &componentsdk.PushPrepareResponse{
        Upload:        componentsdk.UploadInfo{Method: "PUT", URL: url},
        FinalizeToken: token,
    }, nil
}

func (h *handler) PushFinalize(req componentsdk.PushFinalizeRequest) (*componentsdk.PushFinalizeResponse, error) {
    release := createRelease(req.FinalizeToken)
    return &componentsdk.PushFinalizeResponse{
        Release: componentsdk.ReleaseResult{ReleaseID: release.ID, PackDigest: release.Digest},
    }, nil
}

func (h *handler) PullPrepare(req componentsdk.PullPrepareRequest) (*componentsdk.PullPrepareResponse, error) {
    url, digest, token := getDownloadURL(req.Target.Workspace, req.Ref)
    return &componentsdk.PullPrepareResponse{
        Download:      componentsdk.DownloadInfo{URL: url},
        Pack:          componentsdk.PackResult{Digest: digest},
        FinalizeToken: token,
    }, nil
}

func (h *handler) PullFinalize(req componentsdk.PullFinalizeRequest) (*componentsdk.PullFinalizeResponse, error) {
    return &componentsdk.PullFinalizeResponse{Confirmed: true}, nil
}

Release

Tag a release to trigger the scaffolded GitHub workflow:

git tag v0.1.29
git push origin v0.1.29

The workflow builds for linux/amd64, linux/arm64, darwin/amd64, darwin/arm64 with SLSA Level 3 provenance and Sigstore signing.

Configure in epack.yaml

Collectors and tools

epack.yaml
collectors:
  myservice:
    source: ghcr.io/yourorg/epack-collector-myservice@v1
    config:
      organization: acme
    secrets:
      - MYSERVICE_API_TOKEN

tools:
  analyzer:
    source: ghcr.io/yourorg/epack-tool-analyzer@v1

Remotes

epack.yaml
remotes:
  mycloud:
    source: ghcr.io/yourorg/epack-remote-mycloud@v1
    target:
      workspace: acme
    auth:
      mode: api_key

Utilities

Utilities are standalone CLI tools invoked directly:

epack utility viewer evidence.epack