diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 039ec669..b0d0dfb2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -25,15 +25,21 @@ jobs: with: token: ${{ secrets.CODECOV_TOKEN }} build: - runs-on: ubuntu-latest strategy: matrix: - include: - # cgo is currently required by the upstream SQLite driver. - # This case should be un-commented if and when cgo is - # supported again. - # - cgo: "0" - - cgo: "1" + os: [macos-latest, windows-latest, ubuntu-18.04] + flags: + - "" + - -tags headless + cgo: ["0", "1"] + exclude: + # TODO: remove this exception after https://github.com/temporalio/ui-server/issues/104 is closed. + - os: windows-latest + flags: "" + # cgo is currently required by the upstream SQLite driver. + # This exception should be removed if/when non-cgo builds are supported again. + - cgo: "0" + runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v2 - name: Set up Go @@ -43,4 +49,4 @@ jobs: - name: Test env: CGO_ENABLED: ${{ matrix.cgo }} - run: go test -v ./... + run: go test -v ${{ matrix.flags }} ./... diff --git a/README.md b/README.md index b2eca717..e8899675 100644 --- a/README.md +++ b/README.md @@ -63,6 +63,7 @@ temporalite start -h ### Namespace Registration Namespaces can be pre-registered at startup so they're available to use right away: + ```bash temporalite start --namespace foo --namespace bar ``` @@ -86,3 +87,17 @@ An in-memory mode is also available. Note that all data will be lost on each res ```bash temporalite start --ephemeral ``` + +### Web UI + +The `temporalite` binary can be compiled to omit static assets for installations that will never use the UI: + +```bash +go install -tags headless github.com/DataDog/temporalite/cmd/temporalite@latest +``` + +The UI can also be disabled via a runtime flag: + +```bash +temporalite start --headless +``` diff --git a/cmd/temporalite/main.go b/cmd/temporalite/main.go index e86cd202..87bd0036 100644 --- a/cmd/temporalite/main.go +++ b/cmd/temporalite/main.go @@ -11,9 +11,6 @@ import ( "os" "strings" - uiserver "github.com/temporalio/ui-server/server" - uiconfig "github.com/temporalio/ui-server/server/config" - uiserveroptions "github.com/temporalio/ui-server/server/server_options" "github.com/urfave/cli/v2" "go.temporal.io/server/common/headers" "go.temporal.io/server/common/log" @@ -28,6 +25,10 @@ import ( "github.com/DataDog/temporalite/internal/liteconfig" ) +// Name of the ui-server module, used in tests to verify that it is included/excluded +// as a dependency when building with the `headless` tag enabled. +const uiServerModule = "github.com/temporalio/ui-server" + var ( defaultCfg *liteconfig.Config ) @@ -37,6 +38,7 @@ const ( dbPathFlag = "filename" portFlag = "port" uiPortFlag = "ui-port" + headlessFlag = "headless" ipFlag = "ip" logFormatFlag = "log-format" namespaceFlag = "namespace" @@ -93,6 +95,10 @@ func buildCLI() *cli.App { Usage: "port for the temporal web UI", DefaultText: fmt.Sprintf("--port + 1000, eg. %d", liteconfig.DefaultFrontendPort+1000), }, + &cli.BoolFlag{ + Name: headlessFlag, + Usage: "disable the temporal web UI", + }, &cli.StringFlag{ Name: ipFlag, Usage: `IPv4 address to bind the frontend service to instead of localhost`, @@ -144,12 +150,6 @@ func buildCLI() *cli.App { if c.IsSet(uiPortFlag) { uiPort = c.Int(uiPortFlag) } - uiOpts := uiconfig.Config{ - TemporalGRPCAddress: fmt.Sprintf(":%d", c.Int(portFlag)), - Host: ip, - Port: uiPort, - EnableUI: true, - } pragmas, err := getPragmaMap(c.StringSlice(pragmaFlag)) if err != nil { @@ -166,7 +166,12 @@ func buildCLI() *cli.App { temporalite.WithUpstreamOptions( temporal.InterruptOn(temporal.InterruptCh()), ), - temporalite.WithUI(uiserver.NewServer(uiserveroptions.WithConfigProvider(&uiOpts))), + } + if !c.Bool(headlessFlag) { + opt := newUIOption(fmt.Sprintf(":%d", c.Int(portFlag)), ip, uiPort) + if opt != nil { + opts = append(opts, opt) + } } if c.Bool(ephemeralFlag) { opts = append(opts, temporalite.WithPersistenceDisabled()) diff --git a/cmd/temporalite/ui.go b/cmd/temporalite/ui.go new file mode 100644 index 00000000..da017c60 --- /dev/null +++ b/cmd/temporalite/ui.go @@ -0,0 +1,30 @@ +//go:build !headless + +package main + +import ( + // This file should be the only one to import ui-server packages. + // This is to avoid embedding the UI's static assets in the binary when the `headless` build tag is enabled. + uiserver "github.com/temporalio/ui-server/server" + uiconfig "github.com/temporalio/ui-server/server/config" + uiserveroptions "github.com/temporalio/ui-server/server/server_options" + + "github.com/DataDog/temporalite" +) + +func newUIOption(frontendAddr string, uiIP string, uiPort int) temporalite.ServerOption { + return temporalite.WithUI(uiserver.NewServer(uiserveroptions.WithConfigProvider(newUIConfig( + frontendAddr, + uiIP, + uiPort, + )))) +} + +func newUIConfig(frontendAddr string, uiIP string, uiPort int) *uiconfig.Config { + return &uiconfig.Config{ + TemporalGRPCAddress: frontendAddr, + Host: uiIP, + Port: uiPort, + EnableUI: true, + } +} diff --git a/cmd/temporalite/ui_disabled.go b/cmd/temporalite/ui_disabled.go new file mode 100644 index 00000000..7313a050 --- /dev/null +++ b/cmd/temporalite/ui_disabled.go @@ -0,0 +1,9 @@ +//go:build headless + +package main + +import "github.com/DataDog/temporalite" + +func newUIOption(frontendAddr string, uiIP string, uiPort int) temporalite.ServerOption { + return nil +} diff --git a/cmd/temporalite/ui_disabled_test.go b/cmd/temporalite/ui_disabled_test.go new file mode 100644 index 00000000..e883d9f1 --- /dev/null +++ b/cmd/temporalite/ui_disabled_test.go @@ -0,0 +1,19 @@ +//go:build headless + +package main + +import ( + "runtime/debug" + "testing" +) + +// This test ensures that the ui-server module is not a dependency of Temporalite when built +// for headless mode. +func TestNoUIServerDependency(t *testing.T) { + info, _ := debug.ReadBuildInfo() + for _, dep := range info.Deps { + if dep.Path == uiServerModule { + t.Errorf("%s should not be a dependency when headless tag is enabled", uiServerModule) + } + } +} diff --git a/cmd/temporalite/ui_test.go b/cmd/temporalite/ui_test.go new file mode 100644 index 00000000..377a080e --- /dev/null +++ b/cmd/temporalite/ui_test.go @@ -0,0 +1,30 @@ +//go:build !headless + +package main + +import ( + "runtime/debug" + "testing" +) + +// This test ensures that ui-server is a dependency of Temporalite built in non-headless mode. +func TestHasUIServerDependency(t *testing.T) { + info, _ := debug.ReadBuildInfo() + for _, dep := range info.Deps { + if dep.Path == uiServerModule { + return + } + } + t.Errorf("%s should be a dependency when headless tag is not enabled", uiServerModule) + // If the ui-server module name is ever changed, this test should fail and indicate that the + // module name should be updated for this and the equivalent test case in ui_disabled_test.go + // to continue working. + t.Logf("Temporalite's %s dependency is missing. Was this module renamed recently?", uiServerModule) +} + +func TestNewUIConfig(t *testing.T) { + cfg := newUIConfig("localhost:7233", "localhost", 8233) + if err := cfg.Validate(); err != nil { + t.Errorf("config not valid: %s", err) + } +}