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
81 changes: 81 additions & 0 deletions cmd/ob-scheduled-runner/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package main

import (
"context"
"crypto/rand"
"errors"
"fmt"
"os"
"os/signal"
"syscall"

"github.com/spf13/cobra"

"github.com/labstack/onebox/internal/buildinfo"
"github.com/labstack/onebox/internal/onebox"
)

type unavailableScheduledExecutor struct{}

func (unavailableScheduledExecutor) ExecuteScheduledLifecycle(context.Context, onebox.ScheduledLifecycleExecution) error {
return errors.New("scheduled lifecycle backend is not available for this operation in the current build")
}

func executeEnvelope(ctx context.Context, path string) error {
envelope, err := onebox.LoadScheduledOperationEnvelope(path)
if err != nil {
return fmt.Errorf("load scheduled envelope: %w", err)
}
service := onebox.New(onebox.Options{ScheduledLifecycleExecutor: unavailableScheduledExecutor{}})
runner := onebox.ScheduledRunner{Executor: service}
return runner.ExecuteRecurring(ctx, envelope, rand.Reader)
}

func newRootCmd(runEnvelope func(context.Context, string) error) *cobra.Command {
if runEnvelope == nil {
runEnvelope = executeEnvelope
}
root := &cobra.Command{
Use: "ob-scheduled-runner",
Short: "short-lived Onebox scheduled lifecycle runner",
SilenceUsage: true,
SilenceErrors: true,
Args: cobra.NoArgs,
}
run := &cobra.Command{
Use: "run <sealed-envelope>",
Short: "execute one sealed scheduled operation and exit",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
return runEnvelope(cmd.Context(), args[0])
},
}
version := &cobra.Command{
Use: "version",
Short: "print runner and protocol versions",
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, _ []string) error {
info := buildinfo.Read()
compatibility := onebox.CurrentScheduledRunnerCompatibility()
_, err := fmt.Fprintf(cmd.OutOrStdout(), "%s runner_protocol=%d envelope_protocols=%d-%d cli_protocols=%d-%d\n",
info.Version, compatibility.RunnerProtocol,
compatibility.EnvelopeProtocols.Minimum, compatibility.EnvelopeProtocols.Maximum,
compatibility.CLIProtocols.Minimum, compatibility.CLIProtocols.Maximum)
return err
},
}
root.AddCommand(run, version)
return root
}

func main() {
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer stop()
if err := newRootCmd(nil).ExecuteContext(ctx); err != nil {
fmt.Fprintln(os.Stderr, "ob-scheduled-runner:", err)
if errors.Is(err, context.Canceled) || errors.Is(ctx.Err(), context.Canceled) {
os.Exit(130)
}
os.Exit(1)
}
}
47 changes: 47 additions & 0 deletions cmd/ob-scheduled-runner/main_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package main

import (
"context"
"reflect"
"sort"
"testing"
)

func TestScheduledRunnerCommandSurfaceIsRestricted(t *testing.T) {
root := newRootCmd(func(context.Context, string) error { return nil })
var names []string
for _, command := range root.Commands() {
names = append(names, command.Name())
}
sort.Strings(names)
if !reflect.DeepEqual(names, []string{"run", "version"}) {
t.Fatalf("runner commands = %#v", names)
}
for _, forbidden := range []string{"plan", "deploy", "approve", "bootstrap", "destroy", "serve", "listen"} {
for _, name := range names {
if name == forbidden {
t.Fatalf("scheduled runner exposes forbidden command %q", forbidden)
}
}
}
}

func TestScheduledRunnerRunsExactlyOneEnvelope(t *testing.T) {
var calls []string
root := newRootCmd(func(_ context.Context, path string) error {
calls = append(calls, path)
return nil
})
root.SetArgs([]string{"run", "/var/lib/onebox/example/protection/envelope.json"})
if err := root.ExecuteContext(context.Background()); err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(calls, []string{"/var/lib/onebox/example/protection/envelope.json"}) {
t.Fatalf("runner calls = %#v", calls)
}
root = newRootCmd(func(context.Context, string) error { return nil })
root.SetArgs([]string{"run", "one", "two"})
if err := root.ExecuteContext(context.Background()); err == nil {
t.Fatal("runner accepted more than one envelope")
}
}
Loading