diff --git a/.gitignore b/.gitignore index 39e20ff..dfd8bd9 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ bin dist/ config/config.yaml node_modules +.task diff --git a/.goreleaser.yaml b/.goreleaser.yaml index 1fe8a70..aed752e 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -4,10 +4,16 @@ project_name: minecraft-preempt before: hooks: - go mod download +env: + - CGO_ENABLED=0 builds: - - main: ./cmd/minecraft-preempt - env: - - CGO_ENABLED=0 + - &default + id: minecraft-preempt + main: ./cmd/minecraft-preempt + gcflags: + - -trimpath + ldflags: + - -s -w -X github.com/jaredallard/minecraft-preempt/internal/version.Version={{ .Version }} goarch: - amd64 - arm64 @@ -15,9 +21,21 @@ builds: - linux - windows - darwin + - <<: *default + id: &name minecraft-preempt-agent + binary: *name + main: ./cmd/minecraft-preempt-agent + +# Verifiable builds. +gomod: + proxy: true + env: + - GOPROXY=https://proxy.golang.org,direct + - GOSUMDB=sum.golang.org + mod: mod archives: - - format: tar.gz + - format: tar.xz # use zip for windows archives format_overrides: - goos: windows diff --git a/Dockerfile b/Dockerfile index 69f481d..146755b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,3 +1,11 @@ +# CI system runs using amd64, so this allows us to not need QEMU, but +# will break builds on non-amd64 Linux systems (sorry). +FROM --platform=amd64 alpine:3.18 as cacerts +RUN apk add --no-cache ca-certificates + FROM alpine:3.18 ENTRYPOINT ["/usr/local/bin/minecraft-preempt"] +COPY --from=cacerts /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ + COPY minecraft-preempt /usr/local/bin/ +COPY minecraft-preempt-agent /usr/local/bin/ diff --git a/Makefile b/Makefile index b14b029..0236bb5 100644 --- a/Makefile +++ b/Makefile @@ -1,24 +1,6 @@ -# go options -GO ?= go -PKG := go mod download -LDFLAGS := -w -s -BINDIR := $(CURDIR)/bin - -# Required for globs to work correctly -SHELL=/bin/bash - -.PHONY: all -all: build - -.PHONY: dep -dep: - @$(PKG) +SHELL := /usr/bin/env bash .PHONY: build build: - CGO_ENABLED=0 $(GO) build -v -o $(BINDIR)/ -ldflags '$(LDFLAGS)' ./cmd/... - -.PHONY: reload -reload: - @echo "Watching for changes to .go files..." - @go run github.com/cespare/reflex@latest --regex='\.go$$' --decoration=fancy --start-service=true bash -- -c 'make build && exec ./bin/minecraft-preempt' \ No newline at end of file + @command -v task >/dev/null || (echo "task not found, please install it. https://taskfile.dev/installation/" && exit 1) + @task \ No newline at end of file diff --git a/README.md b/README.md index eef9f21..573e8bd 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,8 @@ A lightweight Minecraft server manager. Starts a server when users join, and sto ## Usage +**Hint**: There's an example configuration in [`./config`](./config). + First, define a configuration file for your server. The format is like so: ### Top level diff --git a/Taskfile.yml b/Taskfile.yml new file mode 100644 index 0000000..e9aed78 --- /dev/null +++ b/Taskfile.yml @@ -0,0 +1,21 @@ +version: "3" + +tasks: + default: + cmds: + - task: build + test: + cmds: + - go test -v ./... + build: + generates: + - bin/minecraft-preempt + - bin/minecraft-preempt-agent + sources: + - "./**/*.go" + - .tool-versions # Trigger rebuild on Go version changes. + cmds: + - go build -trimpath -v -o ./bin/ -ldflags="-X github.com/jaredallard/minecraft-preempt/internal/version.Version=dev" ./cmd/... + snapshot: + cmds: + - goreleaser --snapshot --clean diff --git a/cmd/minecraft-preempt-agent/minecraft-preempt-agent.go b/cmd/minecraft-preempt-agent/minecraft-preempt-agent.go new file mode 100644 index 0000000..bf045d9 --- /dev/null +++ b/cmd/minecraft-preempt-agent/minecraft-preempt-agent.go @@ -0,0 +1,178 @@ +// Copyright (C) 2023 Jared Allard +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +// Package main implements a lightweight agent that runs a minecraft +// server (via docker-compose) and handles shutting down the server when +// the proxy informs it to. +// +// Currently, this doesn't do much but tell a prebuilt docker-compose +// stack to stop and start. Shutdown signals are handled by shutting +// down the VM, which this agent in turn listens to (either via Docker +// shutting itself down, or preempting the VM). +package main + +import ( + "context" + "fmt" + "os" + "os/exec" + "os/signal" + "syscall" + "time" + + logger "github.com/charmbracelet/log" + "github.com/egym-playground/go-prefix-writer/prefixer" + "github.com/jaredallard/minecraft-preempt/internal/cloud" + "github.com/jaredallard/minecraft-preempt/internal/cloud/docker" + "github.com/jaredallard/minecraft-preempt/internal/cloud/gcp" + "github.com/jaredallard/minecraft-preempt/internal/version" + "github.com/spf13/cobra" +) + +// log is the global logger for the agent. +var log = logger.NewWithOptions(os.Stderr, logger.Options{ + ReportCaller: true, + ReportTimestamp: true, + Level: logger.DebugLevel, +}) + +// rootCmd is the root command used by cobra +var rootCmd = &cobra.Command{ + Use: "minecraft-preempt-agent", + Version: version.Version, + + Short: "minecraft-preempt-agent is a companion to the minecraft-preempt proxy", + RunE: entrypoint, +} + +// entrypoint is the entrypoint for the root command +func entrypoint(cCmd *cobra.Command, args []string) error { + ctx, cancel := context.WithCancel(cCmd.Context()) + defer cancel() + + dc := cCmd.Flag("docker-compose-file").Value.String() + cloudProvider := cCmd.Flag("cloud").Value.String() + + _, err := os.Stat(dc) + if err != nil { + return fmt.Errorf("failed to find docker-compose file: %w", err) + } + + log.With("version", version.Version, "cloud", cloudProvider).Info("starting agent") + + cmd := exec.CommandContext(ctx, "docker", "compose", "-f", dc, "up") + cmd.Stdout = prefixer.New(os.Stdout, func() string { return "[docker-compose] " }) + cmd.Stderr = prefixer.New(os.Stderr, func() string { return "[docker-compose] " }) + + // Start the process in a new process group so we can kill it and all + // of its children reliably. This also detaches ^C (sent to us) from + // killing the child process, instead allowing our context cancel to + // do that. + cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true} + cmd.Cancel = func() error { + // Send SIGINT to the child process group. + return syscall.Kill(-cmd.Process.Pid, syscall.SIGINT) + } + + if err := cmd.Start(); err != nil { + return fmt.Errorf("failed to start '%s': %w", cmd.String(), err) + } + + // Start the watcher. + if err := watcher(ctx, cancel, cloudProvider); err != nil { + return fmt.Errorf("failed to start watcher: %w", err) + } + + if err := cmd.Wait(); err != nil { + // Only report errors if the context wasn't canceled. + if ctx.Err() == nil { + return fmt.Errorf("failed to run '%s': %w", cmd.String(), err) + } + } + + log.Info("exited") + + return nil +} + +// watcher uses cloud specific APIs to determine when this agent should +// terminate. The provided cancel function will be called when the agent +// should shutdown. +func watcher(ctx context.Context, cancel context.CancelFunc, cloudProvider string) error { + var c cloud.Provider + var err error + + switch cloudProvider { + case "gcp": + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + + c, err = gcp.NewClient(ctx, "", "") + case "docker": + c, err = docker.NewClient() + } + if err != nil { + return fmt.Errorf("failed to start cloud watcher for cloud %s: %w", cloudProvider, err) + } + + // Start the watcher. + go func() { + t := time.NewTicker(2 * time.Second) + defer t.Stop() + + for { + // If we're canceled, exit. + select { + case <-ctx.Done(): + log.Debug("context canceled, exiting watcher") + return + case <-t.C: + shouldStop, err := c.ShouldTerminate(ctx) + if err != nil { + log.With("err", err).Warn("failed to determine if instance should terminate") + continue + } + + if shouldStop { + log.Info("instance is being preempted, starting shutdown") + cancel() + return + } + } + } + }() + + log.Info("started preemption watcher") + + return nil +} + +// main is the entrypoint for the proxy +func main() { + exitCode := 0 + defer func() { + os.Exit(exitCode) + }() + + ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) + defer cancel() + + rootCmd.PersistentFlags().String("docker-compose-file", "docker-compose.yml", "path to docker-compose.yml") + rootCmd.PersistentFlags().String("cloud", "docker", "cloud provider to use") + if err := rootCmd.ExecuteContext(ctx); err != nil { + log.With("err", err).Error("failed to run") + exitCode = 1 + } +} diff --git a/cmd/minecraft-preempt/minecraft-preempt.go b/cmd/minecraft-preempt/minecraft-preempt.go index 65a76d2..6dca435 100644 --- a/cmd/minecraft-preempt/minecraft-preempt.go +++ b/cmd/minecraft-preempt/minecraft-preempt.go @@ -31,11 +31,13 @@ import ( "github.com/spf13/cobra" "github.com/jaredallard/minecraft-preempt/internal/config" + "github.com/jaredallard/minecraft-preempt/internal/version" ) // rootCmd is the root command used by cobra var rootCmd = &cobra.Command{ - Use: "minecraft-preempt", + Use: "minecraft-preempt", + Version: version.Version, Short: "minecraft-preempt is a proxy for minecraft servers that can start and stop them", Long: `minecraft-preempt is a proxy for minecraft servers that can start and stop them based on ` + diff --git a/docker-compose.yaml b/docker-compose.yml similarity index 100% rename from docker-compose.yaml rename to docker-compose.yml diff --git a/go.mod b/go.mod index 78f296c..3073f15 100644 --- a/go.mod +++ b/go.mod @@ -3,20 +3,19 @@ module github.com/jaredallard/minecraft-preempt go 1.21 require ( - github.com/Tnze/go-mc v1.19.4 + cloud.google.com/go/compute v1.23.3 + cloud.google.com/go/compute/metadata v0.2.3 + github.com/Tnze/go-mc v1.20.1 github.com/charmbracelet/log v0.3.0 github.com/docker/docker v24.0.7+incompatible + github.com/egym-playground/go-prefix-writer v0.0.0-20180609083313-7326ea162eca github.com/function61/gokit v0.0.0-20231117065306-355fe206d542 github.com/pkg/errors v0.9.1 github.com/spf13/cobra v1.8.0 - golang.org/x/oauth2 v0.14.0 - google.golang.org/api v0.151.0 gopkg.in/yaml.v3 v3.0.1 ) require ( - cloud.google.com/go/compute v1.23.1 // indirect - cloud.google.com/go/compute/metadata v0.2.3 // indirect github.com/Microsoft/go-winio v0.6.0 // indirect github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/charmbracelet/lipgloss v0.9.1 // indirect @@ -50,12 +49,15 @@ require ( golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect golang.org/x/mod v0.13.0 // indirect golang.org/x/net v0.18.0 // indirect + golang.org/x/oauth2 v0.14.0 // indirect golang.org/x/sync v0.5.0 // indirect golang.org/x/sys v0.14.0 // indirect golang.org/x/text v0.14.0 // indirect - golang.org/x/time v0.3.0 // indirect golang.org/x/tools v0.14.0 // indirect + google.golang.org/api v0.151.0 // indirect google.golang.org/appengine v1.6.7 // indirect + google.golang.org/genproto v0.0.0-20231016165738-49dd2c1f3d0b // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20231016165738-49dd2c1f3d0b // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20231030173426-d783a09b4405 // indirect google.golang.org/grpc v1.59.0 // indirect google.golang.org/protobuf v1.31.0 // indirect diff --git a/go.sum b/go.sum index 5452249..4c24454 100644 --- a/go.sum +++ b/go.sum @@ -1,8 +1,10 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go/compute v1.23.0 h1:tP41Zoavr8ptEqaW6j+LQOnyBBhO7OkOMAGrgLopTwY= -cloud.google.com/go/compute v1.23.0/go.mod h1:4tCnrn48xsqlwSAiLf1HXMQk8CONslYbdiEZc9FEIbM= +cloud.google.com/go v0.110.8 h1:tyNdfIxjzaWctIiLYOTalaLKZ17SI44SKFW26QbOhME= +cloud.google.com/go v0.110.8/go.mod h1:Iz8AkXJf1qmxC3Oxoep8R1T36w8B92yU29PcBhHO5fk= cloud.google.com/go/compute v1.23.1 h1:V97tBoDaZHb6leicZ1G6DLK2BAaZLJ/7+9BB/En3hR0= cloud.google.com/go/compute v1.23.1/go.mod h1:CqB3xpmPKKt3OJpW2ndFIXnA9A4xAy/F3Xp1ixncW78= +cloud.google.com/go/compute v1.23.3 h1:6sVlXXBmbd7jNX0Ipq0trII3e4n1/MsADLK6a+aiVlk= +cloud.google.com/go/compute v1.23.3/go.mod h1:VCgBUoMnIVIR0CscqQiPJLAG25E3ZRZMzcFZeQ+h8CI= cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= @@ -12,20 +14,17 @@ github.com/Microsoft/go-winio v0.6.0 h1:slsWYD/zyx7lCXoZVlvQrj0hPTM1HI4+v1sIda2y github.com/Microsoft/go-winio v0.6.0/go.mod h1:cTAf44im0RAYeL23bpB+fzCyDH2MJiz2BO69KH/soAE= github.com/Tnze/go-mc v1.19.4 h1:9qtxH+xRJWswOYnlf/dsFY4EI2f5jsFhtqTYOObaGIE= github.com/Tnze/go-mc v1.19.4/go.mod h1:c1znJQglgqa1Jjs3Dr29woN/msguiJrlNtWXhKedh2U= +github.com/Tnze/go-mc v1.20.1 h1:1Mwxyg2nvIVgRtebf6CpjMjvXLtRIng95so5hTEQtXA= +github.com/Tnze/go-mc v1.20.1/go.mod h1:c1znJQglgqa1Jjs3Dr29woN/msguiJrlNtWXhKedh2U= github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/charmbracelet/lipgloss v0.8.0 h1:IS00fk4XAHcf8uZKc3eHeMUTCxUH6NkaTrdyCQk84RU= -github.com/charmbracelet/lipgloss v0.8.0/go.mod h1:p4eYUZZJ/0oXTuCQKFF8mqyKCz0ja6y+7DniDDw5KKU= github.com/charmbracelet/lipgloss v0.9.1 h1:PNyd3jvaJbg4jRHKWXnCj1akQm4rh8dbEzN1p/u1KWg= github.com/charmbracelet/lipgloss v0.9.1/go.mod h1:1mPmG4cxScwUQALAAnacHaigiiHB9Pmr+v1VEawJl6I= -github.com/charmbracelet/log v0.2.5 h1:1yVvyKCKVV639RR4LIq1iy1Cs1AKxuNO+Hx2LJtk7Wc= -github.com/charmbracelet/log v0.2.5/go.mod h1:nQGK8tvc4pS9cvVEH/pWJiZ50eUq1aoXUOjGpXvdD0k= github.com/charmbracelet/log v0.3.0 h1:u5aB2KJDgNZo4WOfOC8C+KvGIkJ2rCFNlPWDu6xhnqI= github.com/charmbracelet/log v0.3.0/go.mod h1:OR4E1hutLsax3ZKpXbgUqPtTjQfrh1pG3zwHGWuuq8g= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -33,22 +32,18 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/docker/distribution v2.8.1+incompatible h1:Q50tZOPR6T/hjNsyc9g8/syEs6bk8XXApsHjKukMl68= github.com/docker/distribution v2.8.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/docker v24.0.6+incompatible h1:hceabKCtUgDqPu+qm0NgsaXf28Ljf4/pWFL7xjWWDgE= -github.com/docker/docker v24.0.6+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker v24.0.7+incompatible h1:Wo6l37AuwP3JaMnZa226lzVXGA3F9Ig1seQen0cKYlM= github.com/docker/docker v24.0.7+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/egym-playground/go-prefix-writer v0.0.0-20180609083313-7326ea162eca h1:sWNMfkKG8GW1pGUyNlbsWq6f04pFgcsomY+Fly8XdB4= +github.com/egym-playground/go-prefix-writer v0.0.0-20180609083313-7326ea162eca/go.mod h1:Ar+qogA+fkjeUR18xJfFzrMSjfs/sCPO+yjVvhXpyEg= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/function61/gokit v0.0.0-20231002081253-372832f232f1 h1:zOwGlpkoveS4Q/8FEp8jsnhM7czqRA7m9Ai42jywkzE= -github.com/function61/gokit v0.0.0-20231002081253-372832f232f1/go.mod h1:sJY957+7ush4oj4ElOMhUFaFIriAFNAGYzVh2tFJNy0= -github.com/function61/gokit v0.0.0-20231020102821-0610aaf31f32 h1:a5qgwiHpd76kHzXhy8Mi/oH/iiaxub+dNgXPv4uyeXA= -github.com/function61/gokit v0.0.0-20231020102821-0610aaf31f32/go.mod h1:sJY957+7ush4oj4ElOMhUFaFIriAFNAGYzVh2tFJNy0= github.com/function61/gokit v0.0.0-20231117065306-355fe206d542 h1:a9BTN+DOboRkVih0suT4zrRZ4zLGFpBtHPGNd+EQ4pI= github.com/function61/gokit v0.0.0-20231117065306-355fe206d542/go.mod h1:sJY957+7ush4oj4ElOMhUFaFIriAFNAGYzVh2tFJNy0= github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4= @@ -80,17 +75,13 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= -github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o= github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4= -github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/enterprise-certificate-proxy v0.3.1 h1:SBWmZhjUDRorQxrN0nwzf+AHBxnbFjViHQS4P0yVpmQ= -github.com/googleapis/enterprise-certificate-proxy v0.3.1/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0= github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs= github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0= github.com/googleapis/gax-go/v2 v2.12.0 h1:A+gCJKdRfqXkr+BIRGtZLibNXf0m1f9E4HG56etFpas= @@ -112,8 +103,6 @@ github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98= github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= -github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= -github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/moby/term v0.0.0-20221205130635-1aeaba878587 h1:HfkjXDfhgVaN5rmueG8cL8KKeFNecRCXFhaJ2qZ5SKA= @@ -140,8 +129,6 @@ github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTE github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= -github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= @@ -161,8 +148,6 @@ go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= -golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/crypto v0.15.0 h1:frVn1TEaCEaZcn3Tmd7Y2b5KKPaZ+I32Q2OA3kYp5TA= golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -173,8 +158,6 @@ golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvx golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.9.0 h1:KENHtAZL2y3NLMYZeHY9DW8HW8V+kQyJsY/V9JlKvCs= -golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.13.0 h1:I/DsJXRlw/8l/0c24sM9yb0T4z9liZTduXvdAWYiysY= golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -187,13 +170,9 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.16.0 h1:7eBu7KsSvFDtSXUIDbh3aqlK4DPsZ1rByC8PFfBThos= -golang.org/x/net v0.16.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg= golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.13.0 h1:jDDenyj+WgFtmV3zYVoi8aE2BwtXFLWOA67ZfNWftiY= -golang.org/x/oauth2 v0.13.0/go.mod h1:/JMhi4ZRXAf4HG9LiNmxvk+45+96RUlVThiH8FzNBn0= golang.org/x/oauth2 v0.14.0 h1:P0Vrf/2538nmC0H+pEQ3MNFRRnVR7RlqyVw+bvm26z0= golang.org/x/oauth2 v0.14.0/go.mod h1:lAtNWgaWfL4cm7j2OV8TxGi9Qb7ECORx8DktCY74OwM= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -201,8 +180,6 @@ golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= -golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -211,15 +188,11 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= -golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q= golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= -golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= @@ -233,18 +206,12 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= -golang.org/x/tools v0.7.0 h1:W4OVu8VVOaIO0yzWMNdepAulS7YfoS3Zabrm8DOXXU4= -golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= golang.org/x/tools v0.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc= golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/api v0.145.0 h1:kBjvf1A3/m30kUvnUX9jZJxTu3lJrpGFt5V/1YZrjwg= -google.golang.org/api v0.145.0/go.mod h1:OARJqIfoYjXJj4C1AiBSXYZt03qsoz8FQYU6fBEfrHM= -google.golang.org/api v0.150.0 h1:Z9k22qD289SZ8gCJrk4DrWXkNjtfvKAUo/l1ma8eBYE= -google.golang.org/api v0.150.0/go.mod h1:ccy+MJ6nrYFgE3WgRx/AMXOxOmU8Q4hSa+jjibzhxcg= google.golang.org/api v0.151.0 h1:FhfXLO/NFdJIzQtCqjpysWwqKk8AzGWBUhMIx67cVDU= google.golang.org/api v0.151.0/go.mod h1:ccy+MJ6nrYFgE3WgRx/AMXOxOmU8Q4hSa+jjibzhxcg= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= @@ -254,12 +221,10 @@ google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCID google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20230913181813-007df8e322eb h1:XFBgcDwm7irdHTbz4Zk2h7Mh+eis4nfJEFQFYzJzuIA= -google.golang.org/genproto v0.0.0-20230913181813-007df8e322eb/go.mod h1:yZTlhN0tQnXo3h00fuXNCxJdLdIdnVFVBaRJ5LWBbw4= -google.golang.org/genproto/googleapis/api v0.0.0-20230913181813-007df8e322eb h1:lK0oleSc7IQsUxO3U5TjL9DWlsxpEBemh+zpB7IqhWI= -google.golang.org/genproto/googleapis/api v0.0.0-20230913181813-007df8e322eb/go.mod h1:KjSP20unUpOx5kyQUFa7k4OJg0qeJ7DEZflGDu2p6Bk= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230920204549-e6e6cdab5c13 h1:N3bU/SQDCDyD6R528GJ/PwW9KjYcJA3dgyH+MovAkIM= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230920204549-e6e6cdab5c13/go.mod h1:KSqppvjFjtoCI+KGd4PELB0qLNxdJHRGqRI09mB6pQA= +google.golang.org/genproto v0.0.0-20231016165738-49dd2c1f3d0b h1:+YaDE2r2OG8t/z5qmsh7Y+XXwCbvadxxZ0YY6mTdrVA= +google.golang.org/genproto v0.0.0-20231016165738-49dd2c1f3d0b/go.mod h1:CgAqfJo+Xmu0GwA0411Ht3OU3OntXwsGmrmjI8ioGXI= +google.golang.org/genproto/googleapis/api v0.0.0-20231016165738-49dd2c1f3d0b h1:CIC2YMXmIhYw6evmhPxBKJ4fmLbOFtXQN/GV3XOZR8k= +google.golang.org/genproto/googleapis/api v0.0.0-20231016165738-49dd2c1f3d0b/go.mod h1:IBQ646DjkDkvUIsVq/cc03FUFQ9wbZu7yE396YcL870= google.golang.org/genproto/googleapis/rpc v0.0.0-20231030173426-d783a09b4405 h1:AB/lmRny7e2pLhFEYIbl5qkDAUt2h0ZRO4wGPhZf+ik= google.golang.org/genproto/googleapis/rpc v0.0.0-20231030173426-d783a09b4405/go.mod h1:67X1fPuzjcrkymZzZV1vvkFeTn2Rvc6lYF9MYFGCcwE= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= @@ -267,8 +232,6 @@ google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyac google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.58.2 h1:SXUpjxeVF3FKrTYQI4f4KvbGD5u2xccdYdurwowix5I= -google.golang.org/grpc v1.58.2/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0= google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk= google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= diff --git a/internal/cloud/docker/client.go b/internal/cloud/docker/client.go index 970a7fc..cbb2a04 100644 --- a/internal/cloud/docker/client.go +++ b/internal/cloud/docker/client.go @@ -17,6 +17,7 @@ package docker import ( "context" + "os" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/container" @@ -95,3 +96,15 @@ func (c *Client) Stop(ctx context.Context, containerID string) error { return c.d.ContainerStop(ctx, cont.ID, container.StopOptions{}) } + +// ShouldTerminate returns true if the instance should be terminated. +func (c *Client) ShouldTerminate(ctx context.Context) (bool, error) { + // Here for tests, if `shutdown.txt` exists, shutdown. + if _, err := os.Stat("shutdown.txt"); err == nil { + return true, nil + } + + // There is currently no dynamic system in place to restart in the + // case of using Docker. + return false, nil +} diff --git a/internal/cloud/gcp/client.go b/internal/cloud/gcp/client.go index 97f4536..5c7ed13 100644 --- a/internal/cloud/gcp/client.go +++ b/internal/cloud/gcp/client.go @@ -18,12 +18,14 @@ package gcp import ( "context" "errors" - "net/http" + "fmt" + "strings" "github.com/jaredallard/minecraft-preempt/internal/cloud" - "golang.org/x/oauth2/google" - "google.golang.org/api/compute/v1" - "google.golang.org/api/option" + + compute "cloud.google.com/go/compute/apiv1" + "cloud.google.com/go/compute/apiv1/computepb" + "cloud.google.com/go/compute/metadata" ) var ( @@ -34,8 +36,8 @@ var ( // Client is a gcs client type Client struct { - gclient *http.Client - compute *compute.Service + compute *compute.InstancesClient + metadata *metadata.Client project string zone string @@ -43,34 +45,32 @@ type Client struct { // NewClient creates a new client func NewClient(ctx context.Context, project, zone string) (*Client, error) { - client, err := google.DefaultClient(ctx, compute.ComputeScope) - if err != nil { - return nil, err - } - - comp, err := compute.NewService(ctx, option.WithHTTPClient(client)) + computeCli, err := compute.NewInstancesRESTClient(ctx) if err != nil { return nil, err } return &Client{ - gclient: client, - compute: comp, - project: project, - zone: zone, + compute: computeCli, + metadata: metadata.NewClient(nil), + project: project, + zone: zone, }, nil } // Status returns the status of an instance func (c *Client) Status(ctx context.Context, instanceID string) (cloud.ProviderStatus, error) { - gr := c.compute.Instances.Get(c.project, c.zone, instanceID) - i, err := gr.Do() + inst, err := c.compute.Get(ctx, &computepb.GetInstanceRequest{ + Project: c.project, + Zone: c.zone, + Instance: instanceID, + }) if err != nil { return "", err } // HACK: handle invalid statuses - st := cloud.ProviderStatus(i.Status) + st := cloud.ProviderStatus(inst.GetStatus()) switch st { case cloud.StatusRunning, cloud.StatusStarting, cloud.StatusStopping, cloud.StatusStopped: case "TERMINATED": @@ -82,7 +82,7 @@ func (c *Client) Status(ctx context.Context, instanceID string) (cloud.ProviderS } // convert some of the types to "Starting" - if i.Status == "STAGING" || i.Status == "PROVISIONING" { + if inst.GetStatus() == "STAGING" || inst.GetStatus() == "PROVISIONING" { st = cloud.StatusStarting } @@ -91,23 +91,48 @@ func (c *Client) Status(ctx context.Context, instanceID string) (cloud.ProviderS // Start a instance if it's not already running func (c *Client) Start(ctx context.Context, instanceID string) error { - gr := c.compute.Instances.Get(c.project, c.zone, instanceID) - i, err := gr.Do() + inst, err := c.compute.Get(ctx, &computepb.GetInstanceRequest{ + Project: c.project, + Zone: c.zone, + Instance: instanceID, + }) if err != nil { return err } - if i.Status != "STOPPED" && i.Status != "TERMINATED" { + if inst.GetStatus() != "STOPPED" && inst.GetStatus() != "TERMINATED" { return ErrNotStopped } - sr := c.compute.Instances.Start(c.project, c.zone, instanceID) - _, err = sr.Do() + _, err = c.compute.Start(ctx, &computepb.StartInstanceRequest{ + Project: c.project, + Zone: c.zone, + Instance: instanceID, + }) return err } // Stop a instance if it's not already stopped func (c *Client) Stop(ctx context.Context, instanceID string) error { - _, err := c.compute.Instances.Stop(c.project, c.zone, instanceID).Do() + _, err := c.compute.Stop(ctx, &computepb.StopInstanceRequest{ + Project: c.project, + Zone: c.zone, + Instance: instanceID, + }) return err } + +// ShouldTerminate checks the current instance's status to see if it's +// being preempted or terminated. If so, it returns true. +func (c *Client) ShouldTerminate(ctx context.Context) (bool, error) { + resp, err := c.metadata.Get("instance/preempted") + if err != nil { + return false, fmt.Errorf("failed to determine if instance is being preempted") + } + + if strings.EqualFold(strings.TrimSpace(resp), "TRUE") { + return true, nil + } + + return false, nil +} diff --git a/internal/cloud/instance.go b/internal/cloud/instance.go index 1d5a6f0..286665e 100644 --- a/internal/cloud/instance.go +++ b/internal/cloud/instance.go @@ -57,4 +57,7 @@ type Provider interface { // Stop stops a remote instance Stop(ctx context.Context, instanceID string) error + + // ShouldTerminate returns true if the instance should be terminated. + ShouldTerminate(ctx context.Context) (bool, error) } diff --git a/internal/version/version.go b/internal/version/version.go new file mode 100644 index 0000000..5c75261 --- /dev/null +++ b/internal/version/version.go @@ -0,0 +1,27 @@ +// Copyright (C) 2023 Jared Allard +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +// Package version contains the version of the program. This is set by +// the build tooling (task and goreleaser). +package version + +// Version is the version of the program. +var Version = "unset" + +func init() { + if Version == "unset" { + panic("Version is unset, please run `make`") + } +}