Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[#58] Request device owner's approval #59

Merged
merged 6 commits into from
May 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions api/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,29 @@ type DesiredStateClient interface {
SendDesiredStateCommand(string, *types.DesiredStateCommand) error
SendCurrentStateGet(string) error
}

// OwnerConsentAgentHandler defines functions for handling the owner consent requests
type OwnerConsentAgentHandler interface {
HandleOwnerConsent(string, int64, *types.OwnerConsent) error
}

// OwnerConsentAgentClient defines an interface for handling for owner consent requests
type OwnerConsentAgentClient interface {
BaseClient

Start(OwnerConsentAgentHandler) error
SendOwnerConsentFeedback(string, *types.OwnerConsentFeedback) error
}

// OwnerConsentHandler defines functions for handling the owner consent feedback
type OwnerConsentHandler interface {
HandleOwnerConsentFeedback(string, int64, *types.OwnerConsentFeedback) error
}

// OwnerConsentClient defines an interface for triggering requests for owner consent
type OwnerConsentClient interface {
BaseClient

Start(OwnerConsentHandler) error
SendOwnerConsent(string, *types.OwnerConsent) error
}
34 changes: 34 additions & 0 deletions api/types/owner_consent.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Copyright (c) 2024 Contributors to the Eclipse Foundation
//
// See the NOTICE file(s) distributed with this work for additional
// information regarding copyright ownership.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0

package types

// ConsentStatusType defines values for status within the owner consent
type ConsentStatusType string

const (
// StatusApproved denotes that the owner approved the update operation.
StatusApproved ConsentStatusType = "APPROVED"
// StatusDenied denotes that the owner denied the update operation.
StatusDenied ConsentStatusType = "DENIED"
)

// OwnerConsentFeedback defines the payload for Owner Consent Feedback.
type OwnerConsentFeedback struct {
Status ConsentStatusType `json:"status,omitempty"`
// time field for scheduling could be added here
}

// OwnerConsent defines the payload for Owner Consent.
type OwnerConsent struct {
Command CommandType `json:"command,omitempty"`
}
1 change: 1 addition & 0 deletions api/update_orchestrator.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,5 @@ type UpdateOrchestrator interface {
Apply(context.Context, map[string]UpdateManager, string, *types.DesiredState, DesiredStateFeedbackHandler) bool

DesiredStateFeedbackHandler
OwnerConsentHandler
}
40 changes: 30 additions & 10 deletions cmd/update-manager/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,17 +41,9 @@ func main() {
}
defer loggerOut.Close()

var client api.UpdateAgentClient
if cfg.ThingsEnabled {
client, err = mqtt.NewUpdateAgentThingsClient(cfg.Domain, cfg.MQTT)
} else {
client, err = mqtt.NewUpdateAgentClient(cfg.Domain, cfg.MQTT)
}
uac, um, err := initUpdateManager(cfg)
if err == nil {
updateManager, err := orchestration.NewUpdateManager(version, cfg, client, orchestration.NewUpdateOrchestrator(cfg))
if err == nil {
err = app.Launch(cfg, client, updateManager)
}
err = app.Launch(cfg, uac, um)
}

if err != nil {
Expand All @@ -60,3 +52,31 @@ func main() {
os.Exit(1)
}
}

func initUpdateManager(cfg *config.Config) (api.UpdateAgentClient, api.UpdateManager, error) {
var (
uac api.UpdateAgentClient
occ api.OwnerConsentClient
um api.UpdateManager
err error
)

if cfg.ThingsEnabled {
uac, err = mqtt.NewUpdateAgentThingsClient(cfg.Domain, cfg.MQTT)
} else {
uac, err = mqtt.NewUpdateAgentClient(cfg.Domain, cfg.MQTT)
}
if err != nil {
return nil, nil, err
}

if len(cfg.OwnerConsentCommands) != 0 {
if occ, err = mqtt.NewOwnerConsentClient(cfg.Domain, uac); err != nil {
return nil, nil, err
}
}
if um, err = orchestration.NewUpdateManager(version, cfg, uac, orchestration.NewUpdateOrchestrator(cfg, occ)); err != nil {
return nil, nil, err
}
return uac, um, nil
}
9 changes: 8 additions & 1 deletion config/config_internal.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@

package config

import "github.com/eclipse-kanto/update-manager/api"
import (
"github.com/eclipse-kanto/update-manager/api"
"github.com/eclipse-kanto/update-manager/api/types"
)

const (
// default log config
Expand All @@ -29,6 +32,7 @@ const (
currentStateDelayDefault = "30s"
phaseTimeoutDefault = "10m"
readTimeoutDefault = "1m"
ownerConsentTimeoutDefault = "30m"

domainContainers = "containers"
)
Expand All @@ -42,6 +46,8 @@ type Config struct {
ReportFeedbackInterval string `json:"reportFeedbackInterval"`
CurrentStateDelay string `json:"currentStateDelay"`
PhaseTimeout string `json:"phaseTimeout"`
OwnerConsentCommands []types.CommandType `json:"ownerConsentCommands"`
OwnerConsentTimeout string `json:"ownerConsentTimeout"`
}

func newDefaultConfig() *Config {
Expand All @@ -53,6 +59,7 @@ func newDefaultConfig() *Config {
ReportFeedbackInterval: reportFeedbackIntervalDefault,
CurrentStateDelay: currentStateDelayDefault,
PhaseTimeout: phaseTimeoutDefault,
OwnerConsentTimeout: ownerConsentTimeoutDefault,
}
}

Expand Down
4 changes: 4 additions & 0 deletions config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
"testing"

"github.com/eclipse-kanto/update-manager/api"
"github.com/eclipse-kanto/update-manager/api/types"

"github.com/eclipse-kanto/update-manager/logger"
"github.com/eclipse-kanto/update-manager/mqtt"
Expand Down Expand Up @@ -63,6 +64,7 @@ func TestNewDefaultConfig(t *testing.T) {
ReportFeedbackInterval: "1m",
CurrentStateDelay: "30s",
PhaseTimeout: "10m",
OwnerConsentTimeout: "30m",
}

cfg := newDefaultConfig()
Expand Down Expand Up @@ -136,6 +138,8 @@ func TestLoadConfigFromFile(t *testing.T) {
ReportFeedbackInterval: "2m",
CurrentStateDelay: "1m",
PhaseTimeout: "2m",
OwnerConsentTimeout: "4m",
OwnerConsentCommands: []types.CommandType{types.CommandDownload},
}
assert.True(t, reflect.DeepEqual(*cfg, expectedConfigValues))
})
Expand Down
28 changes: 24 additions & 4 deletions config/flags_internal.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,27 +19,31 @@ import (
"os"
"strings"

"github.com/eclipse-kanto/update-manager/api/types"
"github.com/eclipse-kanto/update-manager/logger"
)

const (
// domains flag
domainsFlagID = "domains"
domainsFlagID = "domains"
domainsDesc = "Specify a comma-separated list of domains handled by the update manager"
ownerConsentCommandsFlagID = "owner-consent-commands"
ownerConsentCommandsDesc = "Specify a comma-separated list of commands, before which an owner consent should be granted. Possible values are: 'download', 'update', 'activate'"
)

// SetupAllUpdateManagerFlags adds all flags for the configuration of the update manager
func SetupAllUpdateManagerFlags(flagSet *flag.FlagSet, cfg *Config) {
SetupFlags(flagSet, cfg.BaseConfig)

flagSet.String(domainsFlagID, "", "Specify a comma-separated list of domains handled by the update manager")
flagSet.String(domainsFlagID, "", domainsDesc)

flagSet.BoolVar(&cfg.RebootEnabled, "reboot-enabled", EnvToBool("REBOOT_ENABLED", cfg.RebootEnabled), "Specify a flag that controls the enabling/disabling of the reboot process after successful update operation")
flagSet.StringVar(&cfg.RebootAfter, "reboot-after", EnvToString("REBOOT_AFTER", cfg.RebootAfter), "Specify the timeout in cron format to wait before a reboot process is initiated after successful update operation. Value should be a positive integer number followed by a unit suffix, such as '60s', '10m', etc")

flagSet.StringVar(&cfg.PhaseTimeout, "phase-timeout", EnvToString("PHASE_TIMEOUT", cfg.PhaseTimeout), "Specify the timeout for completing an Update Orchestration phase. Value should be a positive integer number followed by a unit suffix, such as '60s', '10m', etc")
flagSet.StringVar(&cfg.ReportFeedbackInterval, "report-feedback-interval", EnvToString("REPORT_FEEDBACK_INTERVAL", cfg.ReportFeedbackInterval), "Specify the time interval for reporting intermediate desired state feedback messages during an active update operation. Value should be a positive integer number followed by a unit suffix, such as '60s', '10m', etc")
flagSet.StringVar(&cfg.CurrentStateDelay, "current-state-delay", EnvToString("CURRENT_STATE_DELAY", cfg.CurrentStateDelay), "Specify the time delay for reporting current state messages. Value should be a positive integer number followed by a unit suffix, such as '60s', '10m', etc")

flagSet.StringVar(&cfg.OwnerConsentTimeout, "owner-consent-timeout", EnvToString("OWNER_CONSENT_TIMEOUT", cfg.OwnerConsentTimeout), "Specify the timeout to wait for owner consent. Value should be a positive integer number followed by a unit suffix, such as '60s', '10m', etc")
setupAgentsConfigFlags(flagSet, cfg)
}

Expand All @@ -53,6 +57,7 @@ func parseFlags(cfg *Config, version string) {
SetupAllUpdateManagerFlags(flagSet, cfg)

fVersion := flagSet.Bool("version", false, "Prints current version and exits")
listCommands := flagSet.String(ownerConsentCommandsFlagID, "", ownerConsentCommandsDesc)
if err := flagSet.Parse(os.Args[1:]); err != nil {
logger.ErrorErr(err, "Cannot parse command flags")
}
Expand All @@ -61,13 +66,28 @@ func parseFlags(cfg *Config, version string) {
fmt.Println(version)
os.Exit(0)
}

if len(*listCommands) != 0 {
cfg.OwnerConsentCommands = parseOwnerConsentCommandsFlag(*listCommands)
}
}

func parseOwnerConsentCommandsFlag(listCommands string) []types.CommandType {
var result []types.CommandType
for _, command := range strings.Split(listCommands, ",") {
c := strings.TrimSpace(command)
if len(c) > 0 {
result = append(result, types.CommandType(strings.ToUpper(c)))
}
}
return result
}

func parseDomainsFlag() map[string]bool {
var listDomains string
flagSet := flag.NewFlagSet("", flag.ContinueOnError)
flagSet.SetOutput(io.Discard)
flagSet.StringVar(&listDomains, domainsFlagID, EnvToString("DOMAINS", ""), "Specify a comma-separated list of domains handled by the update manager")
flagSet.StringVar(&listDomains, domainsFlagID, EnvToString("DOMAINS", ""), domainsDesc)
if err := flagSet.Parse(getFlagArgs(domainsFlagID)); err != nil {
logger.ErrorErr(err, "Cannot parse domain flag")
}
Expand Down
2 changes: 2 additions & 0 deletions config/testdata/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
"reportFeedbackInterval": "2m",
"currentStateDelay": "1m",
"phaseTimeout": "2m",
"ownerConsentCommands": ["DOWNLOAD"],
"ownerConsentTimeout": "4m",
"agents": {
"self-update": {
"rebootRequired": false,
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module github.com/eclipse-kanto/update-manager

go 1.17
k-gostev marked this conversation as resolved.
Show resolved Hide resolved
go 1.18

require (
github.com/eclipse/ditto-clients-golang v0.0.0-20230504175246-3e6e17510ac4
Expand Down
11 changes: 3 additions & 8 deletions mqtt/desired_state_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,9 @@ type desiredStateClient struct {

// NewDesiredStateClient instantiates a new client for triggering MQTT requests.
func NewDesiredStateClient(domain string, updateAgent api.UpdateAgentClient) (api.DesiredStateClient, error) {
var mqttClient *mqttClient
switch v := updateAgent.(type) {
case *updateAgentClient:
mqttClient = updateAgent.(*updateAgentClient).mqttClient
case *updateAgentThingsClient:
mqttClient = updateAgent.(*updateAgentThingsClient).mqttClient
default:
return nil, fmt.Errorf("Unexpected type: %T", v)
mqttClient, err := getMQTTClient(updateAgent)
if err != nil {
return nil, err
}
return &desiredStateClient{
mqttClient: newInternalClient(domain, mqttClient.mqttConfig, mqttClient.pahoClient),
Expand Down
4 changes: 2 additions & 2 deletions mqtt/desired_state_client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,14 +66,14 @@ func TestNewDesiredStateClient(t *testing.T) {
},
"test_error": {
client: mockClient,
err: fmt.Sprintf("Unexpected type: %T", mockClient),
err: fmt.Sprintf("unexpected type: %T", mockClient),
},
}
for name, test := range tests {
t.Run(name, func(t *testing.T) {
client, err := NewDesiredStateClient("testDomain", test.client)
if test.err != "" {
assert.EqualError(t, err, fmt.Sprintf("Unexpected type: %T", test.client))
assert.EqualError(t, err, fmt.Sprintf("unexpected type: %T", test.client))
} else {
assert.NoError(t, err)
assert.NotNil(t, client)
Expand Down
Loading
Loading