core/automation-jobs-core/cmd/api/main.go
Tiago Yamamoto a52bd4519d refactor: optimize Dockerfiles and documentation for core services
- Use Google Distroless images for all services (Go & Node.js).
- Standardize documentation with [PROJECT-NAME].md.
- Add .dockerignore and .gitignore to all projects.
- Remove docker-compose.yml in favor of docker run instructions.
- Fix Go version and dependency issues in observability, repo-integrations, and security-governance.
- Add Podman support (fully qualified image names).
- Update Dashboard to use Node.js static server for Distroless compatibility.
2025-12-30 13:22:34 -03:00

136 lines
3.9 KiB
Go

package main
import (
"context"
"encoding/json"
"log/slog"
"net/http"
"os"
"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
"github.com/google/uuid"
"github.com/lab/automation-jobs-core/temporal/workflows"
"go.temporal.io/sdk/client"
)
// The task queue name for our sample workflow.
const SampleTaskQueue = "sample-task-queue"
// application holds the dependencies for our API handlers.
type application struct {
temporalClient client.Client
}
// runJobRequest defines the expected JSON body for the POST /jobs/run endpoint.
type runJobRequest struct {
Name string `json:"name"`
}
// runJobResponse defines the JSON response for a successful job submission.
type runJobResponse struct {
WorkflowID string `json:"workflow_id"`
RunID string `json:"run_id"`
}
// jobStatusResponse defines the JSON response for the job status endpoint.
type jobStatusResponse struct {
WorkflowID string `json:"workflow_id"`
RunID string `json:"run_id"`
Status string `json:"status"`
}
func main() {
slog.SetDefault(slog.New(slog.NewJSONHandler(os.Stdout, nil)))
temporalAddress := os.Getenv("TEMPORAL_ADDRESS")
if temporalAddress == "" {
slog.Warn("TEMPORAL_ADDRESS not set, defaulting to localhost:7233")
temporalAddress = "localhost:7233"
}
c, err := client.Dial(client.Options{
HostPort: temporalAddress,
Logger: slog.Default(),
})
if err != nil {
slog.Error("Unable to create Temporal client", "error", err)
os.Exit(1)
}
defer c.Close()
app := &application{
temporalClient: c,
}
r := chi.NewRouter()
r.Use(middleware.RequestID)
r.Use(middleware.RealIP)
r.Use(middleware.Logger) // Chi's default logger
r.Use(middleware.Recoverer)
r.Post("/jobs/run", app.runJobHandler)
r.Get("/jobs/{workflowID}/status", app.getJobStatusHandler)
slog.Info("Starting API server", "port", "8080")
if err := http.ListenAndServe(":8080", r); err != nil {
slog.Error("Failed to start server", "error", err)
}
}
// runJobHandler starts a new SampleWorkflow execution.
func (app *application) runJobHandler(w http.ResponseWriter, r *http.Request) {
var req runJobRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "Invalid request body", http.StatusBadRequest)
return
}
if req.Name == "" {
http.Error(w, "Name field is required", http.StatusBadRequest)
return
}
options := client.StartWorkflowOptions{
ID: "sample-workflow-" + uuid.NewString(),
TaskQueue: SampleTaskQueue,
}
we, err := app.temporalClient.ExecuteWorkflow(context.Background(), options, workflows.SampleWorkflow, req.Name)
if err != nil {
slog.Error("Unable to start workflow", "error", err)
http.Error(w, "Internal server error", http.StatusInternalServerError)
return
}
slog.Info("Started workflow", "workflow_id", we.GetID(), "run_id", we.GetRunID())
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(runJobResponse{
WorkflowID: we.GetID(),
RunID: we.GetRunID(),
})
}
// getJobStatusHandler retrieves the status of a specific workflow execution.
func (app *application) getJobStatusHandler(w http.ResponseWriter, r *http.Request) {
workflowID := chi.URLParam(r, "workflowID")
// Note: RunID can be empty to get the latest run.
resp, err := app.temporalClient.DescribeWorkflowExecution(context.Background(), workflowID, "")
if err != nil {
slog.Error("Unable to describe workflow", "error", err, "workflow_id", workflowID)
http.Error(w, "Workflow not found", http.StatusNotFound)
return
}
status := resp.GetWorkflowExecutionInfo().GetStatus().String()
slog.Info("Described workflow", "workflow_id", workflowID, "status", status)
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(jobStatusResponse{
WorkflowID: resp.GetWorkflowExecutionInfo().GetExecution().GetWorkflowId(),
RunID: resp.GetWorkflowExecutionInfo().GetExecution().GetRunId(),
Status: status,
})
}