Skip to content

Commit

Permalink
feat: initial implementation of halkyon descriptor support
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
metacosm committed Oct 22, 2019
1 parent 4cd2ad1 commit 27dd291
Show file tree
Hide file tree
Showing 4 changed files with 99 additions and 6 deletions.
90 changes: 84 additions & 6 deletions pkg/cmdutil/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)

Expand All @@ -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 {
Expand All @@ -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)
Expand All @@ -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
}
Expand Down
5 changes: 5 additions & 0 deletions pkg/hal/cli/capability/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down
5 changes: 5 additions & 0 deletions pkg/hal/cli/component/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ type createOptions struct {
Template string
P string
scaffoldP string
target *v1beta1.Component
}

func (o *createOptions) GeneratePrefix() string {
Expand Down Expand Up @@ -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`)
Expand Down
5 changes: 5 additions & 0 deletions pkg/hal/cli/link/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ type createOptions struct {
linkType link.LinkType
*cmdutil.CreateOptions
*cmdutil.EnvOptions
target *link.Link
}

func (o *createOptions) SetEnvOptions(env *cmdutil.EnvOptions) {
Expand Down Expand Up @@ -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{}
Expand Down

0 comments on commit 27dd291

Please sign in to comment.