Skip to content

Commit

Permalink
Node selection capability (#541)
Browse files Browse the repository at this point in the history
* add flag

* add node selection capability

* run tidy

* add Marshal for node structs

* Fixup info command filtering implementation

* Check filters for validity before attempting to use
* Accept filters that start with a '.' character (jq-style)
* Check validity of field at each step, to avoid reflect-related panics
* Minor refactoring to pull filtering into a separate function

---------

Co-authored-by: Angel Misevski <amisevsk@gmail.com>
  • Loading branch information
srikary12 and amisevsk authored Nov 1, 2024
1 parent 5dd7901 commit 9df82d1
Show file tree
Hide file tree
Showing 3 changed files with 70 additions and 4 deletions.
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ require (
golang.org/x/sync v0.8.0
golang.org/x/sys v0.26.0
golang.org/x/term v0.25.0
golang.org/x/text v0.19.0
gopkg.in/yaml.v3 v3.0.1
oras.land/oras-go/v2 v2.5.0
)
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24=
golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M=
golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
Expand Down
71 changes: 67 additions & 4 deletions pkg/cmd/info/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,27 @@
package info

import (
"bytes"
"context"
"errors"
"fmt"
"reflect"
"regexp"
"strings"

"kitops/pkg/artifact"
"kitops/pkg/cmd/options"
"kitops/pkg/lib/constants"
"kitops/pkg/lib/repo/util"
"kitops/pkg/output"

"github.com/spf13/cobra"
"gopkg.in/yaml.v3"
"oras.land/oras-go/v2/errdef"
"oras.land/oras-go/v2/registry"

"golang.org/x/text/cases"
"golang.org/x/text/language"
)

const (
Expand All @@ -49,11 +57,15 @@ kit info mymodel@sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f6
kit info --remote registry.example.com/my-model:1.0.0`
)

// Currently supported filter syntax: alphanumeric (plus dashes and underscores), dot-delimited fields
var validFilterRegexp = regexp.MustCompile(`^\.?[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)*$`)

type infoOptions struct {
options.NetworkOptions
configHome string
checkRemote bool
modelRef *registry.Reference
filter string
}

func InfoCommand() *cobra.Command {
Expand All @@ -70,6 +82,7 @@ func InfoCommand() *cobra.Command {

opts.AddNetworkFlags(cmd)
cmd.Flags().BoolVarP(&opts.checkRemote, "remote", "r", false, "Check remote registry instead of local storage")
cmd.Flags().StringVarP(&opts.filter, "filter", "f", "", "filter with node selectors")
cmd.Flags().SortFlags = false

return cmd
Expand All @@ -87,11 +100,21 @@ func runCommand(opts *infoOptions) func(*cobra.Command, []string) error {
}
return output.Fatalf("Error resolving modelkit: %s", err)
}
yamlBytes, err := config.MarshalToYAML()
if err != nil {
return output.Fatalf("Error formatting manifest: %w", err)

if len(opts.filter) > 0 {
filteredOutput, err := filterKitfile(config, opts.filter)
if err != nil {
return output.Fatalln(err)
}
fmt.Print(string(filteredOutput))
} else {
yamlBytes, err := config.MarshalToYAML()
if err != nil {
return output.Fatalf("Error formatting manifest: %w", err)
}
fmt.Print(string(yamlBytes))
}
fmt.Println(string(yamlBytes))

return nil
}
}
Expand Down Expand Up @@ -122,3 +145,43 @@ func (opts *infoOptions) complete(ctx context.Context, args []string) error {

return nil
}

func filterKitfile(config *artifact.KitFile, filter string) ([]byte, error) {
if err := checkFilterIsValid(filter); err != nil {
return nil, fmt.Errorf("invalid filter: %w", err)
}
// Accept filters that start (jq-style) and don't start with a '.'; we need to trim as otherwise we start the list
// with an empty string
filter = strings.TrimPrefix(filter, ".")

var filterSlice = strings.Split(filter, ".")
value := reflect.ValueOf(config)
for _, str := range filterSlice {
if value.Kind() == reflect.Ptr {
value = value.Elem()
}
field := value.FieldByName(cases.Title(language.Und, cases.NoLower).String(str))
if !field.IsValid() {
return nil, fmt.Errorf("error filtering output: cannot find required node")
}
value = field
}

buf := new(bytes.Buffer)
enc := yaml.NewEncoder(buf)
enc.SetIndent(2)
if err := enc.Encode(value.Interface()); err != nil {
return nil, fmt.Errorf("error formatting manifest: %w", err)
}
return buf.Bytes(), nil
}

func checkFilterIsValid(filter string) error {
if strings.Contains(filter, "[") {
return fmt.Errorf("array access using '[]' is not currently supported")
}
if !validFilterRegexp.MatchString(filter) {
return fmt.Errorf("invalid filter: %s", filter)
}
return nil
}

0 comments on commit 9df82d1

Please sign in to comment.