From 27dd2916e324c5f4d49833e97f1c0183238c4ff3 Mon Sep 17 00:00:00 2001 From: Chris Laprun Date: Tue, 22 Oct 2019 20:59:46 +0200 Subject: [PATCH] feat: initial implementation of halkyon descriptor support When hal runs to attempt to create an entity, it will gather all locally known entities of that type by parsing any existing halkyon descriptors. hal will look for descriptors as follows: halkyon.(yml|yaml) files in the current directory, dekorate-generated descriptor for the current directory and look for the same files in each of the current directory's child directories, not looking further than one level deep. When descriptors are detected, the user is asked whether they want to operate on the detected entities. --- pkg/cmdutil/create.go | 90 +++++++++++++++++++++++++++++--- pkg/hal/cli/capability/create.go | 5 ++ pkg/hal/cli/component/create.go | 5 ++ pkg/hal/cli/link/create.go | 5 ++ 4 files changed, 99 insertions(+), 6 deletions(-) diff --git a/pkg/cmdutil/create.go b/pkg/cmdutil/create.go index 4b42d84..7d5ec42 100644 --- a/pkg/cmdutil/create.go +++ b/pkg/cmdutil/create.go @@ -10,6 +10,8 @@ import ( v1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/client-go/tools/record/util" + "os" + "path/filepath" "time" ) @@ -19,11 +21,13 @@ type Creator interface { Runnable GeneratePrefix() string Build() runtime.Object + Set(entity runtime.Object) } type CreateOptions struct { *GenericOperationOptions - Delegate Creator + Delegate Creator + fromDescriptor bool } func NewCreateOptions(resourceType ResourceType, client HalkyonEntity) *CreateOptions { @@ -37,14 +41,74 @@ func NewCreateOptions(resourceType ResourceType, client HalkyonEntity) *CreateOp return c } -func (o *CreateOptions) Complete(name string, cmd *cobra.Command, args []string) error { - if err := o.Delegate.Complete(name, cmd, args); err != nil { - return err +func entityNames(registry entitiesRegistry) []string { + names := make([]string, 0, len(registry)) + for _, entity := range registry { + names = append(names, entity.Name) } + return names +} +func (o *CreateOptions) Complete(name string, cmd *cobra.Command, args []string) error { if len(args) == 1 { o.Name = args[0] } + + // look for locally existing components that also don't already exist on the remote cluster + currentDir, err := os.Getwd() + if err != nil { + return err + } + hd := LoadAvailableHalkyonEntities(currentDir) + entities := hd.GetDefinedEntitiesWith(o.ResourceType) + size := len(entities) + t := o.ResourceType.String() + if size > 0 { + // if we have an entity with the same name as the current directory, use it by default + currentDirName := filepath.Base(currentDir) + if entity, ok := entities[currentDirName]; ok { + o.Name = entity.Name + o.fromDescriptor = true + o.Delegate.Set(entity.Entity) + } else { + names := entityNames(entities) + if size == 1 { + if o.Name == names[0] { + entity := entities[o.Name] + if IsInteractive(cmd) && ui.Proceed(fmt.Sprintf("Found %s named %s in %s, use it", t, o.Name, entity.Path)) { + o.Name = entity.Name + o.fromDescriptor = true + o.Delegate.Set(entity.Entity) + } + } + } else if IsInteractive(cmd) && ui.Proceed(fmt.Sprintf("Found %d %s(s), do you want to %s from them", size, t, o.operationName)) { + o.Name = ui.Select(t, names, o.Name) + } + } + + } + + entity, ok := entities[o.Name] + if ok { + ui.OutputSelection(fmt.Sprintf("Selected %s from %s", t, entity.Path), o.Name) + o.fromDescriptor = true + // set the component on the delegate so it uses it when we want ask to create it + o.Delegate.Set(entity.Entity) + exists, err := o.Exists() + if err != nil { + return err + } + if exists { + return fmt.Errorf("a %s named '%s' already exists, please use update instead (NOT YET IMPLEMENTED)", o.ResourceType, o.Name) + } + } + + if !o.fromDescriptor { + if err := o.Delegate.Complete(name, cmd, args); err != nil { + return err + } + } + for { o.Name = ui.Ask("Name", o.Name, o.generateName()) err := validation.NameValidator(o.Name) @@ -68,9 +132,23 @@ func (o *CreateOptions) Complete(name string, cmd *cobra.Command, args []string) return nil } +func (o *CreateOptions) Exists() (bool, error) { + err := o.Client.Get(o.Name, v1.GetOptions{}) + if err != nil { + if util.IsKeyNotFoundError(errors.Cause(err)) { + return false, nil + } else { + return false, err + } + } + return true, nil +} + func (o *CreateOptions) Validate() error { - if err := o.Delegate.Validate(); err != nil { - return err + if !o.fromDescriptor { + if err := o.Delegate.Validate(); err != nil { + return err + } } return nil } diff --git a/pkg/hal/cli/capability/create.go b/pkg/hal/cli/capability/create.go index 733726c..64e46b1 100644 --- a/pkg/hal/cli/capability/create.go +++ b/pkg/hal/cli/capability/create.go @@ -22,6 +22,11 @@ type createOptions struct { paramPairs []string parameters []halkyon.NameValuePair *cmdutil.CreateOptions + target *v1beta1.Capability +} + +func (o *createOptions) Set(entity runtime.Object) { + o.target = entity.(*v1beta1.Capability) } var ( diff --git a/pkg/hal/cli/component/create.go b/pkg/hal/cli/component/create.go index c430fab..fe09dca 100644 --- a/pkg/hal/cli/component/create.go +++ b/pkg/hal/cli/component/create.go @@ -64,6 +64,7 @@ type createOptions struct { Template string P string scaffoldP string + target *v1beta1.Component } func (o *createOptions) GeneratePrefix() string { @@ -92,6 +93,10 @@ func (o *createOptions) Build() runtime.Object { } } +func (o *createOptions) Set(entity runtime.Object) { + o.target = entity.(*v1beta1.Component) +} + var ( createExample = ktemplates.Examples(` # Create a new Halkyon component located in the 'foo' child directory of the current directory %[1]s foo`) diff --git a/pkg/hal/cli/link/create.go b/pkg/hal/cli/link/create.go index 55d0c10..f89d551 100644 --- a/pkg/hal/cli/link/create.go +++ b/pkg/hal/cli/link/create.go @@ -24,6 +24,7 @@ type createOptions struct { linkType link.LinkType *cmdutil.CreateOptions *cmdutil.EnvOptions + target *link.Link } func (o *createOptions) SetEnvOptions(env *cmdutil.EnvOptions) { @@ -106,6 +107,10 @@ func (o *createOptions) GeneratePrefix() string { return o.targetName } +func (o *createOptions) Set(entity runtime.Object) { + o.target = entity.(*link.Link) +} + func NewCmdCreate(parent string) *cobra.Command { c := k8s.GetClient() o := &createOptions{}