diff --git a/native/go.mod b/native/go.mod index 353b7bae..e1b5a970 100644 --- a/native/go.mod +++ b/native/go.mod @@ -29,6 +29,7 @@ require ( github.com/Masterminds/semver/v3 v3.2.1 // indirect github.com/Masterminds/sprig/v3 v3.2.3 // indirect github.com/Masterminds/squirrel v1.5.4 // indirect + github.com/Masterminds/vcs v1.13.3 // indirect github.com/Microsoft/hcsshim v0.11.4 // indirect github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535 // indirect github.com/beorn7/perks v1.0.1 // indirect @@ -39,6 +40,7 @@ require ( github.com/containerd/containerd v1.7.12 // indirect github.com/containerd/log v0.1.0 // indirect github.com/coreos/go-systemd/v22 v22.5.0 // indirect + github.com/cpuguy83/go-md2man/v2 v2.0.3 // indirect github.com/cyphar/filepath-securejoin v0.2.4 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect @@ -57,6 +59,7 @@ require ( github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d // indirect github.com/fatih/color v1.13.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect + github.com/fvbommel/sortorder v1.1.0 // indirect github.com/go-errors/errors v1.4.2 // indirect github.com/go-gorp/gorp/v3 v3.1.0 // indirect github.com/go-logr/logr v1.4.1 // indirect diff --git a/native/go.sum b/native/go.sum index 44acf7fe..30179629 100644 --- a/native/go.sum +++ b/native/go.sum @@ -19,6 +19,8 @@ github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBaRMhvYXJNkGuM= github.com/Masterminds/squirrel v1.5.4 h1:uUcX/aBc8O7Fg9kaISIUsHXdKuqehiXAMQTYX8afzqM= github.com/Masterminds/squirrel v1.5.4/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10= +github.com/Masterminds/vcs v1.13.3 h1:IIA2aBdXvfbIM+yl/eTnL4hb1XwdpvuQLglAix1gweE= +github.com/Masterminds/vcs v1.13.3/go.mod h1:TiE7xuEjl1N4j016moRd6vezp6e6Lz23gypeXfzXeW8= github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= github.com/Microsoft/hcsshim v0.11.4 h1:68vKo2VN8DE9AdN4tnkWnmdhqdbpUFM8OF3Airm7fz8= @@ -61,6 +63,7 @@ github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/cpuguy83/go-md2man/v2 v2.0.3 h1:qMCsGGgs+MAzDFyp9LpAe1Lqy/fY/qCovCm0qnXZOBM= github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= @@ -115,6 +118,8 @@ github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3 github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= +github.com/fvbommel/sortorder v1.1.0 h1:fUmoe+HLsBTctBDoaBwpQo5N+nrCp8g/BjKb/6ZQmYw= +github.com/fvbommel/sortorder v1.1.0/go.mod h1:uk88iVf1ovNn1iLfgUVU2F9o5eO30ui720w+kxuqRs0= github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= github.com/go-gorp/gorp/v3 v3.1.0 h1:ItKF/Vbuj31dmV4jxA1qblpSwkl9g1typ24xoe70IGs= @@ -275,6 +280,8 @@ github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPn github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebGE2xrk= +github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/mattn/go-sqlite3 v1.14.15 h1:vfoHhTN1af61xCRSWzFIWzx2YskyMTwHLrExkBOjvxI= github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= diff --git a/native/internal/helm/search.go b/native/internal/helm/search.go new file mode 100644 index 00000000..d562ac8a --- /dev/null +++ b/native/internal/helm/search.go @@ -0,0 +1,82 @@ +package helm + +import ( + "bytes" + "fmt" + "github.com/Masterminds/semver/v3" + "github.com/pkg/errors" + "helm.sh/helm/v3/cmd/helm/search" + "helm.sh/helm/v3/pkg/helmpath" + "helm.sh/helm/v3/pkg/repo" + "net/url" + "path/filepath" + "strconv" +) + +type SearchOptions struct { + RepositoryConfig string + Keyword string + Regexp bool + Devel bool + Version string +} + +// https://github.com/helm/helm/blob/0e72b64797da47c33537d0a8519c9c2e9e6c9362/cmd/helm/search_repo.go#L63 +// Implementation logic is in cmd package +// searchMaxScore suggests that any score higher than this is not considered a match. +const searchMaxScore = 25 + +func SearchRepo(options *SearchOptions) (string, error) { + // https://github.com/helm/helm/blob/0e72b64797da47c33537d0a8519c9c2e9e6c9362/cmd/helm/search_repo.go#L104 + // Implementation logic is in cmd package + // Setup version + if options.Version == "" { + if options.Devel { + options.Version = ">0.0.0-0" + } else { + options.Version = ">0.0.0" + } + } + repositoriesYaml, err := repo.LoadFile(repositoryConfig(&RepoOptions{ + RepositoryConfig: options.RepositoryConfig, + })) + if err != nil { + return "", err + } + searchIndex := search.NewIndex() + for _, repository := range repositoriesYaml.Repositories { + name := repository.Name + indexPath := filepath.Join(helmpath.CachePath("repository"), helmpath.CacheIndexFile(name)) + index, err := repo.LoadIndexFile(indexPath) + if err != nil { + // TODO: see how to propagate warnings to the Java implementation + continue + } + searchIndex.AddRepo(name, index, true) + } + var searchResults []*search.Result + if len(options.Keyword) == 0 { + searchResults = searchIndex.All() + } else { + searchResults, err = searchIndex.Search(options.Keyword, searchMaxScore, options.Regexp) + } + search.SortScore(searchResults) + constraint, err := semver.NewConstraint(options.Version) + if err != nil { + return "", errors.Wrap(err, "an invalid version/constraint format") + } + out := bytes.NewBuffer(make([]byte, 0)) + for _, searchResult := range searchResults { + v, err := semver.NewVersion(searchResult.Chart.Version) + if err == nil && constraint.Check(v) { + values := make(url.Values) + values.Set("name", searchResult.Name) + values.Set("score", strconv.Itoa(searchResult.Score)) + values.Set("chartVersion", searchResult.Chart.Version) + values.Set("chartAppVersion", searchResult.Chart.AppVersion) + values.Set("chartDescription", searchResult.Chart.Description) + _, _ = fmt.Fprintln(out, values.Encode()) + } + } + return out.String(), nil +} diff --git a/native/main.go b/native/main.go index 4a6c1f07..b6fa0b6c 100644 --- a/native/main.go +++ b/native/main.go @@ -119,6 +119,14 @@ struct RepoServerOptions { char* password; }; +struct SearchOptions { + char* repositoryConfig; + char* keyword; + int regexp; + int devel; + char* version; +}; + struct ShowOptions { char* path; char* outputFormat; @@ -471,6 +479,19 @@ func RepoServerStopAll() C.Result { }) } +//export SearchRepo +func SearchRepo(options *C.struct_SearchOptions) C.Result { + return runCommand(func() (string, error) { + return helm.SearchRepo(&helm.SearchOptions{ + RepositoryConfig: C.GoString(options.repositoryConfig), + Keyword: C.GoString(options.keyword), + Regexp: options.regexp == 1, + Devel: options.devel == 1, + Version: C.GoString(options.version), + }) + }) +} + //export Show func Show(options *C.struct_ShowOptions) C.Result { return runCommand(func() (string, error) { diff --git a/native/main_test.go b/native/main_test.go index cb89abe4..43aa7e23 100644 --- a/native/main_test.go +++ b/native/main_test.go @@ -548,3 +548,33 @@ func TestRepoServerStopAll(t *testing.T) { t.Errorf("Expected all servers to be stopped") } } + +func TestSearchRepo(t *testing.T) { + repositoryConfigFile, _ := os.CreateTemp("", "repositories.yaml") + err := helm.RepoAdd(&helm.RepoOptions{ + Name: "helm", + Url: "https://charts.helm.sh/stable", + InsecureSkipTlsVerify: true, + RepositoryConfig: repositoryConfigFile.Name(), + }) + out, err := helm.SearchRepo(&helm.SearchOptions{ + Keyword: "nginx", + RepositoryConfig: repositoryConfigFile.Name(), + }) + if err != nil { + t.Errorf("Expected search to succeed, got %s", err) + return + } + if !strings.Contains(out, "chartAppVersion=") { + t.Errorf("Expected search to contain 'chartAppVersion=', got %s", out) + return + } + if !strings.Contains(out, "&chartDescription=") { + t.Errorf("Expected search to contain '&chartDescription=', got %s", out) + return + } + if !strings.Contains(out, "&name=") { + t.Errorf("Expected search to contain '&name=', got %s", out) + return + } +}