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

[#64] Refactor flags implementation #65

Merged
merged 6 commits into from
Nov 20, 2023
Merged
Show file tree
Hide file tree
Changes from 5 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
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
90 changes: 73 additions & 17 deletions internal/feature.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,33 @@ 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 = ""
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 +67,19 @@ 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"`
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 +99,44 @@ 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,
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 +159,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