Skip to content

Commit

Permalink
Print promt for core types when group is not mentioned
Browse files Browse the repository at this point in the history
  • Loading branch information
varshaprasad96 committed Nov 5, 2022
1 parent 0d79db8 commit 25c8ff6
Show file tree
Hide file tree
Showing 4 changed files with 89 additions and 44 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,6 @@ spec:
- today.clusterissuers.cert-manager.io
- today.issuers.cert-manager.io
- today.orders.acme.cert-manager.io
permissionClaims:
- resource: secrets
all: true
6 changes: 6 additions & 0 deletions pkg/cliplugins/claims/cmd/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,12 @@ var (
# List permission claims and their respective status for all APIBindings in current workspace.
%[1]s claims get apibinding
# Edit the permission claims' status related to a specific APIBinding with an interactive prompt.
%[1]s claims get apibinding cert-manager
# Edit the permission claims' status for all APIBindings in current workspace with an interactive prompt.
%[1]s claims get apibinding
`
)

Expand Down
82 changes: 51 additions & 31 deletions pkg/cliplugins/claims/plugin/edit.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,17 @@ import (
"context"
"fmt"

apiv1alpha1 "github.com/kcp-dev/kcp/pkg/apis/apis/v1alpha1"
kcpclient "github.com/kcp-dev/kcp/pkg/client/clientset/versioned"
"github.com/kcp-dev/kcp/pkg/cliplugins/base"
pluginhelpers "github.com/kcp-dev/kcp/pkg/cliplugins/helpers"
"github.com/kcp-dev/logicalcluster/v2"
"github.com/spf13/cobra"

metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
utilerrors "k8s.io/apimachinery/pkg/util/errors"
"k8s.io/cli-runtime/pkg/genericclioptions"

apiv1alpha1 "github.com/kcp-dev/kcp/pkg/apis/apis/v1alpha1"
kcpclient "github.com/kcp-dev/kcp/pkg/client/clientset/versioned"
"github.com/kcp-dev/kcp/pkg/cliplugins/base"
pluginhelpers "github.com/kcp-dev/kcp/pkg/cliplugins/helpers"
)

// EditAPIBindingOptions contains the options for fetching claims
Expand Down Expand Up @@ -88,12 +90,7 @@ func (e *EditAPIBindingOptions) Run(ctx context.Context) error {
return fmt.Errorf("error while creating kcp client %w", err)
}

_, err = fmt.Fprintf(e.Out, "Running interactive promt. Enter (%s/%s/%s)", "Yes", "No", "Skip")
if err != nil {
return err
}

// The status of an apibinding has a set of `exportClaims`. The subset which is present in exportClaims but not in spec.AcceptableClaims, are the open claims.
// The status of an apibinding has a set of `exportClaims`. The subset which is present in exportClaims but not in spec.AcceptableClaims, are the open claims.
// The command will list those open claims and update `spec.AcceptableClaims` with the status based on the input of the user.

apibindings := []apiv1alpha1.APIBinding{}
Expand All @@ -112,15 +109,39 @@ func (e *EditAPIBindingOptions) Run(ctx context.Context) error {
apibindings = append(apibindings, *binding)
}

if len(apibindings) == 0 {
_, err = fmt.Fprintf(e.Out, "No apibindings available in %s.", currentClusterName)
if err != nil {
return err
}
return nil
}

_, err = fmt.Fprintf(e.Out, "Running interactive prompt. Enter (%s/%s/%s)\n", "Yes", "No", "Skip")
if err != nil {
return err
}

allErrors := []error{}
for _, apibinding := range apibindings {
openClaims := getOpenClaims(apibinding.Status.ExportPermissionClaims, apibinding.Spec.PermissionClaims)
// The status of an apibinding has a set of `exportClaims`. The subset which is present in exportClaims but not in spec.AcceptableClaims, are the open claims.
// The command will list those open claims and update `spec.AcceptableClaims` with the status based on the input of the user.
if len(openClaims) == 0 {
_, err = fmt.Fprintf(e.Out, "No open claims found for apibinding: %q\n", apibinding.Name)
if err != nil {
allErrors = append(allErrors, err)
}
}

// List open claims and update `spec.AcceptableClaims` with the status based on the input of the user.
for _, openClaim := range openClaims {
// print the prompt and infer the user input.
action := getRequiredInput(e.In, apibinding.Name, openClaim.Group, openClaim.Resource)
err := updateAPIBinding(ctx, action, openClaim, &apibinding, currentClusterName, kcpClusterClient)
action, err := getRequiredInput(e.In, e.Out, apibinding.Name, openClaim.Group, openClaim.Resource)
if err != nil {
allErrors = append(allErrors, err)
}

// Update apibinding based on the user input.
err = updateAPIBinding(ctx, action, openClaim, &apibinding, currentClusterName, kcpClusterClient)
if err != nil {
allErrors = append(allErrors, err)
}
Expand All @@ -133,41 +154,40 @@ func (e *EditAPIBindingOptions) Run(ctx context.Context) error {
func getOpenClaims(exportClaims []apiv1alpha1.PermissionClaim, aceptableClaims []apiv1alpha1.AcceptablePermissionClaim) []apiv1alpha1.PermissionClaim {
openPermissionClaims := []apiv1alpha1.PermissionClaim{}

// Convert both the lists into a map, with IdentityHash being the key so that
// the complexity of finding a resource which in in both export and acceptable claim is not n^2.
exportClaimsMap := map[string]apiv1alpha1.PermissionClaim{}
// Find the subset which is in exportClaims but not in AcceptableClaims.
exportClaimsMap := map[apiv1alpha1.GroupResource]apiv1alpha1.PermissionClaim{}
for _, e := range exportClaims {
exportClaimsMap[e.IdentityHash] = e
exportClaimsMap[e.GroupResource] = e
}

acceptableClaimsMap := map[string]apiv1alpha1.PermissionClaim{}
acceptableClaimsMap := map[apiv1alpha1.GroupResource]apiv1alpha1.PermissionClaim{}
for _, a := range aceptableClaims {
acceptableClaimsMap[a.IdentityHash] = a.PermissionClaim
acceptableClaimsMap[a.GroupResource] = a.PermissionClaim
}

for identity, _ := range exportClaimsMap {
if val, ok := acceptableClaimsMap[identity]; !ok {
openPermissionClaims = append(openPermissionClaims, val)
for gr, cl := range exportClaimsMap {
if _, ok := acceptableClaimsMap[gr]; !ok {
openPermissionClaims = append(openPermissionClaims, cl)
}
}
return openPermissionClaims
}

// updateAPIBinding send a request to API server to update the APIBinding with the updated status of the claim.
func updateAPIBinding(ctx context.Context, action ClaimAction, permissionClaim apiv1alpha1.PermissionClaim, apibinding *apiv1alpha1.APIBinding, clusterName logicalcluster.Name, kcpclusterclient kcpclient.ClusterInterface) error {
if action == SkipClaim {
return nil
}

for _, pc := range apibinding.Spec.PermissionClaims {
if pc.IdentityHash == permissionClaim.IdentityHash {
if action == AcceptClaim {
pc.State = apiv1alpha1.ClaimAccepted
} else if action == RejectClaim {
pc.State = apiv1alpha1.ClaimRejected
}
}
var state apiv1alpha1.AcceptablePermissionClaimState
if action == AcceptClaim {
state = apiv1alpha1.ClaimAccepted
} else if action == RejectClaim {
state = apiv1alpha1.ClaimRejected
}

apibinding.Spec.PermissionClaims = append(apibinding.Spec.PermissionClaims, apiv1alpha1.AcceptablePermissionClaim{PermissionClaim: permissionClaim, State: state})

_, err := kcpclusterclient.Cluster(clusterName).ApisV1alpha1().APIBindings().Update(ctx, apibinding, metav1.UpdateOptions{})
return err
}
42 changes: 29 additions & 13 deletions pkg/cliplugins/claims/plugin/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,9 @@ import (
"strings"
)

// ClaimAction captures the user preference on action with permission claim.
// commands.
// ClaimAction captures the user's preference of a specific action
// on permission claim. They can either accept, reject or ignore/ skip
// ay action on an open permission claim.
type ClaimAction int

const (
Expand All @@ -37,20 +38,32 @@ const (
SkipClaim
)

// Print the message for the user in the prompt. The resource name in a claim is a required field,
// but the group can be empty.
func printMessage(bindingName, claimGroup, claimResource string) string {
return fmt.Sprintf("Accept %s-%s (APIBinding: %s) >\n", claimGroup, claimResource, bindingName)
if claimGroup != "" {
return fmt.Sprintf("Accept permission claim for Group: %s, Resource: %s (APIBinding: %s) > ", claimGroup, claimResource, bindingName)
}
return fmt.Sprintf("Accept permission claim for Resource: %s (APIBinding: %s) > ", claimResource, bindingName)
}

func getRequiredInput(rd io.Reader, bindingName, claimGroup, claimResource string) ClaimAction {
func getRequiredInput(rd io.Reader, wr io.Writer, bindingName, claimGroup, claimResource string) (ClaimAction, error) {
reader := bufio.NewReader(rd)

for {
printMessage(bindingName, claimGroup, claimResource)
_, err := fmt.Fprint(wr, printMessage(bindingName, claimGroup, claimResource))
if err != nil {
return -1, err
}

value := readInput(reader)
if value != "" {
return inferText(value)
return inferText(value, wr)
}
_, err = fmt.Fprintf(wr, "Input is required. Enter `skip/s` instead. ")
if err != nil {
return -1, err
}
fmt.Printf("Input is required. ")
}
}

Expand All @@ -70,15 +83,18 @@ func readLine(reader *bufio.Reader) string {
return strings.ToLower(strings.Trim(strings.TrimSpace(text), "`'\""))
}

func inferText(input string) ClaimAction {
func inferText(input string, wr io.Writer) (ClaimAction, error) {
if input == "y" || input == "yes" {
return AcceptClaim
return AcceptClaim, nil
} else if input == "n" || input == "no" {
return RejectClaim
return RejectClaim, nil
} else if input == "skip" || input == "s" {
return SkipClaim
return SkipClaim, nil
} else {
fmt.Println("Unknown input, skipping any action")
return SkipClaim
_, err := fmt.Fprintf(wr, "Unknown input, skipping any action.\n")
if err != nil {
return -1, err
}
return SkipClaim, nil
}
}

0 comments on commit 25c8ff6

Please sign in to comment.