Skip to content

Pipeline service + webhook automation + docs/ADR#74

Merged
poyrazK merged 24 commits intomainfrom
feature/pipeline-service-docs-adr
Feb 26, 2026
Merged

Pipeline service + webhook automation + docs/ADR#74
poyrazK merged 24 commits intomainfrom
feature/pipeline-service-docs-adr

Conversation

@poyrazK
Copy link
Copy Markdown
Owner

@poyrazK poyrazK commented Feb 26, 2026

Why\nAdd a platform-native CI/CD pipeline capability so users can run manual and webhook-triggered builds with persisted step logs and delivery idempotency.\n\n## What Changed\n- Added pipeline domain models, ports, service logic, Postgres repository, and worker execution flow\n- Added pipeline HTTP handlers/routes and webhook endpoints for GitHub/GitLab\n- Added pipeline migrations (pipelines + webhook deliveries)\n- Added unit and handler tests for pipeline and webhook behaviors\n- Updated docs (docs/CI_CD.md, docs/api-reference.md) and added ADR-014\n- Regenerated swagger artifacts to keep CI swagger check in sync\n\n## Validation\n- Local/API verification performed for manual runs, webhook trigger, duplicate-delivery ignore path, and lint scenario\n- PR checks are passing after swagger sync update\n

Copilot AI review requested due to automatic review settings February 26, 2026 14:07
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Feb 26, 2026

Warning

Rate limit exceeded

@poyrazK has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 2 minutes and 16 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 5c29b02 and 1bc4425.

📒 Files selected for processing (23)
  • cmd/api/main.go
  • docs/CI_CD.md
  • docs/adr/ADR-014-pipeline-execution-and-webhook-security.md
  • docs/api-reference.md
  • docs/swagger/docs.go
  • docs/swagger/swagger.json
  • docs/swagger/swagger.yaml
  • internal/api/setup/dependencies.go
  • internal/api/setup/router.go
  • internal/core/domain/jobs.go
  • internal/core/domain/pipeline.go
  • internal/core/domain/rbac.go
  • internal/core/ports/pipeline.go
  • internal/core/services/pipeline.go
  • internal/core/services/pipeline_unit_test.go
  • internal/handlers/pipeline_handler.go
  • internal/handlers/pipeline_handler_test.go
  • internal/repositories/postgres/migrations/093_create_pipelines.down.sql
  • internal/repositories/postgres/migrations/093_create_pipelines.up.sql
  • internal/repositories/postgres/migrations/094_create_pipeline_webhook_deliveries.down.sql
  • internal/repositories/postgres/migrations/094_create_pipeline_webhook_deliveries.up.sql
  • internal/repositories/postgres/pipeline_repo.go
  • internal/workers/pipeline_worker.go
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/pipeline-service-docs-adr

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new CI/CD “pipelines” feature to the backend: persisted pipeline definitions + build execution records, webhook-triggered build creation with signature validation/idempotency, and a background worker to execute pipeline steps via the compute backend.

Changes:

  • Introduces pipeline domain models, ports, service logic, and a Postgres repository + migrations for pipelines/builds/logs and webhook delivery idempotency.
  • Adds HTTP endpoints (authenticated CRUD + runs APIs, and a public webhook trigger endpoint for GitHub/GitLab).
  • Adds a pipeline worker to consume queued build jobs and execute steps, plus tests and documentation/ADR updates.

Reviewed changes

Copilot reviewed 23 out of 23 changed files in this pull request and generated 12 comments.

Show a summary per file
File Description
internal/workers/pipeline_worker.go New worker that dequeues build jobs and executes pipeline steps via the compute backend.
internal/repositories/postgres/pipeline_repo.go New Postgres repository for pipelines, builds, steps, logs, and webhook delivery reservations.
internal/repositories/postgres/migrations/093_create_pipelines.up.sql Adds pipelines, builds, build_steps, build_logs tables and indexes.
internal/repositories/postgres/migrations/093_create_pipelines.down.sql Rollback for pipeline/build tables and indexes.
internal/repositories/postgres/migrations/094_create_pipeline_webhook_deliveries.up.sql Adds webhook delivery idempotency table + unique constraint + index.
internal/repositories/postgres/migrations/094_create_pipeline_webhook_deliveries.down.sql Rollback for webhook deliveries table/index.
internal/handlers/pipeline_handler.go New Gin handler implementing pipeline CRUD/run APIs and webhook trigger endpoint.
internal/handlers/pipeline_handler_test.go Handler tests focused on webhook header mapping + invalid pipeline ID behavior.
internal/core/services/pipeline.go New pipeline service with lifecycle ops, manual triggers, webhook trigger validation/idempotency, and build queueing.
internal/core/services/pipeline_unit_test.go Unit tests for webhook signature validation, idempotency, branch matching, and queueing behavior.
internal/core/ports/pipeline.go New ports/interfaces and DTO option types for pipelines/builds/webhooks.
internal/core/domain/pipeline.go New domain entities for pipelines, builds, steps, logs, and config.
internal/core/domain/jobs.go Adds BuildJob for queue payloads.
internal/core/domain/rbac.go Adds RBAC permission constants for pipeline operations.
internal/api/setup/router.go Wires pipeline routes (including public webhook endpoint) and adds RBAC guards for authenticated routes.
internal/api/setup/dependencies.go Wires pipeline repository/service/worker into dependency graph.
cmd/api/main.go Starts the new pipeline worker when running background workers.
docs/api-reference.md Documents new /pipelines endpoints and webhook headers/behavior.
docs/CI_CD.md Adds overview and example config for the platform-native pipelines feature.
docs/adr/ADR-014-pipeline-execution-and-webhook-security.md Adds ADR describing execution model and webhook security/idempotency decisions.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

INSERT INTO build_logs (id, build_id, step_id, content, created_at)
VALUES ($1, $2, $3, $4, $5)
`
_, err := r.db.Exec(ctx, query, log.ID, log.BuildID, log.StepID, log.Content, log.CreatedAt)
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AppendBuildLog always inserts log.StepID, but build-level logs may not have a step ID and the DB column is nullable. With a plain uuid.UUID this will insert 00000000-... and violate the FK. Update the model/insert to support NULL step IDs when absent.

Suggested change
_, err := r.db.Exec(ctx, query, log.ID, log.BuildID, log.StepID, log.Content, log.CreatedAt)
var stepID interface{}
if log.StepID == uuid.Nil {
stepID = nil
} else {
stepID = log.StepID
}
_, err := r.db.Exec(ctx, query, log.ID, log.BuildID, stepID, log.Content, log.CreatedAt)

Copilot uses AI. Check for mistakes.
Comment on lines +216 to +220
if rawLimit := c.Query("limit"); rawLimit != "" {
parsed, parseErr := strconv.Atoi(rawLimit)
if parseErr != nil {
httputil.Error(c, fmt.Errorf("invalid limit: %w", parseErr))
return
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

limit query parsing errors are returned as a generic fmt.Errorf, which maps to HTTP 500. Since this is client input, wrap it as an errors.InvalidInput error (400) like other handlers.

Copilot uses AI. Check for mistakes.
Comment on lines +235 to +239
pipelineID, err := uuid.Parse(c.Param("id"))
if err != nil {
httputil.Error(c, err)
return
}
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In WebhookTrigger, an invalid pipeline UUID is treated as a generic error (500). For consistency with other handlers, wrap this as errors.InvalidInput so clients get a 400.

Copilot uses AI. Check for mistakes.
}

if err := c.ShouldBindJSON(&req); err != nil {
httputil.Error(c, err)
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ShouldBindJSON errors are currently forwarded directly to httputil.Error, which maps to HTTP 500 for non-domain errors. Other handlers usually wrap these as errors.InvalidInput ("Invalid request body") to return 400. Consider wrapping bind failures consistently so clients get a proper 4xx response.

Suggested change
httputil.Error(c, err)
// Treat JSON binding failures as invalid client input (400) rather than internal errors.
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{
"error": "Invalid request body",
"details": err.Error(),
})

Copilot uses AI. Check for mistakes.

payload, err := io.ReadAll(c.Request.Body)
if err != nil {
httputil.Error(c, fmt.Errorf("failed to read payload: %w", err))
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Payload read failures are currently wrapped with fmt.Errorf, which maps to HTTP 500. This is a request/input error and should be returned as errors.InvalidInput (400) (similar to FunctionHandler.Invoke).

Suggested change
httputil.Error(c, fmt.Errorf("failed to read payload: %w", err))
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "failed to read payload"})

Copilot uses AI. Check for mistakes.
build.FinishedAt = &finish
build.UpdatedAt = finish
_ = w.repo.UpdateBuild(ctx, build)
_ = w.repo.AppendBuildLog(ctx, &domain.BuildLog{ID: uuid.New(), BuildID: build.ID, Content: message, CreatedAt: finish})
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

failBuild appends a BuildLog without setting StepID. Since step_id is nullable, this should write NULL; with the current non-null uuid.UUID field it will insert an all-zero UUID and likely violate the FK, dropping the log. Make StepID nullable and pass NULL for build-level logs.

Suggested change
_ = w.repo.AppendBuildLog(ctx, &domain.BuildLog{ID: uuid.New(), BuildID: build.ID, Content: message, CreatedAt: finish})
_ = w.repo.AppendBuildLog(ctx, &domain.BuildLog{ID: uuid.New(), BuildID: build.ID, StepID: nil, Content: message, CreatedAt: finish})

Copilot uses AI. Check for mistakes.
continue
}

w.processJob(job)
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

w.processJob(job) runs synchronously inside the worker loop. If a build takes a long time, shutdown will block waiting for this worker’s wg.Done() (unlike workers that offload processing to goroutines). Consider running jobs in a goroutine (optionally with a concurrency limit) and/or deriving the job context from the worker ctx for cancellation.

Copilot uses AI. Check for mistakes.
Comment on lines +66 to +70
func (h *PipelineHandler) Get(c *gin.Context) {
id, err := uuid.Parse(c.Param("id"))
if err != nil {
httputil.Error(c, err)
return
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Most handlers wrap ID parsing failures as errors.InvalidInput so they map to HTTP 400. Here uuid.Parse errors are passed directly to httputil.Error, which will become a 500. Wrap these parse errors (and similarly in other methods in this handler) to return a proper 4xx.

Copilot uses AI. Check for mistakes.
w := httptest.NewRecorder()
r.ServeHTTP(w, req)

assert.Equal(t, http.StatusInternalServerError, w.Code)
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the handler is updated to return 400 on invalid UUIDs (consistent with other handlers), this assertion should be updated from http.StatusInternalServerError to http.StatusBadRequest.

Suggested change
assert.Equal(t, http.StatusInternalServerError, w.Code)
assert.Equal(t, http.StatusBadRequest, w.Code)

Copilot uses AI. Check for mistakes.
event VARCHAR(128) NOT NULL,
delivery_id VARCHAR(255) NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE(provider, delivery_id)
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The uniqueness constraint is UNIQUE(provider, delivery_id) but deliveries are recorded per pipeline_id. Without including pipeline_id in the unique constraint, a delivery ID collision across two pipelines for the same provider would cause one pipeline’s valid delivery to be treated as a duplicate (ignored). Consider UNIQUE(pipeline_id, provider, delivery_id).

Suggested change
UNIQUE(provider, delivery_id)
UNIQUE(pipeline_id, provider, delivery_id)

Copilot uses AI. Check for mistakes.
@poyrazK poyrazK changed the title Add pipeline service, webhook flow, docs, and ADR Pipeline service + webhook automation + docs/ADR Feb 26, 2026
@poyrazK poyrazK merged commit bf47a70 into main Feb 26, 2026
39 checks passed
@poyrazK poyrazK deleted the feature/pipeline-service-docs-adr branch April 12, 2026 15:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants