Skip to content

Commit

Permalink
updates
Browse files Browse the repository at this point in the history
Signed-off-by: Bryce Palmer <everettraven@gmail.com>
  • Loading branch information
everettraven committed Nov 15, 2023
1 parent 0820352 commit e1e4e56
Show file tree
Hide file tree
Showing 6 changed files with 282 additions and 187 deletions.
4 changes: 4 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,10 @@ run: generate kind-cluster install ## Create a kind cluster and install a local
build-container: build-linux ## Build docker image for catalogd.
docker build -f Dockerfile -t $(IMAGE) bin/linux

.PHONY: build-cli
build-cli: ## Build the catalogd CLI
go build -o bin/catalogd ./cmd/cli/main.go

##@ Deploy

.PHONY: kind-cluster
Expand Down
166 changes: 166 additions & 0 deletions internal/cli/helpers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
package cli

import (
"context"
"fmt"
"net/url"
"strings"

"github.com/charmbracelet/glamour"
"github.com/operator-framework/catalogd/api/core/v1alpha1"
"github.com/operator-framework/operator-registry/alpha/declcfg"
"k8s.io/apimachinery/pkg/api/meta"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/dynamic"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
)

func CatalogdTermRenderer(style string) (*glamour.TermRenderer, error) {
sc := glamour.DefaultStyles[style]
sc.Document.BlockSuffix = ""
sc.Document.BlockPrefix = ""
return glamour.NewTermRenderer(glamour.WithStyles(*sc))
}

type CatalogFilterFunc func(catalog *v1alpha1.Catalog) bool

func FetchCatalogs(cfg *rest.Config, ctx context.Context, filters ...CatalogFilterFunc) ([]v1alpha1.Catalog, error) {
dynamicClient := dynamic.NewForConfigOrDie(cfg)

catalogList := &v1alpha1.CatalogList{}
unstructCatalogs, err := dynamicClient.Resource(v1alpha1.GroupVersion.WithResource("catalogs")).List(ctx, v1.ListOptions{})
if err != nil {
return nil, err
}

err = runtime.DefaultUnstructuredConverter.FromUnstructured(unstructCatalogs.UnstructuredContent(), catalogList)
if err != nil {
return nil, err
}

catalogs := []v1alpha1.Catalog{}
for _, catalog := range catalogList.Items {
for _, filter := range filters {
if !filter(&catalog) {
continue
}
}

catalogs = append(catalogs, catalog)
}

return catalogs, nil
}

func WithNameCatalogFilter(name string) CatalogFilterFunc {
return func(catalog *v1alpha1.Catalog) bool {
if name == "" {
return true
}
return catalog.Name == name
}
}

type ContentFilterFunc func(meta *declcfg.Meta) bool
type WriteFunc func(meta *declcfg.Meta, catalog *v1alpha1.Catalog) error

func WriteContents(cfg *rest.Config, ctx context.Context, catalogs []v1alpha1.Catalog, writeFunc WriteFunc, filters ...ContentFilterFunc) error {
kubeClient := kubernetes.NewForConfigOrDie(cfg)
for _, catalog := range catalogs {
if !meta.IsStatusConditionTrue(catalog.Status.Conditions, v1alpha1.TypeUnpacked) {
continue
}

url, err := url.Parse(catalog.Status.ContentURL)
if err != nil {
return fmt.Errorf("parsing catalog content url for catalog %q: %w", catalog.Name, err)
}
// url is expected to be in the format of
// http://{service_name}.{namespace}.svc/{catalog_name}/all.json
// so to get the namespace and name of the service we grab only
// the hostname and split it on the '.' character
ns := strings.Split(url.Hostname(), ".")[1]
name := strings.Split(url.Hostname(), ".")[0]
port := url.Port()
// the ProxyGet() call below needs an explicit port value, so if
// value from url.Port() is empty, we assume port 80.
if port == "" {
port = "80"
}

rw := kubeClient.CoreV1().Services(ns).ProxyGet(
url.Scheme,
name,
port,
url.Path,
map[string]string{},
)

rc, err := rw.Stream(ctx)
if err != nil {
return fmt.Errorf("getting catalog contents for catalog %q: %w", catalog.Name, err)
}
defer rc.Close()

err = declcfg.WalkMetasReader(rc, func(meta *declcfg.Meta, err error) error {
if err != nil {
return err
}

for _, filter := range filters {
if !filter(meta) {
return nil
}
}

writeErr := writeFunc(meta, &catalog)
if writeErr != nil {
return writeErr
}
return nil
})
if err != nil {
return fmt.Errorf("reading FBC for catalog %q: %w", catalog.Name, err)
}
}

return nil
}

func WithSchemaContentFilter(schema string) ContentFilterFunc {
return func(meta *declcfg.Meta) bool {
if schema == "" {
return true
}
return meta.Schema == schema
}
}

func WithPackageContentFilter(pkg string) ContentFilterFunc {
return func(meta *declcfg.Meta) bool {
if pkg == "" {
return true
}
return meta.Package == pkg
}
}

func WithNameContentFilter(name string) ContentFilterFunc {
return func(meta *declcfg.Meta) bool {
if name == "" {
return true
}
return meta.Name == name
}
}

func WithNameContainsContentFilter(name string) ContentFilterFunc {
return func(meta *declcfg.Meta) bool {
if name == "" {
return true
}
return strings.Contains(meta.Name, name)
}
}
123 changes: 25 additions & 98 deletions internal/cli/inspect.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,14 @@ package cli
import (
"context"
"encoding/json"
"fmt"
"strings"
"os"

"github.com/charmbracelet/glamour"
"github.com/alecthomas/chroma/quick"
"github.com/operator-framework/catalogd/api/core/v1alpha1"
"github.com/operator-framework/operator-registry/alpha/declcfg"
"github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/api/meta"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/dynamic"
"k8s.io/client-go/kubernetes"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/yaml"
)

var inspectCmd = cobra.Command{
Expand All @@ -25,117 +20,49 @@ var inspectCmd = cobra.Command{
RunE: func(cmd *cobra.Command, args []string) error {
pkg, _ := cmd.Flags().GetString("package")
catalog, _ := cmd.Flags().GetString("catalog")
output, _ := cmd.Flags().GetString("output")
schema := args[0]
name := args[1]
return inspect(schema, pkg, name, catalog)
return inspect(schema, pkg, name, catalog, output)
},
}

func init() {
inspectCmd.Flags().String("package", "", "specify the FBC object package that should be used to filter the resulting output")
inspectCmd.Flags().String("catalog", "", "specify the catalog that should be used. By default it will fetch from all catalogs")
inspectCmd.Flags().String("output", "json", "specify the output format. Valid values are 'json' and 'yaml'")
}

func inspect(schema, pkg, name, catalogName string) error {
sc := glamour.DraculaStyleConfig
sc.Document.BlockSuffix = ""
sc.Document.BlockPrefix = ""
tr, err := glamour.NewTermRenderer(glamour.WithStyles(sc))
if err != nil {
return err
}

func inspect(schema, pkg, name, catalogName, out string) error {
cfg := ctrl.GetConfigOrDie()
kubeClient := kubernetes.NewForConfigOrDie(cfg)
dynamicClient := dynamic.NewForConfigOrDie(cfg)
ctx := context.Background()

catalogs := &v1alpha1.CatalogList{}
if catalogName == "" {
// get Catalog list
unstructCatalogs, err := dynamicClient.Resource(v1alpha1.GroupVersion.WithResource("catalogs")).List(ctx, v1.ListOptions{})
if err != nil {
return err
}

err = runtime.DefaultUnstructuredConverter.FromUnstructured(unstructCatalogs.UnstructuredContent(), catalogs)
if err != nil {
return err
}
} else {
// get Catalog
unstructCatalog, err := dynamicClient.Resource(v1alpha1.GroupVersion.WithResource("catalogs")).Get(ctx, catalogName, v1.GetOptions{})
if err != nil {
return err
}

ctlg := v1alpha1.Catalog{}
err = runtime.DefaultUnstructuredConverter.FromUnstructured(unstructCatalog.UnstructuredContent(), &ctlg)
if err != nil {
return err
}
catalogs.Items = append(catalogs.Items, ctlg)
catalogs, err := FetchCatalogs(cfg, ctx, WithNameCatalogFilter(catalogName))
if err != nil {
return err
}

for _, catalog := range catalogs.Items {
if !meta.IsStatusConditionTrue(catalog.Status.Conditions, v1alpha1.TypeUnpacked) {
continue
}

rw := kubeClient.CoreV1().Services("catalogd-system").ProxyGet(
"http",
"catalogd-catalogserver",
"80",
fmt.Sprintf("catalogs/%s/all.json", catalog.Name),
map[string]string{},
)

rc, err := rw.Stream(ctx)
if err != nil {
return fmt.Errorf("getting catalog contents for catalog %q: %w", catalog.Name, err)
}
defer rc.Close()

err = declcfg.WalkMetasReader(rc, func(meta *declcfg.Meta, err error) error {
err = WriteContents(cfg, ctx, catalogs,
func(meta *declcfg.Meta, _ *v1alpha1.Catalog) error {
outBytes, err := json.MarshalIndent(meta.Blob, "", " ")
if err != nil {
return err
}

if schema != "" {
if meta.Schema != schema {
return nil
if out == "yaml" {
outBytes, err = yaml.JSONToYAML(outBytes)
if err != nil {
return err
}
}
// TODO: This uses ansi escape codes to colorize the output. Unfortunately, this
// means it isn't compatible with jq or yq that expect the output to be plain text.
return quick.Highlight(os.Stdout, string(outBytes), out, "terminal16m", "dracula")
},
WithNameContentFilter(name), WithSchemaContentFilter(schema), WithPackageContentFilter(pkg),
)

if pkg != "" {
if meta.Package != pkg {
return nil
}
}

if name != "" {
if meta.Name != name {
return nil
}
}

outJson, err := json.MarshalIndent(meta.Blob, "", " ")
if err != nil {
return err
}
outMd := strings.Builder{}
outMd.WriteString("```json\n")
outMd.WriteString(string(outJson))
outMd.WriteString("\n```\n")

out, _ := tr.Render(outMd.String())
fmt.Print(out)
return nil
})
if err != nil {
return fmt.Errorf("reading FBC for catalog %q: %w", catalog.Name, err)
}
if err != nil {
return err
}

return nil
}
Loading

0 comments on commit e1e4e56

Please sign in to comment.