diff --git a/cmd/kndp/main.go b/cmd/kndp/main.go index 59a5350..e2d8b1d 100644 --- a/cmd/kndp/main.go +++ b/cmd/kndp/main.go @@ -71,6 +71,7 @@ type cli struct { Registry registry.Cmd `cmd:"" name:"registry" aliases:"reg" help:"Packages registy commands"` InstallCompletions kongplete.InstallCompletions `cmd:"" help:"Install shell completions"` Provider provider.Cmd `cmd:"" name:"provider" help:"KNDP Provider commands"` + Search registry.SearchCmd `cmd:"" help:"Search for packages"` } type helpCmd struct{} diff --git a/cmd/kndp/registry/search.go b/cmd/kndp/registry/search.go new file mode 100644 index 0000000..14a3019 --- /dev/null +++ b/cmd/kndp/registry/search.go @@ -0,0 +1,31 @@ +package registry + +import ( + "context" + + "github.com/charmbracelet/log" + "github.com/kndpio/kndp/internal/search" + "github.com/pterm/pterm" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/rest" +) + +// SearchCmd is the struct representing the search command +type SearchCmd struct { + // Query is the search query + Query string `arg:"" help:"search query"` + Versions bool `optional:"" short:"v" help:"display all versions"` +} + +func (c *SearchCmd) Run(ctx context.Context, client *kubernetes.Clientset, config *rest.Config, logger *log.Logger) error { + tableRegs, err := search.SearchPackages(ctx, client, config, c.Query, c.Versions, logger) + if err != nil { + return err + } + if len(tableRegs) <= 1 { + logger.Info("No packages found") + } else { + pterm.DefaultTable.WithHasHeader().WithData(tableRegs).Render() + } + return nil +} diff --git a/go.mod b/go.mod index b2c86dd..1d2b474 100644 --- a/go.mod +++ b/go.mod @@ -76,6 +76,7 @@ require ( github.com/google/btree v1.0.1 // indirect github.com/google/gnostic-models v0.6.8 // indirect github.com/google/go-cmp v0.6.0 // indirect + github.com/google/go-querystring v1.1.0 // indirect github.com/google/gofuzz v1.2.0 // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect github.com/google/uuid v1.4.0 // indirect @@ -191,6 +192,7 @@ require ( github.com/ghodss/yaml v1.0.0 github.com/go-playground/validator/v10 v10.18.0 github.com/google/go-containerregistry v0.19.0 + github.com/google/go-github/v61 v61.0.0 github.com/mattn/go-runewidth v0.0.15 // indirect github.com/pterm/pterm v0.12.79 github.com/rivo/uniseg v0.4.4 // indirect diff --git a/go.sum b/go.sum index 2a0e54a..eb5d362 100644 --- a/go.sum +++ b/go.sum @@ -244,11 +244,16 @@ github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-containerregistry v0.19.0 h1:uIsMRBV7m/HDkDxE/nXMnv1q+lOOSPlQ/ywc5JbB8Ic= github.com/google/go-containerregistry v0.19.0/go.mod h1:u0qB2l7mvtWVR5kNcbFIhFY1hLbf8eeGapA+vbFDCtQ= +github.com/google/go-github/v61 v61.0.0 h1:VwQCBwhyE9JclCI+22/7mLB1PuU9eowCXKY5pNlu1go= +github.com/google/go-github/v61 v61.0.0/go.mod h1:0WR+KmsWX75G2EbpyGsGmradjo3IiciuI4BmdVCobQY= +github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= +github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= diff --git a/internal/github/github.go b/internal/github/github.go new file mode 100644 index 0000000..7aafada --- /dev/null +++ b/internal/github/github.go @@ -0,0 +1,64 @@ +package github + +import ( + "context" + "encoding/json" + "strings" + + "github.com/charmbracelet/log" + "github.com/kndpio/kndp/internal/registry" + "github.com/pterm/pterm" + + "github.com/google/go-github/v61/github" +) + +// GetPackages list packages and their versions from Container Registry +func GetPackages(ctx context.Context, query string, version bool, r *registry.Registry, registryUrl string, org string, logger *log.Logger) (pterm.TableData, error) { + auth := registry.RegistryConfig{} + json.Unmarshal([]byte(r.Data[".dockerconfigjson"]), &auth) + clientgh := github.NewClient(nil).WithAuthToken(auth.Auths[registryUrl].Password) + tableRegs := pterm.TableData{ + {"URL", "VERSION"}, + } + pkgType := "container" + + pkgs, _, err := clientgh.Organizations.ListPackages(ctx, org, &github.PackageListOptions{PackageType: &pkgType}) + if err != nil { + logger.Errorf("Cannot get packages from %s", registryUrl) + return nil, err + } + + pkgVersions := make(map[string][]*github.PackageVersion) + for _, pkg := range pkgs { + versions, _, err := clientgh.Organizations.PackageGetAllVersions(ctx, org, pkgType, pkg.GetName(), nil) + if err != nil { + logger.Errorf("Cannot get package versions for %s/%s", org, *pkg.Name) + return nil, err + } + if !version { + if len(versions) > 0 { + pkgVersions[*pkg.Name] = []*github.PackageVersion{versions[0]} + } + } else { + pkgVersions[*pkg.Name] = versions + } + } + + for _, pkg := range pkgs { + if !strings.Contains(*pkg.Name, query) { + continue + } + versions := pkgVersions[*pkg.Name] + for _, v := range versions { + tags := v.GetMetadata().Container.Tags + if len(tags) > 0 { + tableRegs = append(tableRegs, []string{ + "ghcr.io/" + org + "/" + *pkg.Name, + tags[0], + }) + } + } + } + + return tableRegs, nil +} diff --git a/internal/search/search.go b/internal/search/search.go new file mode 100644 index 0000000..3388974 --- /dev/null +++ b/internal/search/search.go @@ -0,0 +1,42 @@ +package search + +import ( + "context" + "net/url" + "strings" + + "github.com/charmbracelet/log" + "github.com/pterm/pterm" + + "github.com/kndpio/kndp/internal/github" + "github.com/kndpio/kndp/internal/registry" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/rest" +) + +func SearchPackages(ctx context.Context, client *kubernetes.Clientset, config *rest.Config, query string, versions bool, logger *log.Logger) (pterm.TableData, error) { + registries, err := registry.Registries(ctx, client) + if err != nil { + logger.Error("Cannot get registries") + return nil, err + } + + for _, r := range registries { + registryUrl := r.Annotations["kndp-registry-server-url"] + u, _ := url.Parse(registryUrl) + org := strings.TrimPrefix(u.Path, "/") + // Switch statement to handle different registry types + switch { + case strings.Contains(registryUrl, "ghcr.io"): + tableRegs, err := github.GetPackages(ctx, query, versions, r, registryUrl, org, logger) + if err != nil { + return nil, err + } + return tableRegs, nil + default: + logger.Errorf("Registry %s is not supported", registryUrl) + } + + } + return nil, err +}