Skip to content

Commit f697011

Browse files
authored
Merge pull request #103 from thin-edge/feat-auto-detect-self-update
feat(container): provide tool to check for self updates and filter from module list
2 parents 06969c9 + 2473893 commit f697011

File tree

3 files changed

+159
-43
lines changed

3 files changed

+159
-43
lines changed

.goreleaser.yaml

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -107,12 +107,6 @@ nfpms:
107107
dst: /etc/tedge/sm-plugins/container-group
108108
type: symlink
109109

110-
# apk only (used for self updates)
111-
- src: /usr/bin/tedge-container
112-
dst: /etc/tedge/sm-plugins/self
113-
type: symlink
114-
packager: apk
115-
116110
# Config
117111
- src: ./packaging/config.toml
118112
dst: /etc/tedge/plugins/tedge-container-plugin.toml

cli/self/check.go

Lines changed: 115 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,15 @@ Copyright © 2024 thin-edge.io <[email protected]>
44
package self
55

66
import (
7+
"context"
78
"encoding/json"
89
"fmt"
910
"log/slog"
11+
"strings"
1012

1113
"github.com/spf13/cobra"
1214
"github.com/thin-edge/tedge-container-plugin/pkg/cli"
15+
"github.com/thin-edge/tedge-container-plugin/pkg/container"
1316
)
1417

1518
type SoftwareModule struct {
@@ -18,14 +21,16 @@ type SoftwareModule struct {
1821
}
1922

2023
type SoftwareItem struct {
21-
Action string `json:"action,omitempty"`
2224
Name string `json:"name,omitempty"`
2325
Version string `json:"version,omitempty"`
26+
Url string `json:"url,omitempty"`
27+
Action string `json:"action,omitempty"`
2428
}
2529

2630
type UpdateInfo struct {
27-
ContainerName string `json:"containerName"`
28-
Image string `json:"image"`
31+
ContainerName string `json:"containerName"`
32+
Image string `json:"image"`
33+
UpdateList []SoftwareModule `json:"updateList"`
2934
}
3035

3136
var ExitYes = 0
@@ -39,56 +44,129 @@ func newUnexpectedError(err error) error {
3944
}
4045
}
4146

42-
var SoftwareManagementType = "self"
47+
var SoftwareManagementTypeSelf = "self"
48+
var SoftwareManagementTypeContainer = "container"
4349
var SoftwareManagementActionInstall = "install"
50+
var SoftwareManagementActionRemove = "remove"
51+
52+
type CheckCommand struct {
53+
*cobra.Command
54+
55+
ContainerName string
56+
}
4457

4558
// NewCheckCommand represents the check command
4659
func NewCheckCommand(ctx cli.Cli) *cobra.Command {
47-
return &cobra.Command{
60+
command := &CheckCommand{}
61+
cmd := &cobra.Command{
4862
Use: "check",
4963
Args: cobra.ExactArgs(1),
5064
Short: "Check if an update is required (not part of sm-plugin interface!)",
51-
RunE: func(cmd *cobra.Command, args []string) error {
52-
slog.Info("Executing", "cmd", cmd.CalledAs(), "args", args)
65+
RunE: command.RunE,
66+
}
67+
cmd.Flags().StringVar(&command.ContainerName, "container", "", "Set current container name")
68+
command.Command = cmd
69+
return command.Command
70+
}
5371

54-
updateList := make([]SoftwareModule, 0)
55-
if err := json.Unmarshal([]byte(args[0]), &updateList); err != nil {
56-
return newUnexpectedError(err)
57-
}
72+
func (c *CheckCommand) RunE(cmd *cobra.Command, args []string) error {
73+
slog.Info("Executing", "cmd", cmd.CalledAs(), "args", args)
5874

59-
includesSelfUpdate := false
60-
61-
match := UpdateInfo{}
62-
for _, module := range updateList {
63-
if module.Type == SoftwareManagementType {
64-
for _, item := range module.Modules {
65-
if item.Action == SoftwareManagementActionInstall {
66-
match.ContainerName = item.Name
67-
match.Image = item.Version
68-
includesSelfUpdate = true
69-
break
70-
}
75+
updateList := make([]SoftwareModule, 0)
76+
if err := json.Unmarshal([]byte(args[0]), &updateList); err != nil {
77+
return newUnexpectedError(err)
78+
}
79+
80+
// Check container self
81+
containerCLI, err := container.NewContainerClient()
82+
if err != nil {
83+
return err
84+
}
85+
86+
ctx := context.Background()
87+
88+
selfContainerName := c.ContainerName
89+
selfContainerID := ""
90+
if con, err := containerCLI.Self(ctx); err == nil {
91+
selfContainerName = strings.TrimPrefix(con.Name, "/")
92+
selfContainerID = con.ID
93+
} else if selfContainerName == "" {
94+
slog.Info("self container.", "err", err)
95+
}
96+
97+
includesSelfUpdate := false
98+
outputUpdateModules := make([]SoftwareModule, 0)
99+
100+
match := UpdateInfo{}
101+
for _, module := range updateList {
102+
if module.Type == SoftwareManagementTypeSelf {
103+
// self update (to be removed in the future)
104+
for _, item := range module.Modules {
105+
if item.Action == SoftwareManagementActionInstall {
106+
match.ContainerName = item.Name
107+
match.Image = item.Version
108+
includesSelfUpdate = true
109+
} else if item.Action == SoftwareManagementActionRemove {
110+
return cli.ExitCodeError{
111+
Code: ExitError,
112+
Err: fmt.Errorf("tedge's own container cannot be removed. name=%s, version=%s, containerId=%s", item.Name, item.Version, selfContainerID),
113+
Silent: true,
71114
}
72-
break
73115
}
74116
}
117+
} else if module.Type == SoftwareManagementTypeContainer {
118+
// container update
119+
// Filter non self-updated modules
120+
filteredModules := make([]SoftwareItem, 0)
75121

76-
if !includesSelfUpdate {
77-
return cli.ExitCodeError{
78-
Code: ExitNo,
79-
Silent: true,
122+
for _, item := range module.Modules {
123+
if selfContainerName == item.Name {
124+
if item.Action == SoftwareManagementActionRemove {
125+
// Protect against the user deleting the tedge container itself
126+
return cli.ExitCodeError{
127+
Code: ExitError,
128+
Err: fmt.Errorf("tedge's own container cannot be removed. name=%s, version=%s, containerId=%s", item.Name, item.Version, selfContainerID),
129+
Silent: true,
130+
}
131+
} else if item.Action == SoftwareManagementActionInstall {
132+
// install
133+
match.ContainerName = item.Name
134+
match.Image = item.Version
135+
includesSelfUpdate = true
136+
}
137+
} else {
138+
filteredModules = append(filteredModules, item)
80139
}
81140
}
82-
83-
slog.Info("Update included a self update")
84-
payload, err := json.Marshal(match)
85-
if err != nil {
86-
return newUnexpectedError(err)
141+
if len(filteredModules) > 0 {
142+
outputUpdateModules = append(outputUpdateModules, SoftwareModule{
143+
Type: module.Type,
144+
Modules: filteredModules,
145+
})
87146
}
88-
fmt.Fprintf(cmd.OutOrStdout(), ":::begin-tedge:::\n%s\n:::end-tedge:::\n", payload)
147+
} else {
148+
outputUpdateModules = append(outputUpdateModules, module)
149+
}
150+
}
151+
152+
match.UpdateList = outputUpdateModules
89153

90-
// includes self update
91-
return nil
92-
},
154+
// Check if the container is itself
155+
if !includesSelfUpdate {
156+
return cli.ExitCodeError{
157+
Code: ExitNo,
158+
Err: fmt.Errorf("no self-update detected"),
159+
Silent: true,
160+
}
93161
}
162+
163+
slog.Info("Update included a self update")
164+
payload, err := json.Marshal(match)
165+
if err != nil {
166+
return newUnexpectedError(err)
167+
}
168+
fmt.Fprintf(cmd.OutOrStdout(), ":::begin-tedge:::\n%s\n:::end-tedge:::\n", payload)
169+
170+
// includes self update
171+
return nil
94172
}

tests/self.robot

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
*** Settings ***
2+
Resource ./resources/common.robot
3+
Library Cumulocity
4+
Library DeviceLibrary bootstrap_script=bootstrap.sh
5+
6+
Suite Setup Suite Setup
7+
Test Teardown Collect Logs
8+
9+
Test Tags docker podman
10+
11+
*** Test Cases ***
12+
13+
Self Update Is Present Using Self Type
14+
${output}= DeviceLibrary.Execute Command cmd=tedge-container self check '[{"type":"self","modules":[{"name":"tedge","action":"install","version":"123"}]},{"type":"container","modules":[{"name":"foo","version":"bar:1.0.0","action":"install"}]}]' --container tedge strip=${True}
15+
Should Contain ${output} {"containerName":"tedge","image":"123","updateList":[{"type":"container","modules":[{"name":"foo","version":"bar:1.0.0","action":"install"}]}]}
16+
17+
Self Update Is Present Using Container Type
18+
${output}= DeviceLibrary.Execute Command cmd=tedge-container self check '[{"type":"container","modules":[{"name":"tedge","action":"install","version":"123"},{"name":"foo","action":"install","version":"bar:latest","url":"https://foobar.com/example"}]}]' --container tedge strip=${True}
19+
Should Contain ${output} {"containerName":"tedge","image":"123","updateList":[{"type":"container","modules":[{"name":"foo","version":"bar:latest","url":"https://foobar.com/example","action":"install"}]}]}
20+
21+
Self Update Is Not Present
22+
${output}= DeviceLibrary.Execute Command cmd=tedge-container self check '[{"type":"custom","modules":[{"name":"foo","action":"install","version":"bar:latest","url":"https://foobar.com/example"}]}]' --container tedge exp_exit_code=1
23+
24+
25+
*** Keywords ***
26+
27+
Suite Setup
28+
${DEVICE_SN}= Setup
29+
Set Suite Variable $DEVICE_SN
30+
Cumulocity.External Identity Should Exist ${DEVICE_SN}
31+
Cumulocity.Should Have Services name=tedge-container-plugin service_type=service min_count=1 max_count=1 timeout=30
32+
33+
# Create data directory
34+
DeviceLibrary.Execute Command mkdir /data
35+
36+
Collect Logs
37+
Collect Workflow Logs
38+
Collect Systemd Logs
39+
40+
Collect Systemd Logs
41+
Execute Command sudo journalctl -n 10000
42+
43+
Collect Workflow Logs
44+
Execute Command cat /var/log/tedge/agent/*

0 commit comments

Comments
 (0)