mirror of
https://github.com/compute-blade-community/compute-blade-agent.git
synced 2026-04-16 15:35:42 +02:00
chore: some improvements in bladectl sub-command handling, error logging, and CI (#51)
chore(ci): update Go setup action to v5 and simplify caching configuration for improved performance chore(release): update Go setup action to v5 and simplify caching configuration for improved performance fix(.gitignore): add .idea directory to ignore list to prevent IDE files from being tracked feat(goreleaser): add versioning information to builds for better traceability feat(agent): expose version, commit, and date information in logs for better tracking feat(bladectl): implement command structure for managing compute-blade features fix(bladectl): improve error handling in identify command for better user feedback chore(go.mod): update dependencies to latest versions for improved stability and features
This commit is contained in:
22
.github/workflows/ci.yaml
vendored
22
.github/workflows/ci.yaml
vendored
@@ -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
|
||||
|
||||
24
.github/workflows/release.yaml
vendored
24
.github/workflows/release.yaml
vendored
@@ -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:
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -5,3 +5,4 @@ dist/
|
||||
*.out
|
||||
cover.cov
|
||||
fanunit.uf2
|
||||
.idea
|
||||
@@ -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:
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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 <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),
|
||||
})
|
||||
|
||||
@@ -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'")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
46
cmd/bladectl/cmd_root.go
Normal file
46
cmd/bladectl/cmd_root.go
Normal file
@@ -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
|
||||
},
|
||||
}
|
||||
24
cmd/bladectl/cmd_verbs.go
Normal file
24
cmd/bladectl/cmd_verbs.go
Normal file
@@ -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.",
|
||||
}
|
||||
)
|
||||
22
cmd/bladectl/cmd_version.go
Normal file
22
cmd/bladectl/cmd_version.go
Normal file
@@ -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)
|
||||
},
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
5
go.mod
5
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
|
||||
|
||||
8
go.sum
8
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=
|
||||
|
||||
Reference in New Issue
Block a user