nomadcode/services/core/cmd/plane-smoke/main.go

173 lines
5.1 KiB
Go

package main
import (
"context"
"fmt"
"io"
"log/slog"
"os"
"strings"
"github.com/nomadcode/nomadcode-core/internal/adapters/plane"
)
type EnvConfig struct {
BaseURL string
Token string
WorkspaceSlug string
ProjectID string
WorkItemID string
SmokeApply bool
SmokeComment string
SmokeStateID string
}
func LoadConfig(getenv func(string) string) (EnvConfig, error) {
var cfg EnvConfig
cfg.BaseURL = strings.TrimSpace(getenv("PLANE_BASE_URL"))
cfg.Token = strings.TrimSpace(getenv("PLANE_TOKEN"))
cfg.WorkspaceSlug = strings.TrimSpace(getenv("PLANE_WORKSPACE_SLUG"))
cfg.ProjectID = strings.TrimSpace(getenv("PLANE_PROJECT_ID"))
cfg.WorkItemID = strings.TrimSpace(getenv("PLANE_WORK_ITEM_ID"))
var missing []string
if cfg.BaseURL == "" {
missing = append(missing, "PLANE_BASE_URL")
}
if cfg.Token == "" {
missing = append(missing, "PLANE_TOKEN")
}
if cfg.WorkspaceSlug == "" {
missing = append(missing, "PLANE_WORKSPACE_SLUG")
}
if cfg.ProjectID == "" {
missing = append(missing, "PLANE_PROJECT_ID")
}
if cfg.WorkItemID == "" {
missing = append(missing, "PLANE_WORK_ITEM_ID")
}
if len(missing) > 0 {
return cfg, fmt.Errorf("missing required environment variables: %s", strings.Join(missing, ", "))
}
applyStr := getenv("PLANE_SMOKE_APPLY")
cfg.SmokeApply = applyStr == "1" || strings.ToLower(applyStr) == "true"
if cfg.SmokeApply {
cfg.SmokeComment = strings.TrimSpace(getenv("PLANE_SMOKE_COMMENT"))
cfg.SmokeStateID = strings.TrimSpace(getenv("PLANE_SMOKE_STATE_ID"))
var missingSmoke []string
if cfg.SmokeComment == "" {
missingSmoke = append(missingSmoke, "PLANE_SMOKE_COMMENT")
}
if cfg.SmokeStateID == "" {
missingSmoke = append(missingSmoke, "PLANE_SMOKE_STATE_ID")
}
if len(missingSmoke) > 0 {
return cfg, fmt.Errorf("PLANE_SMOKE_APPLY is enabled but missing required smoke variables: %s", strings.Join(missingSmoke, ", "))
}
}
return cfg, nil
}
type PlaneClient interface {
GetWorkItem(ctx context.Context, ref plane.WorkItemRef) (plane.WorkItem, error)
AddComment(ctx context.Context, input plane.AddCommentInput) error
UpdateIssueStatus(ctx context.Context, input plane.UpdateIssueStatusInput) error
}
func RunSmoke(ctx context.Context, cfg EnvConfig, client PlaneClient, out io.Writer) error {
ref := plane.WorkItemRef{
WorkspaceSlug: cfg.WorkspaceSlug,
ProjectID: cfg.ProjectID,
WorkItemID: cfg.WorkItemID,
}
item, err := client.GetWorkItem(ctx, ref)
if err != nil {
return fmt.Errorf("failed to lookup work item: %w", err)
}
fmt.Fprintf(out, "lookup ok: id=%s name=%s\n", item.ID, item.Name)
if !cfg.SmokeApply {
fmt.Fprintln(out, "write smoke skipped: set PLANE_SMOKE_APPLY=1")
return nil
}
err = client.AddComment(ctx, plane.AddCommentInput{
Ref: ref,
CommentHTML: fmt.Sprintf("<p>%s</p>", cfg.SmokeComment),
ExternalSource: "nomadcode",
ExternalID: "plane-smoke-test",
})
if err != nil {
return fmt.Errorf("failed to add comment: %w", err)
}
fmt.Fprintln(out, "comment ok")
err = client.UpdateIssueStatus(ctx, plane.UpdateIssueStatusInput{
Ref: ref,
Status: cfg.SmokeStateID,
})
if err != nil {
return fmt.Errorf("failed to update state: %w", err)
}
fmt.Fprintf(out, "state update ok: state_id=%s\n", cfg.SmokeStateID)
return nil
}
func main() {
cfg, err := LoadConfig(os.Getenv)
if err != nil {
fmt.Fprintf(os.Stderr, "Configuration error: %v\n", err)
fmt.Fprintln(os.Stderr, "\nUsage:")
fmt.Fprintln(os.Stderr, " Required environment variables:")
fmt.Fprintln(os.Stderr, " PLANE_BASE_URL - Base URL of Plane instance")
fmt.Fprintln(os.Stderr, " PLANE_TOKEN - API Token (will be redacted in logs)")
fmt.Fprintln(os.Stderr, " PLANE_WORKSPACE_SLUG - Workspace slug (e.g. general)")
fmt.Fprintln(os.Stderr, " PLANE_PROJECT_ID - Project UUID")
fmt.Fprintln(os.Stderr, " PLANE_WORK_ITEM_ID - Work Item UUID")
fmt.Fprintln(os.Stderr, " Optional variables for write operations:")
fmt.Fprintln(os.Stderr, " PLANE_SMOKE_APPLY - Set to 1 to enable write smoke tests")
fmt.Fprintln(os.Stderr, " PLANE_SMOKE_COMMENT - HTML comment content to post")
fmt.Fprintln(os.Stderr, " PLANE_SMOKE_STATE_ID - State UUID to transition to")
os.Exit(1)
}
safeURL := cfg.BaseURL
safeToken := "<redacted>"
logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelInfo}))
logger.Info("starting plane smoke test",
"base_url", safeURL,
"token", safeToken,
"workspace", cfg.WorkspaceSlug,
"project_id", cfg.ProjectID,
"work_item_id", cfg.WorkItemID,
"apply", cfg.SmokeApply,
)
planeClient := plane.NewClient(plane.Config{
BaseURL: cfg.BaseURL,
Token: cfg.Token,
}, logger)
if err := RunSmoke(context.Background(), cfg, planeClient, os.Stdout); err != nil {
logger.Error("smoke test failed", "error", redactToken(err.Error(), cfg.Token))
os.Exit(2)
}
logger.Info("plane smoke test finished successfully")
}
func redactToken(s, token string) string {
if token == "" {
return s
}
return strings.ReplaceAll(s, token, "<redacted>")
}