Skip to content
Merged
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
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@

CI/CD config generator for workflow projects — emits GitHub Actions, GitLab CI, Jenkins, and CircleCI pipelines from a workflow app config.

As of **v0.2.0** the **GitHub Actions** and **GitLab CI** platforms are generated by the workflow engine's config-derived `cigen` engine (analyze → CIPlan → render): the output is *derived from your app config* — its required secrets, deploy phases, migrations, health-check smoke, and plugin-install needs — not a fixed template. **Jenkins** and **CircleCI** still use template generators (smart generation for those is future work). This is the same engine behind `wfctl ci plan` / `wfctl ci generate`.
As of **v0.2.0** **all four platforms** — GitHub Actions, GitLab CI, Jenkins, and CircleCI — are generated by the workflow engine's config-derived `cigen` engine (analyze → CIPlan → render): the output is *derived from your app config* — its required secrets, deploy phases, migrations, health-check smoke, and plugin-install needs — not a fixed template. The legacy Jenkins/CircleCI template generators were retired in #804 (see ADR 0044 in the workflow repo). This is the same engine behind `wfctl ci plan` / `wfctl ci generate`. Requires workflow engine **>= v0.68.0**.

## What it provides

**Pipeline step types:**
- `step.ci_generate` — Generate CI/CD configuration files from a workflow app config. GitHub Actions / GitLab CI are config-derived via `cigen`; Jenkins / CircleCI are template-based.
- `step.ci_generate` — Generate CI/CD configuration files from a workflow app config. All four platforms (GitHub Actions, GitLab CI, Jenkins, CircleCI) are config-derived via `cigen`.

## Requirements

Expand Down Expand Up @@ -62,8 +62,8 @@ See [`examples/minimal/config.yaml`](examples/minimal/config.yaml).
|----------|-------------|------------|
| GitHub Actions | `.github/workflows/<name>.yml` | config-derived (`cigen`) |
| GitLab CI | `.gitlab-ci.yml` | config-derived (`cigen`) |
| Jenkins | `Jenkinsfile` | template |
| CircleCI | `.circleci/config.yml` | template |
| Jenkins | `Jenkinsfile` | config-derived (`cigen`) |
| CircleCI | `.circleci/config.yml` | config-derived (`cigen`) |

## Documentation

Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ module github.com/GoCodeAlone/workflow-plugin-ci-generator
go 1.26.0

require (
github.com/GoCodeAlone/workflow v0.67.0
github.com/GoCodeAlone/workflow v0.68.0
google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af
)

Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ github.com/GoCodeAlone/modular/modules/jsonschema v1.17.0 h1:zoWioqUvuNNDfnjHA1s
github.com/GoCodeAlone/modular/modules/jsonschema v1.17.0/go.mod h1:GDU/jsD6AddmXKedj0wZwieUIaQsTBSGMzuj+XHXMrw=
github.com/GoCodeAlone/modular/modules/reverseproxy/v2 v2.10.0 h1:+2M/ecyCxDiXfJM4ibcERuu/BBeIbLTQNcVgRsllR64=
github.com/GoCodeAlone/modular/modules/reverseproxy/v2 v2.10.0/go.mod h1:tlVH1mA5yuU8CB7R7+HXIRaBixZoNid6h+5tew5u3FU=
github.com/GoCodeAlone/workflow v0.67.0 h1:jyzzBq3+axqdqpq7+Z8ozckhjxGISIIheyPcoHbu7lk=
github.com/GoCodeAlone/workflow v0.67.0/go.mod h1:4UwFYm1cM8a/AvGNb1CZAuob0b0gq7552sxcNMdDALA=
github.com/GoCodeAlone/workflow v0.68.0 h1:7dh/7tPtjsJwgS4IG/NZFeCyNtZnp7uoDBOI6S8dADM=
github.com/GoCodeAlone/workflow v0.68.0/go.mod h1:4UwFYm1cM8a/AvGNb1CZAuob0b0gq7552sxcNMdDALA=
github.com/GoCodeAlone/yaegi v0.17.2 h1:WK6Y6e0t1a6U7r+S2dN3CGWW1PizYD3zO0zneToZPxM=
github.com/GoCodeAlone/yaegi v0.17.2/go.mod h1:z5Pr6Wse6QJcQvpgxTxzMAevFarH0N37TG88Y9dprx0=
github.com/IBM/sarama v1.47.0 h1:GcQFEd12+KzfPYeLgN69Fh7vLCtYRhVIx0rO4TZO318=
Expand Down
53 changes: 53 additions & 0 deletions integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,64 @@ package cigenerator_test

import (
"context"
"os"
"strings"
"testing"

"github.com/GoCodeAlone/workflow-plugin-ci-generator/internal"
"github.com/GoCodeAlone/workflow-plugin-ci-generator/internal/contracts"
"github.com/GoCodeAlone/workflow/wftest"

sdk "github.com/GoCodeAlone/workflow/plugin/external/sdk"
)

// TestIntegration_ExecuteCIGenerate_JenkinsCircleCI is the acceptance-#2
// behavior proof for #804: it drives the REAL plugin entry point
// (internal.ExecuteCIGenerate) through the external package boundary for the
// jenkins and circleci platforms and asserts the written artifacts are
// config-derived (secret wiring, `wfctl migrations up`, `wfctl infra apply`) and
// free of the retired legacy stages (ADR 0044) — NOT a mocked step.
func TestIntegration_ExecuteCIGenerate_JenkinsCircleCI(t *testing.T) {
cases := map[string][]string{
internal.PlatformJenkins: {"pipeline {", "wfctl migrations up", "wfctl infra apply"},
internal.PlatformCircleCI: {"version: 2.1", "wfctl migrations up", "wfctl infra apply"},
}
for platform, markers := range cases {
result, err := internal.ExecuteCIGenerate(context.Background(), sdk.TypedStepRequest[*contracts.CIGenerateConfig, *contracts.CIGenerateInput]{
Config: &contracts.CIGenerateConfig{},
Input: &contracts.CIGenerateInput{
Platform: platform,
OutputDir: t.TempDir(),
InfraConfig: "internal/testdata/app.yaml",
},
})
if err != nil {
t.Fatalf("%s: ExecuteCIGenerate: %v", platform, err)
}
if result.Output.Error != "" {
t.Fatalf("%s: %s", platform, result.Output.Error)
}
combined := ""
for _, w := range result.Output.FilesWritten {
raw, rerr := os.ReadFile(w)
if rerr != nil {
t.Fatalf("read %s: %v", w, rerr)
}
combined += string(raw) + "\n"
}
for _, m := range markers {
if !strings.Contains(combined, m) {
t.Errorf("%s: missing config-derived marker %q", platform, m)
}
}
for _, banned := range []string{"go test ./...", "wfctl deploy --image", "docker build"} {
if strings.Contains(combined, banned) {
t.Errorf("%s: found retired legacy marker %q", platform, banned)
}
}
}
}

// TestIntegration_GenerateGitHubActions verifies that a pipeline using
// step.ci_generate executes and returns GitHub Actions output.
func TestIntegration_GenerateGitHubActions(t *testing.T) {
Expand Down
46 changes: 11 additions & 35 deletions internal/generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,33 +10,20 @@ import (
"strings"

"github.com/GoCodeAlone/workflow-plugin-ci-generator/internal/contracts"
"github.com/GoCodeAlone/workflow-plugin-ci-generator/internal/platforms"
"github.com/GoCodeAlone/workflow/cigen"
sdk "github.com/GoCodeAlone/workflow/plugin/external/sdk"
)

// Platform constants.
// Platform constants. All four platforms are config-derived through the cigen
// analyze → CIPlan → render pipeline (the legacy text/template generators were
// retired in #804; see ADR 0044 in the workflow repo).
const (
PlatformGitHubActions = "github_actions"
PlatformGitLabCI = "gitlab_ci"
PlatformJenkins = "jenkins"
PlatformCircleCI = "circleci"
)

// Generator defines the interface all platform generators implement.
type Generator interface {
// Generate produces CI config files. Returns a map of relative output path → content.
Generate(opts platforms.Options) (map[string]string, error)
}

// registry maps platform names to template generator constructors.
// Only jenkins and circleci are handled here; github_actions and gitlab_ci
// are routed through the cigen smart analyzer in ExecuteCIGenerate.
var registry = map[string]func() Generator{
PlatformJenkins: func() Generator { return platforms.NewJenkinsGenerator() },
PlatformCircleCI: func() Generator { return platforms.NewCircleCIGenerator() },
}

// knownPlatforms is the complete set of supported platform names.
var knownPlatforms = map[string]bool{
PlatformGitHubActions: true,
Expand All @@ -46,9 +33,9 @@ var knownPlatforms = map[string]bool{
}

// ExecuteCIGenerate generates CI/CD config files for the specified platform.
// For github_actions and gitlab_ci, the cigen smart analyzer is used
// (analyze → CIPlan → render). For jenkins and circleci, the existing
// template generators are used unchanged.
// All four platforms (github_actions, gitlab_ci, jenkins, circleci) are
// config-derived through the cigen analyze → CIPlan → render pipeline; the
// legacy text/template generators were retired in #804 (see ADR 0044).
func ExecuteCIGenerate(ctx context.Context, req sdk.TypedStepRequest[*contracts.CIGenerateConfig, *contracts.CIGenerateInput]) (*sdk.TypedStepResult[*contracts.CIGenerateOutput], error) {
_ = ctx
platform := resolveTypedString(req.Input.GetPlatform(), req.Config.GetPlatform())
Expand All @@ -75,7 +62,7 @@ func ExecuteCIGenerate(ctx context.Context, req sdk.TypedStepRequest[*contracts.
var files map[string]string

switch platform {
case PlatformGitHubActions, PlatformGitLabCI:
case PlatformGitHubActions, PlatformGitLabCI, PlatformJenkins, PlatformCircleCI:
var plan *cigen.CIPlan
if fromPlan != "" {
// Load a pre-computed CIPlan JSON directly.
Expand Down Expand Up @@ -117,25 +104,14 @@ func ExecuteCIGenerate(ctx context.Context, req sdk.TypedStepRequest[*contracts.
files, err = cigen.RenderGitHubActions(plan)
case PlatformGitLabCI:
files, err = cigen.RenderGitLabCI(plan)
case PlatformJenkins:
files, err = cigen.RenderJenkins(plan)
case PlatformCircleCI:
files, err = cigen.RenderCircleCI(plan)
}
if err != nil {
return typedCIGenerateError(fmt.Sprintf("cigen render: %v", err)), nil
}

default:
// jenkins and circleci: template generators.
opts := platforms.Options{
InfraConfig: infraConfig,
ProjectName: projectName,
Runner: runner,
DefaultBranch: defaultBranch,
}
gen := registry[platform]()
var err error
files, err = gen.Generate(opts)
if err != nil {
return typedCIGenerateError(err.Error()), nil
}
}

written := make([]string, 0, len(files))
Expand Down
Loading
Loading