Revert "Mnovich/temporal scheduler (#2745)" (#2839)

This commit is contained in:
Max Novich
2025-06-09 15:41:46 -07:00
committed by GitHub
parent e6f89cde76
commit 996f727ec5
35 changed files with 172 additions and 4896 deletions
-35
View File
@@ -1,35 +0,0 @@
#!/bin/bash
# Build script for Temporal service
set -e
echo "Building Temporal service..."
# Change to temporal-service directory
cd "$(dirname "$0")"
# Initialize Go module if not already done
if [ ! -f "go.sum" ]; then
echo "Initializing Go module..."
go mod tidy
fi
# Build the service
echo "Compiling Go binary..."
go build -o temporal-service main.go
# Make it executable
chmod +x temporal-service
echo "Build completed successfully!"
echo "Binary location: $(pwd)/temporal-service"
echo ""
echo "Prerequisites:"
echo " 1. Install Temporal CLI: brew install temporal"
echo " 2. Start Temporal server: temporal server start-dev"
echo ""
echo "To run the service:"
echo " ./temporal-service"
echo ""
echo "Environment variables:"
echo " PORT - HTTP port (default: 8080)"
-117
View File
@@ -1,117 +0,0 @@
#!/bin/bash
# Example usage script for the Temporal service
set -e
echo "Temporal Service Example Usage"
echo "=============================="
echo ""
# Check if service is running
if ! curl -s http://localhost:8080/health > /dev/null; then
echo "Starting Temporal service..."
echo "Please run in another terminal: ./temporal-service"
echo "Then run this script again."
exit 1
fi
echo "✓ Temporal service is running"
echo ""
# Create example recipe
RECIPE_FILE="/tmp/example-recipe.yaml"
cat > $RECIPE_FILE << EOF
version: "1.0.0"
title: "Daily Report Generator"
description: "Generates a daily report"
prompt: |
Generate a daily report with the following information:
- Current date and time
- System status
- Recent activity summary
Please format the output as a structured report.
EOF
echo "Created example recipe: $RECIPE_FILE"
echo ""
# Function to make API calls
make_api_call() {
local action="$1"
local job_id="$2"
local cron="$3"
local recipe_path="$4"
local payload="{\"action\": \"$action\""
if [ -n "$job_id" ]; then
payload="$payload, \"job_id\": \"$job_id\""
fi
if [ -n "$cron" ]; then
payload="$payload, \"cron\": \"$cron\""
fi
if [ -n "$recipe_path" ]; then
payload="$payload, \"recipe_path\": \"$recipe_path\""
fi
payload="$payload}"
echo "API Call: $payload"
curl -s -X POST http://localhost:8080/jobs \
-H "Content-Type: application/json" \
-d "$payload" | jq .
echo ""
}
# Example 1: Create a daily job
echo "1. Creating a daily job (runs at 9 AM every day)..."
make_api_call "create" "daily-report" "0 9 * * *" "$RECIPE_FILE"
# Example 2: Create an hourly job
echo "2. Creating an hourly job..."
make_api_call "create" "hourly-check" "0 * * * *" "$RECIPE_FILE"
# Example 3: List all jobs
echo "3. Listing all scheduled jobs..."
make_api_call "list"
# Example 4: Pause a job
echo "4. Pausing the hourly job..."
make_api_call "pause" "hourly-check"
# Example 5: List jobs again to see paused status
echo "5. Listing jobs to see paused status..."
make_api_call "list"
# Example 6: Unpause the job
echo "6. Unpausing the hourly job..."
make_api_call "unpause" "hourly-check"
# Example 7: Run a job immediately
echo "7. Running daily-report job immediately..."
echo "Note: This will fail without goose-scheduler-executor binary"
make_api_call "run_now" "daily-report"
# Example 8: Delete jobs
echo "8. Cleaning up - deleting jobs..."
make_api_call "delete" "daily-report"
make_api_call "delete" "hourly-check"
# Example 9: Final list (should be empty)
echo "9. Final job list (should be empty)..."
make_api_call "list"
# Clean up
rm -f $RECIPE_FILE
echo "Example completed!"
echo ""
echo "Common cron expressions:"
echo " '0 9 * * *' - Daily at 9 AM"
echo " '0 */6 * * *' - Every 6 hours"
echo " '*/15 * * * *' - Every 15 minutes"
echo " '0 0 * * 0' - Weekly on Sunday at midnight"
echo " '0 0 1 * *' - Monthly on the 1st at midnight"
-35
View File
@@ -1,35 +0,0 @@
module temporal-service
go 1.21
require go.temporal.io/sdk v1.24.0
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/facebookgo/clock v0.0.0-20150410010913-600d898af40a // indirect
github.com/gogo/googleapis v1.4.1 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/gogo/status v1.1.1 // indirect
github.com/golang/mock v1.6.0 // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect
github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect
github.com/pborman/uuid v1.2.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/robfig/cron v1.2.0 // indirect
github.com/stretchr/objx v0.5.0 // indirect
github.com/stretchr/testify v1.8.4 // indirect
go.temporal.io/api v1.24.0 // indirect
go.uber.org/atomic v1.9.0 // indirect
golang.org/x/net v0.14.0 // indirect
golang.org/x/sys v0.11.0 // indirect
golang.org/x/text v0.12.0 // indirect
golang.org/x/time v0.3.0 // indirect
google.golang.org/genproto v0.0.0-20230815205213-6bfd019c3878 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20230815205213-6bfd019c3878 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20230815205213-6bfd019c3878 // indirect
google.golang.org/grpc v1.57.0 // indirect
google.golang.org/protobuf v1.31.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
File diff suppressed because it is too large Load Diff
-544
View File
@@ -1,544 +0,0 @@
package main
import (
"context"
"encoding/json"
"fmt"
"log"
"net/http"
"os"
"os/exec"
"os/signal"
"strings"
"syscall"
"time"
"go.temporal.io/sdk/activity"
"go.temporal.io/sdk/client"
"go.temporal.io/sdk/temporal"
"go.temporal.io/sdk/worker"
"go.temporal.io/sdk/workflow"
)
const (
TaskQueueName = "goose-task-queue"
Namespace = "default"
)
// Global service instance for activities to access
var globalService *TemporalService
// Request/Response types for HTTP API
type JobRequest struct {
Action string `json:"action"` // create, delete, pause, unpause, list, run_now
JobID string `json:"job_id"`
CronExpr string `json:"cron"`
RecipePath string `json:"recipe_path"`
}
type JobResponse struct {
Success bool `json:"success"`
Message string `json:"message"`
Jobs []JobStatus `json:"jobs,omitempty"`
Data interface{} `json:"data,omitempty"`
}
type JobStatus struct {
ID string `json:"id"`
CronExpr string `json:"cron"`
RecipePath string `json:"recipe_path"`
LastRun *string `json:"last_run,omitempty"`
NextRun *string `json:"next_run,omitempty"`
CurrentlyRunning bool `json:"currently_running"`
Paused bool `json:"paused"`
CreatedAt time.Time `json:"created_at"`
}
type RunNowResponse struct {
SessionID string `json:"session_id"`
}
// TemporalService manages the Temporal client and provides HTTP API
type TemporalService struct {
client client.Client
worker worker.Worker
scheduleJobs map[string]*JobStatus // In-memory job tracking
runningJobs map[string]bool // Track which jobs are currently running
}
// NewTemporalService creates a new Temporal service that connects to existing server
func NewTemporalService() (*TemporalService, error) {
// Create client (assumes Temporal server is already running)
c, err := client.Dial(client.Options{
HostPort: "127.0.0.1:7233",
Namespace: Namespace,
})
if err != nil {
return nil, fmt.Errorf("failed to create temporal client: %w", err)
}
// Create worker
w := worker.New(c, TaskQueueName, worker.Options{})
w.RegisterWorkflow(GooseJobWorkflow)
w.RegisterActivity(ExecuteGooseRecipe)
if err := w.Start(); err != nil {
c.Close()
return nil, fmt.Errorf("failed to start worker: %w", err)
}
log.Println("Connected to Temporal server successfully")
service := &TemporalService{
client: c,
worker: w,
scheduleJobs: make(map[string]*JobStatus),
runningJobs: make(map[string]bool),
}
// Set global service for activities
globalService = service
return service, nil
}
// Stop gracefully shuts down the Temporal service
func (ts *TemporalService) Stop() {
log.Println("Shutting down Temporal service...")
if ts.worker != nil {
ts.worker.Stop()
}
if ts.client != nil {
ts.client.Close()
}
log.Println("Temporal service stopped")
}
// Workflow definition for executing Goose recipes
func GooseJobWorkflow(ctx workflow.Context, jobID, recipePath string) (string, error) {
logger := workflow.GetLogger(ctx)
logger.Info("Starting Goose job workflow", "jobID", jobID, "recipePath", recipePath)
ao := workflow.ActivityOptions{
StartToCloseTimeout: 2 * time.Hour, // Allow up to 2 hours for job execution
RetryPolicy: &temporal.RetryPolicy{
InitialInterval: time.Second,
BackoffCoefficient: 2.0,
MaximumInterval: time.Minute,
MaximumAttempts: 3,
NonRetryableErrorTypes: []string{"InvalidRecipeError"},
},
}
ctx = workflow.WithActivityOptions(ctx, ao)
var sessionID string
err := workflow.ExecuteActivity(ctx, ExecuteGooseRecipe, jobID, recipePath).Get(ctx, &sessionID)
if err != nil {
logger.Error("Goose job workflow failed", "jobID", jobID, "error", err)
return "", err
}
logger.Info("Goose job workflow completed", "jobID", jobID, "sessionID", sessionID)
return sessionID, nil
}
// Activity definition for executing Goose recipes
func ExecuteGooseRecipe(ctx context.Context, jobID, recipePath string) (string, error) {
logger := activity.GetLogger(ctx)
logger.Info("Executing Goose recipe", "jobID", jobID, "recipePath", recipePath)
// Mark job as running at the start
if globalService != nil {
globalService.markJobAsRunning(jobID)
// Ensure we mark it as not running when we're done
defer globalService.markJobAsNotRunning(jobID)
}
// Check if recipe file exists
if _, err := os.Stat(recipePath); os.IsNotExist(err) {
return "", temporal.NewNonRetryableApplicationError(
fmt.Sprintf("recipe file not found: %s", recipePath),
"InvalidRecipeError",
err,
)
}
// Execute the Goose recipe via the executor binary
cmd := exec.CommandContext(ctx, "goose-scheduler-executor", jobID, recipePath)
cmd.Env = append(os.Environ(), fmt.Sprintf("GOOSE_JOB_ID=%s", jobID))
output, err := cmd.Output()
if err != nil {
if exitError, ok := err.(*exec.ExitError); ok {
logger.Error("Recipe execution failed", "jobID", jobID, "stderr", string(exitError.Stderr))
return "", fmt.Errorf("recipe execution failed: %s", string(exitError.Stderr))
}
return "", fmt.Errorf("failed to execute recipe: %w", err)
}
sessionID := strings.TrimSpace(string(output))
logger.Info("Recipe executed successfully", "jobID", jobID, "sessionID", sessionID)
return sessionID, nil
}
// HTTP API handlers
func (ts *TemporalService) handleJobs(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
if r.Method != http.MethodPost {
ts.writeErrorResponse(w, http.StatusMethodNotAllowed, "Method not allowed")
return
}
var req JobRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
ts.writeErrorResponse(w, http.StatusBadRequest, fmt.Sprintf("Invalid JSON: %v", err))
return
}
var resp JobResponse
switch req.Action {
case "create":
resp = ts.createSchedule(req)
case "delete":
resp = ts.deleteSchedule(req)
case "pause":
resp = ts.pauseSchedule(req)
case "unpause":
resp = ts.unpauseSchedule(req)
case "list":
resp = ts.listSchedules()
case "run_now":
resp = ts.runNow(req)
default:
resp = JobResponse{Success: false, Message: fmt.Sprintf("Unknown action: %s", req.Action)}
}
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(resp)
}
func (ts *TemporalService) createSchedule(req JobRequest) JobResponse {
if req.JobID == "" || req.CronExpr == "" || req.RecipePath == "" {
return JobResponse{Success: false, Message: "Missing required fields: job_id, cron, recipe_path"}
}
// Check if job already exists
if _, exists := ts.scheduleJobs[req.JobID]; exists {
return JobResponse{Success: false, Message: fmt.Sprintf("Job with ID '%s' already exists", req.JobID)}
}
// Validate recipe file exists
if _, err := os.Stat(req.RecipePath); os.IsNotExist(err) {
return JobResponse{Success: false, Message: fmt.Sprintf("Recipe file not found: %s", req.RecipePath)}
}
scheduleID := fmt.Sprintf("goose-job-%s", req.JobID)
// Create Temporal schedule
schedule := client.ScheduleOptions{
ID: scheduleID,
Spec: client.ScheduleSpec{
CronExpressions: []string{req.CronExpr},
},
Action: &client.ScheduleWorkflowAction{
ID: fmt.Sprintf("workflow-%s-{{.ScheduledTime.Unix}}", req.JobID),
Workflow: GooseJobWorkflow,
Args: []interface{}{req.JobID, req.RecipePath},
TaskQueue: TaskQueueName,
},
}
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
_, err := ts.client.ScheduleClient().Create(ctx, schedule)
if err != nil {
return JobResponse{Success: false, Message: fmt.Sprintf("Failed to create schedule: %v", err)}
}
// Track job in memory
jobStatus := &JobStatus{
ID: req.JobID,
CronExpr: req.CronExpr,
RecipePath: req.RecipePath,
CurrentlyRunning: false,
Paused: false,
CreatedAt: time.Now(),
}
ts.scheduleJobs[req.JobID] = jobStatus
log.Printf("Created schedule for job: %s", req.JobID)
return JobResponse{Success: true, Message: "Schedule created successfully"}
}
func (ts *TemporalService) deleteSchedule(req JobRequest) JobResponse {
if req.JobID == "" {
return JobResponse{Success: false, Message: "Missing job_id"}
}
scheduleID := fmt.Sprintf("goose-job-%s", req.JobID)
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
handle := ts.client.ScheduleClient().GetHandle(ctx, scheduleID)
err := handle.Delete(ctx)
if err != nil {
return JobResponse{Success: false, Message: fmt.Sprintf("Failed to delete schedule: %v", err)}
}
// Remove from memory
delete(ts.scheduleJobs, req.JobID)
log.Printf("Deleted schedule for job: %s", req.JobID)
return JobResponse{Success: true, Message: "Schedule deleted successfully"}
}
func (ts *TemporalService) pauseSchedule(req JobRequest) JobResponse {
if req.JobID == "" {
return JobResponse{Success: false, Message: "Missing job_id"}
}
scheduleID := fmt.Sprintf("goose-job-%s", req.JobID)
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
handle := ts.client.ScheduleClient().GetHandle(ctx, scheduleID)
err := handle.Pause(ctx, client.SchedulePauseOptions{
Note: "Paused via API",
})
if err != nil {
return JobResponse{Success: false, Message: fmt.Sprintf("Failed to pause schedule: %v", err)}
}
// Update in memory
if job, exists := ts.scheduleJobs[req.JobID]; exists {
job.Paused = true
}
log.Printf("Paused schedule for job: %s", req.JobID)
return JobResponse{Success: true, Message: "Schedule paused successfully"}
}
func (ts *TemporalService) unpauseSchedule(req JobRequest) JobResponse {
if req.JobID == "" {
return JobResponse{Success: false, Message: "Missing job_id"}
}
scheduleID := fmt.Sprintf("goose-job-%s", req.JobID)
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
handle := ts.client.ScheduleClient().GetHandle(ctx, scheduleID)
err := handle.Unpause(ctx, client.ScheduleUnpauseOptions{
Note: "Unpaused via API",
})
if err != nil {
return JobResponse{Success: false, Message: fmt.Sprintf("Failed to unpause schedule: %v", err)}
}
// Update in memory
if job, exists := ts.scheduleJobs[req.JobID]; exists {
job.Paused = false
}
log.Printf("Unpaused schedule for job: %s", req.JobID)
return JobResponse{Success: true, Message: "Schedule unpaused successfully"}
}
func (ts *TemporalService) listSchedules() JobResponse {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
// List all schedules from Temporal
iter, err := ts.client.ScheduleClient().List(ctx, client.ScheduleListOptions{})
if err != nil {
return JobResponse{Success: false, Message: fmt.Sprintf("Failed to list schedules: %v", err)}
}
var jobs []JobStatus
for iter.HasNext() {
schedule, err := iter.Next()
if err != nil {
log.Printf("Error listing schedules: %v", err)
continue
}
// Extract job ID from schedule ID
if strings.HasPrefix(schedule.ID, "goose-job-") {
jobID := strings.TrimPrefix(schedule.ID, "goose-job-")
// Get additional details from in-memory tracking
var jobStatus JobStatus
if tracked, exists := ts.scheduleJobs[jobID]; exists {
jobStatus = *tracked
} else {
// Fallback for schedules not in memory
jobStatus = JobStatus{
ID: jobID,
CreatedAt: time.Now(), // We don't have the real creation time
}
}
// Update with Temporal schedule info
if len(schedule.Spec.CronExpressions) > 0 {
jobStatus.CronExpr = schedule.Spec.CronExpressions[0]
}
// Get detailed schedule information including paused state and running status
scheduleHandle := ts.client.ScheduleClient().GetHandle(ctx, schedule.ID)
if desc, err := scheduleHandle.Describe(ctx); err == nil {
jobStatus.Paused = desc.Schedule.State.Paused
// Check if there are any running workflows for this job
jobStatus.CurrentlyRunning = ts.isJobCurrentlyRunning(ctx, jobID)
// Update last run time if available
if len(desc.Info.RecentActions) > 0 {
lastAction := desc.Info.RecentActions[len(desc.Info.RecentActions)-1]
if !lastAction.ActualTime.IsZero() {
lastRunStr := lastAction.ActualTime.Format(time.RFC3339)
jobStatus.LastRun = &lastRunStr
}
}
// Update next run time if available - this field may not exist in older SDK versions
// We'll skip this for now to avoid compilation errors
} else {
log.Printf("Warning: Could not get detailed info for schedule %s: %v", schedule.ID, err)
}
// Update in-memory tracking with latest info
ts.scheduleJobs[jobID] = &jobStatus
jobs = append(jobs, jobStatus)
}
}
return JobResponse{Success: true, Jobs: jobs}
}
// isJobCurrentlyRunning checks if there are any running workflows for the given job ID
func (ts *TemporalService) isJobCurrentlyRunning(ctx context.Context, jobID string) bool {
// Check our in-memory tracking of running jobs
if running, exists := ts.runningJobs[jobID]; exists && running {
return true
}
return false
}
// markJobAsRunning sets a job as currently running
func (ts *TemporalService) markJobAsRunning(jobID string) {
ts.runningJobs[jobID] = true
log.Printf("Marked job %s as running", jobID)
}
// markJobAsNotRunning sets a job as not currently running
func (ts *TemporalService) markJobAsNotRunning(jobID string) {
delete(ts.runningJobs, jobID)
log.Printf("Marked job %s as not running", jobID)
}
func (ts *TemporalService) runNow(req JobRequest) JobResponse {
if req.JobID == "" {
return JobResponse{Success: false, Message: "Missing job_id"}
}
// Get job details
job, exists := ts.scheduleJobs[req.JobID]
if !exists {
return JobResponse{Success: false, Message: fmt.Sprintf("Job '%s' not found", req.JobID)}
}
// Execute workflow immediately
workflowOptions := client.StartWorkflowOptions{
ID: fmt.Sprintf("manual-%s-%d", req.JobID, time.Now().Unix()),
TaskQueue: TaskQueueName,
}
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
we, err := ts.client.ExecuteWorkflow(ctx, workflowOptions, GooseJobWorkflow, req.JobID, job.RecipePath)
if err != nil {
return JobResponse{Success: false, Message: fmt.Sprintf("Failed to start workflow: %v", err)}
}
// Don't wait for completion in run_now, just return the workflow ID
log.Printf("Manual execution started for job: %s, workflow: %s", req.JobID, we.GetID())
return JobResponse{
Success: true,
Message: "Job execution started",
Data: RunNowResponse{SessionID: we.GetID()}, // Return workflow ID as session ID for now
}
}
func (ts *TemporalService) writeErrorResponse(w http.ResponseWriter, statusCode int, message string) {
w.WriteHeader(statusCode)
json.NewEncoder(w).Encode(JobResponse{Success: false, Message: message})
}
func (ts *TemporalService) handleHealth(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(map[string]string{"status": "healthy"})
}
func main() {
port := os.Getenv("PORT")
if port == "" {
port = "8080"
}
log.Println("Starting Temporal service...")
log.Println("Note: This service requires a running Temporal server at 127.0.0.1:7233")
log.Println("Start Temporal server with: temporal server start-dev")
// Create Temporal service
service, err := NewTemporalService()
if err != nil {
log.Fatalf("Failed to create Temporal service: %v", err)
}
// Set up HTTP server
mux := http.NewServeMux()
mux.HandleFunc("/jobs", service.handleJobs)
mux.HandleFunc("/health", service.handleHealth)
server := &http.Server{
Addr: ":" + port,
Handler: mux,
}
// Handle graceful shutdown
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
go func() {
<-sigChan
log.Println("Received shutdown signal")
// Shutdown HTTP server
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
server.Shutdown(ctx)
// Stop Temporal service
service.Stop()
os.Exit(0)
}()
log.Printf("Temporal service starting on port %s", port)
log.Printf("Health endpoint: http://localhost:%s/health", port)
log.Printf("Jobs endpoint: http://localhost:%s/jobs", port)
if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatalf("HTTP server failed: %v", err)
}
}
-92
View File
@@ -1,92 +0,0 @@
#!/bin/bash
# Startup script for Temporal service with integrated Temporal server
set -e
echo "Starting Temporal development environment..."
# Check if temporal CLI is available
if ! command -v temporal &> /dev/null; then
echo "Error: Temporal CLI not found!"
echo "Please install it first:"
echo " brew install temporal"
echo " # or download from https://github.com/temporalio/cli/releases"
exit 1
fi
# Check if temporal-service binary exists
if [ ! -f "./temporal-service" ]; then
echo "Error: temporal-service binary not found!"
echo "Please build it first: ./build.sh"
exit 1
fi
# Set data directory
DATA_DIR="${GOOSE_DATA_DIR:-./data}"
mkdir -p "$DATA_DIR"
echo "Data directory: $DATA_DIR"
echo "Starting Temporal server..."
# Start Temporal server in background
temporal server start-dev \
--db-filename "$DATA_DIR/temporal.db" \
--port 7233 \
--ui-port 8233 \
--log-level warn &
TEMPORAL_PID=$!
echo "Temporal server started with PID: $TEMPORAL_PID"
# Function to cleanup on exit
cleanup() {
echo ""
echo "Shutting down..."
if [ ! -z "$SERVICE_PID" ]; then
echo "Stopping temporal-service (PID: $SERVICE_PID)..."
kill $SERVICE_PID 2>/dev/null || true
fi
echo "Stopping Temporal server (PID: $TEMPORAL_PID)..."
kill $TEMPORAL_PID 2>/dev/null || true
wait $TEMPORAL_PID 2>/dev/null || true
echo "Shutdown complete"
}
# Set trap for cleanup
trap cleanup EXIT INT TERM
# Wait for Temporal server to be ready
echo "Waiting for Temporal server to be ready..."
for i in {1..30}; do
if curl -s http://localhost:7233/api/v1/namespaces > /dev/null 2>&1; then
echo "Temporal server is ready!"
break
fi
if [ $i -eq 30 ]; then
echo "Error: Temporal server failed to start within 30 seconds"
exit 1
fi
sleep 1
done
# Start the temporal service
echo "Starting temporal-service..."
PORT="${PORT:-8080}" ./temporal-service &
SERVICE_PID=$!
echo ""
echo "🎉 Temporal development environment is running!"
echo ""
echo "Services:"
echo " - Temporal Server: http://localhost:7233 (gRPC)"
echo " - Temporal Web UI: http://localhost:8233"
echo " - Goose Scheduler API: http://localhost:${PORT:-8080}"
echo ""
echo "API Endpoints:"
echo " - Health: http://localhost:${PORT:-8080}/health"
echo " - Jobs: http://localhost:${PORT:-8080}/jobs"
echo ""
echo "Press Ctrl+C to stop all services"
# Wait for the service to exit
wait $SERVICE_PID
Binary file not shown.
-123
View File
@@ -1,123 +0,0 @@
#!/bin/bash
# Test script for Temporal service
set -e
echo "Testing Temporal service..."
# Check if service is running
if ! curl -s http://localhost:8080/health > /dev/null; then
echo "Error: Temporal service is not running on port 8080"
echo "Please start it with: ./temporal-service"
exit 1
fi
echo "✓ Service is running"
# Test health endpoint
echo "Testing health endpoint..."
HEALTH_RESPONSE=$(curl -s http://localhost:8080/health)
if [[ $HEALTH_RESPONSE == *"healthy"* ]]; then
echo "✓ Health check passed"
else
echo "✗ Health check failed: $HEALTH_RESPONSE"
exit 1
fi
# Test list schedules (should be empty initially)
echo "Testing list schedules..."
LIST_RESPONSE=$(curl -s -X POST http://localhost:8080/jobs \
-H "Content-Type: application/json" \
-d '{"action": "list"}')
if [[ $LIST_RESPONSE == *"\"success\":true"* ]]; then
echo "✓ List schedules works"
else
echo "✗ List schedules failed: $LIST_RESPONSE"
exit 1
fi
# Create a test recipe file
TEST_RECIPE="/tmp/test-recipe.yaml"
cat > $TEST_RECIPE << EOF
version: "1.0.0"
title: "Test Recipe"
description: "A test recipe for the scheduler"
prompt: "This is a test prompt for scheduled execution."
EOF
echo "Created test recipe at $TEST_RECIPE"
# Test create schedule
echo "Testing create schedule..."
CREATE_RESPONSE=$(curl -s -X POST http://localhost:8080/jobs \
-H "Content-Type: application/json" \
-d "{\"action\": \"create\", \"job_id\": \"test-job\", \"cron\": \"0 */6 * * *\", \"recipe_path\": \"$TEST_RECIPE\"}")
if [[ $CREATE_RESPONSE == *"\"success\":true"* ]]; then
echo "✓ Create schedule works"
else
echo "✗ Create schedule failed: $CREATE_RESPONSE"
exit 1
fi
# Test list schedules again (should have one job)
echo "Testing list schedules with job..."
LIST_RESPONSE=$(curl -s -X POST http://localhost:8080/jobs \
-H "Content-Type: application/json" \
-d '{"action": "list"}')
if [[ $LIST_RESPONSE == *"test-job"* ]]; then
echo "✓ Job appears in list"
else
echo "✗ Job not found in list: $LIST_RESPONSE"
exit 1
fi
# Test pause schedule
echo "Testing pause schedule..."
PAUSE_RESPONSE=$(curl -s -X POST http://localhost:8080/jobs \
-H "Content-Type: application/json" \
-d '{"action": "pause", "job_id": "test-job"}')
if [[ $PAUSE_RESPONSE == *"\"success\":true"* ]]; then
echo "✓ Pause schedule works"
else
echo "✗ Pause schedule failed: $PAUSE_RESPONSE"
exit 1
fi
# Test unpause schedule
echo "Testing unpause schedule..."
UNPAUSE_RESPONSE=$(curl -s -X POST http://localhost:8080/jobs \
-H "Content-Type: application/json" \
-d '{"action": "unpause", "job_id": "test-job"}')
if [[ $UNPAUSE_RESPONSE == *"\"success\":true"* ]]; then
echo "✓ Unpause schedule works"
else
echo "✗ Unpause schedule failed: $UNPAUSE_RESPONSE"
exit 1
fi
# Test delete schedule
echo "Testing delete schedule..."
DELETE_RESPONSE=$(curl -s -X POST http://localhost:8080/jobs \
-H "Content-Type: application/json" \
-d '{"action": "delete", "job_id": "test-job"}')
if [[ $DELETE_RESPONSE == *"\"success\":true"* ]]; then
echo "✓ Delete schedule works"
else
echo "✗ Delete schedule failed: $DELETE_RESPONSE"
exit 1
fi
# Clean up
rm -f $TEST_RECIPE
echo ""
echo "🎉 All tests passed!"
echo ""
echo "The Temporal service is working correctly."
echo "You can now integrate it with the Rust scheduler."