Skip to content

Commit 7d78cca

Browse files
Merge dev-m5 branch into main (#72)
[#73] Merge `dev-m5` branch into `main` * [#64] Refactor flags implementation (#65) * [#3] MQTTS support in the software-update's local connection (#63) --------- Signed-off-by: Antonia Avramova <[email protected]> Signed-off-by: Kristiyan Gostev <[email protected]> Co-authored-by: Antonia Avramova <[email protected]>
1 parent 309fa6f commit 7d78cca

File tree

19 files changed

+690
-407
lines changed

19 files changed

+690
-407
lines changed

cmd/software-update/main.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,23 +25,23 @@ var version = "N/A"
2525

2626
func main() {
2727
// Initialize flags.
28-
suConfig, logConfig, err := feature.InitFlags(version)
28+
cfg, err := feature.LoadConfig(version)
2929
if err != nil {
3030
fmt.Println(err)
3131
os.Exit(1)
3232
}
3333

3434
// Initialize logs.
35-
loggerOut := logger.SetupLogger(logConfig)
35+
loggerOut := logger.SetupLogger(&cfg.LogConfig)
3636
defer loggerOut.Close()
3737

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

4343
// Create new Script-Based software updatable
44-
edgeCtr, err := feature.InitScriptBasedSU(suConfig)
44+
edgeCtr, err := feature.InitScriptBasedSU(&cfg.ScriptBasedSoftwareUpdatableConfig)
4545
if err != nil {
4646
logger.Errorf("failed to create script-based software updatable: %v", err)
4747
os.Exit(1)

internal/command.go

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
package feature
1414

1515
import (
16+
"encoding/json"
1617
"fmt"
1718
"os/exec"
1819
"path/filepath"
@@ -22,32 +23,39 @@ import (
2223
"github.com/eclipse-kanto/software-update/internal/logger"
2324
)
2425

26+
// command is custom type of command name and arguments of command in order to add json unmarshal support
2527
type command struct {
2628
cmd string
2729
args []string
2830
}
2931

32+
// String is representation of command as combination of name and arguments of the command
3033
func (i *command) String() string {
3134
if len(i.args) == 0 {
3235
return i.cmd
3336
}
3437
return fmt.Sprint(i.cmd, " ", strings.Join(i.args, " "))
3538
}
3639

40+
// Set command from string, used for flag set
3741
func (i *command) Set(value string) error {
3842
if i.cmd == "" {
39-
i.cmd = value
40-
i.args = []string{}
41-
if runtime.GOOS != "windows" && strings.HasSuffix(value, ".sh") {
42-
i.cmd = "/bin/sh"
43-
i.args = []string{value}
44-
}
43+
i.setCommand(value)
4544
} else {
4645
i.args = append(i.args, value)
4746
}
4847
return nil
4948
}
5049

50+
func (i *command) setCommand(value string) {
51+
i.cmd = value
52+
i.args = []string{}
53+
if runtime.GOOS != "windows" && strings.HasSuffix(value, ".sh") {
54+
i.cmd = "/bin/sh"
55+
i.args = []string{value}
56+
}
57+
}
58+
5159
func (i *command) run(dir string, def string) (err error) {
5260
script := i.cmd
5361
args := i.args
@@ -71,3 +79,20 @@ func (i *command) run(dir string, def string) (err error) {
7179
}
7280
return err
7381
}
82+
83+
// UnmarshalJSON unmarshal command type
84+
func (i *command) UnmarshalJSON(b []byte) error {
85+
var v []string
86+
if err := json.Unmarshal(b, &v); err != nil {
87+
return err
88+
}
89+
90+
for num, elem := range v {
91+
if num == 0 {
92+
i.setCommand(elem)
93+
} else {
94+
i.args = append(i.args, elem)
95+
}
96+
}
97+
return nil
98+
}

internal/duration.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@ func (d *durationTime) UnmarshalJSON(b []byte) error {
2727
if err := json.Unmarshal(b, &v); err != nil {
2828
return err
2929
}
30-
switch value := v.(type) {
3130

31+
switch value := v.(type) {
3232
case string:
3333
duration, err := time.ParseDuration(value)
3434
if err != nil {

internal/edge.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,10 @@ package feature
1414

1515
import (
1616
"encoding/json"
17+
"net/url"
1718

1819
"github.com/eclipse-kanto/software-update/internal/logger"
20+
"github.com/eclipse-kanto/software-update/util/tls"
1921

2022
MQTT "github.com/eclipse/paho.mqtt.golang"
2123
"github.com/google/uuid"
@@ -58,6 +60,17 @@ func newEdgeConnector(scriptSUPConfig *ScriptBasedSoftwareUpdatableConfig, ecl e
5860
if len(scriptSUPConfig.Username) > 0 {
5961
opts = opts.SetUsername(scriptSUPConfig.Username).SetPassword(scriptSUPConfig.Password)
6062
}
63+
u, err := url.Parse(scriptSUPConfig.Broker)
64+
if err != nil {
65+
return nil, err
66+
}
67+
if isConnectionSecure(u.Scheme) {
68+
tlsConfig, err := tls.NewTLSConfig(scriptSUPConfig.CACert, scriptSUPConfig.Cert, scriptSUPConfig.Key)
69+
if err != nil {
70+
return nil, err
71+
}
72+
opts.SetTLSConfig(tlsConfig)
73+
}
6174

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

114+
func isConnectionSecure(schema string) bool {
115+
switch schema {
116+
case "wss", "ssl", "tls", "mqtts", "mqtt+ssl", "tcps":
117+
return true
118+
default:
119+
}
120+
return false
121+
}
122+
101123
// Close the EdgeConnector
102124
func (p *EdgeConnector) Close() {
103125
if p.cfg != nil {

internal/feature.go

Lines changed: 82 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -27,15 +27,36 @@ import (
2727
)
2828

2929
const (
30-
defaultDisconnectTimeout = 250 * time.Millisecond
31-
defaultKeepAlive = 20 * time.Second
32-
3330
modeStrict = "strict"
3431
modeScoped = "scoped"
3532
modeLax = "lax"
3633

3734
typeArchive = "archive"
3835
typePlain = "plain"
36+
37+
defaultDisconnectTimeout = 250 * time.Millisecond
38+
defaultKeepAlive = 20 * time.Second
39+
defaultBroker = "tcp://localhost:1883"
40+
defaultUsername = ""
41+
defaultPassword = ""
42+
defaultCACert = ""
43+
defaultCert = ""
44+
defaultKey = ""
45+
defaultStorageLocation = "."
46+
defaultFeatureID = "SoftwareUpdatable"
47+
defaultModuleType = "software"
48+
defaultArtifactType = "archive"
49+
defaultServerCert = ""
50+
defaultDownloadRetryCount = 0
51+
defaultDownloadRetryInterval = "5s"
52+
defaultInstallDirs = ""
53+
defaultMode = modeStrict
54+
defaultInstallCommand = ""
55+
defaultLogFile = "log/software-update.log"
56+
defaultLogLevel = "INFO"
57+
defaultLogFileSize = 2
58+
defaultLogFileCount = 5
59+
defaultLogFileMaxAge = 28
3960
)
4061

4162
var (
@@ -49,19 +70,22 @@ type operationFunc func() bool
4970

5071
// ScriptBasedSoftwareUpdatableConfig provides the Script-Based SoftwareUpdatable configuration.
5172
type ScriptBasedSoftwareUpdatableConfig struct {
52-
Broker string
53-
Username string
54-
Password string
55-
StorageLocation string
56-
FeatureID string
57-
ModuleType string
58-
ArtifactType string
59-
ServerCert string
60-
DownloadRetryCount int
61-
DownloadRetryInterval durationTime
62-
InstallDirs pathArgs
63-
Mode string
64-
InstallCommand command
73+
Broker string `json:"broker,omitempty"`
74+
Username string `json:"username,omitempty"`
75+
Password string `json:"password,omitempty"`
76+
CACert string `json:"caCert,omitempty"`
77+
Cert string `json:"cert,omitempty"`
78+
Key string `json:"key,omitempty"`
79+
StorageLocation string `json:"storageLocation,omitempty"`
80+
FeatureID string `json:"featureId,omitempty"`
81+
ModuleType string `json:"moduleType,omitempty"`
82+
ArtifactType string `json:"artifactType,omitempty"`
83+
ServerCert string `json:"serverCert,omitempty"`
84+
DownloadRetryCount int `json:"downloadRetryCount,omitempty"`
85+
DownloadRetryInterval durationTime `json:"downloadRetryInterval,omitempty"`
86+
InstallDirs []string `json:"installDirs,omitempty"`
87+
Mode string `json:"mode,omitempty"`
88+
InstallCommand command `json:"install,omitempty"`
6589
}
6690

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

108+
// BasicConfig combine ScriptBaseSoftwareUpdatable configuration and Log configuration
109+
type BasicConfig struct {
110+
ScriptBasedSoftwareUpdatableConfig
111+
logger.LogConfig
112+
ConfigFile string `json:"configFile,omitempty"`
113+
}
114+
115+
// NewDefaultConfig returns a default mqtt client connection config instance
116+
func NewDefaultConfig() *BasicConfig {
117+
duration, err := time.ParseDuration(defaultDownloadRetryInterval)
118+
if err != nil {
119+
duration = 0
120+
}
121+
return &BasicConfig{
122+
ScriptBasedSoftwareUpdatableConfig: ScriptBasedSoftwareUpdatableConfig{
123+
Broker: defaultBroker,
124+
Username: defaultUsername,
125+
Password: defaultPassword,
126+
CACert: defaultCACert,
127+
Cert: defaultCert,
128+
Key: defaultKey,
129+
StorageLocation: defaultStorageLocation,
130+
FeatureID: defaultFeatureID,
131+
ModuleType: defaultModuleType,
132+
ArtifactType: defaultArtifactType,
133+
ServerCert: defaultServerCert,
134+
DownloadRetryCount: defaultDownloadRetryCount,
135+
Mode: defaultMode,
136+
DownloadRetryInterval: durationTime(duration),
137+
InstallDirs: make([]string, 0),
138+
},
139+
LogConfig: logger.LogConfig{
140+
LogFile: defaultLogFile,
141+
LogLevel: defaultLogLevel,
142+
LogFileSize: defaultLogFileSize,
143+
LogFileCount: defaultLogFileCount,
144+
LogFileMaxAge: defaultLogFileMaxAge,
145+
},
146+
}
147+
}
148+
84149
// InitScriptBasedSU creates a new Script-Based SoftwareUpdatable instance, listening for edge configuration.
85150
func InitScriptBasedSU(scriptSUPConfig *ScriptBasedSoftwareUpdatableConfig) (*EdgeConnector, error) {
86151
logger.Infof("New Script-Based SoftwareUpdatable [Broker: %s, Type: %s]",
@@ -103,7 +168,7 @@ func InitScriptBasedSU(scriptSUPConfig *ScriptBasedSoftwareUpdatableConfig) (*Ed
103168
// Interval between download reattempts
104169
downloadRetryInterval: time.Duration(scriptSUPConfig.DownloadRetryInterval),
105170
// Install locations for local artifacts
106-
installDirs: scriptSUPConfig.InstallDirs.args,
171+
installDirs: scriptSUPConfig.InstallDirs,
107172
// Access mode for local artifacts
108173
accessMode: initAccessMode(scriptSUPConfig.Mode),
109174
// Define the module artifact(s) type: archive or plain

internal/feature_test.go

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -99,9 +99,9 @@ func TestScriptBasedInitLoadDependencies(t *testing.T) {
9999

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

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

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

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

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

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

302302
feature, mc, err := mockScriptBasedSoftwareUpdatable(t, &testConfig{
303-
clientConnected: true, featureID: getDefaultFlagValue(t, flagFeatureID), storageLocation: dir,
303+
clientConnected: true, featureID: NewDefaultConfig().FeatureID, storageLocation: dir,
304304
installDirs: installDirs, mode: mode})
305305
if err != nil {
306306
t.Fatalf("failed to initialize ScriptBasedSoftwareUpdatable: %v", err)

0 commit comments

Comments
 (0)