Skip to content

Commit

Permalink
Merge dev-m5 branch into main (eclipse-kanto#72)
Browse files Browse the repository at this point in the history
[eclipse-kanto#73] Merge `dev-m5` branch into `main`
* [eclipse-kanto#64] Refactor flags implementation (eclipse-kanto#65)
* [eclipse-kanto#3] MQTTS support in the software-update's local connection (eclipse-kanto#63)

---------

Signed-off-by: Antonia Avramova <antonia.avramova@bosch.io>
Signed-off-by: Kristiyan Gostev <kristiyan.gostev@bosch.com>
Co-authored-by: Antonia Avramova <antonia.avramova@bosch.io>
  • Loading branch information
k-gostev and antonia-avramova authored May 10, 2024
1 parent 309fa6f commit 7d78cca
Show file tree
Hide file tree
Showing 19 changed files with 690 additions and 407 deletions.
8 changes: 4 additions & 4 deletions cmd/software-update/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,23 +25,23 @@ var version = "N/A"

func main() {
// Initialize flags.
suConfig, logConfig, err := feature.InitFlags(version)
cfg, err := feature.LoadConfig(version)
if err != nil {
fmt.Println(err)
os.Exit(1)
}

// Initialize logs.
loggerOut := logger.SetupLogger(logConfig)
loggerOut := logger.SetupLogger(&cfg.LogConfig)
defer loggerOut.Close()

if err := suConfig.Validate(); err != nil {
if err := cfg.Validate(); err != nil {
logger.Errorf("failed to validate script-based software updatable configuration: %v\n", err)
os.Exit(1)
}

// Create new Script-Based software updatable
edgeCtr, err := feature.InitScriptBasedSU(suConfig)
edgeCtr, err := feature.InitScriptBasedSU(&cfg.ScriptBasedSoftwareUpdatableConfig)
if err != nil {
logger.Errorf("failed to create script-based software updatable: %v", err)
os.Exit(1)
Expand Down
37 changes: 31 additions & 6 deletions internal/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
package feature

import (
"encoding/json"
"fmt"
"os/exec"
"path/filepath"
Expand All @@ -22,32 +23,39 @@ import (
"github.com/eclipse-kanto/software-update/internal/logger"
)

// command is custom type of command name and arguments of command in order to add json unmarshal support
type command struct {
cmd string
args []string
}

// String is representation of command as combination of name and arguments of the command
func (i *command) String() string {
if len(i.args) == 0 {
return i.cmd
}
return fmt.Sprint(i.cmd, " ", strings.Join(i.args, " "))
}

// Set command from string, used for flag set
func (i *command) Set(value string) error {
if i.cmd == "" {
i.cmd = value
i.args = []string{}
if runtime.GOOS != "windows" && strings.HasSuffix(value, ".sh") {
i.cmd = "/bin/sh"
i.args = []string{value}
}
i.setCommand(value)
} else {
i.args = append(i.args, value)
}
return nil
}

func (i *command) setCommand(value string) {
i.cmd = value
i.args = []string{}
if runtime.GOOS != "windows" && strings.HasSuffix(value, ".sh") {
i.cmd = "/bin/sh"
i.args = []string{value}
}
}

func (i *command) run(dir string, def string) (err error) {
script := i.cmd
args := i.args
Expand All @@ -71,3 +79,20 @@ func (i *command) run(dir string, def string) (err error) {
}
return err
}

// UnmarshalJSON unmarshal command type
func (i *command) UnmarshalJSON(b []byte) error {
var v []string
if err := json.Unmarshal(b, &v); err != nil {
return err
}

for num, elem := range v {
if num == 0 {
i.setCommand(elem)
} else {
i.args = append(i.args, elem)
}
}
return nil
}
2 changes: 1 addition & 1 deletion internal/duration.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ func (d *durationTime) UnmarshalJSON(b []byte) error {
if err := json.Unmarshal(b, &v); err != nil {
return err
}
switch value := v.(type) {

switch value := v.(type) {
case string:
duration, err := time.ParseDuration(value)
if err != nil {
Expand Down
22 changes: 22 additions & 0 deletions internal/edge.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,10 @@ package feature

import (
"encoding/json"
"net/url"

"github.com/eclipse-kanto/software-update/internal/logger"
"github.com/eclipse-kanto/software-update/util/tls"

MQTT "github.com/eclipse/paho.mqtt.golang"
"github.com/google/uuid"
Expand Down Expand Up @@ -58,6 +60,17 @@ func newEdgeConnector(scriptSUPConfig *ScriptBasedSoftwareUpdatableConfig, ecl e
if len(scriptSUPConfig.Username) > 0 {
opts = opts.SetUsername(scriptSUPConfig.Username).SetPassword(scriptSUPConfig.Password)
}
u, err := url.Parse(scriptSUPConfig.Broker)
if err != nil {
return nil, err
}
if isConnectionSecure(u.Scheme) {
tlsConfig, err := tls.NewTLSConfig(scriptSUPConfig.CACert, scriptSUPConfig.Cert, scriptSUPConfig.Key)
if err != nil {
return nil, err
}
opts.SetTLSConfig(tlsConfig)
}

p := &EdgeConnector{mqttClient: MQTT.NewClient(opts), edgeClient: ecl}
if token := p.mqttClient.Connect(); token.Wait() && token.Error() != nil {
Expand Down Expand Up @@ -98,6 +111,15 @@ func newEdgeConnector(scriptSUPConfig *ScriptBasedSoftwareUpdatableConfig, ecl e
return p, nil
}

func isConnectionSecure(schema string) bool {
switch schema {
case "wss", "ssl", "tls", "mqtts", "mqtt+ssl", "tcps":
return true
default:
}
return false
}

// Close the EdgeConnector
func (p *EdgeConnector) Close() {
if p.cfg != nil {
Expand Down
99 changes: 82 additions & 17 deletions internal/feature.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,36 @@ import (
)

const (
defaultDisconnectTimeout = 250 * time.Millisecond
defaultKeepAlive = 20 * time.Second

modeStrict = "strict"
modeScoped = "scoped"
modeLax = "lax"

typeArchive = "archive"
typePlain = "plain"

defaultDisconnectTimeout = 250 * time.Millisecond
defaultKeepAlive = 20 * time.Second
defaultBroker = "tcp://localhost:1883"
defaultUsername = ""
defaultPassword = ""
defaultCACert = ""
defaultCert = ""
defaultKey = ""
defaultStorageLocation = "."
defaultFeatureID = "SoftwareUpdatable"
defaultModuleType = "software"
defaultArtifactType = "archive"
defaultServerCert = ""
defaultDownloadRetryCount = 0
defaultDownloadRetryInterval = "5s"
defaultInstallDirs = ""
defaultMode = modeStrict
defaultInstallCommand = ""
defaultLogFile = "log/software-update.log"
defaultLogLevel = "INFO"
defaultLogFileSize = 2
defaultLogFileCount = 5
defaultLogFileMaxAge = 28
)

var (
Expand All @@ -49,19 +70,22 @@ type operationFunc func() bool

// ScriptBasedSoftwareUpdatableConfig provides the Script-Based SoftwareUpdatable configuration.
type ScriptBasedSoftwareUpdatableConfig struct {
Broker string
Username string
Password string
StorageLocation string
FeatureID string
ModuleType string
ArtifactType string
ServerCert string
DownloadRetryCount int
DownloadRetryInterval durationTime
InstallDirs pathArgs
Mode string
InstallCommand command
Broker string `json:"broker,omitempty"`
Username string `json:"username,omitempty"`
Password string `json:"password,omitempty"`
CACert string `json:"caCert,omitempty"`
Cert string `json:"cert,omitempty"`
Key string `json:"key,omitempty"`
StorageLocation string `json:"storageLocation,omitempty"`
FeatureID string `json:"featureId,omitempty"`
ModuleType string `json:"moduleType,omitempty"`
ArtifactType string `json:"artifactType,omitempty"`
ServerCert string `json:"serverCert,omitempty"`
DownloadRetryCount int `json:"downloadRetryCount,omitempty"`
DownloadRetryInterval durationTime `json:"downloadRetryInterval,omitempty"`
InstallDirs []string `json:"installDirs,omitempty"`
Mode string `json:"mode,omitempty"`
InstallCommand command `json:"install,omitempty"`
}

// ScriptBasedSoftwareUpdatable is the Script-Based SoftwareUpdatable actual implementation.
Expand All @@ -81,6 +105,47 @@ type ScriptBasedSoftwareUpdatable struct {
installCommand *command
}

// BasicConfig combine ScriptBaseSoftwareUpdatable configuration and Log configuration
type BasicConfig struct {
ScriptBasedSoftwareUpdatableConfig
logger.LogConfig
ConfigFile string `json:"configFile,omitempty"`
}

// NewDefaultConfig returns a default mqtt client connection config instance
func NewDefaultConfig() *BasicConfig {
duration, err := time.ParseDuration(defaultDownloadRetryInterval)
if err != nil {
duration = 0
}
return &BasicConfig{
ScriptBasedSoftwareUpdatableConfig: ScriptBasedSoftwareUpdatableConfig{
Broker: defaultBroker,
Username: defaultUsername,
Password: defaultPassword,
CACert: defaultCACert,
Cert: defaultCert,
Key: defaultKey,
StorageLocation: defaultStorageLocation,
FeatureID: defaultFeatureID,
ModuleType: defaultModuleType,
ArtifactType: defaultArtifactType,
ServerCert: defaultServerCert,
DownloadRetryCount: defaultDownloadRetryCount,
Mode: defaultMode,
DownloadRetryInterval: durationTime(duration),
InstallDirs: make([]string, 0),
},
LogConfig: logger.LogConfig{
LogFile: defaultLogFile,
LogLevel: defaultLogLevel,
LogFileSize: defaultLogFileSize,
LogFileCount: defaultLogFileCount,
LogFileMaxAge: defaultLogFileMaxAge,
},
}
}

// InitScriptBasedSU creates a new Script-Based SoftwareUpdatable instance, listening for edge configuration.
func InitScriptBasedSU(scriptSUPConfig *ScriptBasedSoftwareUpdatableConfig) (*EdgeConnector, error) {
logger.Infof("New Script-Based SoftwareUpdatable [Broker: %s, Type: %s]",
Expand All @@ -103,7 +168,7 @@ func InitScriptBasedSU(scriptSUPConfig *ScriptBasedSoftwareUpdatableConfig) (*Ed
// Interval between download reattempts
downloadRetryInterval: time.Duration(scriptSUPConfig.DownloadRetryInterval),
// Install locations for local artifacts
installDirs: scriptSUPConfig.InstallDirs.args,
installDirs: scriptSUPConfig.InstallDirs,
// Access mode for local artifacts
accessMode: initAccessMode(scriptSUPConfig.Mode),
// Define the module artifact(s) type: archive or plain
Expand Down
14 changes: 7 additions & 7 deletions internal/feature_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,9 +99,9 @@ func TestScriptBasedInitLoadDependencies(t *testing.T) {

// 1. Try to init a new ScriptBasedSoftwareUpdatable with error for loading install dependencies
_, _, err := mockScriptBasedSoftwareUpdatable(t, &testConfig{
clientConnected: true, storageLocation: dir, featureID: getDefaultFlagValue(t, flagFeatureID)})
clientConnected: true, storageLocation: dir, featureID: NewDefaultConfig().FeatureID})
if err == nil {
t.Fatalf("expected to fail when mandatory field is missing in insalled dept file")
t.Fatalf("expected to fail when mandatory field is missing in installed dept file")
}
}

Expand All @@ -114,7 +114,7 @@ func TestScriptBasedInit(t *testing.T) {

// 1. Try to init a new ScriptBasedSoftwareUpdatable with error for not connected client
_, _, err := mockScriptBasedSoftwareUpdatable(t, &testConfig{
clientConnected: false, storageLocation: dir, featureID: getDefaultFlagValue(t, flagFeatureID)})
clientConnected: false, storageLocation: dir, featureID: NewDefaultConfig().FeatureID})
if err == nil {
t.Fatal("ditto Client shall not be connected!")
}
Expand Down Expand Up @@ -150,7 +150,7 @@ func testScriptBasedSoftwareUpdatableOperations(noResume bool, t *testing.T) {

// 1. Try to init a new ScriptBasedSoftwareUpdatable.
feature, mc, err := mockScriptBasedSoftwareUpdatable(t, &testConfig{
clientConnected: true, featureID: getDefaultFlagValue(t, flagFeatureID), storageLocation: dir})
clientConnected: true, featureID: NewDefaultConfig().FeatureID, storageLocation: dir})
if err != nil {
t.Fatalf("failed to initialize ScriptBasedSoftwareUpdatable: %v", err)
}
Expand Down Expand Up @@ -195,7 +195,7 @@ func testDisconnectWhileRunningOperation(feature *ScriptBasedSoftwareUpdatable,

statuses = append(statuses, pullStatusChanges(mc, postDisconnectEventCount)...)
waitDisconnect.Wait()
defer connectFeature(t, mc, feature, getDefaultFlagValue(t, flagFeatureID))
defer connectFeature(t, mc, feature, NewDefaultConfig().FeatureID)
if install {
checkInstallStatusEvents(0, statuses, t)
} else {
Expand All @@ -212,7 +212,7 @@ func TestScriptBasedDownloadAndInstallMixedResources(t *testing.T) {
defer os.RemoveAll(storageDir)

feature, mc, err := mockScriptBasedSoftwareUpdatable(t, &testConfig{
clientConnected: true, featureID: getDefaultFlagValue(t, flagFeatureID), storageLocation: storageDir, mode: modeLax,
clientConnected: true, featureID: NewDefaultConfig().FeatureID, storageLocation: storageDir, mode: modeLax,
})
if err != nil {
t.Fatalf("failed to initialize ScriptBasedSoftwareUpdatable: %v", err)
Expand Down Expand Up @@ -300,7 +300,7 @@ func testScriptBasedSoftwareUpdatableOperationsLocal(t *testing.T, installDirs [
defer os.RemoveAll(dir)

feature, mc, err := mockScriptBasedSoftwareUpdatable(t, &testConfig{
clientConnected: true, featureID: getDefaultFlagValue(t, flagFeatureID), storageLocation: dir,
clientConnected: true, featureID: NewDefaultConfig().FeatureID, storageLocation: dir,
installDirs: installDirs, mode: mode})
if err != nil {
t.Fatalf("failed to initialize ScriptBasedSoftwareUpdatable: %v", err)
Expand Down
Loading

0 comments on commit 7d78cca

Please sign in to comment.