Repository: skupperproject/skupper
Pull request #1822 (2024-12-05 17:31:24)

Add support for token linkcost
Now that accesstoken CRD supports a link cost, add the ability to configure it in the CLI.

nluaces: LGTM! some merge conflicts need to be solved though.

Patch:
diff --git a/internal/cmd/skupper/common/flags.go b/internal/cmd/skupper/common/flags.go
index 5c8bcbd0..a36e89e1 100644
--- a/internal/cmd/skupper/common/flags.go
+++ b/internal/cmd/skupper/common/flags.go
@@ -141,6 +141,7 @@ type CommandTokenIssueFlags struct {
 	Timeout            time.Duration
 	ExpirationWindow   time.Duration
 	RedemptionsAllowed int
+	Cost               string
 }
 
 type CommandTokenRedeemFlags struct {
diff --git a/internal/cmd/skupper/token/kube/token_issue.go b/internal/cmd/skupper/token/kube/token_issue.go
index d6a0873e..9283cfa1 100644
--- a/internal/cmd/skupper/token/kube/token_issue.go
+++ b/internal/cmd/skupper/token/kube/token_issue.go
@@ -4,6 +4,7 @@ import (
 	"context"
 	"fmt"
 	"os"
+	"strconv"
 
 	"github.com/skupperproject/skupper/internal/cmd/skupper/common"
 	"github.com/skupperproject/skupper/internal/cmd/skupper/common/utils"
@@ -25,6 +26,7 @@ type CmdTokenIssue struct {
 	namespace string
 	grantName string
 	fileName  string
+	cost      int
 }
 
 func NewCmdTokenIssue() *CmdTokenIssue {
@@ -47,6 +49,7 @@ func (cmd *CmdTokenIssue) ValidateInput(args []string) []error {
 	tokenStringValidator := validator.NewFilePathStringValidator()
 	timeoutValidator := validator.NewTimeoutInSecondsValidator()
 	expirationValidator := validator.NewExpirationInSecondsValidator()
+	numberValidator := validator.NewNumberValidator()
 
 	// Validate token file name
 	if len(args) < 1 {
@@ -114,6 +117,17 @@ func (cmd *CmdTokenIssue) ValidateInput(args []string) []error {
 		}
 	}
 
+	selectedCost, err := strconv.Atoi(cmd.Flags.Cost)
+	if err != nil {
+		validationErrors = append(validationErrors, fmt.Errorf("link cost is not valid: %s", err))
+	}
+	ok, err := numberValidator.Evaluate(selectedCost)
+	if !ok {
+		validationErrors = append(validationErrors, fmt.Errorf("link cost is not valid: %s", err))
+	} else {
+		cmd.cost = selectedCost
+	}
+
 	return validationErrors
 }
 
@@ -157,9 +171,10 @@ func (cmd *CmdTokenIssue) WaitUntil() error {
 					Name: accessGrant.Name,
 				},
 				Spec: v2alpha1.AccessTokenSpec{
-					Url:  accessGrant.Status.Url,
-					Code: accessGrant.Status.Code,
-					Ca:   accessGrant.Status.Ca,
+					Url:      accessGrant.Status.Url,
+					Code:     accessGrant.Status.Code,
+					Ca:       accessGrant.Status.Ca,
+					LinkCost: cmd.cost,
 				},
 			}
 
diff --git a/internal/cmd/skupper/token/token.go b/internal/cmd/skupper/token/token.go
index 274c3dc3..35956b2c 100644
--- a/internal/cmd/skupper/token/token.go
+++ b/internal/cmd/skupper/token/token.go
@@ -49,6 +49,7 @@ func CmdTokenIssueFactory(configuredPlatform types.Platform) *cobra.Command {
 	cmd.Flags().DurationVarP(&cmdFlags.ExpirationWindow, common.FlagNameExpirationWindow, "e", 15*time.Minute, common.FlagDescExpirationWindow)
 	cmd.Flags().DurationVarP(&cmdFlags.Timeout, common.FlagNameTimeout, "t", 60*time.Second, common.FlagDescTimeout)
 	cmd.Flags().StringVar(&cmdFlags.Name, common.FlagNameToken, "", common.FlagDescToken)
+	cmd.Flags().StringVar(&cmdFlags.Cost, common.FlagNameCost, "1", common.FlagDescCost)
 
 	kubeCommand.CobraCmd = cmd
 	kubeCommand.Flags = &cmdFlags


Test patch:
diff --git a/internal/cmd/skupper/token/kube/token_issue_test.go b/internal/cmd/skupper/token/kube/token_issue_test.go
index f9a538b2..0502d760 100644
--- a/internal/cmd/skupper/token/kube/token_issue_test.go
+++ b/internal/cmd/skupper/token/kube/token_issue_test.go
@@ -34,6 +34,7 @@ func TestCmdTokenIssue_ValidateInput(t *testing.T) {
 				RedemptionsAllowed: 1,
 				Timeout:            60 * time.Second,
 				Name:               "my-token",
+				Cost:               "1",
 			},
 			skupperObjects: []runtime.Object{
 				&v2alpha1.SiteList{
@@ -94,6 +95,7 @@ func TestCmdTokenIssue_ValidateInput(t *testing.T) {
 				ExpirationWindow:   15 * time.Minute,
 				RedemptionsAllowed: 1,
 				Timeout:            60 * time.Second,
+				Cost:               "1",
 			},
 			skupperObjects: []runtime.Object{
 				&v2alpha1.AccessGrant{
@@ -122,6 +124,7 @@ func TestCmdTokenIssue_ValidateInput(t *testing.T) {
 				ExpirationWindow:   15 * time.Minute,
 				RedemptionsAllowed: 1,
 				Timeout:            60 * time.Second,
+				Cost:               "1",
 			},
 			skupperObjects: []runtime.Object{
 				&v2alpha1.SiteList{
@@ -153,6 +156,7 @@ func TestCmdTokenIssue_ValidateInput(t *testing.T) {
 				ExpirationWindow:   15 * time.Minute,
 				RedemptionsAllowed: 1,
 				Timeout:            60 * time.Second,
+				Cost:               "1",
 			},
 			skupperObjects: []runtime.Object{
 				&v2alpha1.SiteList{
@@ -193,6 +197,7 @@ func TestCmdTokenIssue_ValidateInput(t *testing.T) {
 				ExpirationWindow:   15 * time.Minute,
 				RedemptionsAllowed: 1,
 				Timeout:            60 * time.Second,
+				Cost:               "1",
 			},
 			skupperObjects: []runtime.Object{
 				&v2alpha1.SiteList{
@@ -233,6 +238,7 @@ func TestCmdTokenIssue_ValidateInput(t *testing.T) {
 				ExpirationWindow:   15 * time.Minute,
 				RedemptionsAllowed: 1,
 				Timeout:            60 * time.Second,
+				Cost:               "1",
 			},
 			skupperObjects: []runtime.Object{
 				&v2alpha1.SiteList{
@@ -274,6 +280,7 @@ func TestCmdTokenIssue_ValidateInput(t *testing.T) {
 				RedemptionsAllowed: 1,
 				Timeout:            60 * time.Second,
 				Name:               "my new token",
+				Cost:               "1",
 			},
 			skupperObjects: []runtime.Object{
 				&v2alpha1.SiteList{
@@ -314,6 +321,7 @@ func TestCmdTokenIssue_ValidateInput(t *testing.T) {
 				ExpirationWindow:   15 * time.Minute,
 				RedemptionsAllowed: 1,
 				Timeout:            60 * time.Second,
+				Cost:               "1",
 			},
 			skupperObjects: []runtime.Object{
 				&v2alpha1.SiteList{
@@ -354,6 +362,7 @@ func TestCmdTokenIssue_ValidateInput(t *testing.T) {
 				ExpirationWindow:   15 * time.Minute,
 				RedemptionsAllowed: 0,
 				Timeout:            60 * time.Second,
+				Cost:               "1",
 			},
 			skupperObjects: []runtime.Object{
 				&v2alpha1.SiteList{
@@ -395,6 +404,7 @@ func TestCmdTokenIssue_ValidateInput(t *testing.T) {
 				ExpirationWindow:   10 * time.Second,
 				RedemptionsAllowed: 1,
 				Timeout:            10 * time.Second,
+				Cost:               "1",
 			},
 			skupperObjects: []runtime.Object{
 				&v2alpha1.SiteList{
@@ -435,6 +445,7 @@ func TestCmdTokenIssue_ValidateInput(t *testing.T) {
 				ExpirationWindow:   15 * time.Minute,
 				RedemptionsAllowed: 1,
 				Timeout:            0 * time.Second,
+				Cost:               "1",
 			},
 			skupperObjects: []runtime.Object{
 				&v2alpha1.SiteList{
@@ -468,6 +479,48 @@ func TestCmdTokenIssue_ValidateInput(t *testing.T) {
 			},
 			expectedErrors: []string{"timeout is not valid: duration must not be less than 10s; got 0s"},
 		},
+		{
+			name: "cost is not valid",
+			args: []string{"~/token.yaml"},
+			flags: common.CommandTokenIssueFlags{
+				ExpirationWindow:   15 * time.Minute,
+				RedemptionsAllowed: 1,
+				Timeout:            60 * time.Second,
+				Cost:               "Not-valid",
+			},
+			skupperObjects: []runtime.Object{
+				&v2alpha1.SiteList{
+					Items: []v2alpha1.Site{
+						{
+							ObjectMeta: v1.ObjectMeta{
+								Name:      "site1",
+								Namespace: "test",
+							},
+							Status: v2alpha1.SiteStatus{
+								Status: v2alpha1.Status{
+									Conditions: []v1.Condition{
+										{
+											Type:   "Configured",
+											Status: "True",
+										},
+										{
+											Type:   "Running",
+											Status: "True",
+										},
+										{
+											Type:   "Ready",
+											Status: "True",
+										},
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+			expectedErrors: []string{
+				`link cost is not valid: strconv.Atoi: parsing "Not-valid": invalid syntax`},
+		},
 		{
 			name: "flags all valid",
 			args: []string{"~/token.yaml"},
@@ -475,6 +528,7 @@ func TestCmdTokenIssue_ValidateInput(t *testing.T) {
 				ExpirationWindow:   15 * time.Minute,
 				RedemptionsAllowed: 1,
 				Timeout:            60 * time.Second,
+				Cost:               "5",
 			},
 			skupperObjects: []runtime.Object{
 				&v2alpha1.SiteList{
@@ -569,6 +623,7 @@ func TestCmdTokenIssue_Run(t *testing.T) {
 				ExpirationWindow:   20 * time.Minute,
 				RedemptionsAllowed: 2,
 				Timeout:            60 * time.Second,
+				Cost:               "5",
 			},
 			skupperObjects: []runtime.Object{
 				&v2alpha1.AccessGrant{
diff --git a/internal/cmd/skupper/token/token_test.go b/internal/cmd/skupper/token/token_test.go
index c3608f6c..8adbd880 100644
--- a/internal/cmd/skupper/token/token_test.go
+++ b/internal/cmd/skupper/token/token_test.go
@@ -27,6 +27,7 @@ func TestCmdTokenFactory(t *testing.T) {
 				common.FlagNameExpirationWindow:   "15m0s",
 				common.FlagNameRedemptionsAllowed: "1",
 				common.FlagNameToken:              "",
+				common.FlagNameCost:               "1",
 			},
 			command: CmdTokenIssueFactory(types.PlatformKubernetes),
 		},