Skip to content

Commit

Permalink
feat: GetKnown returns DisplayableMap, show more details when binding
Browse files Browse the repository at this point in the history
Fixes #81
  • Loading branch information
metacosm committed Mar 23, 2020
1 parent e205663 commit a0fd5ab
Show file tree
Hide file tree
Showing 6 changed files with 107 additions and 49 deletions.
4 changes: 2 additions & 2 deletions pkg/cmdutil/delete.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,15 +51,15 @@ func (o *DeleteOptions) Validate() error {
}
if needName {
known := o.Client.GetKnown()
if len(known) == 0 {
if known.Len() == 0 {
return fmt.Errorf("no %s currently exist in '%s'", o.ResourceType, o.Client.GetNamespace())
}
s := "Unknown " + o.ResourceType
if len(o.Name) == 0 {
s = "No provided " + o.ResourceType + " name"
}
message := ui.SelectFromOtherErrorMessage(s.String(), o.Name)
o.Name = ui.Select(message, known)
o.Name = ui.SelectDisplayable(message, known).Name()
}
return nil
}
Expand Down
3 changes: 2 additions & 1 deletion pkg/cmdutil/entity.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package cmdutil
import (
"fmt"
"github.com/spf13/cobra"
"halkyon.io/hal/pkg/ui"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
ktemplates "k8s.io/kubectl/pkg/util/templates"
Expand Down Expand Up @@ -88,6 +89,6 @@ type HalkyonEntity interface {
Get(string, v1.GetOptions) error
Create(runtime.Object) error
Delete(string, *v1.DeleteOptions) error
GetKnown() []string
GetKnown() ui.DisplayableMap
GetNamespace() string
}
77 changes: 53 additions & 24 deletions pkg/hal/cli/capability/entity.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
package capability

import (
"fmt"
"halkyon.io/api/capability/clientset/versioned/typed/capability/v1beta1"
v1beta12 "halkyon.io/api/capability/v1beta1"
"halkyon.io/hal/pkg/cmdutil"
"halkyon.io/hal/pkg/k8s"
"halkyon.io/hal/pkg/ui"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
)
Expand All @@ -13,6 +16,8 @@ type client struct {
ns string
}

var _ cmdutil.HalkyonEntity = &client{}

func (lc client) Create(toCreate runtime.Object) error {
_, err := lc.client.Create(toCreate.(*v1beta12.Capability))
return err
Expand All @@ -23,33 +28,57 @@ func (lc client) Get(name string, options v1.GetOptions) error {
return err
}

func (lc client) GetKnown() []string {
list, err := lc.client.List(v1.ListOptions{})
if err != nil {
return []string{}
}
items := list.Items
names := make([]string, 0, len(items))
for _, item := range items {
names = append(names, item.Name)
}
return names
func (lc client) GetKnown() ui.DisplayableMap {
return lc.GetMatching()
}

func (lc client) GetMatching(spec ...v1beta12.CapabilitySpec) map[string]v1beta12.CapabilitySpec {
list, err := lc.client.List(v1.ListOptions{})
if err != nil {
return map[string]v1beta12.CapabilitySpec{}
}
items := list.Items
matching := make(map[string]v1beta12.CapabilitySpec, len(items))
skipMatch := len(spec) != 1
for _, item := range items {
if skipMatch || item.Spec.Matches(spec[0]) {
matching[item.Name] = item.Spec
type displayableCapability struct {
capability v1beta12.Capability
}

var _ ui.Displayable = displayableCapability{}

func (d displayableCapability) Help() string {
return fmt.Sprintf("%s (%v/%v/%s)", d.Name(), d.capability.Spec.Category, d.capability.Spec.Type, d.capability.Spec.Version)
}

func (d displayableCapability) Display() string {
return d.Help()
}

func (d displayableCapability) Name() string {
return d.capability.Name
}

func (d displayableCapability) GetUnderlying() interface{} {
return d.capability
}

func NewDisplayableCapability(capability v1beta12.Capability) ui.Displayable {
return displayableCapability{capability}
}

func (lc client) GetMatching(spec ...v1beta12.CapabilitySpec) ui.DisplayableMap {
r := make(chan ui.DisplayableMap)

go func() {
list, err := lc.client.List(v1.ListOptions{})
if err != nil {
r <- ui.Empty
return
}
}
return matching
items := list.Items
result := ui.NewDisplayableMap(len(items))
skipMatch := len(spec) != 1
for _, item := range items {
if skipMatch || item.Spec.Matches(spec[0]) {
result.Add(NewDisplayableCapability(item))
}
}
r <- result
}()

return <-r
}

func (lc client) Delete(name string, options *v1.DeleteOptions) error {
Expand Down
15 changes: 10 additions & 5 deletions pkg/hal/cli/component/bind.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,17 +33,22 @@ func (o *bindOptions) Complete(name string, cmd *cobra.Command, args []string) (
// get list of required capabilities and check if they are already bound
requires := o.component.Spec.Capabilities.Requires
for i, required := range requires {
// filter capabilities that don't match the requirements
matching := capability.Entity.GetMatching(required.Spec)

// only consider unbound capabilities for now
isBound := len(required.BoundTo) > 0
if isBound {
ui.OutputSelection("Already bound capability", required.BoundTo)
specified, found := matching.GetByName(required.BoundTo)
if found {
ui.OutputSelection("Already bound capability", specified.Display())
} else {
ui.OutputError(fmt.Sprintf("No capability matching %v named %s was found", required.Spec, required.BoundTo))
}
}
if !isBound || ui.Proceed("Change bound capability") {
// filter capabilities that don't match the requirements
matching := capability.Entity.GetMatching(required.Spec)

// ask user to select which matching capability to bind
selected := ui.Select("Matching capability", getCapabilityNames(matching))
selected := ui.Select("Matching capability", matching.AsDisplayableOptions())
updated := required.DeepCopy()
updated.BoundTo = selected
requires[i] = *updated
Expand Down
16 changes: 4 additions & 12 deletions pkg/hal/cli/component/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -149,15 +149,16 @@ func (o *createOptions) Complete(name string, cmd *cobra.Command, args []string)
required := v1beta1.RequiredCapabilityConfig{}
o.requiredCaps = make([]v1beta1.RequiredCapabilityConfig, 0, 10)
existing := capability.Entity.GetMatching()
hasCaps := len(existing) > 0
hasCaps := existing.Len() > 0
for {
required.Name = ui.AskOrReturnToExit("Required capability name, simply press enter to finish")
if len(required.Name) == 0 {
break
}
if hasCaps && ui.Proceed("Bind to existing capability") {
required.BoundTo = ui.Select("Target capability", getCapabilityNames(existing))
required.Spec = existing[required.BoundTo]
required.BoundTo = ui.Select("Target capability", existing.AsDisplayableOptions())
displayable, _ := existing.GetByName(required.BoundTo)
required.Spec = displayable.GetUnderlying().(v1beta13.Capability).Spec
} else {
capCreate := capability.CapabilityCreateOptions{}
if err := capCreate.Complete(); err != nil {
Expand Down Expand Up @@ -212,15 +213,6 @@ func (o *createOptions) Complete(name string, cmd *cobra.Command, args []string)
return nil
}

func getCapabilityNames(caps map[string]v1beta13.CapabilitySpec) []string {
result := make([]string, 0, len(caps))
for k := range caps {
result = append(result, k)
}
sort.Strings(result)
return result
}

func (o *createOptions) Validate() error {
matched, err := regexp.MatchString("^([a-zA-Z][a-zA-Z\\d_]*\\.)*", o.PackageName)
if !matched {
Expand Down
41 changes: 36 additions & 5 deletions pkg/hal/cli/component/entity.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
package component

import (
"fmt"
"halkyon.io/api/component/clientset/versioned/typed/component/v1beta1"
v1beta12 "halkyon.io/api/component/v1beta1"
"halkyon.io/hal/pkg/cmdutil"
"halkyon.io/hal/pkg/k8s"
"halkyon.io/hal/pkg/ui"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
)
Expand All @@ -13,6 +16,8 @@ type client struct {
ns string
}

var _ cmdutil.HalkyonEntity = client{}

func (lc client) Create(toCreate runtime.Object) error {
_, err := lc.client.Create(toCreate.(*v1beta12.Component))
return err
Expand All @@ -23,17 +28,43 @@ func (lc client) Get(name string, options v1.GetOptions) error {
return err
}

func (lc client) GetKnown() []string {
type displayableCapability struct {
c v1beta12.Component
}

var _ ui.Displayable = displayableCapability{}

func (d displayableCapability) Help() string {
return fmt.Sprintf("%s (%v/%v)", d.Name(), d.c.Spec.Runtime, d.c.Spec.Version)
}

func (d displayableCapability) Display() string {
return d.Help()
}

func (d displayableCapability) Name() string {
return d.c.Name
}

func (d displayableCapability) GetUnderlying() interface{} {
return d.c
}

func NewDisplayableCapability(capability v1beta12.Component) ui.Displayable {
return displayableCapability{capability}
}

func (lc client) GetKnown() ui.DisplayableMap {
list, err := lc.client.List(v1.ListOptions{})
if err != nil {
return []string{}
return ui.Empty
}
items := list.Items
names := make([]string, 0, len(items))
result := ui.NewDisplayableMap(len(items))
for _, item := range items {
names = append(names, item.Name)
result.Add(NewDisplayableCapability(item))
}
return names
return result
}

func (lc client) Delete(name string, options *v1.DeleteOptions) error {
Expand Down

0 comments on commit a0fd5ab

Please sign in to comment.