Skip to content

Commit 8e0f712

Browse files
authored
Merge pull request #14 from supercontainers/add-randomize
add support for cache and randomize
2 parents 55bef4f + c8a7ad4 commit 8e0f712

File tree

6 files changed

+188
-13
lines changed

6 files changed

+188
-13
lines changed

cmd/compspec/compspec.go

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,9 @@ func main() {
4949
printGraph := matchCmd.Flag("g", "print-graph", &argparse.Options{Help: "Print schema graph"})
5050
checkArtifacts := matchCmd.Flag("c", "check-artifacts", &argparse.Options{Help: "Check that all artifacts exist"})
5151
allowFailMatch := matchCmd.Flag("f", "allow-fail", &argparse.Options{Help: "Allow an artifact to be missing (and not included)"})
52+
randomize := matchCmd.Flag("r", "randomize", &argparse.Options{Help: "Shuffle match results in random order"})
53+
single := matchCmd.Flag("s", "single", &argparse.Options{Help: "Only return a single result"})
54+
cachePath := matchCmd.String("", "cache", &argparse.Options{Help: "A path to a cache for artifacts"})
5255

5356
// Create arguments
5457
options := createCmd.StringList("a", "append", &argparse.Options{Help: "Append one or more custom metadata fields to append"})
@@ -76,11 +79,21 @@ func main() {
7679
log.Fatal(err.Error())
7780
}
7881
} else if matchCmd.Happened() {
79-
err := match.Run(*manifestFile, *matchFields, *mediaType, *printMapping, *printGraph, *allowFailMatch, *checkArtifacts)
82+
err := match.Run(
83+
*manifestFile,
84+
*matchFields,
85+
*mediaType,
86+
*cachePath,
87+
*printMapping,
88+
*printGraph,
89+
*allowFailMatch,
90+
*checkArtifacts,
91+
*randomize,
92+
*single,
93+
)
8094
if err != nil {
8195
log.Fatal(err.Error())
8296
}
83-
8497
} else if listCmd.Happened() {
8598
err := list.Run(*pluginNames)
8699
if err != nil {

cmd/compspec/match/match.go

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"github.com/supercontainers/compspec-go/pkg/graph"
99
"github.com/supercontainers/compspec-go/pkg/oras"
1010
"github.com/supercontainers/compspec-go/pkg/types"
11+
"github.com/supercontainers/compspec-go/pkg/utils"
1112
"sigs.k8s.io/yaml"
1213
)
1314

@@ -36,10 +37,13 @@ func Run(
3637
manifestFile string,
3738
hostFields []string,
3839
mediaType string,
40+
cachePath string,
3941
printMapping bool,
4042
printGraph bool,
4143
allowFail bool,
4244
checkArtifacts bool,
45+
randomize bool,
46+
single bool,
4347
) error {
4448

4549
// Default media type if one not provided
@@ -71,7 +75,7 @@ func Run(
7175
lookup := map[string]types.CompatibilityRequest{}
7276
for _, item := range manifestList.Images {
7377

74-
compspec, err := oras.LoadArtifact(item.Artifact, mediaType)
78+
compspec, err := oras.LoadArtifact(item.Artifact, mediaType, cachePath)
7579
if err != nil {
7680
fmt.Printf("warning, there was an issue loading the artifact for %s: %s, skipping\n", item.Name, err)
7781

@@ -142,11 +146,23 @@ func Run(
142146
}
143147
if len(matches) == 0 {
144148
fmt.Println("There was no match. Try changig your constaints.")
145-
} else {
146-
fmt.Println(" --- Found matches ---")
147-
for _, match := range matches {
148-
fmt.Println(match)
149-
}
149+
return nil
150150
}
151+
152+
// Do we want to randomize (randomly shuffle the list)?
153+
if randomize {
154+
matches = utils.RandomSort(matches)
155+
}
156+
157+
// Do we want just one match?
158+
if single && len(matches) > 0 {
159+
matches = []string{matches[0]}
160+
}
161+
162+
fmt.Println(" --- Found matches ---")
163+
for _, match := range matches {
164+
fmt.Println(match)
165+
}
166+
151167
return nil
152168
}

docs/usage.md

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,46 @@ ghcr.io/rse-ops/lammps-matrix:intel-mpi-rocky-8-amd64
204204

205205
That is very simple, but should serve our purposes for now.
206206

207+
### Match with randomize or single
208+
209+
You can choose to shuffle the results:
210+
211+
```bash
212+
$ ./bin/compspec match -i ./examples/check-lammps/manifest.yaml --randomize
213+
```
214+
```console
215+
Schema io.archspec is being added to the graph
216+
Schema org.supercontainers is being added to the graph
217+
No field criteria provided, all images are matches.
218+
--- Found matches ---
219+
ghcr.io/rse-ops/lammps-matrix:openmpi-ubuntu-gpu-20.04
220+
ghcr.io/rse-ops/lammps-matrix:intel-mpi-rocky-9-amd64
221+
ghcr.io/rse-ops/lammps-matrix:intel-mpi-rocky-8-amd64
222+
ghcr.io/rse-ops/lammps-matrix:openmpi-ubuntu-gpu-22.04
223+
```
224+
Or do the same, but ask for only one result (note we might) change this to N results depending on use cases:
225+
226+
```bash
227+
$ ./bin/compspec match -i ./examples/check-lammps/manifest.yaml --randomize --single
228+
```
229+
```console
230+
Schema io.archspec is being added to the graph
231+
Schema org.supercontainers is being added to the graph
232+
No field criteria provided, all images are matches.
233+
--- Found matches ---
234+
ghcr.io/rse-ops/lammps-matrix:intel-mpi-rocky-9-amd64
235+
```
236+
237+
### Match and use cache
238+
239+
If you intend to run a match request many times (using repeated images) it's good practice to use a cache. This means you'll look in the cache before asking a registry, and save after if it doesn't exist. The directory must exist.
240+
241+
```bash
242+
mkdir -p ./cache
243+
./bin/compspec match -i ./examples/check-lammps/manifest.yaml --cache ./cache
244+
```
245+
246+
207247

208248
### Match to print Metadata attributes
209249

pkg/oras/oras.go

Lines changed: 97 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,117 @@ import (
44
"bytes"
55
"context"
66
"encoding/json"
7+
"fmt"
78
"io"
9+
"os"
10+
"path/filepath"
811
"strings"
912

1013
oci "github.com/opencontainers/image-spec/specs-go/v1"
1114
"github.com/supercontainers/compspec-go/pkg/types"
15+
"github.com/supercontainers/compspec-go/pkg/utils"
1216
"oras.land/oras-go/v2/content"
1317
"oras.land/oras-go/v2/registry/remote"
1418
"sigs.k8s.io/yaml"
1519
)
1620

21+
// toFilename converts the uri of an image to a filename
22+
func toFilename(uri string) string {
23+
for _, repl := range []string{"/", ":"} {
24+
uri = strings.ReplaceAll(uri, repl, "-")
25+
}
26+
return fmt.Sprintf("%s.json", uri)
27+
}
28+
29+
// LoadFromCache loads the compatibility request from cache
30+
func LoadFromCache(uri, cache string) (types.CompatibilityRequest, error) {
31+
var request types.CompatibilityRequest
32+
var err error
33+
cachePath := filepath.Join(cache, toFilename(uri))
34+
exists, err := utils.PathExists(cachePath)
35+
if err != nil {
36+
return request, err
37+
}
38+
if exists {
39+
fd, err := os.Open(cachePath)
40+
b, err := io.ReadAll(fd)
41+
if err != nil {
42+
return request, err
43+
}
44+
defer fd.Close()
45+
46+
err = json.Unmarshal(b, &request)
47+
}
48+
return request, err
49+
}
50+
1751
// Oras will provide an interface to retrieve an artifact, specifically
1852
// a compatibillity spec artifact media type
1953
// LoadArtifact retrieves the artifact from the url string
2054
// and returns based on the media type
21-
func LoadArtifact(uri string, mediaType string) (types.CompatibilityRequest, error) {
55+
func LoadArtifact(
56+
uri string,
57+
mediaType string,
58+
cache string,
59+
) (types.CompatibilityRequest, error) {
60+
61+
request := types.CompatibilityRequest{}
62+
var err error
63+
64+
// If cache is desired and we have the artifact
65+
if cache != "" {
66+
67+
// Must exist
68+
exists, err := utils.PathExists(cache)
69+
if err != nil {
70+
return request, err
71+
}
72+
if !exists {
73+
return request, fmt.Errorf("Cache path %s does not exist", cache)
74+
}
75+
request, err := LoadFromCache(uri, cache)
76+
if err != nil {
77+
return request, err
78+
}
79+
}
80+
81+
// If we didn't get matches, load from registry
82+
if request.Kind == "" {
83+
request, err = LoadFromRegistry(uri, mediaType)
84+
85+
// If we loaded it and have a cache, save to cache
86+
if cache != "" {
87+
err = SaveToCache(request, uri, cache)
88+
}
89+
}
90+
return request, err
91+
}
92+
93+
// Save to cache
94+
func SaveToCache(request types.CompatibilityRequest, uri, cache string) error {
95+
cachePath := filepath.Join(cache, toFilename(uri))
96+
exists, err := utils.PathExists(cachePath)
97+
if err != nil {
98+
return err
99+
}
100+
101+
// Don't overwrite
102+
if exists {
103+
return nil
104+
}
105+
content, err := json.Marshal(request)
106+
if err != nil {
107+
return err
108+
}
109+
err = os.WriteFile(cachePath, content, 0644)
110+
if err != nil {
111+
return err
112+
}
113+
return nil
114+
}
115+
116+
// Load the artifact from a registry
117+
func LoadFromRegistry(uri, mediaType string) (types.CompatibilityRequest, error) {
22118
request := types.CompatibilityRequest{}
23119
ctx := context.Background()
24120
repo, err := remote.NewRepository(uri)

pkg/utils/utils.go

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,19 @@ import (
44
"bufio"
55
"errors"
66
"fmt"
7+
"math/rand"
78
"os"
89
"strings"
910
)
1011

11-
// FileExists determines if a file exists
12-
func FileExists(path string) (bool, error) {
12+
// PathExists determines if a path exists
13+
func PathExists(path string) (bool, error) {
1314
_, err := os.Stat(path)
1415
if err != nil {
1516
if errors.Is(err, os.ErrNotExist) {
1617
return false, nil
1718
}
18-
return true, fmt.Errorf("warning: file exists but another error happened (debug): %s", err)
19+
return true, fmt.Errorf("warning: exists but another error happened (debug): %s", err)
1920
}
2021
return true, nil
2122
}
@@ -107,3 +108,12 @@ func ParseConfigFile(path, comment, delim string) (map[string]string, error) {
107108
}
108109
return data, nil
109110
}
111+
112+
// RandomSort an array of strings, in place
113+
func RandomSort(items []string) []string {
114+
for i := range items {
115+
j := rand.Intn(i + 1)
116+
items[i], items[j] = items[j], items[i]
117+
}
118+
return items
119+
}

plugins/extractors/system/arch.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ func getOsArch() (string, error) {
2323

2424
// Detect OS architecture based on presence of this file
2525
for arch, path := range linkerPaths {
26-
exists, err := utils.FileExists(path)
26+
exists, err := utils.PathExists(path)
2727
if err != nil {
2828
return "", err
2929
}

0 commit comments

Comments
 (0)