Skip to content

Commit f3c85b3

Browse files
author
oluwole.fadeyi
committed
Add architecture and os checks when fetching tags
This updates the controller to check the architecture of the cluster nodes and select only tags for the current architecture. Signed-off-by: oluwole.fadeyi <[email protected]>
1 parent 9bfbe1d commit f3c85b3

File tree

17 files changed

+561
-82
lines changed

17 files changed

+561
-82
lines changed

deploy/yaml/deploy.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ metadata:
6262
name: version-checker
6363
rules:
6464
- apiGroups: [""]
65-
resources: ["pods"]
65+
resources: ["pods","nodes"]
6666
verbs: ["get", "watch", "list"]
6767
---
6868
kind: ClusterRoleBinding

pkg/api/types.go

+7
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,10 @@ type Options struct {
5858
PinPatch *int64 `json:"pin-patch,omitempty"`
5959

6060
RegexMatcher *regexp.Regexp `json:"-"`
61+
62+
// Architecture and OS to search for
63+
Architecture Architecture `json:"pin-architecture,omitempty"`
64+
OS OS `json:"pin-os,omitempty"`
6165
}
6266

6367
// ImageTag describes a container image tag.
@@ -68,3 +72,6 @@ type ImageTag struct {
6872
Architecture string `json:"architecture,omitempty"`
6973
OS string `json:"os,omitempty"`
7074
}
75+
76+
type Architecture string
77+
type OS string
+70
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package architecture
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
"sync"
7+
8+
corev1 "k8s.io/api/core/v1"
9+
10+
"github.com/jetstack/version-checker/pkg/api"
11+
)
12+
13+
// NodeMetadata metadata about a particular node
14+
type nodeMetadata struct {
15+
OS api.OS
16+
Architecture api.Architecture
17+
}
18+
19+
type NodeMap struct {
20+
mu sync.RWMutex
21+
nodes map[string]*nodeMetadata
22+
}
23+
24+
func New() *NodeMap {
25+
// might need to pass an initial map
26+
return &NodeMap{
27+
nodes: make(map[string]*nodeMetadata),
28+
}
29+
}
30+
31+
func (m *NodeMap) GetArchitecture(nodeName string) (*nodeMetadata, bool) {
32+
m.mu.RLock()
33+
defer m.mu.RUnlock()
34+
35+
meta, ok := m.nodes[nodeName]
36+
return meta, ok
37+
}
38+
39+
func (m *NodeMap) AddNode(node *corev1.Node) error {
40+
m.mu.Lock()
41+
defer m.mu.Unlock()
42+
if node == nil {
43+
return errors.New("passed node is nil")
44+
}
45+
46+
arch, ok := node.Labels[corev1.LabelArchStable]
47+
if !ok {
48+
return fmt.Errorf("missing %q label on node %q", corev1.LabelArchStable, node.Name)
49+
}
50+
51+
os, ok := node.Labels[corev1.LabelOSStable]
52+
if !ok {
53+
return fmt.Errorf("missing %q label on node %q", corev1.LabelOSStable, node.Name)
54+
}
55+
56+
// change name to uid or selflink
57+
m.nodes[node.Name] = &nodeMetadata{
58+
OS: api.OS(os),
59+
Architecture: api.Architecture(arch),
60+
}
61+
return nil
62+
}
63+
64+
func (m *NodeMap) Delete(nodeName string) error {
65+
m.mu.Lock()
66+
defer m.mu.Unlock()
67+
// change name to uid or selflink
68+
delete(m.nodes, nodeName)
69+
return nil
70+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
package architecture
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
"reflect"
7+
"testing"
8+
9+
corev1 "k8s.io/api/core/v1"
10+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
11+
12+
utils "github.com/jetstack/version-checker/pkg/checker/internal/testutils"
13+
)
14+
15+
func TestAdd(t *testing.T) {
16+
tests := map[string]struct {
17+
input []*corev1.Node
18+
expOperationOutput error
19+
expResult map[string]*nodeMetadata
20+
}{
21+
"update valid node": {
22+
input: []*corev1.Node{
23+
utils.CreateNode("node1", utils.ArchAMD64, utils.OsLinux),
24+
utils.CreateNode("node1", utils.ArchARM, utils.OsLinux),
25+
},
26+
expOperationOutput: nil,
27+
expResult: map[string]*nodeMetadata{
28+
"node1": &nodeMetadata{
29+
Architecture: utils.ArchARM,
30+
OS: utils.OsLinux,
31+
},
32+
},
33+
},
34+
"add valid node": {
35+
input: []*corev1.Node{
36+
utils.CreateNode("node1", utils.ArchAMD64, utils.OsLinux),
37+
},
38+
expOperationOutput: nil,
39+
expResult: map[string]*nodeMetadata{
40+
"node1": &nodeMetadata{
41+
Architecture: utils.ArchAMD64,
42+
OS: utils.OsLinux,
43+
},
44+
},
45+
},
46+
"add nil node": {
47+
input: nil,
48+
expOperationOutput: errors.New("passed node is nil"),
49+
expResult: make(map[string]*nodeMetadata),
50+
},
51+
"add node with no architecture label": {
52+
input: []*corev1.Node{
53+
&corev1.Node{
54+
ObjectMeta: metav1.ObjectMeta{
55+
Name: "node1",
56+
Labels: map[string]string{
57+
corev1.LabelOSStable: "linux",
58+
},
59+
},
60+
},
61+
},
62+
expOperationOutput: fmt.Errorf("missing \"kubernetes.io/arch\" label on node \"node1\""),
63+
expResult: make(map[string]*nodeMetadata),
64+
},
65+
"add node with no os label": {
66+
input: []*corev1.Node{
67+
&corev1.Node{
68+
ObjectMeta: metav1.ObjectMeta{
69+
Name: "node1",
70+
Labels: map[string]string{
71+
corev1.LabelArchStable: "amd64",
72+
},
73+
},
74+
},
75+
},
76+
expOperationOutput: fmt.Errorf("missing %q label on node \"node1\"", corev1.LabelOSStable),
77+
expResult: make(map[string]*nodeMetadata),
78+
},
79+
}
80+
81+
for name, test := range tests {
82+
t.Run(name, func(t *testing.T) {
83+
var nodeMap = New()
84+
for _, node := range test.input {
85+
err := nodeMap.AddNode(node)
86+
if !reflect.DeepEqual(test.expOperationOutput, err) {
87+
t.Errorf("got unexpected operation error, exp=%s act=%s", test.expOperationOutput, err)
88+
}
89+
}
90+
if !reflect.DeepEqual(nodeMap.nodes, test.expResult) {
91+
t.Errorf("got unexpected result, exp=%#+v got=%#+v", test.expResult, nodeMap.nodes)
92+
}
93+
94+
})
95+
}
96+
}
97+
98+
func TestDelete(t *testing.T) {
99+
tests := map[string]struct {
100+
mapInitialState []*corev1.Node
101+
input string
102+
expOperationOutput error
103+
expResult map[string]*nodeMetadata
104+
}{
105+
"delete valid node": {
106+
mapInitialState: []*corev1.Node{
107+
utils.CreateNode("node1", utils.ArchAMD64, utils.OsLinux),
108+
utils.CreateNode("node2", utils.ArchARM, utils.OsLinux),
109+
},
110+
input: "node1",
111+
expOperationOutput: nil,
112+
expResult: map[string]*nodeMetadata{
113+
"node2": &nodeMetadata{
114+
OS: utils.OsLinux,
115+
Architecture: utils.ArchARM,
116+
},
117+
},
118+
},
119+
"delete empty node name": {
120+
mapInitialState: []*corev1.Node{
121+
utils.CreateNode("node1", utils.ArchAMD64, utils.OsLinux),
122+
utils.CreateNode("node2", utils.ArchARM, utils.OsLinux),
123+
},
124+
input: "",
125+
expOperationOutput: nil,
126+
expResult: map[string]*nodeMetadata{
127+
"node1": &nodeMetadata{
128+
OS: utils.OsLinux,
129+
Architecture: utils.ArchAMD64,
130+
},
131+
"node2": &nodeMetadata{
132+
OS: utils.OsLinux,
133+
Architecture: utils.ArchARM,
134+
},
135+
},
136+
},
137+
}
138+
139+
for name, test := range tests {
140+
t.Run(name, func(t *testing.T) {
141+
var nodeMap = New()
142+
for _, node := range test.mapInitialState {
143+
err := nodeMap.AddNode(node)
144+
if err != nil {
145+
t.Errorf("unexpected error: %s", err)
146+
}
147+
}
148+
149+
nodeMap.Delete(test.input)
150+
if !reflect.DeepEqual(nodeMap.nodes, test.expResult) {
151+
t.Errorf("got unexpected result, exp=%#+v got=%#+v", test.expResult, nodeMap.nodes)
152+
}
153+
154+
})
155+
}
156+
}

pkg/controller/checker/checker.go renamed to pkg/checker/checker.go

+19-3
Original file line numberDiff line numberDiff line change
@@ -8,32 +8,44 @@ import (
88
corev1 "k8s.io/api/core/v1"
99

1010
"github.com/jetstack/version-checker/pkg/api"
11-
"github.com/jetstack/version-checker/pkg/controller/search"
12-
"github.com/jetstack/version-checker/pkg/version/semver"
11+
"github.com/jetstack/version-checker/pkg/checker/architecture"
12+
"github.com/jetstack/version-checker/pkg/checker/search"
13+
"github.com/jetstack/version-checker/pkg/checker/version/semver"
1314
"github.com/sirupsen/logrus"
1415
)
1516

1617
type Checker struct {
1718
search search.Searcher
19+
nodes *architecture.NodeMap
1820
}
1921

2022
type Result struct {
2123
CurrentVersion string
2224
LatestVersion string
2325
IsLatest bool
2426
ImageURL string
27+
OS string
28+
Architecture string
2529
}
2630

27-
func New(search search.Searcher) *Checker {
31+
func New(search search.Searcher, nodesArchInfo *architecture.NodeMap) *Checker {
2832
return &Checker{
2933
search: search,
34+
nodes: nodesArchInfo,
3035
}
3136
}
3237

3338
// Container will return the result of the given container's current version, compared to the latest upstream
3439
func (c *Checker) Container(ctx context.Context, log *logrus.Entry,
3540
pod *corev1.Pod, container *corev1.Container, opts *api.Options) (*Result, error) {
3641

42+
// Get information about the pod node architecture
43+
arch, ok := c.nodes.GetArchitecture(pod.Spec.NodeName)
44+
if ok {
45+
opts.OS = arch.OS
46+
opts.Architecture = arch.Architecture
47+
}
48+
3749
// If the container image SHA status is not ready yet, exit early
3850
statusSHA := containerStatusImageSHA(pod, container.Name)
3951
if len(statusSHA) == 0 {
@@ -90,6 +102,8 @@ func (c *Checker) Container(ctx context.Context, log *logrus.Entry,
90102
LatestVersion: latestVersion,
91103
IsLatest: isLatest,
92104
ImageURL: imageURL,
105+
OS: latestImage.OS,
106+
Architecture: latestImage.Architecture,
93107
}, nil
94108
}
95109

@@ -161,6 +175,8 @@ func (c *Checker) isLatestSHA(ctx context.Context, imageURL, currentSHA string,
161175
LatestVersion: latestVersion,
162176
IsLatest: isLatest,
163177
ImageURL: imageURL,
178+
OS: latestImage.OS,
179+
Architecture: latestImage.Architecture,
164180
}, nil
165181
}
166182

0 commit comments

Comments
 (0)