Skip to content

Commit a46efa3

Browse files
authored
✨ Safe manipulation of inventory.Platform (#5417)
One of the core data structures of Mondoo’s plugin system is the `inventory.Asset` struct that contains details about an asset, including platform information that is stored in `Asset.Platform`. The `inventory.Platform` struct stores important information used by the Mondoo platform (`server`) like the runtime, the platform kind, and additional metadata. When we run `cnquery` or `cnspec` most of the times the first thing we do is an initial discovery to detect the asset and platform we are going to scan, then we pass these core data structures to our providers where, sometimes, we do additional discovery and enrichment of data. An example of this is the discovery of AWS instances (`aws` provider) that later gets enriched with additional information from the `os` provider. **Change** We are introducing two accessors. One at the Asset level and the other one directly into the Platform struct. These new accessor should be used through out the code base to safely manipulate the platform of an asset. --------- Signed-off-by: Salim Afiune Maya <afiune@mondoo.com>
1 parent 29e8495 commit a46efa3

File tree

7 files changed

+108
-16
lines changed

7 files changed

+108
-16
lines changed

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ require (
117117
)
118118

119119
require (
120-
dario.cat/mergo v1.0.1 // indirect
120+
dario.cat/mergo v1.0.1
121121
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect
122122
github.com/ChrisTrenkamp/goxpath v0.0.0-20210404020558-97928f7e12b6 // indirect
123123
github.com/anchore/go-struct-converter v0.0.0-20221118182256-c68fdcfa2092 // indirect

providers-sdk/v1/inventory/asset.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,3 +178,13 @@ func (a *Asset) AddMondooLabels(root *Asset) {
178178
}
179179
}
180180
}
181+
182+
// MergePlatform merges the provided platform into the asset's platform.
183+
// If the asset's platform is `nil`, the provided platform is set.
184+
func (a *Asset) MergePlatform(pf *Platform) {
185+
if a.Platform == nil {
186+
a.Platform = pf
187+
} else {
188+
a.Platform.Merge(pf)
189+
}
190+
}

providers-sdk/v1/inventory/inventory.go

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,10 @@ import (
77
"os"
88
"os/user"
99
"path/filepath"
10+
"slices"
1011
"strings"
1112

13+
"dario.cat/mergo"
1214
"github.com/cockroachdb/errors"
1315
"github.com/rs/zerolog/log"
1416
"github.com/segmentio/ksuid"
@@ -318,13 +320,20 @@ var (
318320
FAMILY_WINDOWS = "windows"
319321
)
320322

321-
func (p *Platform) IsFamily(family string) bool {
322-
for i := range p.Family {
323-
if p.Family[i] == family {
324-
return true
323+
// Merge performs a deep merge of the provided platform.
324+
func (p *Platform) Merge(pf *Platform) {
325+
if pf != nil {
326+
if err := mergo.Merge(p, pf, mergo.WithOverride); err != nil {
327+
log.Error().Err(err).
328+
Interface("target", p).
329+
Interface("source", pf).
330+
Msg("unable to merge platform details")
325331
}
326332
}
327-
return false
333+
}
334+
335+
func (p *Platform) IsFamily(family string) bool {
336+
return slices.Contains(p.Family, family)
328337
}
329338

330339
func (p *Platform) PrettyTitle() string {

providers-sdk/v1/inventory/inventory_test.go

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,79 @@ func TestInventoryParser(t *testing.T) {
2222
assert.Equal(t, "{ id: 'secret-1' }", inventory.Spec.CredentialQuery)
2323
}
2424

25+
func TestPlatformMerge(t *testing.T) {
26+
base := &Platform{
27+
Name: "linux",
28+
Arch: "",
29+
Family: []string{"unix"},
30+
Metadata: map[string]string{"env": "prod"},
31+
TechnologyUrlSegments: []string{"a", "b", "c"},
32+
Version: "",
33+
}
34+
35+
incoming := &Platform{
36+
Name: "", // Should not override
37+
Arch: "amd64",
38+
Family: []string{"gnu"}, // Should override (because of option)
39+
Metadata: map[string]string{"region": "us-east-1"},
40+
TechnologyUrlSegments: []string{"x", "y", "z"},
41+
Version: "1.0.0",
42+
}
43+
44+
expected := &Platform{
45+
Name: "linux", // original
46+
Arch: "amd64", // merged in
47+
Family: []string{"gnu"}, // overridden (because we say so)
48+
Metadata: map[string]string{
49+
"env": "prod",
50+
"region": "us-east-1", // merged map
51+
},
52+
Version: "1.0.0", // merged in
53+
TechnologyUrlSegments: []string{"x", "y", "z"},
54+
}
55+
56+
base.Merge(incoming)
57+
58+
assert.Equal(t, expected.Name, base.Name)
59+
assert.Equal(t, expected.Arch, base.Arch)
60+
assert.Equal(t, expected.Family, base.Family)
61+
assert.Equal(t, expected.Version, base.Version)
62+
assert.Equal(t, expected.Metadata["env"], base.Metadata["env"])
63+
assert.Equal(t, expected.Metadata["region"], base.Metadata["region"])
64+
assert.Equal(t, expected.TechnologyUrlSegments, base.TechnologyUrlSegments)
65+
66+
t.Run("cases", func(t *testing.T) {
67+
p := &Platform{
68+
Name: "terraform-plan",
69+
Title: "Terraform Plan",
70+
Family: []string{"terraform"},
71+
Kind: "code",
72+
Runtime: "terraform",
73+
TechnologyUrlSegments: []string{"iac", "terraform", "plan"},
74+
}
75+
76+
expectTheSame := &Platform{
77+
Name: "terraform-plan",
78+
Title: "Terraform Plan",
79+
Family: []string{"terraform"},
80+
Kind: "code",
81+
Runtime: "terraform",
82+
TechnologyUrlSegments: []string{"iac", "terraform", "plan"},
83+
}
84+
85+
t.Run("nil", func(t *testing.T) {
86+
p.Merge(nil)
87+
assert.Equal(t, expectTheSame, p)
88+
})
89+
90+
t.Run("empty", func(t *testing.T) {
91+
p.Merge(&Platform{})
92+
assert.Equal(t, expectTheSame, p)
93+
})
94+
95+
})
96+
}
97+
2598
func TestPreprocess(t *testing.T) {
2699
t.Run("preprocess empty inventory", func(t *testing.T) {
27100
v1inventory := &Inventory{}

providers/os/provider/detector.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,11 +47,11 @@ func mapDetectors(raw []string) map[string]struct{} {
4747
}
4848

4949
func (s *Service) detect(asset *inventory.Asset, conn shared.Connection) error {
50-
var ok bool
51-
asset.Platform, ok = detector.DetectOS(conn)
50+
pf, ok := detector.DetectOS(conn)
5251
if !ok {
5352
return errors.New("failed to detect OS")
5453
}
54+
asset.MergePlatform(pf)
5555
if asset.Platform.Kind == "" {
5656
asset.Platform.Kind = inventory.AssetKindBaremetal
5757
}

providers/os/provider/provider.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -379,7 +379,7 @@ func (s *Service) connect(req *plugin.ConnectReq, callback plugin.ProviderCallba
379379
}
380380
asset.PlatformIds = fingerprint.PlatformIDs
381381
asset.IdDetector = fingerprint.ActiveIdDetectors
382-
asset.Platform = p
382+
asset.MergePlatform(p)
383383
appendRelatedAssetsFromFingerprint(fingerprint, asset)
384384
}
385385
case shared.Type_Device.String():
@@ -397,7 +397,7 @@ func (s *Service) connect(req *plugin.ConnectReq, callback plugin.ProviderCallba
397397
}
398398
asset.PlatformIds = fingerprint.PlatformIDs
399399
asset.IdDetector = fingerprint.ActiveIdDetectors
400-
asset.Platform = p
400+
asset.MergePlatform(p)
401401
appendRelatedAssetsFromFingerprint(fingerprint, asset)
402402
}
403403

@@ -412,7 +412,7 @@ func (s *Service) connect(req *plugin.ConnectReq, callback plugin.ProviderCallba
412412
asset.Name = fingerprint.Name
413413
asset.PlatformIds = fingerprint.PlatformIDs
414414
asset.IdDetector = fingerprint.ActiveIdDetectors
415-
asset.Platform = p
415+
asset.MergePlatform(p)
416416
appendRelatedAssetsFromFingerprint(fingerprint, asset)
417417
}
418418

@@ -427,7 +427,7 @@ func (s *Service) connect(req *plugin.ConnectReq, callback plugin.ProviderCallba
427427
asset.Name = fingerprint.Name
428428
asset.PlatformIds = fingerprint.PlatformIDs
429429
asset.IdDetector = fingerprint.ActiveIdDetectors
430-
asset.Platform = p
430+
asset.MergePlatform(p)
431431
appendRelatedAssetsFromFingerprint(fingerprint, asset)
432432
}
433433

@@ -442,7 +442,7 @@ func (s *Service) connect(req *plugin.ConnectReq, callback plugin.ProviderCallba
442442
asset.Name = fingerprint.Name
443443
asset.PlatformIds = fingerprint.PlatformIDs
444444
asset.IdDetector = fingerprint.ActiveIdDetectors
445-
asset.Platform = p
445+
asset.MergePlatform(p)
446446
appendRelatedAssetsFromFingerprint(fingerprint, asset)
447447
}
448448

@@ -494,7 +494,7 @@ func (s *Service) connect(req *plugin.ConnectReq, callback plugin.ProviderCallba
494494
asset.Name = fingerprint.Name
495495
asset.PlatformIds = fingerprint.PlatformIDs
496496
asset.IdDetector = fingerprint.ActiveIdDetectors
497-
asset.Platform = p
497+
asset.MergePlatform(p)
498498
}
499499
} else {
500500
// In this case asset.Name should already be set via the inventory

providers/terraform/provider/detector.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import (
1717
"go.mondoo.com/cnquery/v11/utils/urlx"
1818
)
1919

20-
func (s *Service) detect(asset *inventory.Asset, conn *connection.Connection) error {
20+
func (s *Service) detect(asset *inventory.Asset, _ *connection.Connection) error {
2121
var p *inventory.Platform
2222
connType := asset.Connections[0].Type
2323
switch connType {
@@ -51,7 +51,7 @@ func (s *Service) detect(asset *inventory.Asset, conn *connection.Connection) er
5151
TechnologyUrlSegments: []string{"iac", "terraform", "hcl"},
5252
}
5353
}
54-
asset.Platform = p
54+
asset.MergePlatform(p)
5555

5656
// we always prefer the git url since it is more reliable
5757
url, ok := asset.Connections[0].Options["ssh-url"]

0 commit comments

Comments
 (0)