kubectl create token: use duration instead of expiration seconds
Expiration seconds is great for an unambiguous REST API. It it not a great input for a command line meant to be used by humans. Signed-off-by: Monis Khan <mok@vmware.com>
This commit is contained in:
		@@ -20,6 +20,7 @@ import (
 | 
				
			|||||||
	"context"
 | 
						"context"
 | 
				
			||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
	"strings"
 | 
						"strings"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/spf13/cobra"
 | 
						"github.com/spf13/cobra"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -35,6 +36,7 @@ import (
 | 
				
			|||||||
	"k8s.io/kubectl/pkg/util"
 | 
						"k8s.io/kubectl/pkg/util"
 | 
				
			||||||
	"k8s.io/kubectl/pkg/util/templates"
 | 
						"k8s.io/kubectl/pkg/util/templates"
 | 
				
			||||||
	"k8s.io/kubectl/pkg/util/term"
 | 
						"k8s.io/kubectl/pkg/util/term"
 | 
				
			||||||
 | 
						"k8s.io/utils/pointer"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// TokenOptions is the data required to perform a token request operation.
 | 
					// TokenOptions is the data required to perform a token request operation.
 | 
				
			||||||
@@ -57,8 +59,8 @@ type TokenOptions struct {
 | 
				
			|||||||
	// Audiences indicate the valid audiences for the requested token. If unset, defaults to the Kubernetes API server audiences.
 | 
						// Audiences indicate the valid audiences for the requested token. If unset, defaults to the Kubernetes API server audiences.
 | 
				
			||||||
	Audiences []string
 | 
						Audiences []string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// ExpirationSeconds is the requested token lifetime. Optional.
 | 
						// Duration is the requested token lifetime. Optional.
 | 
				
			||||||
	ExpirationSeconds int64
 | 
						Duration time.Duration
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// CoreClient is the API client used to request the token. Required.
 | 
						// CoreClient is the API client used to request the token. Required.
 | 
				
			||||||
	CoreClient corev1client.CoreV1Interface
 | 
						CoreClient corev1client.CoreV1Interface
 | 
				
			||||||
@@ -78,7 +80,7 @@ var (
 | 
				
			|||||||
		kubectl create token myapp --namespace myns
 | 
							kubectl create token myapp --namespace myns
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		# Request a token with a custom expiration
 | 
							# Request a token with a custom expiration
 | 
				
			||||||
		kubectl create token myapp --expiration-seconds 600
 | 
							kubectl create token myapp --duration 10m
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		# Request a token with a custom audience
 | 
							# Request a token with a custom audience
 | 
				
			||||||
		kubectl create token myapp --audience https://example.com
 | 
							kubectl create token myapp --audience https://example.com
 | 
				
			||||||
@@ -134,7 +136,7 @@ func NewCmdCreateToken(f cmdutil.Factory, ioStreams genericclioptions.IOStreams)
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	cmd.Flags().StringArrayVar(&o.Audiences, "audience", o.Audiences, "Audience of the requested token. If unset, defaults to requesting a token for use with the Kubernetes API server. May be repeated to request a token valid for multiple audiences.")
 | 
						cmd.Flags().StringArrayVar(&o.Audiences, "audience", o.Audiences, "Audience of the requested token. If unset, defaults to requesting a token for use with the Kubernetes API server. May be repeated to request a token valid for multiple audiences.")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	cmd.Flags().Int64Var(&o.ExpirationSeconds, "expiration-seconds", o.ExpirationSeconds, "Requested lifetime of the issued token. The server may return a token with a longer or shorter lifetime.")
 | 
						cmd.Flags().DurationVar(&o.Duration, "duration", o.Duration, "Requested lifetime of the issued token. The server may return a token with a longer or shorter lifetime.")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	cmd.Flags().StringVar(&o.BoundObjectKind, "bound-object-kind", o.BoundObjectKind, "Kind of an object to bind the token to. "+
 | 
						cmd.Flags().StringVar(&o.BoundObjectKind, "bound-object-kind", o.BoundObjectKind, "Kind of an object to bind the token to. "+
 | 
				
			||||||
		"Supported kinds are "+strings.Join(sets.StringKeySet(boundObjectKindToAPIVersion).List(), ", ")+". "+
 | 
							"Supported kinds are "+strings.Join(sets.StringKeySet(boundObjectKindToAPIVersion).List(), ", ")+". "+
 | 
				
			||||||
@@ -192,8 +194,11 @@ func (o *TokenOptions) Validate() error {
 | 
				
			|||||||
	if len(o.Namespace) == 0 {
 | 
						if len(o.Namespace) == 0 {
 | 
				
			||||||
		return fmt.Errorf("--namespace is required")
 | 
							return fmt.Errorf("--namespace is required")
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	if o.ExpirationSeconds < 0 {
 | 
						if o.Duration < 0 {
 | 
				
			||||||
		return fmt.Errorf("--expiration-seconds must be positive")
 | 
							return fmt.Errorf("--duration must be positive")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if o.Duration%time.Second != 0 {
 | 
				
			||||||
 | 
							return fmt.Errorf("--duration cannot be expressed in units less than seconds")
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	for _, aud := range o.Audiences {
 | 
						for _, aud := range o.Audiences {
 | 
				
			||||||
		if len(aud) == 0 {
 | 
							if len(aud) == 0 {
 | 
				
			||||||
@@ -227,8 +232,8 @@ func (o *TokenOptions) Run() error {
 | 
				
			|||||||
			Audiences: o.Audiences,
 | 
								Audiences: o.Audiences,
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	if o.ExpirationSeconds > 0 {
 | 
						if o.Duration > 0 {
 | 
				
			||||||
		request.Spec.ExpirationSeconds = &o.ExpirationSeconds
 | 
							request.Spec.ExpirationSeconds = pointer.Int64(int64(o.Duration / time.Second))
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	if len(o.BoundObjectKind) > 0 {
 | 
						if len(o.BoundObjectKind) > 0 {
 | 
				
			||||||
		request.Spec.BoundObjectRef = &authenticationv1.BoundObjectReference{
 | 
							request.Spec.BoundObjectRef = &authenticationv1.BoundObjectReference{
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -22,8 +22,8 @@ import (
 | 
				
			|||||||
	"io/ioutil"
 | 
						"io/ioutil"
 | 
				
			||||||
	"net/http"
 | 
						"net/http"
 | 
				
			||||||
	"reflect"
 | 
						"reflect"
 | 
				
			||||||
	"strconv"
 | 
					 | 
				
			||||||
	"testing"
 | 
						"testing"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/google/go-cmp/cmp"
 | 
						"github.com/google/go-cmp/cmp"
 | 
				
			||||||
	"k8s.io/utils/pointer"
 | 
						"k8s.io/utils/pointer"
 | 
				
			||||||
@@ -51,7 +51,7 @@ func TestCreateToken(t *testing.T) {
 | 
				
			|||||||
		boundObjectName string
 | 
							boundObjectName string
 | 
				
			||||||
		boundObjectUID  string
 | 
							boundObjectUID  string
 | 
				
			||||||
		audiences       []string
 | 
							audiences       []string
 | 
				
			||||||
		expirationSeconds int
 | 
							duration        time.Duration
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		serverResponseToken string
 | 
							serverResponseToken string
 | 
				
			||||||
		serverResponseError string
 | 
							serverResponseError string
 | 
				
			||||||
@@ -183,16 +183,22 @@ status:
 | 
				
			|||||||
		},
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		{
 | 
							{
 | 
				
			||||||
			test:              "invalid expiration",
 | 
								test:         "invalid duration",
 | 
				
			||||||
			name:         "mysa",
 | 
								name:         "mysa",
 | 
				
			||||||
			expirationSeconds: -1,
 | 
								duration:     -1,
 | 
				
			||||||
			expectStderr:      `error: --expiration-seconds must be positive`,
 | 
								expectStderr: `error: --duration must be positive`,
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
		{
 | 
							{
 | 
				
			||||||
			test: "valid expiration",
 | 
								test:         "invalid duration unit",
 | 
				
			||||||
 | 
								name:         "mysa",
 | 
				
			||||||
 | 
								duration:     time.Microsecond,
 | 
				
			||||||
 | 
								expectStderr: `error: --duration cannot be expressed in units less than seconds`,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								test: "valid duration",
 | 
				
			||||||
			name: "mysa",
 | 
								name: "mysa",
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			expirationSeconds: 1000,
 | 
								duration: 1000 * time.Second,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			expectRequestPath: "/api/v1/namespaces/test/serviceaccounts/mysa/token",
 | 
								expectRequestPath: "/api/v1/namespaces/test/serviceaccounts/mysa/token",
 | 
				
			||||||
			expectTokenRequest: &authenticationv1.TokenRequest{
 | 
								expectTokenRequest: &authenticationv1.TokenRequest{
 | 
				
			||||||
@@ -310,8 +316,8 @@ status:
 | 
				
			|||||||
			for _, aud := range test.audiences {
 | 
								for _, aud := range test.audiences {
 | 
				
			||||||
				cmd.Flags().Set("audience", aud)
 | 
									cmd.Flags().Set("audience", aud)
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			if test.expirationSeconds != 0 {
 | 
								if test.duration != 0 {
 | 
				
			||||||
				cmd.Flags().Set("expiration-seconds", strconv.Itoa(test.expirationSeconds))
 | 
									cmd.Flags().Set("duration", test.duration.String())
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			cmd.Run(cmd, []string{test.name})
 | 
								cmd.Run(cmd, []string{test.name})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user