Skip to content

Commit 0a2e8ef

Browse files
committed
finish support for create
The arch system section should be separate from the os. This change will fully read in a compatibiity request (yaml), and then map requested fields to extractor output fields, and with the option to add on-the-fly custom metadata. We output a finished compatibility artifact that is ready to be pushed to a registry and later used paired with an image. Note that I need to run a final jsonSchema validate on generation (before save) still. Signed-off-by: vsoch <[email protected]>
1 parent c4991c4 commit 0a2e8ef

File tree

18 files changed

+522
-98
lines changed

18 files changed

+522
-98
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ This is a prototype compatibility checking tool. Right now our aim is to use in
1515
## TODO
1616

1717
- metadata namespace and exposure: someone writing a spec to create an artifact needs to know the extract namespace (and what is available) for the mapping.
18+
- create: the final step of create should be validation of the spec with the jsonSchema linked (not done yet)
1819
- tests: matrix that has several different flavors of builds, generating compspec json output to validate generation and correctness
1920
- likely we want a common configuration file to take an extraction -> check recipe
2021
- need to develop check plugin family

cmd/compspec/compspec.go

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ import (
66
"os"
77

88
"github.com/akamensky/argparse"
9-
"github.com/supercontainers/compspec-go/cmd/compspec/extract"
109
"github.com/supercontainers/compspec-go/cmd/compspec/create"
10+
"github.com/supercontainers/compspec-go/cmd/compspec/extract"
1111
"github.com/supercontainers/compspec-go/cmd/compspec/list"
1212
"github.com/supercontainers/compspec-go/pkg/types"
1313
)
@@ -38,7 +38,11 @@ func main() {
3838

3939
// Extract arguments
4040
filename := extractCmd.String("o", "out", &argparse.Options{Help: "Save extraction to json file"})
41-
specname := createCmd.String("i", "in", &argparse.Options{Help: "Input yaml that contains spec for creation"})
41+
42+
// Create arguments
43+
options := parser.StringList("a", "append", &argparse.Options{Help: "One or more custom metadata fields to append"})
44+
specname := createCmd.String("i", "in", &argparse.Options{Required: true, Help: "Input yaml that contains spec for creation"})
45+
specfile := createCmd.String("o", "out", &argparse.Options{Help: "Save compatibility json artifact to this file"})
4246

4347
// Now parse the arguments
4448
err := parser.Parse(os.Args)
@@ -51,12 +55,18 @@ func main() {
5155
if extractCmd.Happened() {
5256
err := extract.Run(*filename, *pluginNames)
5357
if err != nil {
54-
log.Fatalf("Issue with extraction: %s", err)
58+
log.Fatalf("Issue with extraction: %s\n", err)
5559
}
5660
} else if createCmd.Happened() {
57-
create.Run(*specname)
61+
err := create.Run(*specname, *options, *specfile)
62+
if err != nil {
63+
log.Fatal(err.Error())
64+
}
5865
} else if listCmd.Happened() {
59-
list.Run(*pluginNames)
66+
err := list.Run(*pluginNames)
67+
if err != nil {
68+
log.Fatal(err.Error())
69+
}
6070
} else if versionCmd.Happened() {
6171
RunVersion()
6272
} else {

cmd/compspec/create/create.go

Lines changed: 113 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,118 @@
11
package create
22

3+
import (
4+
"fmt"
5+
"os"
6+
7+
"github.com/supercontainers/compspec-go/pkg/types"
8+
p "github.com/supercontainers/compspec-go/plugins"
9+
"sigs.k8s.io/yaml"
10+
)
11+
12+
// loadRequest loads a Compatibility Request YAML into a struct
13+
func loadRequest(filename string) (*types.CompatibilityRequest, error) {
14+
request := types.CompatibilityRequest{}
15+
yamlFile, err := os.ReadFile(filename)
16+
if err != nil {
17+
return &request, err
18+
}
19+
20+
err = yaml.Unmarshal(yamlFile, &request)
21+
if err != nil {
22+
return &request, err
23+
}
24+
return &request, nil
25+
}
26+
327
// Run will create a compatibility artifact based on a request in YAML
4-
func Run(specname string) error {
28+
func Run(specname string, fields []string, saveto string) error {
29+
30+
// Cut out early if a spec not provided
31+
if specname == "" {
32+
return fmt.Errorf("A spec input -i/--input is required")
33+
}
34+
request, err := loadRequest(specname)
35+
if err != nil {
36+
return err
37+
}
38+
39+
// Right now we only know about extractors, when we define subfields
40+
// we can further filter here.
41+
extractors := request.GetExtractors()
42+
plugins, err := p.GetPlugins(extractors)
43+
if err != nil {
44+
return err
45+
}
46+
47+
// Finally, add custom fields and extract metadata
48+
result, err := plugins.Extract()
49+
50+
// Update with custom fields (either new or overwrite)
51+
result.AddCustomFields(fields)
52+
53+
// The compspec returned is the populated Compatibility request!
54+
compspec, err := PopulateExtractors(&result, request)
55+
output, err := compspec.ToJson()
56+
if err != nil {
57+
return err
58+
}
59+
if saveto == "" {
60+
fmt.Println(string(output))
61+
} else {
62+
err = os.WriteFile(saveto, output, 0644)
63+
if err != nil {
64+
return err
65+
}
66+
}
567
return nil
668
}
69+
70+
// LoadExtractors loads a compatibility result into a compatibility request
71+
// After this we can save the populated thing into an artifact (json DUMP)
72+
func PopulateExtractors(result *p.Result, request *types.CompatibilityRequest) (*types.CompatibilityRequest, error) {
73+
74+
for i, compat := range request.Compatibilities {
75+
for key, extractorKey := range compat.Annotations {
76+
77+
// Get the extractor, section, and subfield from the extractor lookup key
78+
f, err := p.ParseField(extractorKey)
79+
if err != nil {
80+
fmt.Printf("warning: cannot parse %s: %s, setting to empty\n", key, extractorKey)
81+
compat.Annotations[key] = ""
82+
continue
83+
}
84+
85+
// If we get here, we can parse it and look it up in our result metadata
86+
extractor, ok := result.Results[f.Extractor]
87+
if !ok {
88+
fmt.Printf("warning: extractor %s is unknown, setting to empty\n", f.Extractor)
89+
compat.Annotations[key] = ""
90+
continue
91+
}
92+
93+
// Now get the section
94+
section, ok := extractor.Sections[f.Section]
95+
if !ok {
96+
fmt.Printf("warning: section %s.%s is unknown, setting to empty\n", f.Extractor, f.Section)
97+
compat.Annotations[key] = ""
98+
continue
99+
}
100+
101+
// Now get the value!
102+
value, ok := section[f.Field]
103+
if !ok {
104+
fmt.Printf("warning: field %s.%s.%s is unknown, setting to empty\n", f.Extractor, f.Section, f.Field)
105+
compat.Annotations[key] = ""
106+
continue
107+
}
108+
109+
// If we get here - we found it! Hooray!
110+
compat.Annotations[key] = value
111+
}
112+
113+
// Update the compatibiity
114+
request.Compatibilities[i] = compat
115+
}
116+
117+
return request, nil
118+
}

docs/design.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ The compatibility tool is responsible for extracting information about a system,
99

1010
### Extractor
1111

12-
An **extractor** is a core plugin that knows how to retrieve metadata about a host. An extractor is ususally going to be run for two cases:
12+
An **extractor** is a core plugin that knows how to retrieve metadata about a host. An extractor is usually going to be run for two cases:
1313

1414
1. During CI to extract (and save) metadata about a particular build to put in a compatibility artifact.
1515
2. During image selection to extract information about the host to compare to.

docs/usage.md

Lines changed: 113 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -56,21 +56,91 @@ $ ./bin/compspec list
5656
```console
5757
Compatibility Plugins
5858
TYPE NAME SECTION
59-
extrator kernel boot
60-
extrator kernel config
61-
extrator kernel modules
62-
extrator system cpu
63-
extrator system processor
64-
extrator system os
65-
extrator library mpi
66-
TOTAL 7
59+
extractor kernel boot
60+
extractor kernel config
61+
extractor kernel modules
62+
extractor system cpu
63+
extractor system processor
64+
extractor system os
65+
extractor system arch
66+
extractor library mpi
67+
TOTAL 8
6768
```
6869

6970
Note that we will eventually add a description column - it's not really warranted yet!
7071

72+
## Create
73+
74+
The create command is how you take a compatibility request, or a YAML file that has a mapping between the extractors defined by this tool and your compatibility metadata namespace, and generate an artifact. The artifact typically will be a JSON dump of key value pairs, scoped under different namespaces, that you might push to a registry to live alongside a container image, and with the intention to eventually use it to check compatiility against a new system. To run create
75+
we can use the example in the top level repository:
76+
77+
```bash
78+
./bin/compspec create --in ./examples/lammps-experiment.yaml
79+
```
80+
81+
Note that you'll see some errors about fields not being found! This is because we've implemented this for the fields to be added custom, on the command line.
82+
The idea here is that you can add custom metadata fields during your build, which can be easier than adding for automated extraction. Let's add them now.
83+
84+
```bash
85+
# a stands for "append" and it can write a new field or overwrite an existing one
86+
./bin/compspec create --in ./examples/lammps-experiment.yaml -a custom.gpu.available=yes
87+
```
88+
```console
89+
{
90+
"version": "0.0.0",
91+
"kind": "CompatibilitySpec",
92+
"metadata": {
93+
"name": "lammps-prototype",
94+
"jsonSchema": "https://raw.githubusercontent.com/supercontainers/compspec/main/supercontainers/compspec.json"
95+
},
96+
"compatibilities": [
97+
{
98+
"name": "org.supercontainers.mpi",
99+
"version": "0.0.0",
100+
"annotations": {
101+
"implementation": "mpich",
102+
"version": "4.1.1"
103+
}
104+
},
105+
{
106+
"name": "org.supercontainers.hardware.gpu",
107+
"version": "0.0.0",
108+
"annotations": {
109+
"available": "yes"
110+
}
111+
},
112+
{
113+
"name": "io.archspec.cpu",
114+
"version": "0.0.0",
115+
"annotations": {
116+
"model": "13th Gen Intel(R) Core(TM) i5-1335U",
117+
"target": "amd64",
118+
"vendor": "GenuineIntel"
119+
}
120+
}
121+
]
122+
}
123+
```
124+
125+
Awesome! That, as simple as it is, is our compatibility artifact. I ran the command on my host just now, but run for a container image during
126+
a build will generate it for that context. We would want to save this to file:
127+
128+
```bash
129+
./bin/compspec create --in ./examples/lammps-experiment.yaml -a custom.gpu.available=yes -o ./examples/generated-compatibility-spec.json
130+
```
131+
132+
And that's it! We would next (likely during CI) push this compatibility artifact to a URI that is likely (TBA) linked to the image.
133+
For now we will manually remember the pairing, at least until the compatibility working group figures out the final design!
134+
71135
## Extract
72136

73-
If you want to extract metadata to your local machine, you can use extract! Either just run all extractors and dump to the terminal:
137+
Extraction has two use cases, and likely you won't be running this manually, but within the context of another command:
138+
139+
1. Extracting metadata about the container image at build time to generate an artifact (done via "create")
140+
2. Extracting metadata about the host at image selection time, and comparing against a set of contender container images to select the best one (done via "check").
141+
142+
However, for the advanced or interested user, you can run extract as a standalone utility to inspect or otherwise save metadata from extractors.
143+
For example, if you want to extract metadata to your local machine, you can use extract! Either just run all extractors and dump to the terminal:
74144

75145
```bash
76146
# Not recommend, it's a lot!
@@ -84,7 +154,17 @@ the full ability to specify:
84154
2. One or more specific sections known to an extractor
85155
3. Saving to json metadata instead of dumping to terminal
86156

87-
### Library
157+
158+
### Extractors
159+
160+
Current Extractors include:
161+
162+
- Library: library-specific metadata (e.g., mpi)
163+
- System: system-specific metadata (e.g., processor, cpu, arch, os)
164+
- Kernel: kernel-speific metadata (e.g., boot, config, modules)
165+
166+
167+
#### Library
88168

89169
The library extractor currently just has one section for "mpi"
90170

@@ -96,7 +176,7 @@ The library extractor currently just has one section for "mpi"
96176
--Result for library
97177
-- Section mpi
98178
mpi.variant: mpich
99-
mpi.version: 4.0
179+
mpi.version: 4.1.1
100180
Extraction has run!
101181
```
102182

@@ -119,7 +199,7 @@ cat test-library.json
119199
"sections": {
120200
"mpi": {
121201
"mpi.variant": "mpich",
122-
"mpi.version": "4.0"
202+
"mpi.version": "4.1.1"
123203
}
124204
}
125205
}
@@ -129,32 +209,45 @@ cat test-library.json
129209

130210
That shows the generic structure of an extractor output. The "library" extractor owns a set of groups (sections) each with their own namespaced attributes.
131211

132-
### System
212+
#### System
133213

134214
The system extractor supports three sections
135215

136216
- cpu: Basic CPU counts and metadata
137217
- processor: detailed information on every processor
138218
- os: operating system information
219+
- arch: architecture
139220

140221
For example:
141222

142223
```bash
143-
./bin/comspec extract --name system[os]
224+
./bin/compspec extract --name system[os]
144225
```
145226
```console
146227
⭐️ Running extract...
147228
--Result for system
148229
-- Section os
149-
arch.os.name: Ubuntu 22.04.3 LTS
150-
arch.os.version: 22.04
151-
arch.os.vendor: ubuntu
152-
arch.os.release: 22.04.3
153-
arch.name: amd64
230+
release: 22.04.3
231+
name: Ubuntu 22.04.3 LTS
232+
version: 22.04
233+
vendor: ubuntu
234+
Extraction has run!
235+
```
236+
237+
Or for arch:
238+
239+
```bash
240+
./bin/compspec extract --name system[arch]
241+
```
242+
```console
243+
⭐️ Running extract...
244+
--Result for system
245+
-- Section arch
246+
name: amd64
154247
Extraction has run!
155248
```
156249

157-
### Kernel
250+
#### Kernel
158251

159252
Kernel supports three sections:
160253

0 commit comments

Comments
 (0)