Skip to content

Commit c8a7ad4

Browse files
committed
add support for cache and randomize
If we are going to make many requests, it is good practice to cache the artifacts. This adds a parameter to do that, along with one to randomize results and return a single result Signed-off-by: vsoch <vsoch@users.noreply.github.com>
1 parent 55bef4f commit c8a7ad4

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)