Kubectl Data Driven Command KEP

This commit is contained in:
Phillip Wittrock 2018-11-13 10:11:02 -08:00
parent 37e8fe7cca
commit a1eaa11e1a
1 changed files with 412 additions and 0 deletions

View File

@ -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