Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add interpret command #2750

Merged
merged 1 commit into from
Nov 22, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 93 additions & 0 deletions pkg/karmadactl/interpret/check.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package interpret

import (
"context"
"fmt"
"strings"
"time"

"k8s.io/cli-runtime/pkg/printers"
"k8s.io/cli-runtime/pkg/resource"
cmdutil "k8s.io/kubectl/pkg/cmd/util"

"github.com/karmada-io/karmada/pkg/resourceinterpreter/configurableinterpreter/luavm"
)

func (o *Options) runCheck() error {
w := printers.GetNewTabWriter(o.Out)
defer w.Flush()

failed := false

err := o.CustomizationResult.Visit(func(info *resource.Info, _ error) error {
var visitErr error
fmt.Fprintln(w, "-----------------------------------")

source := info.Source
if info.Name != "" {
source = info.Name
}
fmt.Fprintf(w, "SOURCE: %s\n", source)

customization, visitErr := asResourceInterpreterCustomization(info.Object)
if visitErr != nil {
failed = true
fmt.Fprintf(w, "%v\n", visitErr)
return nil
}

kind := customization.Spec.Target.Kind
if kind == "" {
failed = true
fmt.Fprintln(w, "target.kind no set")
return nil
}
apiVersion := customization.Spec.Target.APIVersion
if apiVersion == "" {
failed = true
fmt.Fprintln(w, "target.apiVersion no set")
return nil
}

fmt.Fprintf(w, "TARGET: %s %s\t\n", apiVersion, kind)
fmt.Fprintf(w, "RULERS:\n")
for _, r := range o.Rules {
fmt.Fprintf(w, " %s:\t", r.Name())

script := r.GetScript(customization)
if script == "" {
fmt.Fprintln(w, "UNSET")
continue
}
checkErr := checkScrip(script)
if checkErr != nil {
failed = true
fmt.Fprintf(w, "%s: %s\t\n", "ERROR", strings.TrimSpace(checkErr.Error()))
continue
}

fmt.Fprintln(w, "PASS")
}
return nil
})
if err != nil {
return err
}
if failed {
// As failed infos are printed above. So don't print it again.
return cmdutil.ErrExit
}
return nil
}

func checkScrip(script string) error {
ctx, cancel := context.WithTimeout(context.TODO(), time.Second)
defer cancel()
l, err := luavm.NewWithContext(ctx)
if err != nil {
return err
}
defer l.Close()
_, err = l.LoadString(script)
return err
}
16 changes: 16 additions & 0 deletions pkg/karmadactl/interpret/execute.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package interpret

import (
"fmt"

"github.com/spf13/cobra"
"k8s.io/kubectl/pkg/cmd/util"
)

func (o *Options) completeExecute(_ util.Factory, _ *cobra.Command, _ []string) []error {
return nil
}

func (o *Options) runExecute() error {
return fmt.Errorf("not implement")
}
150 changes: 150 additions & 0 deletions pkg/karmadactl/interpret/interpret.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
package interpret

import (
"fmt"
"strings"

"github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/errors"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/cli-runtime/pkg/resource"
cmdutil "k8s.io/kubectl/pkg/cmd/util"
"k8s.io/kubectl/pkg/util/templates"

configv1alpha1 "github.com/karmada-io/karmada/pkg/apis/config/v1alpha1"
"github.com/karmada-io/karmada/pkg/karmadactl/options"
"github.com/karmada-io/karmada/pkg/karmadactl/util"
"github.com/karmada-io/karmada/pkg/util/gclient"
)

var (
interpretLong = templates.LongDesc(`
Validate and test interpreter customization before applying it to the control plane.

1. Validate the ResourceInterpreterCustomization configuration as per API schema
and try to load the scripts for syntax check.

2. Run the rules locally and test if the result is expected. Similar to the dry run.

`)

interpretExample = templates.Examples(`
# Check the customizations in file
%[1]s interpret -f customization.json --check
ikaven1024 marked this conversation as resolved.
Show resolved Hide resolved
# Execute the retention rule for
%[1]s interpret -f customization.yml --operation retain --desired-file desired.yml --observed-file observed.yml
# Execute the replicaRevision rule for
%[1]s interpret -f customization.yml --operation reviseReplica --observed-file observed.yml --desired-replica 2
# Execute the statusReflection rule for
%[1]s interpret -f customization.yml --operation interpretStatus --observed-file observed.yml
# Execute the healthInterpretation rule
%[1]s interpret -f customization.yml --operation interpretHealth --observed-file observed.yml
# Execute the dependencyInterpretation rule
%[1]s interpret -f customization.yml --operation interpretDependency --observed-file observed.yml
# Execute the statusAggregation rule
%[1]s interpret -f customization.yml --operation aggregateStatus --status-file status1.yml --status-file status2.yml

`)
)

const (
customizationResourceName = "resourceinterpretercustomizations"
)

// NewCmdInterpret new interpret command.
func NewCmdInterpret(f util.Factory, parentCommand string, streams genericclioptions.IOStreams) *cobra.Command {
o := &Options{
IOStreams: streams,
Rules: allRules,
}
cmd := &cobra.Command{
Use: "interpret (-f FILENAME) (--operation OPERATION) [--ARGS VALUE]... ",
Short: "Validate and test interpreter customization before applying it to the control plane",
Long: interpretLong,
SilenceUsage: true,
DisableFlagsInUseLine: true,
Example: fmt.Sprintf(interpretExample, parentCommand),
Run: func(cmd *cobra.Command, args []string) {
cmdutil.CheckErr(o.Complete(f, cmd, args))
cmdutil.CheckErr(o.Validate())
cmdutil.CheckErr(o.Run())
},
Annotations: map[string]string{
util.TagCommandGroup: util.GroupClusterTroubleshootingAndDebugging,
},
}

flags := cmd.Flags()
options.AddKubeConfigFlags(flags)
flags.StringVar(&o.Operation, "operation", o.Operation, "The interpret operation to use. One of: ("+strings.Join(o.Rules.Names(), ",")+")")
flags.BoolVar(&o.Check, "check", false, "Validates the given ResourceInterpreterCustomization configuration(s)")
flags.StringVar(&o.DesiredFile, "desired-file", o.DesiredFile, "Filename, directory, or URL to files identifying the resource to use as desiredObj argument in rule script.")
flags.StringVar(&o.ObservedFile, "observed-file", o.ObservedFile, "Filename, directory, or URL to files identifying the resource to use as observedObj argument in rule script.")
flags.StringSliceVar(&o.StatusFile, "status-file", o.StatusFile, "Filename, directory, or URL to files identifying the resource to use as statusItems argument in rule script.")
flags.Int32Var(&o.DesiredReplica, "desired-replica", o.DesiredReplica, "The desiredReplica argument in rule script.")
cmdutil.AddJsonFilenameFlag(flags, &o.FilenameOptions.Filenames, "Filename, directory, or URL to files containing the customizations")
flags.BoolVarP(&o.FilenameOptions.Recursive, "recursive", "R", false, "Process the directory used in -f, --filename recursively. Useful when you want to manage related manifests organized within the same directory.")

return cmd
}

// Options contains the input to the interpret command.
type Options struct {
resource.FilenameOptions

Operation string
Check bool

// args
DesiredFile string
ObservedFile string
StatusFile []string
DesiredReplica int32

CustomizationResult *resource.Result

Rules Rules

genericclioptions.IOStreams
}

// Complete ensures that options are valid and marshals them if necessary
func (o *Options) Complete(f util.Factory, cmd *cobra.Command, args []string) error {
scheme := gclient.NewSchema()
o.CustomizationResult = f.NewBuilder().
WithScheme(scheme, scheme.PrioritizedVersionsAllGroups()...).
FilenameParam(false, &o.FilenameOptions).
ResourceNames(customizationResourceName, args...).
RequireObject(true).
Local().
Do()

var errs []error
errs = append(errs, o.CustomizationResult.Err())
errs = append(errs, o.completeExecute(f, cmd, args)...)
return errors.NewAggregate(errs)
}

// Validate checks the EditOptions to see if there is sufficient information to run the command.
func (o *Options) Validate() error {
return nil
}

// Run describe information of resources
func (o *Options) Run() error {
switch {
case o.Check:
return o.runCheck()
default:
return o.runExecute()
}
}

func asResourceInterpreterCustomization(o runtime.Object) (*configv1alpha1.ResourceInterpreterCustomization, error) {
c, ok := o.(*configv1alpha1.ResourceInterpreterCustomization)
RainbowMango marked this conversation as resolved.
Show resolved Hide resolved
if !ok {
return nil, fmt.Errorf("not a ResourceInterpreterCustomization: %#v", o)
}
return c, nil
}
Loading