Skip to content

Commit f7199fd

Browse files
authored
Merge pull request #5 from supercontainers/add-create-prototype
add create prototype
2 parents 05ab562 + 0a2e8ef commit f7199fd

File tree

21 files changed

+830
-237
lines changed

21 files changed

+830
-237
lines changed

README.md

Lines changed: 5 additions & 184 deletions
Original file line numberDiff line numberDiff line change
@@ -3,183 +3,9 @@
33
![img/compspec.png](img/compspec.png)
44

55
This is a prototype compatibility checking tool. Right now our aim is to use in the context of
6-
[these build matrices](https://github.com/rse-ops/lammps-matrix) for LAMMPS and these prototype [specifications](https://github.com/supercontainers/compspec) that are based off of [Proposal C](https://github.com/opencontainers/wg-image-compatibility/pull/8) of the Compatibility Working Group. This is experimental because all of that is subject (and likely) to change.
7-
8-
## Design
9-
10-
The design is based on the prototype from that pull request, shown below.
11-
12-
![img/proposal-c-plugin-design.png](img/proposal-c-plugin-design.png)
13-
14-
Specifically, I'll try to do simple interfaces (in [plugins](plugins)) to:
15-
16-
- **Extract** Scan a system to collect compatibility metadata / attributes for a named set of extractions
17-
- this maybe isn't great practice, but if I build the containers I trust them
18-
- I can also do something simple like run this tool in a pod-> container small cluster
19-
- load in an artifact spec (json) from a URL
20-
- compare with a request for a specific system (with any level of detail desired)
21-
- "basic" might just be providing the architecture
22-
- "descriptive" might include other variables
23-
24-
The extraction step is also important, but likely this would happen at build time (maybe by another tool).
25-
For now (since this is a prototype) I'm going to just manually do it.
26-
27-
## Usage
28-
29-
### Build
30-
31-
Build the `compspec` binary with:
32-
33-
```bash
34-
make
35-
```
36-
37-
This generates the `bin/compspec` that you can use:
38-
39-
```bash
40-
./bin/compspec
41-
```
42-
```console
43-
┏┏┓┏┳┓┏┓┏┏┓┏┓┏
44-
┗┗┛┛┗┗┣┛┛┣┛┗ ┗
45-
┛ ┛
46-
47-
[sub]Command required
48-
usage: compspec <Command> [-h|--help] [-n|--name "<value>" [-n|--name "<value>"
49-
...]]
50-
51-
Compatibility checking for container images
52-
53-
Commands:
54-
55-
version See the version of compspec
56-
extract Run one or more extractors
57-
58-
Arguments:
59-
60-
-h --help Print help information
61-
-n --name One or more specific extractor plugin names
62-
```
63-
64-
65-
### Version
66-
67-
```bash
68-
$ ./bin/compspec version
69-
```
70-
```console
71-
⭐️ compspec version 0.1.0-draft
72-
```
73-
74-
I know, the star should not be there. Fight me.
75-
76-
### List
77-
78-
The list command lists each extractor, and sections available for it.
79-
80-
```bash
81-
$ ./bin/compspec list
82-
```
83-
```console
84-
Compatibility Plugins
85-
TYPE NAME SECTION
86-
extrator kernel boot
87-
extrator kernel config
88-
extrator kernel modules
89-
extrator system cpu
90-
extrator system processor
91-
extrator system os
92-
extrator library mpi
93-
TOTAL 7
94-
```
95-
96-
Note that we will eventually add a description column - it's not really warranted yet!
97-
98-
### Extract
99-
100-
If you want to extract metadata to your local machine, you can use extract! Either just run all extractors and dump to the terminal:
101-
102-
```bash
103-
# Not recommend, it's a lot!
104-
./bin/compspec extract
105-
```
106-
107-
Or target a specific one:
108-
109-
```bash
110-
./bin/compspec extract --name kernel
111-
```
112-
113-
Better, save to json file:
114-
115-
```bash
116-
./bin/compspec extract --name kernel -o test-kernel.json
117-
```
118-
119-
This has a better structure for inspecting easily (only the top of the file is shown):
120-
121-
<details>
122-
123-
<summary>Kernel JSON output</summary>
124-
125-
```json
126-
{
127-
"extractors": {
128-
"kernel": {
129-
"sections": {
130-
"boot": {
131-
"BOOT_IMAGE": "/boot/vmlinuz-6.1.0-1028-oem",
132-
"quiet": "",
133-
"ro": "",
134-
"root": "UUID",
135-
"splash": "",
136-
"vt.handoff": "7"
137-
},
138-
"config": {
139-
"CONFIG_104_QUAD_8": "m",
140-
"CONFIG_60XX_WDT": "m",
141-
"CONFIG_64BIT": "y",
142-
"CONFIG_6LOWPAN": "m"
143-
}
144-
}
145-
}
146-
}
147-
}
148-
```
149-
150-
</details>
151-
152-
An extractor is made up of sections, and you can ask for parsing just a specific one.
153-
154-
```bash
155-
./bin/compspec extract --name kernel[config]
156-
```
157-
```console
158-
⭐️ Running extract...
159-
--Result for kernel
160-
-- Section boot
161-
root: UUID
162-
ro:
163-
quiet:
164-
splash:
165-
vt.handoff: 7
166-
BOOT_IMAGE: /boot/vmlinuz-6.1.0-1028-oem
167-
Extraction has run!
168-
```
169-
170-
To ask for more than one, it's a comma separated list.
171-
172-
```bash
173-
./bin/compspec extract --name kernel[config,boot]
174-
```
175-
176-
The ordering of your list is honored.
177-
178-
## Developer
179-
180-
Note that there is a [developer environment](.devcontainer) that provides a consistent version of Go, etc.
181-
However, it won't work with all extractors. Note that for any command that uses a plugin (e.g., `extract` and `check`)
6+
[these build matrices](https://github.com/rse-ops/lammps-matrix) for LAMMPS and these prototype [specifications](https://github.com/supercontainers/compspec) that are based off of [Proposal C](https://github.com/opencontainers/wg-image-compatibility/pull/8) of the Compatibility Working Group. This is experimental because all of that is subject (and likely) to change. This project is under development, and you can see our [docs](docs) for early documentation.
1827

8+
- ⭐️ [Documentation](docs) ⭐️
1839

18410
### Limitations
18511

@@ -188,6 +14,9 @@ However, it won't work with all extractors. Note that for any command that uses
18814

18915
## TODO
19016

17+
- 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)
19+
- tests: matrix that has several different flavors of builds, generating compspec json output to validate generation and correctness
19120
- likely we want a common configuration file to take an extraction -> check recipe
19221
- need to develop check plugin family
19322
- todo thinking around manifest.yaml that has listing of images / artifacts
@@ -197,16 +26,8 @@ However, it won't work with all extractors. Note that for any command that uses
19726
A `*` indicates required for the work / prototype I want to do
19827

19928
- power usage data [valorium](https://ipo.llnl.gov/sites/default/files/2023-08/Final_variorum-rnd-100-award.pdf)
200-
- * architecture [archspec](https://github.com/archspec)
201-
- * MPI existence / variants
202-
- * operating system stuff
20329
- ... please add more!
20430

205-
206-
## Thanks and Previous Art
207-
208-
- I learned about kernel parsing from [mfranczy/compat](https://github.com/mfranczy/compat)
209-
21031
## License
21132

21233
This repository contains code derived from [sysinfo](https://github.com/zcalusic/sysinfo/tree/30169cfb37112a562cbf9133494a323764ad852c)

cmd/compspec/compspec.go

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"os"
77

88
"github.com/akamensky/argparse"
9+
"github.com/supercontainers/compspec-go/cmd/compspec/create"
910
"github.com/supercontainers/compspec-go/cmd/compspec/extract"
1011
"github.com/supercontainers/compspec-go/cmd/compspec/list"
1112
"github.com/supercontainers/compspec-go/pkg/types"
@@ -30,13 +31,19 @@ func main() {
3031
versionCmd := parser.NewCommand("version", "See the version of compspec")
3132
extractCmd := parser.NewCommand("extract", "Run one or more extractors")
3233
listCmd := parser.NewCommand("list", "List plugins and known sections")
34+
createCmd := parser.NewCommand("create", "Create a compatibility artifact for the current host according to a definition")
3335

3436
// Shared arguments (likely this will break into check and extract, shared for now)
3537
pluginNames := parser.StringList("n", "name", &argparse.Options{Help: "One or more specific plugins to target names"})
3638

3739
// Extract arguments
3840
filename := extractCmd.String("o", "out", &argparse.Options{Help: "Save extraction to json file"})
3941

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"})
46+
4047
// Now parse the arguments
4148
err := parser.Parse(os.Args)
4249
if err != nil {
@@ -48,10 +55,18 @@ func main() {
4855
if extractCmd.Happened() {
4956
err := extract.Run(*filename, *pluginNames)
5057
if err != nil {
51-
log.Fatalf("Issue with extraction: %s", err)
58+
log.Fatalf("Issue with extraction: %s\n", err)
59+
}
60+
} else if createCmd.Happened() {
61+
err := create.Run(*specname, *options, *specfile)
62+
if err != nil {
63+
log.Fatal(err.Error())
5264
}
5365
} else if listCmd.Happened() {
54-
list.Run(*pluginNames)
66+
err := list.Run(*pluginNames)
67+
if err != nil {
68+
log.Fatal(err.Error())
69+
}
5570
} else if versionCmd.Happened() {
5671
RunVersion()
5772
} else {

cmd/compspec/create/create.go

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
package create
2+
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+
27+
// Run will create a compatibility artifact based on a request in YAML
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+
}
67+
return nil
68+
}
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+
}

cmd/compspec/list/list.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import (
44
p "github.com/supercontainers/compspec-go/plugins"
55
)
66

7-
// Run will run an extraction of host metadata
7+
// Run will list the extractor names and sections known
88
func Run(pluginNames []string) error {
99
// parse [section,...,section] into named plugins and sections
1010
// return plugins

0 commit comments

Comments
 (0)