Merge pull request #12245 from hiddeco/json-index

Add support for creating repository indexes in JSON format
pull/12352/head
Joe Julian 1 year ago committed by GitHub
commit e7bb860d9a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -43,6 +43,7 @@ type repoIndexOptions struct {
dir string dir string
url string url string
merge string merge string
json bool
} }
func newRepoIndexCmd(out io.Writer) *cobra.Command { func newRepoIndexCmd(out io.Writer) *cobra.Command {
@ -70,6 +71,7 @@ func newRepoIndexCmd(out io.Writer) *cobra.Command {
f := cmd.Flags() f := cmd.Flags()
f.StringVar(&o.url, "url", "", "url of chart repository") f.StringVar(&o.url, "url", "", "url of chart repository")
f.StringVar(&o.merge, "merge", "", "merge the generated index into the given index") f.StringVar(&o.merge, "merge", "", "merge the generated index into the given index")
f.BoolVar(&o.json, "json", false, "output in JSON format")
return cmd return cmd
} }
@ -80,10 +82,10 @@ func (i *repoIndexOptions) run(out io.Writer) error {
return err return err
} }
return index(path, i.url, i.merge) return index(path, i.url, i.merge, i.json)
} }
func index(dir, url, mergeTo string) error { func index(dir, url, mergeTo string, json bool) error {
out := filepath.Join(dir, "index.yaml") out := filepath.Join(dir, "index.yaml")
i, err := repo.IndexDirectory(dir, url) i, err := repo.IndexDirectory(dir, url)
@ -95,7 +97,7 @@ func index(dir, url, mergeTo string) error {
var i2 *repo.IndexFile var i2 *repo.IndexFile
if _, err := os.Stat(mergeTo); os.IsNotExist(err) { if _, err := os.Stat(mergeTo); os.IsNotExist(err) {
i2 = repo.NewIndexFile() i2 = repo.NewIndexFile()
i2.WriteFile(mergeTo, 0644) writeIndexFile(i2, mergeTo, json)
} else { } else {
i2, err = repo.LoadIndexFile(mergeTo) i2, err = repo.LoadIndexFile(mergeTo)
if err != nil { if err != nil {
@ -105,5 +107,12 @@ func index(dir, url, mergeTo string) error {
i.Merge(i2) i.Merge(i2)
} }
i.SortEntries() i.SortEntries()
return writeIndexFile(i, out, json)
}
func writeIndexFile(i *repo.IndexFile, out string, json bool) error {
if json {
return i.WriteJSONFile(out, 0644)
}
return i.WriteFile(out, 0644) return i.WriteFile(out, 0644)
} }

@ -18,6 +18,7 @@ package main
import ( import (
"bytes" "bytes"
"encoding/json"
"io" "io"
"os" "os"
"path/filepath" "path/filepath"
@ -68,6 +69,28 @@ func TestRepoIndexCmd(t *testing.T) {
t.Errorf("expected %q, got %q", expectedVersion, vs[0].Version) t.Errorf("expected %q, got %q", expectedVersion, vs[0].Version)
} }
b, err := os.ReadFile(destIndex)
if err != nil {
t.Fatal(err)
}
if json.Valid(b) {
t.Error("did not expect index file to be valid json")
}
// Test with `--json`
c.ParseFlags([]string{"--json", "true"})
if err := c.RunE(c, []string{dir}); err != nil {
t.Error(err)
}
if b, err = os.ReadFile(destIndex); err != nil {
t.Fatal(err)
}
if !json.Valid(b) {
t.Error("index file is not valid json")
}
// Test with `--merge` // Test with `--merge`
// Remove first two charts. // Remove first two charts.

@ -18,6 +18,7 @@ package repo
import ( import (
"bytes" "bytes"
"encoding/json"
"log" "log"
"os" "os"
"path" "path"
@ -232,6 +233,18 @@ func (i IndexFile) WriteFile(dest string, mode os.FileMode) error {
return fileutil.AtomicWriteFile(dest, bytes.NewReader(b), mode) return fileutil.AtomicWriteFile(dest, bytes.NewReader(b), mode)
} }
// WriteJSONFile writes an index file in JSON format to the given destination
// path.
//
// The mode on the file is set to 'mode'.
func (i IndexFile) WriteJSONFile(dest string, mode os.FileMode) error {
b, err := json.MarshalIndent(i, "", " ")
if err != nil {
return err
}
return fileutil.AtomicWriteFile(dest, bytes.NewReader(b), mode)
}
// Merge merges the given index file into this index. // Merge merges the given index file into this index.
// //
// This merges by name and version. // This merges by name and version.
@ -336,7 +349,7 @@ func loadIndex(data []byte, source string) (*IndexFile, error) {
return i, ErrEmptyIndexYaml return i, ErrEmptyIndexYaml
} }
if err := yaml.UnmarshalStrict(data, i); err != nil { if err := jsonOrYamlUnmarshal(data, i); err != nil {
return i, err return i, err
} }
@ -361,3 +374,17 @@ func loadIndex(data []byte, source string) (*IndexFile, error) {
} }
return i, nil return i, nil
} }
// jsonOrYamlUnmarshal unmarshals the given byte slice containing JSON or YAML
// into the provided interface.
//
// It automatically detects whether the data is in JSON or YAML format by
// checking its validity as JSON. If the data is valid JSON, it will use the
// `encoding/json` package to unmarshal it. Otherwise, it will use the
// `sigs.k8s.io/yaml` package to unmarshal the YAML data.
func jsonOrYamlUnmarshal(b []byte, i interface{}) error {
if json.Valid(b) {
return json.Unmarshal(b, i)
}
return yaml.UnmarshalStrict(b, i)
}

@ -19,6 +19,7 @@ package repo
import ( import (
"bufio" "bufio"
"bytes" "bytes"
"encoding/json"
"net/http" "net/http"
"os" "os"
"path/filepath" "path/filepath"
@ -37,6 +38,7 @@ const (
annotationstestfile = "testdata/local-index-annotations.yaml" annotationstestfile = "testdata/local-index-annotations.yaml"
chartmuseumtestfile = "testdata/chartmuseum-index.yaml" chartmuseumtestfile = "testdata/chartmuseum-index.yaml"
unorderedTestfile = "testdata/local-index-unordered.yaml" unorderedTestfile = "testdata/local-index-unordered.yaml"
jsonTestfile = "testdata/local-index.json"
testRepo = "test-repo" testRepo = "test-repo"
indexWithDuplicates = ` indexWithDuplicates = `
apiVersion: v1 apiVersion: v1
@ -145,6 +147,10 @@ func TestLoadIndex(t *testing.T) {
Name: "chartmuseum index file", Name: "chartmuseum index file",
Filename: chartmuseumtestfile, Filename: chartmuseumtestfile,
}, },
{
Name: "JSON index file",
Filename: jsonTestfile,
},
} }
for _, tc := range tests { for _, tc := range tests {
@ -548,6 +554,27 @@ func TestIndexWrite(t *testing.T) {
} }
} }
func TestIndexJSONWrite(t *testing.T) {
i := NewIndexFile()
if err := i.MustAdd(&chart.Metadata{APIVersion: "v2", Name: "clipper", Version: "0.1.0"}, "clipper-0.1.0.tgz", "http://example.com/charts", "sha256:1234567890"); err != nil {
t.Fatalf("unexpected error: %s", err)
}
dir := t.TempDir()
testpath := filepath.Join(dir, "test")
i.WriteJSONFile(testpath, 0600)
got, err := os.ReadFile(testpath)
if err != nil {
t.Fatal(err)
}
if !json.Valid(got) {
t.Fatal("Index files doesn't contain valid JSON")
}
if !strings.Contains(string(got), "clipper-0.1.0.tgz") {
t.Fatal("Index files doesn't contain expected content")
}
}
func TestAddFileIndexEntriesNil(t *testing.T) { func TestAddFileIndexEntriesNil(t *testing.T) {
i := NewIndexFile() i := NewIndexFile()
i.APIVersion = chart.APIVersionV1 i.APIVersion = chart.APIVersionV1

@ -0,0 +1,53 @@
{
"apiVersion": "v1",
"entries": {
"nginx": [
{
"urls": ["https://charts.helm.sh/stable/nginx-0.2.0.tgz"],
"name": "nginx",
"description": "string",
"version": "0.2.0",
"home": "https://github.com/something/else",
"digest": "sha256:1234567890abcdef",
"keywords": ["popular", "web server", "proxy"],
"apiVersion": "v2"
},
{
"urls": ["https://charts.helm.sh/stable/nginx-0.1.0.tgz"],
"name": "nginx",
"description": "string",
"version": "0.1.0",
"home": "https://github.com/something",
"digest": "sha256:1234567890abcdef",
"keywords": ["popular", "web server", "proxy"],
"apiVersion": "v2"
}
],
"alpine": [
{
"urls": [
"https://charts.helm.sh/stable/alpine-1.0.0.tgz",
"http://storage2.googleapis.com/kubernetes-charts/alpine-1.0.0.tgz"
],
"name": "alpine",
"description": "string",
"version": "1.0.0",
"home": "https://github.com/something",
"keywords": ["linux", "alpine", "small", "sumtin"],
"digest": "sha256:1234567890abcdef",
"apiVersion": "v2"
}
],
"chartWithNoURL": [
{
"name": "chartWithNoURL",
"description": "string",
"version": "1.0.0",
"home": "https://github.com/something",
"keywords": ["small", "sumtin"],
"digest": "sha256:1234567890abcdef",
"apiVersion": "v2"
}
]
}
}
Loading…
Cancel
Save