Kubectl Data Driven Command KEP
This commit is contained in:
parent
37e8fe7cca
commit
a1eaa11e1a
|
@ -0,0 +1,412 @@
|
||||||
|
---
|
||||||
|
kep-number: 31
|
||||||
|
title: Data Driven Commands for Kubectl
|
||||||
|
authors:
|
||||||
|
- "@pwittrock"
|
||||||
|
owning-sig: sig-cli
|
||||||
|
participating-sigs:
|
||||||
|
reviewers:
|
||||||
|
- "@soltysh"
|
||||||
|
- "@juanvallejo"
|
||||||
|
- "@seans3 "
|
||||||
|
approvers:
|
||||||
|
- "@soltysh"
|
||||||
|
editor: TBD
|
||||||
|
creation-date: 2018-11-13
|
||||||
|
last-updated: 2018-11-13
|
||||||
|
status: provisional
|
||||||
|
see-also:
|
||||||
|
replaces:
|
||||||
|
superseded-by:
|
||||||
|
---
|
||||||
|
|
||||||
|
# data driven commands
|
||||||
|
|
||||||
|
## Table of Contents
|
||||||
|
|
||||||
|
* [Table of Contents](#table-of-contents)
|
||||||
|
* [Summary](#summary)
|
||||||
|
* [Motivation](#motivation)
|
||||||
|
* [Goals](#goals)
|
||||||
|
* [Non-Goals](#non-goals)
|
||||||
|
* [Proposal](#proposal)
|
||||||
|
* [Implementation Details](#implementation-details)
|
||||||
|
* [Risks and Mitigations](#risks-and-mitigations)
|
||||||
|
* [Graduation Criteria](#graduation-criteria)
|
||||||
|
* [Alternatives](#alternatives)
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
Many Kubectl commands make requests to specific Resource endpoints. The request bodies are populated by flags
|
||||||
|
provided by the user.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
|
||||||
|
- `create <resource>`
|
||||||
|
- `set <field> <resource>`
|
||||||
|
- `logs`
|
||||||
|
|
||||||
|
Although these commands are compiled into the kubectl binary, their workflow is similar to a form on a webpage and
|
||||||
|
could be complete driven by the server providing the client with the request (endpoint + body) and a set of flags
|
||||||
|
to populate the request body.
|
||||||
|
|
||||||
|
Publishing commands as data from the server addresses cli integration with API extensions as well as client-server
|
||||||
|
version skew.
|
||||||
|
|
||||||
|
## Motivation
|
||||||
|
|
||||||
|
Kubectl provides a number of commands to simplify working with Kubernetes by making requests to
|
||||||
|
Resources and SubResources. These requests are mostly static, with fields filled in by user
|
||||||
|
supplied flags. Today the commands are compiled into the client, which as the following challenges:
|
||||||
|
|
||||||
|
- Extension APIs cannot be compiled into the client
|
||||||
|
- Version-Skewed clients (old client) may be missing commands for new APIs or send outdated requests
|
||||||
|
- Version-Skewed clients (new client) may have commands for APIs that are not present in the server or expose
|
||||||
|
fields not present in older API versions
|
||||||
|
|
||||||
|
### Goals
|
||||||
|
|
||||||
|
Allow client commands that make a single request to a specific resource and output the result to be data driven
|
||||||
|
from the server.
|
||||||
|
|
||||||
|
- Address cli support for extension APIs
|
||||||
|
- Address user experience for version skewed clients
|
||||||
|
|
||||||
|
### Non-Goals
|
||||||
|
|
||||||
|
Allow client commands that have complex client-side logic to be data driven.
|
||||||
|
|
||||||
|
- Require a TTY
|
||||||
|
- Are Agnostic to Specific Resources
|
||||||
|
|
||||||
|
## Proposal
|
||||||
|
|
||||||
|
Define a format for publishing simple cli commands as data. CLI commands would be limited to:
|
||||||
|
|
||||||
|
- Sending one or more requests to Resource or SubResource Endpoints
|
||||||
|
- Populating requests from command line flags and arguments
|
||||||
|
- Writing output populated from the Responses
|
||||||
|
|
||||||
|
**Proof of Concept:** [cnctl](https://github.com/pwittrock/kubectl/tree/cnctl/cmd/cnctl)
|
||||||
|
|
||||||
|
Instructions to run PoC:
|
||||||
|
|
||||||
|
- `go run ./main.go` (no commands show up)
|
||||||
|
- `kubectl apply` the `cli_v1alpha1_clitestresource.yaml` (add the CRD with the commands)
|
||||||
|
- `go run ./main.go` (create command shows up)
|
||||||
|
- `go run ./main create deployment -h` (view create command help)
|
||||||
|
- `go run ./main create deploy --image nginx --name nginx` (create a deployment)
|
||||||
|
- `kubectl get deployments`
|
||||||
|
|
||||||
|
### Implementation Details
|
||||||
|
|
||||||
|
**Publishing Data:**
|
||||||
|
|
||||||
|
Alpha: No apimachinery changes required
|
||||||
|
|
||||||
|
- Alpha: publish extension Resource Commands as an annotation on CRDs.
|
||||||
|
- Alpha: publish core Resource Commands as openapi extension.
|
||||||
|
|
||||||
|
Beta: apimachinery changes required
|
||||||
|
|
||||||
|
- Beta: publish extension Resource Commands a part of the CRD Spec.
|
||||||
|
- Beta: publish core Resource Commands from new endpoint (like *swagger.json*)
|
||||||
|
|
||||||
|
**Data Command Structure:**
|
||||||
|
|
||||||
|
```go
|
||||||
|
/*
|
||||||
|
Copyright 2018 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package v1alpha1
|
||||||
|
|
||||||
|
// ResourceCommand defines a command that is dynamically defined as an annotation on a CRD
|
||||||
|
type ResourceCommand struct {
|
||||||
|
// Command is the cli Command
|
||||||
|
Command Command `json:"command"`
|
||||||
|
|
||||||
|
// Requests are the requests the command will send to the apiserver.
|
||||||
|
// +optional
|
||||||
|
Requests []ResourceRequest `json:"requests,omitempty"`
|
||||||
|
|
||||||
|
// Output is a go-template used write the command output. It may reference values specified as flags using
|
||||||
|
// {{index .Flags.Strings "flag-name"}}, {{index .Flags.Ints "flag-name"}}, {{index .Flags.Bools "flag-name"}},
|
||||||
|
// {{index .Flags.Floats "flag-name"}}.
|
||||||
|
//
|
||||||
|
// It may also reference values from the responses that were saved using saveResponseValues
|
||||||
|
// - {{index .Responses.Strings "response-value-name"}}.
|
||||||
|
//
|
||||||
|
// Example:
|
||||||
|
// deployment.apps/{{index .Responses.Strings "responsename"}} created
|
||||||
|
//
|
||||||
|
// +optional
|
||||||
|
Output string `json:"output,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ResourceOperation string
|
||||||
|
|
||||||
|
const (
|
||||||
|
CREATE_RESOURCE ResourceOperation = "Create"
|
||||||
|
UPDATE_RESOURCE = "Update"
|
||||||
|
DELETE_RESOURCE = "Delete"
|
||||||
|
GET_RESOURCE = "Get"
|
||||||
|
PATCH_RESOURCE = "Patch"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ResourceRequest struct {
|
||||||
|
// Group is the API group of the request endpoint
|
||||||
|
//
|
||||||
|
// Example: apps
|
||||||
|
Group string `json:"group"`
|
||||||
|
|
||||||
|
// Version is the API version of the request endpoint
|
||||||
|
//
|
||||||
|
// Example: v1
|
||||||
|
Version string `json:"version"`
|
||||||
|
|
||||||
|
// Resource is the API resource of the request endpoint
|
||||||
|
//
|
||||||
|
// Example: deployments
|
||||||
|
Resource string `json:"resource"`
|
||||||
|
|
||||||
|
// Operation is the type of operation to perform for the request. One of: Create, Update, Delete, Get, Patch
|
||||||
|
Operation ResourceOperation `json:"operation"`
|
||||||
|
|
||||||
|
// BodyTemplate is a go-template for the request Body. It may reference values specified as flags using
|
||||||
|
// {{index .Flags.Strings "flag-name"}}, {{index .Flags.Ints "flag-name"}}, {{index .Flags.Bools "flag-name"}},
|
||||||
|
// {{index .Flags.Floats "flag-name"}}
|
||||||
|
//
|
||||||
|
// Example:
|
||||||
|
// apiVersion: apps/v1
|
||||||
|
// kind: Deployment
|
||||||
|
// metadata:
|
||||||
|
// name: {{index .Flags.Strings "name"}}
|
||||||
|
// namespace: {{index .Flags.Strings "namespace"}}
|
||||||
|
// labels:
|
||||||
|
// app: nginx
|
||||||
|
// spec:
|
||||||
|
// replicas: {{index .Flags.Ints "replicas"}}
|
||||||
|
// selector:
|
||||||
|
// matchLabels:
|
||||||
|
// app: {{index .Flags.Strings "name"}}
|
||||||
|
// template:
|
||||||
|
// metadata:
|
||||||
|
// labels:
|
||||||
|
// app: {{index .Flags.Strings "name"}}
|
||||||
|
// spec:
|
||||||
|
// containers:
|
||||||
|
// - name: {{index .Flags.Strings "name"}}
|
||||||
|
// image: {{index .Flags.Strings "image"}}
|
||||||
|
//
|
||||||
|
// +optional
|
||||||
|
BodyTemplate string `json:"bodyTemplate,omitempty"`
|
||||||
|
|
||||||
|
// SaveResponseValues are values read from the response and saved in {{index .Responses.Strings "flag-name"}}.
|
||||||
|
// They may be used in the ResourceCommand.Output go-template.
|
||||||
|
//
|
||||||
|
// Example:
|
||||||
|
// - name: responsename
|
||||||
|
// jsonPath: "{.metadata.name}"
|
||||||
|
//
|
||||||
|
// +optional
|
||||||
|
SaveResponseValues []ResponseValue `json:"saveResponseValues,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flag defines a cli flag that should be registered and available in request / output templates
|
||||||
|
type Flag struct {
|
||||||
|
Type FlagType `json:"type"`
|
||||||
|
|
||||||
|
Name string `json:"name"`
|
||||||
|
|
||||||
|
Description string `json:"description"`
|
||||||
|
|
||||||
|
// +optional
|
||||||
|
StringValue string `json:"stringValue,omitempty"`
|
||||||
|
|
||||||
|
// +optional
|
||||||
|
StringSliceValue []string `json:"stringSliceValue,omitempty"`
|
||||||
|
|
||||||
|
// +optional
|
||||||
|
BoolValue bool `json:"boolValue,omitempty"`
|
||||||
|
|
||||||
|
// +optional
|
||||||
|
IntValue int32 `json:"intValue,omitempty"`
|
||||||
|
|
||||||
|
// +optional
|
||||||
|
FloatValue float64 `json:"floatValue,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResponseValue defines a value that should be parsed from a response and available in output templates
|
||||||
|
type ResponseValue struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
JsonPath string `json:"jsonPath"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type FlagType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
STRING FlagType = "String"
|
||||||
|
BOOL = "Bool"
|
||||||
|
FLOAT = "Float"
|
||||||
|
INT = "Int"
|
||||||
|
STRING_SLICE = "StringSlice"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Command struct {
|
||||||
|
// Use is the one-line usage message.
|
||||||
|
Use string `json:"use"`
|
||||||
|
|
||||||
|
// Path is the path to the sub-command. Omit if the command is directly under the root command.
|
||||||
|
// +optional
|
||||||
|
Path []string `json:"path,omitempty"`
|
||||||
|
|
||||||
|
// Short is the short description shown in the 'help' output.
|
||||||
|
// +optional
|
||||||
|
Short string `json:"short,omitempty"`
|
||||||
|
|
||||||
|
// Long is the long message shown in the 'help <this-command>' output.
|
||||||
|
// +optional
|
||||||
|
Long string `json:"long,omitempty"`
|
||||||
|
|
||||||
|
// Example is examples of how to use the command.
|
||||||
|
// +optional
|
||||||
|
Example string `json:"example,omitempty"`
|
||||||
|
|
||||||
|
// Deprecated defines, if this command is deprecated and should print this string when used.
|
||||||
|
// +optional
|
||||||
|
Deprecated string `json:"deprecated,omitempty"`
|
||||||
|
|
||||||
|
// Flags are the command line flags.
|
||||||
|
//
|
||||||
|
// Example:
|
||||||
|
// - name: namespace
|
||||||
|
// type: String
|
||||||
|
// stringValue: "default"
|
||||||
|
// description: "deployment namespace"
|
||||||
|
//
|
||||||
|
// +optional
|
||||||
|
Flags []Flag `json:"flags,omitempty"`
|
||||||
|
|
||||||
|
// SuggestFor is an array of command names for which this command will be suggested -
|
||||||
|
// similar to aliases but only suggests.
|
||||||
|
SuggestFor []string `json:"suggestFor,omitempty"`
|
||||||
|
|
||||||
|
// Aliases is an array of aliases that can be used instead of the first word in Use.
|
||||||
|
Aliases []string `json:"aliases,omitempty"`
|
||||||
|
|
||||||
|
// Version defines the version for this command. If this value is non-empty and the command does not
|
||||||
|
// define a "version" flag, a "version" boolean flag will be added to the command and, if specified,
|
||||||
|
// will print content of the "Version" variable.
|
||||||
|
// +optional
|
||||||
|
Version string `json:"version,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResourceCommandList contains a list of Commands
|
||||||
|
type ResourceCommandList struct {
|
||||||
|
Items []ResourceCommand `json:"items"`
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Example Command:**
|
||||||
|
|
||||||
|
```go
|
||||||
|
# Set Label: "cli.sigs.k8s.io/cli.v1alpha1.CommandList": ""
|
||||||
|
# Set Annotation: "cli.sigs.k8s.io/cli.v1alpha1.CommandList": '<json>'
|
||||||
|
---
|
||||||
|
items:
|
||||||
|
- command:
|
||||||
|
path:
|
||||||
|
- "create" # Command is a subcommand of this path
|
||||||
|
use: "deployment" # Command use
|
||||||
|
aliases: # Command alias'
|
||||||
|
- "deploy"
|
||||||
|
- "deployments"
|
||||||
|
short: Create a deployment with the specified name.
|
||||||
|
long: Create a deployment with the specified name.
|
||||||
|
example: |
|
||||||
|
# Create a new deployment named my-dep that runs the busybox image.
|
||||||
|
kubectl create deployment --name my-dep --image=busybox
|
||||||
|
flags:
|
||||||
|
- name: name
|
||||||
|
type: String
|
||||||
|
stringValue: ""
|
||||||
|
description: deployment name
|
||||||
|
- name: image
|
||||||
|
type: String
|
||||||
|
stringValue: ""
|
||||||
|
description: Image name to run.
|
||||||
|
- name: replicas
|
||||||
|
type: Int
|
||||||
|
intValue: 1
|
||||||
|
description: Image name to run.
|
||||||
|
- name: namespace
|
||||||
|
type: String
|
||||||
|
stringValue: "default"
|
||||||
|
description: deployment namespace
|
||||||
|
requests:
|
||||||
|
- group: apps
|
||||||
|
version: v1
|
||||||
|
resource: deployments
|
||||||
|
operation: Create
|
||||||
|
bodyTemplate: |
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: {{index .Flags.Strings "name"}}
|
||||||
|
namespace: {{index .Flags.Strings "namespace"}}
|
||||||
|
labels:
|
||||||
|
app: nginx
|
||||||
|
spec:
|
||||||
|
replicas: {{index .Flags.Ints "replicas"}}
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: {{index .Flags.Strings "name"}}
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: {{index .Flags.Strings "name"}}
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: {{index .Flags.Strings "name"}}
|
||||||
|
image: {{index .Flags.Strings "image"}}
|
||||||
|
saveResponseValues:
|
||||||
|
- name: responsename
|
||||||
|
jsonPath: "{.metadata.name}"
|
||||||
|
output: |
|
||||||
|
deployment.apps/{{index .Responses.Strings "responsename"}} created
|
||||||
|
```
|
||||||
|
|
||||||
|
### Risks and Mitigations
|
||||||
|
|
||||||
|
- Command name collisions: CRD publishes command that conflicts with another command
|
||||||
|
- Initially require the resource name to be the command name (e.g. `create foo`, `set image foo`)
|
||||||
|
- Approach is hard to maintain, complex, etc
|
||||||
|
- Initially restrict to only `create` commands, get feedback
|
||||||
|
|
||||||
|
|
||||||
|
## Graduation Criteria
|
||||||
|
|
||||||
|
- Simple commands for Core Resources have been migrated to be data driven
|
||||||
|
- In use by high profile extension APIs - e.g. Istio
|
||||||
|
- Published as first class item for Extension and Core Resources
|
||||||
|
|
||||||
|
## Alternatives
|
||||||
|
|
||||||
|
- Use plugins for these cases
|
||||||
|
- Still suffer from version skew
|
||||||
|
- Require users to download and install binaries
|
||||||
|
- Hard to keep in sync with set of Resources for each cluster
|
||||||
|
- Don't support cli commands for Extension Resources
|
Loading…
Reference in New Issue