Skip to content

Commit

Permalink
[APP-7099] [APP-7209] Integrate subsystems and use new configuration …
Browse files Browse the repository at this point in the history
…proto/format (#59)
  • Loading branch information
Otterverse authored Feb 20, 2025
1 parent 90b7f59 commit 562b6e7
Show file tree
Hide file tree
Showing 41 changed files with 2,977 additions and 2,848 deletions.
2 changes: 1 addition & 1 deletion .golangci.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
service:
golangci-lint-version: 1.60.3
golangci-lint-version: 1.64.5
run:
deadline: 900s
modules-download-mode: readonly
Expand Down
6 changes: 3 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ else
PATH_VERSION = v$(TAG_VERSION)
endif

LDFLAGS = "-s -w -X 'github.com/viamrobotics/agent.Version=${TAG_VERSION}' -X 'github.com/viamrobotics/agent.GitRevision=${GIT_REVISION}'"
LDFLAGS = "-s -w -X 'github.com/viamrobotics/agent/utils.Version=${TAG_VERSION}' -X 'github.com/viamrobotics/agent/utils.GitRevision=${GIT_REVISION}'"
TAGS = osusergo,netgo


Expand All @@ -33,7 +33,7 @@ arm64:
amd64:
make GOARCH=amd64

bin/viam-agent-$(PATH_VERSION)-$(LINUX_ARCH): go.* *.go */*.go */*/*.go subsystems/viamagent/*.service Makefile
bin/viam-agent-$(PATH_VERSION)-$(LINUX_ARCH): go.* *.go */*.go */*/*.go *.service Makefile
go build -o $@ -trimpath -tags $(TAGS) -ldflags $(LDFLAGS) ./cmd/viam-agent/main.go
test "$(PATH_VERSION)" != "custom" && cp $@ bin/viam-agent-stable-$(LINUX_ARCH) || true

Expand All @@ -42,7 +42,7 @@ clean:
rm -rf bin/

bin/golangci-lint: Makefile
GOBIN=`pwd`/bin go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.60.3
GOBIN=`pwd`/bin go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.64.5

.PHONY: lint
lint: bin/golangci-lint
Expand Down
73 changes: 16 additions & 57 deletions subsystems/viamagent/viamagent.go → agent.go
Original file line number Diff line number Diff line change
@@ -1,88 +1,47 @@
// Package viamagent is the subsystem for the viam-agent itself. It contains code to install/update the systemd service as well.
package viamagent
// Package agent is the viam-agent itself. It contains code to install/update the systemd service as well.
package agent

import (
"context"
_ "embed"
"errors"
"fmt"
"io/fs"
"os"
"os/exec"
"path/filepath"
"strings"

errw "github.com/pkg/errors"
"github.com/viamrobotics/agent"
"github.com/viamrobotics/agent/subsystems"
"github.com/viamrobotics/agent/subsystems/registry"
pb "go.viam.com/api/app/agent/v1"
"github.com/viamrobotics/agent/utils"
"go.viam.com/rdk/logging"
)

func init() {
registry.Register(subsysName, NewSubsystem)
}

const (
subsysName = "viam-agent"
serviceFileDir = "/usr/local/lib/systemd/system"
fallbackFileDir = "/etc/systemd/system"
serviceFileName = "viam-agent.service"
)

var (
// versions embedded at build time.
Version = ""
GitRevision = ""

//go:embed viam-agent.service
serviceFileContents []byte
)

type agentSubsystem struct{}

func NewSubsystem(ctx context.Context, logger logging.Logger, updateConf *pb.DeviceSubsystemConfig) (subsystems.Subsystem, error) {
return agent.NewAgentSubsystem(ctx, subsysName, logger, &agentSubsystem{})
}

// Start does nothing (we're already running as we ARE the agent.)
func (a *agentSubsystem) Start(ctx context.Context) error {
return nil
}

// Stop does nothing (special logic elsewhere handles self-restart.)
func (a *agentSubsystem) Stop(ctx context.Context) error {
return nil
}

// HealthCheck does nothing (we're obviously runnning as we are the agent.)
func (a *agentSubsystem) HealthCheck(ctx context.Context) error {
return nil
}

// Update here handles the post-update installation of systemd files and the like.
// The actual update check and download is done in the wrapper (agent.AgentSubsystem).
func (a *agentSubsystem) Update(ctx context.Context, cfg *pb.DeviceSubsystemConfig, newVersion bool) (bool, error) {
if !newVersion {
return false, nil
}
//go:embed viam-agent.service
var serviceFileContents []byte

expectedPath := filepath.Join(agent.ViamDirs["bin"], subsysName)
// InstallNewVersion runs the newly downloaded binary's Install() for installation of systemd files and the like.
func InstallNewVersion(ctx context.Context, logger logging.Logger) (bool, error) {
expectedPath := filepath.Join(utils.ViamDirs["bin"], SubsystemName)

// Run the newly updated version to install systemd and other service files.
//nolint:gosec
cmd := exec.Command(expectedPath, "--install")
output, err := cmd.CombinedOutput()
logger.Info("running viam-agent --install for new version")
logger.Info(string(output))
if err != nil {
return false, errw.Wrapf(err, "running post install step %s", output)
}
//nolint:forbidigo
fmt.Print(string(output))

return true, nil
}

// Install is directly executed from main() when --install is passed.
func Install(logger logging.Logger) error {
// Check for systemd
cmd := exec.Command("systemctl", "--version")
Expand All @@ -92,18 +51,18 @@ func Install(logger logging.Logger) error {
}

// Create/check required folder structure exists.
if err := agent.InitPaths(); err != nil {
if err := utils.InitPaths(); err != nil {
return err
}

// If this is a brand new install, we want to symlink ourselves into place temporarily.
expectedPath := filepath.Join(agent.ViamDirs["bin"], subsysName)
expectedPath := filepath.Join(utils.ViamDirs["bin"], SubsystemName)
curPath, err := os.Executable()
if err != nil {
return errw.Wrap(err, "getting path to self")
}

isSelf, err := agent.CheckIfSame(curPath, expectedPath)
isSelf, err := utils.CheckIfSame(curPath, expectedPath)
if err != nil {
return errw.Wrap(err, "checking if installed viam-agent is myself")
}
Expand All @@ -129,7 +88,7 @@ func Install(logger logging.Logger) error {

logger.Infof("writing systemd service file to %s", serviceFilePath)

newFile, err := agent.WriteFileIfNew(serviceFilePath, serviceFileContents)
newFile, err := utils.WriteFileIfNew(serviceFilePath, serviceFileContents)
if err != nil {
return errw.Wrapf(err, "writing systemd service file %s", serviceFilePath)
}
Expand Down Expand Up @@ -171,7 +130,7 @@ func Install(logger logging.Logger) error {

logger.Info("Install complete. Please (re)start the service with 'systemctl restart viam-agent' when ready.")

return errors.Join(agent.SyncFS("/etc"), agent.SyncFS(serviceFilePath), agent.SyncFS(agent.ViamDirs["viam"]))
return errors.Join(utils.SyncFS("/etc"), utils.SyncFS(serviceFilePath), utils.SyncFS(utils.ViamDirs["viam"]))
}

func inSystemdPath(path string, logger logging.Logger) bool {
Expand Down
4 changes: 2 additions & 2 deletions cmd/provisioning-client/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (
"strings"

"github.com/jessevdk/go-flags"
"github.com/viamrobotics/agent/subsystems/provisioning"
"github.com/viamrobotics/agent/subsystems/networking"
pb "go.viam.com/api/provisioning/v1"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
Expand Down Expand Up @@ -134,7 +134,7 @@ func SetDeviceCreds(ctx context.Context, client pb.ProvisioningServiceClient, id

func SetWifiCreds(ctx context.Context, client pb.ProvisioningServiceClient, ssid, psk string) {
req := &pb.SetNetworkCredentialsRequest{
Type: provisioning.NetworkTypeWifi,
Type: networking.NetworkTypeWifi,
Ssid: ssid,
Psk: psk,
}
Expand Down
148 changes: 148 additions & 0 deletions cmd/test-client/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
// A simple test client to fetch and print the results of DeviceAgentConfig() for use when testing changes in App.
package main

import (
"bytes"
"context"
"encoding/json"
"fmt"
"net/url"
"os"

"github.com/jessevdk/go-flags"
pb "go.viam.com/api/app/agent/v1"
"go.viam.com/rdk/logging"
"go.viam.com/utils/rpc"
"google.golang.org/protobuf/encoding/prototext"
)

func main() {
ctx := context.TODO()
logger := logging.NewLogger("agent-test-client")

var opts struct {
Config string `description:"Path to credentials (viam.json)" long:"config" required:"true" short:"c"`
Platform string `default:"linux/arm64" description:"Platform to send in request" long:"platform" short:"p"`
Help bool `description:"Show this help message" long:"help" short:"h"`
}
parser := flags.NewParser(&opts, flags.IgnoreUnknown)
parser.Usage = "Makes a DeviceAgentConfigRequest() and prints the return"
_, err := parser.Parse()
if err != nil {
logger.Error(err)
opts.Help = true
}

if opts.Help {
var b bytes.Buffer
parser.WriteHelp(&b)

//nolint:forbidigo
fmt.Println(b.String())
return
}

cloudConfig, err := loadCredentials(opts.Config)
if err != nil {
logger.Fatal(err)
}

client, err := dial(ctx, logger, cloudConfig)
if err != nil {
logger.Fatal(err)
}

err = fetchAgentConfig(ctx, client, cloudConfig, opts.Platform)
if err != nil {
logger.Fatal(err)
}
}

func fetchAgentConfig(ctx context.Context, client pb.AgentDeviceServiceClient, cloudConfig *logging.CloudConfig, platform string) error {
req := &pb.DeviceAgentConfigRequest{
Id: cloudConfig.ID,
HostInfo: &pb.HostInfo{
Platform: platform,
},
VersionInfo: &pb.VersionInfo{
AgentRunning: "testClient",
},
}

resp, err := client.DeviceAgentConfig(ctx, req)
if err != nil {
return err
}

text, err := prototext.MarshalOptions{Multiline: true, Indent: " "}.Marshal(resp)
if err != nil {
return err
}

//nolint:forbidigo
fmt.Printf("DeviceAgentConfig() Response:\n%s\n", text)
return nil
}

func loadCredentials(path string) (*logging.CloudConfig, error) {
//nolint:gosec
b, err := os.ReadFile(path)
if err != nil {
return nil, err
}

cfg := make(map[string]map[string]string)
err = json.Unmarshal(b, &cfg)
if err != nil {
return nil, err
}

cloud, ok := cfg["cloud"]
if !ok {
return nil, fmt.Errorf("no cloud section in file %s", path)
}

for _, req := range []string{"app_address", "id", "secret"} {
field, ok := cloud[req]
if !ok {
return nil, fmt.Errorf("no cloud config field for %s", field)
}
}

cloudConfig := &logging.CloudConfig{
AppAddress: cloud["app_address"],
ID: cloud["id"],
Secret: cloud["secret"],
}

return cloudConfig, nil
}

func dial(ctx context.Context, logger logging.Logger, cloudConfig *logging.CloudConfig) (pb.AgentDeviceServiceClient, error) {
u, err := url.Parse(cloudConfig.AppAddress)
if err != nil {
logger.Fatal(err)
}

dialOpts := make([]rpc.DialOption, 0, 2)
// Only add credentials when secret is set.
if cloudConfig.Secret != "" {
dialOpts = append(dialOpts, rpc.WithEntityCredentials(cloudConfig.ID,
rpc.Credentials{
Type: "robot-secret",
Payload: cloudConfig.Secret,
},
))
}

if u.Scheme == "http" {
dialOpts = append(dialOpts, rpc.WithInsecure())
}

conn, err := rpc.DialDirectGRPC(ctx, u.Host, logger.AsZap(), dialOpts...)
if err != nil {
return nil, err
}

return pb.NewAgentDeviceServiceClient(conn), nil
}
Loading

0 comments on commit 562b6e7

Please sign in to comment.