diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index bf571b3..8134abe 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -12,24 +12,12 @@ jobs: uses: actions/checkout@v4 # Setup golang with caching - - name: Setup Golang - uses: actions/setup-go@v4 + - name: Set up Go + uses: actions/setup-go@v5 with: - go-version: stable - - id: go-cache-paths - run: | - echo "go-build=$(go env GOCACHE)" >> "$GITHUB_OUTPUT" - echo "go-mod=$(go env GOMODCACHE)" >> "$GITHUB_OUTPUT" - - name: Go Build Cache - uses: actions/cache@v3 - with: - path: ${{ steps.go-cache-paths.outputs.go-build }} - key: ${{ runner.os }}-go-build-${{ hashFiles('**/go.sum') }} - - name: Go Mod Cache - uses: actions/cache@v3 - with: - path: ${{ steps.go-cache-paths.outputs.go-mod }} - key: ${{ runner.os }}-go-mod-${{ hashFiles('**/go.sum') }} + go-version-file: 'go.mod' + cache-dependency-path: "**/go.sum" + cache: true # Run tests - name: Run tests diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index bc8d931..4eb0095 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -33,24 +33,12 @@ jobs: fetch-depth: 0 # Setup golang with caching - - name: Setup Golang - uses: actions/setup-go@v4 + - name: Set up Go + uses: actions/setup-go@v5 with: - go-version: 1.22 - - id: go-cache-paths - run: | - echo "go-build=$(go env GOCACHE)" >> "$GITHUB_OUTPUT" - echo "go-mod=$(go env GOMODCACHE)" >> "$GITHUB_OUTPUT" - - name: Go Build Cache - uses: actions/cache@v3 - with: - path: ${{ steps.go-cache-paths.outputs.go-build }} - key: ${{ runner.os }}-go-build-${{ hashFiles('**/go.sum') }} - - name: Go Mod Cache - uses: actions/cache@v3 - with: - path: ${{ steps.go-cache-paths.outputs.go-mod }} - key: ${{ runner.os }}-go-mod-${{ hashFiles('**/go.sum') }} + go-version-file: 'go.mod' + cache-dependency-path: "**/go.sum" + cache: true # Setup tinygo - uses: acifani/setup-tinygo@v2 @@ -60,8 +48,10 @@ jobs: # Setup docker buildx - name: Set up QEMU uses: docker/setup-qemu-action@v3 + - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 + - name: 'Login to GitHub Container Registry' uses: docker/login-action@v3 with: diff --git a/.gitignore b/.gitignore index f8e0586..2aacebe 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ dist/ *.out cover.cov fanunit.uf2 +.idea \ No newline at end of file diff --git a/.goreleaser.yaml b/.goreleaser.yaml index 3cf39f4..220065c 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -10,6 +10,11 @@ builds: binary: compute-blade-agent id: agent dir: ./cmd/agent/ + mod_timestamp: "{{ .CommitTimestamp }}" + ldflags: + - -X=main.Version={{.Version}} + - -X=main.Commit={{.Commit}} + - -X=main.Date={{ .CommitTimestamp }} - env: *env goos: *goos @@ -17,6 +22,11 @@ builds: binary: bladectl id: bladectl dir: ./cmd/bladectl/ + mod_timestamp: "{{ .CommitTimestamp }}" + ldflags: + - -X=main.Version={{.Version}} + - -X=main.Commit={{.Commit}} + - -X=main.Date={{ .CommitTimestamp }} # Docker image including both agent and bladectl dockers: diff --git a/cmd/agent/main.go b/cmd/agent/main.go index 1219b98..edee5e7 100644 --- a/cmd/agent/main.go +++ b/cmd/agent/main.go @@ -22,6 +22,12 @@ import ( "google.golang.org/grpc" ) +var ( + Version string + Commit string + Date string +) + func main() { var wg sync.WaitGroup @@ -84,7 +90,7 @@ func main() { } }() - log.FromContext(ctx).Info("Bootstrapping compute-blade-agent", zap.String("version", viper.GetString("version"))) + log.FromContext(ctx).Info("Bootstrapping compute-blade-agent", zap.String("version", Version), zap.String("commit", Commit), zap.String("date", Date)) computebladeAgent, err := agent.NewComputeBladeAgent(ctx, cbAgentConfig) if err != nil { log.FromContext(ctx).Error("Failed to create agent", zap.Error(err)) diff --git a/cmd/bladectl/cmd_fan.go b/cmd/bladectl/cmd_fan.go index e16df71..34ca1f5 100644 --- a/cmd/bladectl/cmd_fan.go +++ b/cmd/bladectl/cmd_fan.go @@ -1,40 +1,33 @@ package main import ( - "strconv" - "github.com/spf13/cobra" bladeapiv1alpha1 "github.com/uptime-induestries/compute-blade-agent/api/bladeapi/v1alpha1" ) +var ( + percent int +) + func init() { - cmdFan.AddCommand(cmdFanSetPercent) - rootCmd.AddCommand(cmdFan) + cmdFan.Flags().IntVarP(&percent, "percent", "p", 40, "Fan speed in percent (Default: 40).") + _ = cmdFan.MarkFlagRequired("percent") + + cmdSet.AddCommand(cmdFan) } var ( cmdFan = &cobra.Command{ - Use: "fan", - Short: "Fan-related commands for the compute blade", - } - - cmdFanSetPercent = &cobra.Command{ - Use: "set-percent ", - Example: "bladectl fan set-percent 50", - Short: "Set the fan speed in percent", - Args: cobra.ExactArgs(1), + Use: "fan", + Short: "Control the fan behavior of the compute-blade", + Example: "bladectl set fan --percent 50", + Args: cobra.ExactArgs(0), RunE: func(cmd *cobra.Command, args []string) error { var err error ctx := cmd.Context() client := clientFromContext(ctx) - // convert string to int - percent, err := strconv.Atoi(args[0]) - if err != nil { - return err - } - _, err = client.SetFanSpeed(ctx, &bladeapiv1alpha1.SetFanSpeedRequest{ Percent: int64(percent), }) diff --git a/cmd/bladectl/cmd_identify.go b/cmd/bladectl/cmd_identify.go index c2d1f91..90b6e0b 100644 --- a/cmd/bladectl/cmd_identify.go +++ b/cmd/bladectl/cmd_identify.go @@ -1,21 +1,28 @@ package main import ( + "github.com/sierrasoftworks/humane-errors-go" "github.com/spf13/cobra" bladeapiv1alpha1 "github.com/uptime-induestries/compute-blade-agent/api/bladeapi/v1alpha1" "google.golang.org/protobuf/types/known/emptypb" ) +var ( + confirm bool + wait bool +) + func init() { - cmdIdentify.Flags().Bool("confirm", false, "confirm the identify state") - cmdIdentify.Flags().Bool("wait", false, "Wait for the identify state to be confirmed (e.g. by a physical button press)") - rootCmd.AddCommand(cmdIdentify) + cmdIdentify.Flags().BoolVarP(&confirm, "confirm", "c", false, "confirm the identify state") + cmdIdentify.Flags().BoolVarP(&wait, "wait", "w", false, "Wait for the identify state to be confirmed (e.g. by a physical button press)") + cmdSet.AddCommand(cmdIdentify) } var cmdIdentify = &cobra.Command{ - Use: "identify", - Short: "interact with the compute-blade identity LED", - RunE: runIdentity, + Use: "identify", + Example: "bladectl set identify --wait", + Short: "interact with the compute-blade identity LED", + RunE: runIdentity, } func runIdentity(cmd *cobra.Command, _ []string) error { @@ -24,16 +31,6 @@ func runIdentity(cmd *cobra.Command, _ []string) error { ctx := cmd.Context() client := clientFromContext(ctx) - // Get flags - confirm, err := cmd.Flags().GetBool("confirm") - if err != nil { - return err - } - wait, err := cmd.Flags().GetBool("wait") - if err != nil { - return err - } - // Check if we should wait for the identify state to be confirmed event := bladeapiv1alpha1.Event_IDENTIFY if confirm { @@ -43,14 +40,14 @@ func runIdentity(cmd *cobra.Command, _ []string) error { // Emit the event to the compute-blade-agent _, err = client.EmitEvent(ctx, &bladeapiv1alpha1.EmitEventRequest{Event: event}) if err != nil { - return err + return humane.Wrap(err, "failed to emit event", "ensure the compute-blade agent is running and responsive to requests", "check the compute-blade agent logs for more information using 'journalctl -u compute-blade-agent.service'") } // Check if we should wait for the identify state to be confirmed if wait { _, err := client.WaitForIdentifyConfirm(ctx, &emptypb.Empty{}) if err != nil { - return err + return humane.Wrap(err, "unable to wait for confirmation", "ensure the compute-blade agent is running and responsive to requests", "check the compute-blade agent logs for more information using 'journalctl -u compute-blade-agent.service'") } } diff --git a/cmd/bladectl/cmd_root.go b/cmd/bladectl/cmd_root.go new file mode 100644 index 0000000..ee52b43 --- /dev/null +++ b/cmd/bladectl/cmd_root.go @@ -0,0 +1,46 @@ +package main + +import ( + "context" + humane "github.com/sierrasoftworks/humane-errors-go" + "github.com/spf13/cobra" + bladeapiv1alpha1 "github.com/uptime-induestries/compute-blade-agent/api/bladeapi/v1alpha1" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" + "os" + "os/signal" + "syscall" +) + +var rootCmd = &cobra.Command{ + Use: "bladectl", + Short: "bladectl interacts with the compute-blade-agent and allows you to manage hardware-features of your compute blade(s)", + PersistentPreRunE: func(cmd *cobra.Command, _ []string) error { + origCtx := cmd.Context() + + // setup signal handlers for SIGINT and SIGTERM + ctx, cancelCtx := context.WithTimeout(origCtx, timeout) + + // setup signal handler channels + sigs := make(chan os.Signal, 1) + signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) + go func() { + // Wait for context cancel or signal + select { + case <-ctx.Done(): + case <-sigs: + // On signal, cancel context + cancelCtx() + } + }() + + conn, err := grpc.Dial(grpcAddr, grpc.WithTransportCredentials(insecure.NewCredentials())) + if err != nil { + return humane.Wrap(err, "failed to dial grpc server", "ensure the gRPC server you are trying to connect to is running and the address is correct") + } + client := bladeapiv1alpha1.NewBladeAgentServiceClient(conn) + + cmd.SetContext(clientIntoContext(ctx, client)) + return nil + }, +} diff --git a/cmd/bladectl/cmd_verbs.go b/cmd/bladectl/cmd_verbs.go new file mode 100644 index 0000000..55017b8 --- /dev/null +++ b/cmd/bladectl/cmd_verbs.go @@ -0,0 +1,24 @@ +package main + +import ( + "github.com/spf13/cobra" +) + +func init() { + rootCmd.AddCommand(cmdGet) + rootCmd.AddCommand(cmdSet) +} + +var ( + cmdGet = &cobra.Command{ + Use: "get", + Short: "Display compute-blade related information", + Long: "Prints information about compute-blade related information, e.g. fan speed, temperature, etc.", + } + + cmdSet = &cobra.Command{ + Use: "set", + Short: "Configure compute-blade", + Long: "These commands allow you make changes to compute-blade related information.", + } +) diff --git a/cmd/bladectl/cmd_version.go b/cmd/bladectl/cmd_version.go new file mode 100644 index 0000000..960272a --- /dev/null +++ b/cmd/bladectl/cmd_version.go @@ -0,0 +1,22 @@ +package main + +import ( + "fmt" + "github.com/spf13/cobra" +) + +func init() { + rootCmd.AddCommand(cmdVersion) +} + +var cmdVersion = &cobra.Command{ + Use: "version", + Short: "Shows version information", + Example: "bladectl version", + Args: cobra.ExactArgs(0), + Run: func(cmd *cobra.Command, args []string) { + fmt.Printf("Version: %s\n", Version) + fmt.Printf("Date: %s\n", Date) + fmt.Printf("Commit: %s\n", Commit) + }, +} diff --git a/cmd/bladectl/main.go b/cmd/bladectl/main.go index 93ee8fd..29e3c4e 100644 --- a/cmd/bladectl/main.go +++ b/cmd/bladectl/main.go @@ -2,17 +2,10 @@ package main import ( "context" - "fmt" "log" - "os" - "os/signal" - "syscall" "time" - "github.com/spf13/cobra" bladeapiv1alpha1 "github.com/uptime-induestries/compute-blade-agent/api/bladeapi/v1alpha1" - "google.golang.org/grpc" - "google.golang.org/grpc/credentials/insecure" ) type grpcClientContextKey int @@ -25,6 +18,10 @@ const ( var ( grpcAddr string timeout time.Duration + + Version string + Commit string + Date string ) func init() { @@ -45,39 +42,6 @@ func clientFromContext(ctx context.Context) bladeapiv1alpha1.BladeAgentServiceCl return client } -var rootCmd = &cobra.Command{ - Use: "bladectl", - Short: "bladectl interacts with the compute-blade-agent and allows you to manage hardware-features of your compute blade(s)", - PersistentPreRunE: func(cmd *cobra.Command, _ []string) error { - origCtx := cmd.Context() - - // setup signal handlers for SIGINT and SIGTERM - ctx, cancelCtx := context.WithTimeout(origCtx, timeout) - - // setup signal handler channels - sigs := make(chan os.Signal, 1) - signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) - go func() { - // Wait for context cancel or signal - select { - case <-ctx.Done(): - case <-sigs: - // On signal, cancel context - cancelCtx() - } - }() - - conn, err := grpc.Dial(grpcAddr, grpc.WithTransportCredentials(insecure.NewCredentials())) - if err != nil { - return fmt.Errorf("failed to dial grpc server: %w", err) - } - client := bladeapiv1alpha1.NewBladeAgentServiceClient(conn) - - cmd.SetContext(clientIntoContext(ctx, client)) - return nil - }, -} - func main() { if err := rootCmd.Execute(); err != nil { log.Fatal(err) diff --git a/go.mod b/go.mod index 7e3c778..9bdf15c 100644 --- a/go.mod +++ b/go.mod @@ -6,9 +6,10 @@ toolchain go1.22.5 require ( github.com/prometheus/client_golang v1.16.0 + github.com/sierrasoftworks/humane-errors-go v0.0.0-20241125132722-d032d7dd359e github.com/spf13/cobra v1.6.1 github.com/spf13/viper v1.16.0 - github.com/stretchr/testify v1.8.3 + github.com/stretchr/testify v1.10.0 github.com/warthog618/gpiod v0.8.1 go.bug.st/serial v1.6.1 go.uber.org/zap v1.24.0 @@ -40,7 +41,7 @@ require ( github.com/spf13/cast v1.5.1 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/spf13/pflag v1.0.5 // indirect - github.com/stretchr/objx v0.5.0 // indirect + github.com/stretchr/objx v0.5.2 // indirect github.com/subosito/gotenv v1.4.2 // indirect go.uber.org/atomic v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect diff --git a/go.sum b/go.sum index 3b9f733..b0f2e21 100644 --- a/go.sum +++ b/go.sum @@ -181,6 +181,8 @@ github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/sierrasoftworks/humane-errors-go v0.0.0-20241125132722-d032d7dd359e h1:TRjhbclA0br24WOCSltVovqcwOLTKRLRzQmuSBY5mQc= +github.com/sierrasoftworks/humane-errors-go v0.0.0-20241125132722-d032d7dd359e/go.mod h1:j7jIbd4vmh7c8kUuM4wNOqqEaMcG+UWVyem6d26pai4= github.com/spf13/afero v1.9.5 h1:stMpOSZFs//0Lv29HduCmli3GUfpFoF3Y1Q/aXj/wVM= github.com/spf13/afero v1.9.5/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ= github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA= @@ -195,16 +197,18 @@ github.com/spf13/viper v1.16.0 h1:rGGH0XDZhdUOryiDWjmIvUSWpbNqisK8Wk0Vyefw8hc= github.com/spf13/viper v1.16.0/go.mod h1:yg78JgCJcbrQOvV9YLXgkLaZqUidkY9K+Dd1FofRzQg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY= github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8= github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= github.com/warthog618/gpiod v0.8.1 h1:+8iHpHd3fljAd6l4AT8jPbMDQNKdvBIpW/hmLgAcHiM=