Merge pull request #948 from simonferquel/k8s-watch-stack-status

K8s: more robust stack error detection on deploy
This commit is contained in:
Vincent Demeester 2018-06-04 10:42:42 +02:00 committed by GitHub
commit eaa9149e29
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 3085 additions and 88 deletions

View File

@ -7,6 +7,7 @@ import (
"github.com/docker/cli/cli/command"
"github.com/docker/cli/kubernetes"
cliv1beta1 "github.com/docker/cli/kubernetes/client/clientset/typed/compose/v1beta1"
flag "github.com/spf13/pflag"
kubeclient "k8s.io/client-go/kubernetes"
restclient "k8s.io/client-go/rest"
@ -112,3 +113,11 @@ func (c *KubeCli) checkHostsMatch() error {
" Update $DOCKER_HOST (or pass -H), or use 'kubectl config use-context' to match.\n", daemonEndpoint.Hostname(), kubeEndpoint.Hostname())
return nil
}
func (c *KubeCli) stacksv1beta1() (cliv1beta1.StackInterface, error) {
raw, err := newStackV1Beta1(c.kubeConfig, c.kubeNamespace)
if err != nil {
return nil, err
}
return raw.stacks, nil
}

View File

@ -2,9 +2,12 @@ package kubernetes
import (
"fmt"
"io"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/stack/loader"
"github.com/docker/cli/cli/command/stack/options"
"github.com/morikuni/aec"
"github.com/pkg/errors"
)
@ -39,10 +42,6 @@ func RunDeploy(dockerCli *KubeCli, opts options.Deploy) error {
configMaps := composeClient.ConfigMaps()
secrets := composeClient.Secrets()
services := composeClient.Services()
pods := composeClient.Pods()
watcher := DeployWatcher{
Pods: pods,
}
if err := stacks.IsColliding(services, stack); err != nil {
return err
@ -61,10 +60,109 @@ func RunDeploy(dockerCli *KubeCli, opts options.Deploy) error {
}
fmt.Fprintln(cmdOut, "Waiting for the stack to be stable and running...")
v1beta1Cli, err := dockerCli.stacksv1beta1()
if err != nil {
return err
}
<-watcher.Watch(stack.name, stack.getServices())
fmt.Fprintf(cmdOut, "Stack %s is stable and running\n\n", stack.name)
pods := composeClient.Pods()
watcher := &deployWatcher{
stacks: v1beta1Cli,
pods: pods,
}
statusUpdates := make(chan serviceStatus)
displayDone := make(chan struct{})
go func() {
defer close(displayDone)
display := newStatusDisplay(dockerCli.Out())
for status := range statusUpdates {
display.OnStatus(status)
}
}()
err = watcher.Watch(stack.name, stack.getServices(), statusUpdates)
close(statusUpdates)
<-displayDone
if err != nil {
return err
}
fmt.Fprintf(cmdOut, "\nStack %s is stable and running\n\n", stack.name)
return nil
}
type statusDisplay interface {
OnStatus(serviceStatus)
}
type metaServiceState string
const (
metaServiceStateReady = metaServiceState("Ready")
metaServiceStatePending = metaServiceState("Pending")
metaServiceStateFailed = metaServiceState("Failed")
)
func metaStateFromStatus(status serviceStatus) metaServiceState {
switch {
case status.podsReady > 0:
return metaServiceStateReady
case status.podsPending > 0:
return metaServiceStatePending
default:
return metaServiceStateFailed
}
}
type forwardOnlyStatusDisplay struct {
o *command.OutStream
states map[string]metaServiceState
}
func (d *forwardOnlyStatusDisplay) OnStatus(status serviceStatus) {
state := metaStateFromStatus(status)
if d.states[status.name] != state {
d.states[status.name] = state
fmt.Fprintf(d.o, "%s: %s\n", status.name, state)
}
}
type interactiveStatusDisplay struct {
o *command.OutStream
statuses []serviceStatus
}
func (d *interactiveStatusDisplay) OnStatus(status serviceStatus) {
b := aec.EmptyBuilder
for ix := 0; ix < len(d.statuses); ix++ {
b = b.Up(1).EraseLine(aec.EraseModes.All)
}
b = b.Column(0)
fmt.Fprint(d.o, b.ANSI)
updated := false
for ix, s := range d.statuses {
if s.name == status.name {
d.statuses[ix] = status
s = status
updated = true
}
displayInteractiveServiceStatus(s, d.o)
}
if !updated {
d.statuses = append(d.statuses, status)
displayInteractiveServiceStatus(status, d.o)
}
}
func displayInteractiveServiceStatus(status serviceStatus, o io.Writer) {
state := metaStateFromStatus(status)
totalFailed := status.podsFailed + status.podsSucceeded + status.podsUnknown
fmt.Fprintf(o, "%[1]s: %[2]s\t\t[pod status: %[3]d/%[6]d ready, %[4]d/%[6]d pending, %[5]d/%[6]d failed]\n", status.name, state,
status.podsReady, status.podsPending, totalFailed, status.podsTotal)
}
func newStatusDisplay(o *command.OutStream) statusDisplay {
if !o.IsTerminal() {
return &forwardOnlyStatusDisplay{o: o, states: map[string]metaServiceState{}}
}
return &interactiveStatusDisplay{o: o}
}

View File

@ -33,7 +33,7 @@ type stackV1Beta1 struct {
stacks composev1beta1.StackInterface
}
func newStackV1Beta1(config *rest.Config, namespace string) (StackClient, error) {
func newStackV1Beta1(config *rest.Config, namespace string) (*stackV1Beta1, error) {
client, err := composev1beta1.NewForConfig(config)
if err != nil {
return nil, err
@ -136,7 +136,7 @@ type stackV1Beta2 struct {
stacks composev1beta2.StackInterface
}
func newStackV1Beta2(config *rest.Config, namespace string) (StackClient, error) {
func newStackV1Beta2(config *rest.Config, namespace string) (*stackV1Beta2, error) {
client, err := composev1beta2.NewForConfig(config)
if err != nil {
return nil, err

View File

@ -1,116 +1,255 @@
package kubernetes
import (
"fmt"
"context"
"sync"
"time"
apiv1beta1 "github.com/docker/cli/kubernetes/compose/v1beta1"
"github.com/docker/cli/kubernetes/labels"
"github.com/pkg/errors"
apiv1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
corev1 "k8s.io/client-go/kubernetes/typed/core/v1"
"k8s.io/apimachinery/pkg/runtime"
runtimeutil "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/apimachinery/pkg/watch"
cache "k8s.io/client-go/tools/cache"
podutils "k8s.io/kubernetes/pkg/api/v1/pod"
)
type stackListWatch interface {
List(opts metav1.ListOptions) (*apiv1beta1.StackList, error)
Watch(opts metav1.ListOptions) (watch.Interface, error)
}
type podListWatch interface {
List(opts metav1.ListOptions) (*apiv1.PodList, error)
Watch(opts metav1.ListOptions) (watch.Interface, error)
}
// DeployWatcher watches a stack deployement
type DeployWatcher struct {
Pods corev1.PodInterface
type deployWatcher struct {
pods podListWatch
stacks stackListWatch
}
// Watch watches a stuck deployement and return a chan that will holds the state of the stack
func (w DeployWatcher) Watch(name string, serviceNames []string) chan bool {
stop := make(chan bool)
func (w *deployWatcher) Watch(name string, serviceNames []string, statusUpdates chan serviceStatus) error {
errC := make(chan error, 1)
defer close(errC)
go w.waitForPods(name, serviceNames, stop)
handlers := runtimeutil.ErrorHandlers
return stop
}
func (w DeployWatcher) waitForPods(stackName string, serviceNames []string, stop chan bool) {
starts := map[string]int32{}
for {
time.Sleep(1 * time.Second)
list, err := w.Pods.List(metav1.ListOptions{
LabelSelector: labels.SelectorForStack(stackName),
IncludeUninitialized: true,
// informer errors are reported using global error handlers
runtimeutil.ErrorHandlers = append(handlers, func(err error) {
errC <- err
})
if err != nil {
stop <- true
defer func() {
runtimeutil.ErrorHandlers = handlers
}()
ctx, cancel := context.WithCancel(context.Background())
wg := sync.WaitGroup{}
defer func() {
cancel()
wg.Wait()
}()
wg.Add(2)
go func() {
defer wg.Done()
w.watchStackStatus(ctx, name, errC)
}()
go func() {
defer wg.Done()
w.waitForPods(ctx, name, serviceNames, errC, statusUpdates)
}()
return <-errC
}
type stackWatcher struct {
resultChan chan error
stackName string
}
var _ cache.ResourceEventHandler = &stackWatcher{}
func (sw *stackWatcher) OnAdd(obj interface{}) {
stack, ok := obj.(*apiv1beta1.Stack)
switch {
case !ok:
sw.resultChan <- errors.Errorf("stack %s has incorrect type", sw.stackName)
case stack.Status.Phase == apiv1beta1.StackFailure:
sw.resultChan <- errors.Errorf("stack %s failed with status %s: %s", sw.stackName, stack.Status.Phase, stack.Status.Message)
}
}
func (sw *stackWatcher) OnUpdate(oldObj, newObj interface{}) {
sw.OnAdd(newObj)
}
func (sw *stackWatcher) OnDelete(obj interface{}) {
}
func (w *deployWatcher) watchStackStatus(ctx context.Context, stackname string, e chan error) {
informer := newStackInformer(w.stacks, stackname)
sw := &stackWatcher{
resultChan: e,
}
informer.AddEventHandler(sw)
informer.Run(ctx.Done())
}
type serviceStatus struct {
name string
podsPending int
podsRunning int
podsSucceeded int
podsFailed int
podsUnknown int
podsReady int
podsTotal int
}
type podWatcher struct {
stackName string
services map[string]serviceStatus
resultChan chan error
starts map[string]int32
indexer cache.Indexer
statusUpdates chan serviceStatus
}
var _ cache.ResourceEventHandler = &podWatcher{}
func (pw *podWatcher) handlePod(obj interface{}) {
pod, ok := obj.(*apiv1.Pod)
if !ok {
pw.resultChan <- errors.Errorf("Pod has incorrect type in stack %s", pw.stackName)
return
}
for i := range list.Items {
pod := list.Items[i]
if pod.Status.Phase != apiv1.PodRunning {
continue
}
startCount := startCount(pod)
serviceName := pod.Labels[labels.ForServiceName]
if startCount != starts[serviceName] {
if startCount == 1 {
fmt.Printf(" - Service %s has one container running\n", serviceName)
} else {
fmt.Printf(" - Service %s was restarted %d %s\n", serviceName, startCount-1, timeTimes(startCount-1))
}
starts[serviceName] = startCount
}
}
if allReady(list.Items, serviceNames) {
stop <- true
return
pw.updateServiceStatus(serviceName)
if pw.allReady() {
select {
case pw.resultChan <- nil:
default:
// result has already been reported, just don't block
}
}
}
func startCount(pod apiv1.Pod) int32 {
restart := int32(0)
for _, status := range pod.Status.ContainerStatuses {
restart += status.RestartCount
func (pw *podWatcher) updateServiceStatus(serviceName string) {
pods, _ := pw.indexer.ByIndex("byservice", serviceName)
status := serviceStatus{name: serviceName}
for _, obj := range pods {
if pod, ok := obj.(*apiv1.Pod); ok {
switch pod.Status.Phase {
case apiv1.PodPending:
status.podsPending++
case apiv1.PodRunning:
status.podsRunning++
case apiv1.PodSucceeded:
status.podsSucceeded++
case apiv1.PodFailed:
status.podsFailed++
case apiv1.PodUnknown:
status.podsUnknown++
}
return 1 + restart
if podutils.IsPodReady(pod) {
status.podsReady++
}
}
}
status.podsTotal = len(pods)
oldStatus := pw.services[serviceName]
if oldStatus != status {
pw.statusUpdates <- status
}
pw.services[serviceName] = status
}
func allReady(pods []apiv1.Pod, serviceNames []string) bool {
serviceUp := map[string]bool{}
for _, pod := range pods {
if time.Since(pod.GetCreationTimestamp().Time) < 10*time.Second {
return false
}
ready := false
for _, cond := range pod.Status.Conditions {
if cond.Type == apiv1.PodReady && cond.Status == apiv1.ConditionTrue {
ready = true
}
}
if !ready {
return false
}
serviceName := pod.Labels[labels.ForServiceName]
serviceUp[serviceName] = true
}
for _, serviceName := range serviceNames {
if !serviceUp[serviceName] {
func (pw *podWatcher) allReady() bool {
for _, status := range pw.services {
if status.podsReady == 0 {
return false
}
}
return true
}
func timeTimes(n int32) string {
if n == 1 {
return "time"
}
return "times"
func (pw *podWatcher) OnAdd(obj interface{}) {
pw.handlePod(obj)
}
func (pw *podWatcher) OnUpdate(oldObj, newObj interface{}) {
pw.handlePod(newObj)
}
func (pw *podWatcher) OnDelete(obj interface{}) {
pw.handlePod(obj)
}
func (w *deployWatcher) waitForPods(ctx context.Context, stackName string, serviceNames []string, e chan error, statusUpdates chan serviceStatus) {
informer := newPodInformer(w.pods, stackName, cache.Indexers{
"byservice": func(obj interface{}) ([]string, error) {
pod, ok := obj.(*apiv1.Pod)
if !ok {
return nil, errors.Errorf("Pod has incorrect type in stack %s", stackName)
}
return []string{pod.Labels[labels.ForServiceName]}, nil
}})
services := map[string]serviceStatus{}
for _, name := range serviceNames {
services[name] = serviceStatus{name: name}
}
pw := &podWatcher{
stackName: stackName,
services: services,
resultChan: e,
starts: map[string]int32{},
indexer: informer.GetIndexer(),
statusUpdates: statusUpdates,
}
informer.AddEventHandler(pw)
informer.Run(ctx.Done())
}
func newPodInformer(podsClient podListWatch, stackName string, indexers cache.Indexers) cache.SharedIndexInformer {
return cache.NewSharedIndexInformer(
&cache.ListWatch{
ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
options.LabelSelector = labels.SelectorForStack(stackName)
options.IncludeUninitialized = true
return podsClient.List(options)
},
WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
options.LabelSelector = labels.SelectorForStack(stackName)
options.IncludeUninitialized = true
return podsClient.Watch(options)
},
},
&apiv1.Pod{},
time.Second*5,
indexers,
)
}
func newStackInformer(stacksClient stackListWatch, stackName string) cache.SharedInformer {
return cache.NewSharedInformer(
&cache.ListWatch{
ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
options.LabelSelector = labels.SelectorForStack(stackName)
return stacksClient.List(options)
},
WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
options.LabelSelector = labels.SelectorForStack(stackName)
return stacksClient.Watch(options)
},
},
&apiv1beta1.Stack{},
time.Second*5,
)
}

View File

@ -0,0 +1,218 @@
package kubernetes
import (
"testing"
apiv1beta1 "github.com/docker/cli/kubernetes/compose/v1beta1"
composelabels "github.com/docker/cli/kubernetes/labels"
"github.com/gotestyourself/gotestyourself/assert"
apiv1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/runtime/serializer"
"k8s.io/apimachinery/pkg/watch"
k8stesting "k8s.io/client-go/testing"
)
var podsResource = apiv1.SchemeGroupVersion.WithResource("pods")
var podKind = apiv1.SchemeGroupVersion.WithKind("Pod")
var stacksResource = apiv1beta1.SchemeGroupVersion.WithResource("stacks")
var stackKind = apiv1beta1.SchemeGroupVersion.WithKind("Stack")
type testPodAndStackRepository struct {
fake *k8stesting.Fake
}
func (r *testPodAndStackRepository) stackListWatchForNamespace(ns string) *testStackListWatch {
return &testStackListWatch{fake: r.fake, ns: ns}
}
func (r *testPodAndStackRepository) podListWatchForNamespace(ns string) *testPodListWatch {
return &testPodListWatch{fake: r.fake, ns: ns}
}
func newTestPodAndStackRepository(initialPods []apiv1.Pod, initialStacks []apiv1beta1.Stack, podWatchHandler, stackWatchHandler k8stesting.WatchReactionFunc) *testPodAndStackRepository {
var scheme = runtime.NewScheme()
var codecs = serializer.NewCodecFactory(scheme)
metav1.AddToGroupVersion(scheme, schema.GroupVersion{Version: "v1"})
apiv1.AddToScheme(scheme)
apiv1beta1.AddToScheme(scheme)
o := k8stesting.NewObjectTracker(scheme, codecs.UniversalDecoder())
for _, obj := range initialPods {
if err := o.Add(&obj); err != nil {
panic(err)
}
}
for _, obj := range initialStacks {
if err := o.Add(&obj); err != nil {
panic(err)
}
}
fakePtr := &k8stesting.Fake{}
fakePtr.AddReactor("*", "*", k8stesting.ObjectReaction(o))
if podWatchHandler != nil {
fakePtr.AddWatchReactor(podsResource.Resource, podWatchHandler)
}
if stackWatchHandler != nil {
fakePtr.AddWatchReactor(stacksResource.Resource, stackWatchHandler)
}
fakePtr.AddWatchReactor("*", k8stesting.DefaultWatchReactor(watch.NewFake(), nil))
return &testPodAndStackRepository{fake: fakePtr}
}
type testStackListWatch struct {
fake *k8stesting.Fake
ns string
}
func (s *testStackListWatch) List(opts metav1.ListOptions) (*apiv1beta1.StackList, error) {
obj, err := s.fake.Invokes(k8stesting.NewListAction(stacksResource, stackKind, s.ns, opts), &apiv1beta1.StackList{})
if obj == nil {
return nil, err
}
label, _, _ := k8stesting.ExtractFromListOptions(opts)
if label == nil {
label = labels.Everything()
}
list := &apiv1beta1.StackList{}
for _, item := range obj.(*apiv1beta1.StackList).Items {
if label.Matches(labels.Set(item.Labels)) {
list.Items = append(list.Items, item)
}
}
return list, err
}
func (s *testStackListWatch) Watch(opts metav1.ListOptions) (watch.Interface, error) {
return s.fake.InvokesWatch(k8stesting.NewWatchAction(stacksResource, s.ns, opts))
}
type testPodListWatch struct {
fake *k8stesting.Fake
ns string
}
func (p *testPodListWatch) List(opts metav1.ListOptions) (*apiv1.PodList, error) {
obj, err := p.fake.Invokes(k8stesting.NewListAction(podsResource, podKind, p.ns, opts), &apiv1.PodList{})
if obj == nil {
return nil, err
}
label, _, _ := k8stesting.ExtractFromListOptions(opts)
if label == nil {
label = labels.Everything()
}
list := &apiv1.PodList{}
for _, item := range obj.(*apiv1.PodList).Items {
if label.Matches(labels.Set(item.Labels)) {
list.Items = append(list.Items, item)
}
}
return list, err
}
func (p *testPodListWatch) Watch(opts metav1.ListOptions) (watch.Interface, error) {
return p.fake.InvokesWatch(k8stesting.NewWatchAction(podsResource, p.ns, opts))
}
func TestDeployWatchOk(t *testing.T) {
stack := apiv1beta1.Stack{
ObjectMeta: metav1.ObjectMeta{Name: "test-stack", Namespace: "test-ns"},
}
serviceNames := []string{"svc1", "svc2"}
testRepo := newTestPodAndStackRepository(nil, []apiv1beta1.Stack{stack}, func(action k8stesting.Action) (handled bool, ret watch.Interface, err error) {
res := watch.NewFake()
go func() {
pod1 := &apiv1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "test1",
Namespace: "test-ns",
Labels: composelabels.ForService("test-stack", "svc1"),
},
Status: apiv1.PodStatus{
Phase: apiv1.PodRunning,
Conditions: []apiv1.PodCondition{
{
Type: apiv1.PodReady,
Status: apiv1.ConditionTrue,
},
},
},
}
pod2 := &apiv1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "test2",
Namespace: "test-ns",
Labels: composelabels.ForService("test-stack", "svc2"),
},
Status: apiv1.PodStatus{
Phase: apiv1.PodRunning,
Conditions: []apiv1.PodCondition{
{
Type: apiv1.PodReady,
Status: apiv1.ConditionTrue,
},
},
},
}
res.Add(pod1)
res.Add(pod2)
}()
return true, res, nil
}, nil)
testee := &deployWatcher{
stacks: testRepo.stackListWatchForNamespace("test-ns"),
pods: testRepo.podListWatchForNamespace("test-ns"),
}
statusUpdates := make(chan serviceStatus)
go func() {
for range statusUpdates {
}
}()
defer close(statusUpdates)
err := testee.Watch(stack.Name, serviceNames, statusUpdates)
assert.NilError(t, err)
}
func TestDeployReconcileFailure(t *testing.T) {
stack := apiv1beta1.Stack{
ObjectMeta: metav1.ObjectMeta{Name: "test-stack", Namespace: "test-ns"},
}
serviceNames := []string{"svc1", "svc2"}
testRepo := newTestPodAndStackRepository(nil, []apiv1beta1.Stack{stack}, nil, func(action k8stesting.Action) (handled bool, ret watch.Interface, err error) {
res := watch.NewFake()
go func() {
sfailed := stack
sfailed.Status = apiv1beta1.StackStatus{
Phase: apiv1beta1.StackFailure,
Message: "test error",
}
res.Modify(&sfailed)
}()
return true, res, nil
})
testee := &deployWatcher{
stacks: testRepo.stackListWatchForNamespace("test-ns"),
pods: testRepo.podListWatchForNamespace("test-ns"),
}
statusUpdates := make(chan serviceStatus)
go func() {
for range statusUpdates {
}
}()
defer close(statusUpdates)
err := testee.Watch(stack.Name, serviceNames, statusUpdates)
assert.ErrorContains(t, err, "Failure: test error")
}

View File

@ -36,6 +36,7 @@ github.com/go-openapi/spec 6aced65f8501fe1217321abf0749d354824ba2ff
github.com/go-openapi/swag 1d0bd113de87027671077d3c71eb3ac5d7dbba72
github.com/gregjones/httpcache c1f8028e62adb3d518b823a2f8e6a95c38bdd3aa
github.com/grpc-ecosystem/grpc-gateway 1a03ca3bad1e1ebadaedd3abb76bc58d4ac8143b
github.com/hashicorp/golang-lru 0a025b7e63adc15a622f29b0b2c4c3848243bbf6
github.com/howeyc/gopass 3ca23474a7c7203e0a0a070fd33508f6efdb9b3d
github.com/imdario/mergo 9d5f1277e9a8ed20c3684bda8fde67c05628518c # v0.3.4
github.com/inconshreveable/mousetrap 76626ae9c91c4f2a10f34cad8ce83ea42c93bb75
@ -48,6 +49,7 @@ github.com/Microsoft/go-winio v0.4.6
github.com/miekg/pkcs11 5f6e0d0dad6f472df908c8e968a98ef00c9224bb
github.com/mitchellh/mapstructure f3009df150dadf309fdee4a54ed65c124afad715
github.com/moby/buildkit aaff9d591ef128560018433fe61beb802e149de8
github.com/morikuni/aec 39771216ff4c63d11f5e604076f9c45e8be1067b
github.com/Nvveen/Gotty a8b993ba6abdb0e0c12b0125c603323a71c7790c https://github.com/ijc25/Gotty
github.com/opencontainers/go-digest 21dfd564fd89c944783d00d069f33e3e7123c448
github.com/opencontainers/image-spec v1.0.1
@ -87,4 +89,3 @@ k8s.io/client-go kubernetes-1.8.2
k8s.io/kubernetes v1.8.2
k8s.io/kube-openapi 61b46af70dfed79c6d24530cd23b41440a7f22a5
vbom.ml/util 928aaa586d7718c70f4090ddf83f2b34c16fdc8d
github.com/hashicorp/golang-lru 0a025b7e63adc15a622f29b0b2c4c3848243bbf6

21
vendor/github.com/morikuni/aec/LICENSE generated vendored Normal file
View File

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2016 Taihei Morikuni
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

178
vendor/github.com/morikuni/aec/README.md generated vendored Normal file
View File

@ -0,0 +1,178 @@
# aec
[![GoDoc](https://godoc.org/github.com/morikuni/aec?status.svg)](https://godoc.org/github.com/morikuni/aec)
Go wrapper for ANSI escape code.
## Install
```bash
go get github.com/morikuni/aec
```
## Features
ANSI escape codes depend on terminal environment.
Some of these features may not work.
Check supported Font-Style/Font-Color features with [checkansi](./checkansi).
[Wikipedia](https://en.wikipedia.org/wiki/ANSI_escape_code) for more detail.
### Cursor
- `Up(n)`
- `Down(n)`
- `Right(n)`
- `Left(n)`
- `NextLine(n)`
- `PreviousLine(n)`
- `Column(col)`
- `Position(row, col)`
- `Save`
- `Restore`
- `Hide`
- `Show`
- `Report`
### Erase
- `EraseDisplay(mode)`
- `EraseLine(mode)`
### Scroll
- `ScrollUp(n)`
- `ScrollDown(n)`
### Font Style
- `Bold`
- `Faint`
- `Italic`
- `Underline`
- `BlinkSlow`
- `BlinkRapid`
- `Inverse`
- `Conceal`
- `CrossOut`
- `Frame`
- `Encircle`
- `Overline`
### Font Color
Foreground color.
- `DefaultF`
- `BlackF`
- `RedF`
- `GreenF`
- `YellowF`
- `BlueF`
- `MagentaF`
- `CyanF`
- `WhiteF`
- `LightBlackF`
- `LightRedF`
- `LightGreenF`
- `LightYellowF`
- `LightBlueF`
- `LightMagentaF`
- `LightCyanF`
- `LightWhiteF`
- `Color3BitF(color)`
- `Color8BitF(color)`
- `FullColorF(r, g, b)`
Background color.
- `DefaultB`
- `BlackB`
- `RedB`
- `GreenB`
- `YellowB`
- `BlueB`
- `MagentaB`
- `CyanB`
- `WhiteB`
- `LightBlackB`
- `LightRedB`
- `LightGreenB`
- `LightYellowB`
- `LightBlueB`
- `LightMagentaB`
- `LightCyanB`
- `LightWhiteB`
- `Color3BitB(color)`
- `Color8BitB(color)`
- `FullColorB(r, g, b)`
### Color Converter
24bit RGB color to ANSI color.
- `NewRGB3Bit(r, g, b)`
- `NewRGB8Bit(r, g, b)`
### Builder
To mix these features.
```go
custom := aec.EmptyBuilder.Right(2).RGB8BitF(128, 255, 64).RedB().ANSI
custom.Apply("Hello World")
```
## Usage
1. Create ANSI by `aec.XXX().With(aec.YYY())` or `aec.EmptyBuilder.XXX().YYY().ANSI`
2. Print ANSI by `fmt.Print(ansi, "some string", aec.Reset)` or `fmt.Print(ansi.Apply("some string"))`
`aec.Reset` should be added when using font style or font color features.
## Example
Simple progressbar.
![sample](./sample.gif)
```go
package main
import (
"fmt"
"strings"
"time"
"github.com/morikuni/aec"
)
func main() {
const n = 20
builder := aec.EmptyBuilder
up2 := aec.Up(2)
col := aec.Column(n + 2)
bar := aec.Color8BitF(aec.NewRGB8Bit(64, 255, 64))
label := builder.LightRedF().Underline().With(col).Right(1).ANSI
// for up2
fmt.Println()
fmt.Println()
for i := 0; i <= n; i++ {
fmt.Print(up2)
fmt.Println(label.Apply(fmt.Sprint(i, "/", n)))
fmt.Print("[")
fmt.Print(bar.Apply(strings.Repeat("=", i)))
fmt.Println(col.Apply("]"))
time.Sleep(100 * time.Millisecond)
}
}
```
## License
[MIT](./LICENSE)

137
vendor/github.com/morikuni/aec/aec.go generated vendored Normal file
View File

@ -0,0 +1,137 @@
package aec
import "fmt"
// EraseMode is listed in a variable EraseModes.
type EraseMode uint
var (
// EraseModes is a list of EraseMode.
EraseModes struct {
// All erase all.
All EraseMode
// Head erase to head.
Head EraseMode
// Tail erase to tail.
Tail EraseMode
}
// Save saves the cursor position.
Save ANSI
// Restore restores the cursor position.
Restore ANSI
// Hide hides the cursor.
Hide ANSI
// Show shows the cursor.
Show ANSI
// Report reports the cursor position.
Report ANSI
)
// Up moves up the cursor.
func Up(n uint) ANSI {
if n == 0 {
return empty
}
return newAnsi(fmt.Sprintf(esc+"%dA", n))
}
// Down moves down the cursor.
func Down(n uint) ANSI {
if n == 0 {
return empty
}
return newAnsi(fmt.Sprintf(esc+"%dB", n))
}
// Right moves right the cursor.
func Right(n uint) ANSI {
if n == 0 {
return empty
}
return newAnsi(fmt.Sprintf(esc+"%dC", n))
}
// Left moves left the cursor.
func Left(n uint) ANSI {
if n == 0 {
return empty
}
return newAnsi(fmt.Sprintf(esc+"%dD", n))
}
// NextLine moves down the cursor to head of a line.
func NextLine(n uint) ANSI {
if n == 0 {
return empty
}
return newAnsi(fmt.Sprintf(esc+"%dE", n))
}
// PreviousLine moves up the cursor to head of a line.
func PreviousLine(n uint) ANSI {
if n == 0 {
return empty
}
return newAnsi(fmt.Sprintf(esc+"%dF", n))
}
// Column set the cursor position to a given column.
func Column(col uint) ANSI {
return newAnsi(fmt.Sprintf(esc+"%dG", col))
}
// Position set the cursor position to a given absolute position.
func Position(row, col uint) ANSI {
return newAnsi(fmt.Sprintf(esc+"%d;%dH", row, col))
}
// EraseDisplay erases display by given EraseMode.
func EraseDisplay(m EraseMode) ANSI {
return newAnsi(fmt.Sprintf(esc+"%dJ", m))
}
// EraseLine erases lines by given EraseMode.
func EraseLine(m EraseMode) ANSI {
return newAnsi(fmt.Sprintf(esc+"%dK", m))
}
// ScrollUp scrolls up the page.
func ScrollUp(n int) ANSI {
if n == 0 {
return empty
}
return newAnsi(fmt.Sprintf(esc+"%dS", n))
}
// ScrollDown scrolls down the page.
func ScrollDown(n int) ANSI {
if n == 0 {
return empty
}
return newAnsi(fmt.Sprintf(esc+"%dT", n))
}
func init() {
EraseModes = struct {
All EraseMode
Head EraseMode
Tail EraseMode
}{
Tail: 0,
Head: 1,
All: 2,
}
Save = newAnsi(esc + "s")
Restore = newAnsi(esc + "u")
Hide = newAnsi(esc + "?25l")
Show = newAnsi(esc + "?25h")
Report = newAnsi(esc + "6n")
}

59
vendor/github.com/morikuni/aec/ansi.go generated vendored Normal file
View File

@ -0,0 +1,59 @@
package aec
import (
"fmt"
"strings"
)
const esc = "\x1b["
// Reset resets SGR effect.
const Reset string = "\x1b[0m"
var empty = newAnsi("")
// ANSI represents ANSI escape code.
type ANSI interface {
fmt.Stringer
// With adapts given ANSIs.
With(...ANSI) ANSI
// Apply wraps given string in ANSI.
Apply(string) string
}
type ansiImpl string
func newAnsi(s string) *ansiImpl {
r := ansiImpl(s)
return &r
}
func (a *ansiImpl) With(ansi ...ANSI) ANSI {
return concat(append([]ANSI{a}, ansi...))
}
func (a *ansiImpl) Apply(s string) string {
return a.String() + s + Reset
}
func (a *ansiImpl) String() string {
return string(*a)
}
// Apply wraps given string in ANSIs.
func Apply(s string, ansi ...ANSI) string {
if len(ansi) == 0 {
return s
}
return concat(ansi).Apply(s)
}
func concat(ansi []ANSI) ANSI {
strs := make([]string, 0, len(ansi))
for _, p := range ansi {
strs = append(strs, p.String())
}
return newAnsi(strings.Join(strs, ""))
}

388
vendor/github.com/morikuni/aec/builder.go generated vendored Normal file
View File

@ -0,0 +1,388 @@
package aec
// Builder is a lightweight syntax to construct customized ANSI.
type Builder struct {
ANSI ANSI
}
// EmptyBuilder is an initialized Builder.
var EmptyBuilder *Builder
// NewBuilder creates a Builder from existing ANSI.
func NewBuilder(a ...ANSI) *Builder {
return &Builder{concat(a)}
}
// With is a syntax for With.
func (builder *Builder) With(a ...ANSI) *Builder {
return NewBuilder(builder.ANSI.With(a...))
}
// Up is a syntax for Up.
func (builder *Builder) Up(n uint) *Builder {
return builder.With(Up(n))
}
// Down is a syntax for Down.
func (builder *Builder) Down(n uint) *Builder {
return builder.With(Down(n))
}
// Right is a syntax for Right.
func (builder *Builder) Right(n uint) *Builder {
return builder.With(Right(n))
}
// Left is a syntax for Left.
func (builder *Builder) Left(n uint) *Builder {
return builder.With(Left(n))
}
// NextLine is a syntax for NextLine.
func (builder *Builder) NextLine(n uint) *Builder {
return builder.With(NextLine(n))
}
// PreviousLine is a syntax for PreviousLine.
func (builder *Builder) PreviousLine(n uint) *Builder {
return builder.With(PreviousLine(n))
}
// Column is a syntax for Column.
func (builder *Builder) Column(col uint) *Builder {
return builder.With(Column(col))
}
// Position is a syntax for Position.
func (builder *Builder) Position(row, col uint) *Builder {
return builder.With(Position(row, col))
}
// EraseDisplay is a syntax for EraseDisplay.
func (builder *Builder) EraseDisplay(m EraseMode) *Builder {
return builder.With(EraseDisplay(m))
}
// EraseLine is a syntax for EraseLine.
func (builder *Builder) EraseLine(m EraseMode) *Builder {
return builder.With(EraseLine(m))
}
// ScrollUp is a syntax for ScrollUp.
func (builder *Builder) ScrollUp(n int) *Builder {
return builder.With(ScrollUp(n))
}
// ScrollDown is a syntax for ScrollDown.
func (builder *Builder) ScrollDown(n int) *Builder {
return builder.With(ScrollDown(n))
}
// Save is a syntax for Save.
func (builder *Builder) Save() *Builder {
return builder.With(Save)
}
// Restore is a syntax for Restore.
func (builder *Builder) Restore() *Builder {
return builder.With(Restore)
}
// Hide is a syntax for Hide.
func (builder *Builder) Hide() *Builder {
return builder.With(Hide)
}
// Show is a syntax for Show.
func (builder *Builder) Show() *Builder {
return builder.With(Show)
}
// Report is a syntax for Report.
func (builder *Builder) Report() *Builder {
return builder.With(Report)
}
// Bold is a syntax for Bold.
func (builder *Builder) Bold() *Builder {
return builder.With(Bold)
}
// Faint is a syntax for Faint.
func (builder *Builder) Faint() *Builder {
return builder.With(Faint)
}
// Italic is a syntax for Italic.
func (builder *Builder) Italic() *Builder {
return builder.With(Italic)
}
// Underline is a syntax for Underline.
func (builder *Builder) Underline() *Builder {
return builder.With(Underline)
}
// BlinkSlow is a syntax for BlinkSlow.
func (builder *Builder) BlinkSlow() *Builder {
return builder.With(BlinkSlow)
}
// BlinkRapid is a syntax for BlinkRapid.
func (builder *Builder) BlinkRapid() *Builder {
return builder.With(BlinkRapid)
}
// Inverse is a syntax for Inverse.
func (builder *Builder) Inverse() *Builder {
return builder.With(Inverse)
}
// Conceal is a syntax for Conceal.
func (builder *Builder) Conceal() *Builder {
return builder.With(Conceal)
}
// CrossOut is a syntax for CrossOut.
func (builder *Builder) CrossOut() *Builder {
return builder.With(CrossOut)
}
// BlackF is a syntax for BlackF.
func (builder *Builder) BlackF() *Builder {
return builder.With(BlackF)
}
// RedF is a syntax for RedF.
func (builder *Builder) RedF() *Builder {
return builder.With(RedF)
}
// GreenF is a syntax for GreenF.
func (builder *Builder) GreenF() *Builder {
return builder.With(GreenF)
}
// YellowF is a syntax for YellowF.
func (builder *Builder) YellowF() *Builder {
return builder.With(YellowF)
}
// BlueF is a syntax for BlueF.
func (builder *Builder) BlueF() *Builder {
return builder.With(BlueF)
}
// MagentaF is a syntax for MagentaF.
func (builder *Builder) MagentaF() *Builder {
return builder.With(MagentaF)
}
// CyanF is a syntax for CyanF.
func (builder *Builder) CyanF() *Builder {
return builder.With(CyanF)
}
// WhiteF is a syntax for WhiteF.
func (builder *Builder) WhiteF() *Builder {
return builder.With(WhiteF)
}
// DefaultF is a syntax for DefaultF.
func (builder *Builder) DefaultF() *Builder {
return builder.With(DefaultF)
}
// BlackB is a syntax for BlackB.
func (builder *Builder) BlackB() *Builder {
return builder.With(BlackB)
}
// RedB is a syntax for RedB.
func (builder *Builder) RedB() *Builder {
return builder.With(RedB)
}
// GreenB is a syntax for GreenB.
func (builder *Builder) GreenB() *Builder {
return builder.With(GreenB)
}
// YellowB is a syntax for YellowB.
func (builder *Builder) YellowB() *Builder {
return builder.With(YellowB)
}
// BlueB is a syntax for BlueB.
func (builder *Builder) BlueB() *Builder {
return builder.With(BlueB)
}
// MagentaB is a syntax for MagentaB.
func (builder *Builder) MagentaB() *Builder {
return builder.With(MagentaB)
}
// CyanB is a syntax for CyanB.
func (builder *Builder) CyanB() *Builder {
return builder.With(CyanB)
}
// WhiteB is a syntax for WhiteB.
func (builder *Builder) WhiteB() *Builder {
return builder.With(WhiteB)
}
// DefaultB is a syntax for DefaultB.
func (builder *Builder) DefaultB() *Builder {
return builder.With(DefaultB)
}
// Frame is a syntax for Frame.
func (builder *Builder) Frame() *Builder {
return builder.With(Frame)
}
// Encircle is a syntax for Encircle.
func (builder *Builder) Encircle() *Builder {
return builder.With(Encircle)
}
// Overline is a syntax for Overline.
func (builder *Builder) Overline() *Builder {
return builder.With(Overline)
}
// LightBlackF is a syntax for LightBlueF.
func (builder *Builder) LightBlackF() *Builder {
return builder.With(LightBlackF)
}
// LightRedF is a syntax for LightRedF.
func (builder *Builder) LightRedF() *Builder {
return builder.With(LightRedF)
}
// LightGreenF is a syntax for LightGreenF.
func (builder *Builder) LightGreenF() *Builder {
return builder.With(LightGreenF)
}
// LightYellowF is a syntax for LightYellowF.
func (builder *Builder) LightYellowF() *Builder {
return builder.With(LightYellowF)
}
// LightBlueF is a syntax for LightBlueF.
func (builder *Builder) LightBlueF() *Builder {
return builder.With(LightBlueF)
}
// LightMagentaF is a syntax for LightMagentaF.
func (builder *Builder) LightMagentaF() *Builder {
return builder.With(LightMagentaF)
}
// LightCyanF is a syntax for LightCyanF.
func (builder *Builder) LightCyanF() *Builder {
return builder.With(LightCyanF)
}
// LightWhiteF is a syntax for LightWhiteF.
func (builder *Builder) LightWhiteF() *Builder {
return builder.With(LightWhiteF)
}
// LightBlackB is a syntax for LightBlackB.
func (builder *Builder) LightBlackB() *Builder {
return builder.With(LightBlackB)
}
// LightRedB is a syntax for LightRedB.
func (builder *Builder) LightRedB() *Builder {
return builder.With(LightRedB)
}
// LightGreenB is a syntax for LightGreenB.
func (builder *Builder) LightGreenB() *Builder {
return builder.With(LightGreenB)
}
// LightYellowB is a syntax for LightYellowB.
func (builder *Builder) LightYellowB() *Builder {
return builder.With(LightYellowB)
}
// LightBlueB is a syntax for LightBlueB.
func (builder *Builder) LightBlueB() *Builder {
return builder.With(LightBlueB)
}
// LightMagentaB is a syntax for LightMagentaB.
func (builder *Builder) LightMagentaB() *Builder {
return builder.With(LightMagentaB)
}
// LightCyanB is a syntax for LightCyanB.
func (builder *Builder) LightCyanB() *Builder {
return builder.With(LightCyanB)
}
// LightWhiteB is a syntax for LightWhiteB.
func (builder *Builder) LightWhiteB() *Builder {
return builder.With(LightWhiteB)
}
// Color3BitF is a syntax for Color3BitF.
func (builder *Builder) Color3BitF(c RGB3Bit) *Builder {
return builder.With(Color3BitF(c))
}
// Color3BitB is a syntax for Color3BitB.
func (builder *Builder) Color3BitB(c RGB3Bit) *Builder {
return builder.With(Color3BitB(c))
}
// Color8BitF is a syntax for Color8BitF.
func (builder *Builder) Color8BitF(c RGB8Bit) *Builder {
return builder.With(Color8BitF(c))
}
// Color8BitB is a syntax for Color8BitB.
func (builder *Builder) Color8BitB(c RGB8Bit) *Builder {
return builder.With(Color8BitB(c))
}
// FullColorF is a syntax for FullColorF.
func (builder *Builder) FullColorF(r, g, b uint8) *Builder {
return builder.With(FullColorF(r, g, b))
}
// FullColorB is a syntax for FullColorB.
func (builder *Builder) FullColorB(r, g, b uint8) *Builder {
return builder.With(FullColorB(r, g, b))
}
// RGB3BitF is a syntax for Color3BitF with NewRGB3Bit.
func (builder *Builder) RGB3BitF(r, g, b uint8) *Builder {
return builder.Color3BitF(NewRGB3Bit(r, g, b))
}
// RGB3BitB is a syntax for Color3BitB with NewRGB3Bit.
func (builder *Builder) RGB3BitB(r, g, b uint8) *Builder {
return builder.Color3BitB(NewRGB3Bit(r, g, b))
}
// RGB8BitF is a syntax for Color8BitF with NewRGB8Bit.
func (builder *Builder) RGB8BitF(r, g, b uint8) *Builder {
return builder.Color8BitF(NewRGB8Bit(r, g, b))
}
// RGB8BitB is a syntax for Color8BitB with NewRGB8Bit.
func (builder *Builder) RGB8BitB(r, g, b uint8) *Builder {
return builder.Color8BitB(NewRGB8Bit(r, g, b))
}
func init() {
EmptyBuilder = &Builder{empty}
}

202
vendor/github.com/morikuni/aec/sgr.go generated vendored Normal file
View File

@ -0,0 +1,202 @@
package aec
import (
"fmt"
)
// RGB3Bit is a 3bit RGB color.
type RGB3Bit uint8
// RGB8Bit is a 8bit RGB color.
type RGB8Bit uint8
func newSGR(n uint) ANSI {
return newAnsi(fmt.Sprintf(esc+"%dm", n))
}
// NewRGB3Bit create a RGB3Bit from given RGB.
func NewRGB3Bit(r, g, b uint8) RGB3Bit {
return RGB3Bit((r >> 7) | ((g >> 6) & 0x2) | ((b >> 5) & 0x4))
}
// NewRGB8Bit create a RGB8Bit from given RGB.
func NewRGB8Bit(r, g, b uint8) RGB8Bit {
return RGB8Bit(16 + 36*(r/43) + 6*(g/43) + b/43)
}
// Color3BitF set the foreground color of text.
func Color3BitF(c RGB3Bit) ANSI {
return newAnsi(fmt.Sprintf(esc+"%dm", c+30))
}
// Color3BitB set the background color of text.
func Color3BitB(c RGB3Bit) ANSI {
return newAnsi(fmt.Sprintf(esc+"%dm", c+40))
}
// Color8BitF set the foreground color of text.
func Color8BitF(c RGB8Bit) ANSI {
return newAnsi(fmt.Sprintf(esc+"38;5;%dm", c))
}
// Color8BitB set the background color of text.
func Color8BitB(c RGB8Bit) ANSI {
return newAnsi(fmt.Sprintf(esc+"48;5;%dm", c))
}
// FullColorF set the foreground color of text.
func FullColorF(r, g, b uint8) ANSI {
return newAnsi(fmt.Sprintf(esc+"38;2;%d;%d;%dm", r, g, b))
}
// FullColorB set the foreground color of text.
func FullColorB(r, g, b uint8) ANSI {
return newAnsi(fmt.Sprintf(esc+"48;2;%d;%d;%dm", r, g, b))
}
// Style
var (
// Bold set the text style to bold or increased intensity.
Bold ANSI
// Faint set the text style to faint.
Faint ANSI
// Italic set the text style to italic.
Italic ANSI
// Underline set the text style to underline.
Underline ANSI
// BlinkSlow set the text style to slow blink.
BlinkSlow ANSI
// BlinkRapid set the text style to rapid blink.
BlinkRapid ANSI
// Inverse swap the foreground color and background color.
Inverse ANSI
// Conceal set the text style to conceal.
Conceal ANSI
// CrossOut set the text style to crossed out.
CrossOut ANSI
// Frame set the text style to framed.
Frame ANSI
// Encircle set the text style to encircled.
Encircle ANSI
// Overline set the text style to overlined.
Overline ANSI
)
// Foreground color of text.
var (
// DefaultF is the default color of foreground.
DefaultF ANSI
// Normal color
BlackF ANSI
RedF ANSI
GreenF ANSI
YellowF ANSI
BlueF ANSI
MagentaF ANSI
CyanF ANSI
WhiteF ANSI
// Light color
LightBlackF ANSI
LightRedF ANSI
LightGreenF ANSI
LightYellowF ANSI
LightBlueF ANSI
LightMagentaF ANSI
LightCyanF ANSI
LightWhiteF ANSI
)
// Background color of text.
var (
// DefaultB is the default color of background.
DefaultB ANSI
// Normal color
BlackB ANSI
RedB ANSI
GreenB ANSI
YellowB ANSI
BlueB ANSI
MagentaB ANSI
CyanB ANSI
WhiteB ANSI
// Light color
LightBlackB ANSI
LightRedB ANSI
LightGreenB ANSI
LightYellowB ANSI
LightBlueB ANSI
LightMagentaB ANSI
LightCyanB ANSI
LightWhiteB ANSI
)
func init() {
Bold = newSGR(1)
Faint = newSGR(2)
Italic = newSGR(3)
Underline = newSGR(4)
BlinkSlow = newSGR(5)
BlinkRapid = newSGR(6)
Inverse = newSGR(7)
Conceal = newSGR(8)
CrossOut = newSGR(9)
BlackF = newSGR(30)
RedF = newSGR(31)
GreenF = newSGR(32)
YellowF = newSGR(33)
BlueF = newSGR(34)
MagentaF = newSGR(35)
CyanF = newSGR(36)
WhiteF = newSGR(37)
DefaultF = newSGR(39)
BlackB = newSGR(40)
RedB = newSGR(41)
GreenB = newSGR(42)
YellowB = newSGR(43)
BlueB = newSGR(44)
MagentaB = newSGR(45)
CyanB = newSGR(46)
WhiteB = newSGR(47)
DefaultB = newSGR(49)
Frame = newSGR(51)
Encircle = newSGR(52)
Overline = newSGR(53)
LightBlackF = newSGR(90)
LightRedF = newSGR(91)
LightGreenF = newSGR(92)
LightYellowF = newSGR(93)
LightBlueF = newSGR(94)
LightMagentaF = newSGR(95)
LightCyanF = newSGR(96)
LightWhiteF = newSGR(97)
LightBlackB = newSGR(100)
LightRedB = newSGR(101)
LightGreenB = newSGR(102)
LightYellowB = newSGR(103)
LightBlueB = newSGR(104)
LightMagentaB = newSGR(105)
LightCyanB = newSGR(106)
LightWhiteB = newSGR(107)
}

528
vendor/k8s.io/client-go/testing/actions.go generated vendored Normal file
View File

@ -0,0 +1,528 @@
/*
Copyright 2015 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 testing
import (
"fmt"
"path"
"strings"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/fields"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
)
func NewRootGetAction(resource schema.GroupVersionResource, name string) GetActionImpl {
action := GetActionImpl{}
action.Verb = "get"
action.Resource = resource
action.Name = name
return action
}
func NewGetAction(resource schema.GroupVersionResource, namespace, name string) GetActionImpl {
action := GetActionImpl{}
action.Verb = "get"
action.Resource = resource
action.Namespace = namespace
action.Name = name
return action
}
func NewGetSubresourceAction(resource schema.GroupVersionResource, namespace, subresource, name string) GetActionImpl {
action := GetActionImpl{}
action.Verb = "get"
action.Resource = resource
action.Subresource = subresource
action.Namespace = namespace
action.Name = name
return action
}
func NewRootListAction(resource schema.GroupVersionResource, kind schema.GroupVersionKind, opts interface{}) ListActionImpl {
action := ListActionImpl{}
action.Verb = "list"
action.Resource = resource
action.Kind = kind
labelSelector, fieldSelector, _ := ExtractFromListOptions(opts)
action.ListRestrictions = ListRestrictions{labelSelector, fieldSelector}
return action
}
func NewListAction(resource schema.GroupVersionResource, kind schema.GroupVersionKind, namespace string, opts interface{}) ListActionImpl {
action := ListActionImpl{}
action.Verb = "list"
action.Resource = resource
action.Kind = kind
action.Namespace = namespace
labelSelector, fieldSelector, _ := ExtractFromListOptions(opts)
action.ListRestrictions = ListRestrictions{labelSelector, fieldSelector}
return action
}
func NewListSubresourceAction(resource schema.GroupVersionResource, name, subresource string, kind schema.GroupVersionKind, namespace string, opts interface{}) ListActionImpl {
action := ListActionImpl{}
action.Verb = "list"
action.Resource = resource
action.Subresource = subresource
action.Kind = kind
action.Namespace = namespace
action.Name = name
labelSelector, fieldSelector, _ := ExtractFromListOptions(opts)
action.ListRestrictions = ListRestrictions{labelSelector, fieldSelector}
return action
}
func NewRootCreateAction(resource schema.GroupVersionResource, object runtime.Object) CreateActionImpl {
action := CreateActionImpl{}
action.Verb = "create"
action.Resource = resource
action.Object = object
return action
}
func NewCreateAction(resource schema.GroupVersionResource, namespace string, object runtime.Object) CreateActionImpl {
action := CreateActionImpl{}
action.Verb = "create"
action.Resource = resource
action.Namespace = namespace
action.Object = object
return action
}
func NewCreateSubresourceAction(resource schema.GroupVersionResource, name, subresource string, namespace string, object runtime.Object) CreateActionImpl {
action := CreateActionImpl{}
action.Verb = "create"
action.Resource = resource
action.Subresource = subresource
action.Namespace = namespace
action.Name = name
action.Object = object
return action
}
func NewRootUpdateAction(resource schema.GroupVersionResource, object runtime.Object) UpdateActionImpl {
action := UpdateActionImpl{}
action.Verb = "update"
action.Resource = resource
action.Object = object
return action
}
func NewUpdateAction(resource schema.GroupVersionResource, namespace string, object runtime.Object) UpdateActionImpl {
action := UpdateActionImpl{}
action.Verb = "update"
action.Resource = resource
action.Namespace = namespace
action.Object = object
return action
}
func NewRootPatchAction(resource schema.GroupVersionResource, name string, patch []byte) PatchActionImpl {
action := PatchActionImpl{}
action.Verb = "patch"
action.Resource = resource
action.Name = name
action.Patch = patch
return action
}
func NewPatchAction(resource schema.GroupVersionResource, namespace string, name string, patch []byte) PatchActionImpl {
action := PatchActionImpl{}
action.Verb = "patch"
action.Resource = resource
action.Namespace = namespace
action.Name = name
action.Patch = patch
return action
}
func NewRootPatchSubresourceAction(resource schema.GroupVersionResource, name string, patch []byte, subresources ...string) PatchActionImpl {
action := PatchActionImpl{}
action.Verb = "patch"
action.Resource = resource
action.Subresource = path.Join(subresources...)
action.Name = name
action.Patch = patch
return action
}
func NewPatchSubresourceAction(resource schema.GroupVersionResource, namespace, name string, patch []byte, subresources ...string) PatchActionImpl {
action := PatchActionImpl{}
action.Verb = "patch"
action.Resource = resource
action.Subresource = path.Join(subresources...)
action.Namespace = namespace
action.Name = name
action.Patch = patch
return action
}
func NewRootUpdateSubresourceAction(resource schema.GroupVersionResource, subresource string, object runtime.Object) UpdateActionImpl {
action := UpdateActionImpl{}
action.Verb = "update"
action.Resource = resource
action.Subresource = subresource
action.Object = object
return action
}
func NewUpdateSubresourceAction(resource schema.GroupVersionResource, subresource string, namespace string, object runtime.Object) UpdateActionImpl {
action := UpdateActionImpl{}
action.Verb = "update"
action.Resource = resource
action.Subresource = subresource
action.Namespace = namespace
action.Object = object
return action
}
func NewRootDeleteAction(resource schema.GroupVersionResource, name string) DeleteActionImpl {
action := DeleteActionImpl{}
action.Verb = "delete"
action.Resource = resource
action.Name = name
return action
}
func NewDeleteAction(resource schema.GroupVersionResource, namespace, name string) DeleteActionImpl {
action := DeleteActionImpl{}
action.Verb = "delete"
action.Resource = resource
action.Namespace = namespace
action.Name = name
return action
}
func NewRootDeleteCollectionAction(resource schema.GroupVersionResource, opts interface{}) DeleteCollectionActionImpl {
action := DeleteCollectionActionImpl{}
action.Verb = "delete-collection"
action.Resource = resource
labelSelector, fieldSelector, _ := ExtractFromListOptions(opts)
action.ListRestrictions = ListRestrictions{labelSelector, fieldSelector}
return action
}
func NewDeleteCollectionAction(resource schema.GroupVersionResource, namespace string, opts interface{}) DeleteCollectionActionImpl {
action := DeleteCollectionActionImpl{}
action.Verb = "delete-collection"
action.Resource = resource
action.Namespace = namespace
labelSelector, fieldSelector, _ := ExtractFromListOptions(opts)
action.ListRestrictions = ListRestrictions{labelSelector, fieldSelector}
return action
}
func NewRootWatchAction(resource schema.GroupVersionResource, opts interface{}) WatchActionImpl {
action := WatchActionImpl{}
action.Verb = "watch"
action.Resource = resource
labelSelector, fieldSelector, resourceVersion := ExtractFromListOptions(opts)
action.WatchRestrictions = WatchRestrictions{labelSelector, fieldSelector, resourceVersion}
return action
}
func ExtractFromListOptions(opts interface{}) (labelSelector labels.Selector, fieldSelector fields.Selector, resourceVersion string) {
var err error
switch t := opts.(type) {
case metav1.ListOptions:
labelSelector, err = labels.Parse(t.LabelSelector)
if err != nil {
panic(fmt.Errorf("invalid selector %q: %v", t.LabelSelector, err))
}
fieldSelector, err = fields.ParseSelector(t.FieldSelector)
if err != nil {
panic(fmt.Errorf("invalid selector %q: %v", t.FieldSelector, err))
}
resourceVersion = t.ResourceVersion
default:
panic(fmt.Errorf("expect a ListOptions %T", opts))
}
if labelSelector == nil {
labelSelector = labels.Everything()
}
if fieldSelector == nil {
fieldSelector = fields.Everything()
}
return labelSelector, fieldSelector, resourceVersion
}
func NewWatchAction(resource schema.GroupVersionResource, namespace string, opts interface{}) WatchActionImpl {
action := WatchActionImpl{}
action.Verb = "watch"
action.Resource = resource
action.Namespace = namespace
labelSelector, fieldSelector, resourceVersion := ExtractFromListOptions(opts)
action.WatchRestrictions = WatchRestrictions{labelSelector, fieldSelector, resourceVersion}
return action
}
func NewProxyGetAction(resource schema.GroupVersionResource, namespace, scheme, name, port, path string, params map[string]string) ProxyGetActionImpl {
action := ProxyGetActionImpl{}
action.Verb = "get"
action.Resource = resource
action.Namespace = namespace
action.Scheme = scheme
action.Name = name
action.Port = port
action.Path = path
action.Params = params
return action
}
type ListRestrictions struct {
Labels labels.Selector
Fields fields.Selector
}
type WatchRestrictions struct {
Labels labels.Selector
Fields fields.Selector
ResourceVersion string
}
type Action interface {
GetNamespace() string
GetVerb() string
GetResource() schema.GroupVersionResource
GetSubresource() string
Matches(verb, resource string) bool
}
type GenericAction interface {
Action
GetValue() interface{}
}
type GetAction interface {
Action
GetName() string
}
type ListAction interface {
Action
GetListRestrictions() ListRestrictions
}
type CreateAction interface {
Action
GetObject() runtime.Object
}
type UpdateAction interface {
Action
GetObject() runtime.Object
}
type DeleteAction interface {
Action
GetName() string
}
type DeleteCollectionAction interface {
Action
GetListRestrictions() ListRestrictions
}
type PatchAction interface {
Action
GetName() string
GetPatch() []byte
}
type WatchAction interface {
Action
GetWatchRestrictions() WatchRestrictions
}
type ProxyGetAction interface {
Action
GetScheme() string
GetName() string
GetPort() string
GetPath() string
GetParams() map[string]string
}
type ActionImpl struct {
Namespace string
Verb string
Resource schema.GroupVersionResource
Subresource string
}
func (a ActionImpl) GetNamespace() string {
return a.Namespace
}
func (a ActionImpl) GetVerb() string {
return a.Verb
}
func (a ActionImpl) GetResource() schema.GroupVersionResource {
return a.Resource
}
func (a ActionImpl) GetSubresource() string {
return a.Subresource
}
func (a ActionImpl) Matches(verb, resource string) bool {
return strings.ToLower(verb) == strings.ToLower(a.Verb) &&
strings.ToLower(resource) == strings.ToLower(a.Resource.Resource)
}
type GenericActionImpl struct {
ActionImpl
Value interface{}
}
func (a GenericActionImpl) GetValue() interface{} {
return a.Value
}
type GetActionImpl struct {
ActionImpl
Name string
}
func (a GetActionImpl) GetName() string {
return a.Name
}
type ListActionImpl struct {
ActionImpl
Kind schema.GroupVersionKind
Name string
ListRestrictions ListRestrictions
}
func (a ListActionImpl) GetKind() schema.GroupVersionKind {
return a.Kind
}
func (a ListActionImpl) GetListRestrictions() ListRestrictions {
return a.ListRestrictions
}
type CreateActionImpl struct {
ActionImpl
Name string
Object runtime.Object
}
func (a CreateActionImpl) GetObject() runtime.Object {
return a.Object
}
type UpdateActionImpl struct {
ActionImpl
Object runtime.Object
}
func (a UpdateActionImpl) GetObject() runtime.Object {
return a.Object
}
type PatchActionImpl struct {
ActionImpl
Name string
Patch []byte
}
func (a PatchActionImpl) GetName() string {
return a.Name
}
func (a PatchActionImpl) GetPatch() []byte {
return a.Patch
}
type DeleteActionImpl struct {
ActionImpl
Name string
}
func (a DeleteActionImpl) GetName() string {
return a.Name
}
type DeleteCollectionActionImpl struct {
ActionImpl
ListRestrictions ListRestrictions
}
func (a DeleteCollectionActionImpl) GetListRestrictions() ListRestrictions {
return a.ListRestrictions
}
type WatchActionImpl struct {
ActionImpl
WatchRestrictions WatchRestrictions
}
func (a WatchActionImpl) GetWatchRestrictions() WatchRestrictions {
return a.WatchRestrictions
}
type ProxyGetActionImpl struct {
ActionImpl
Scheme string
Name string
Port string
Path string
Params map[string]string
}
func (a ProxyGetActionImpl) GetScheme() string {
return a.Scheme
}
func (a ProxyGetActionImpl) GetName() string {
return a.Name
}
func (a ProxyGetActionImpl) GetPort() string {
return a.Port
}
func (a ProxyGetActionImpl) GetPath() string {
return a.Path
}
func (a ProxyGetActionImpl) GetParams() map[string]string {
return a.Params
}

259
vendor/k8s.io/client-go/testing/fake.go generated vendored Normal file
View File

@ -0,0 +1,259 @@
/*
Copyright 2016 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 testing
import (
"fmt"
"sync"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/version"
"k8s.io/apimachinery/pkg/watch"
kubeversion "k8s.io/client-go/pkg/version"
restclient "k8s.io/client-go/rest"
)
// Fake implements client.Interface. Meant to be embedded into a struct to get
// a default implementation. This makes faking out just the method you want to
// test easier.
type Fake struct {
sync.RWMutex
actions []Action // these may be castable to other types, but "Action" is the minimum
// ReactionChain is the list of reactors that will be attempted for every
// request in the order they are tried.
ReactionChain []Reactor
// WatchReactionChain is the list of watch reactors that will be attempted
// for every request in the order they are tried.
WatchReactionChain []WatchReactor
// ProxyReactionChain is the list of proxy reactors that will be attempted
// for every request in the order they are tried.
ProxyReactionChain []ProxyReactor
Resources []*metav1.APIResourceList
}
// Reactor is an interface to allow the composition of reaction functions.
type Reactor interface {
// Handles indicates whether or not this Reactor deals with a given
// action.
Handles(action Action) bool
// React handles the action and returns results. It may choose to
// delegate by indicated handled=false.
React(action Action) (handled bool, ret runtime.Object, err error)
}
// WatchReactor is an interface to allow the composition of watch functions.
type WatchReactor interface {
// Handles indicates whether or not this Reactor deals with a given
// action.
Handles(action Action) bool
// React handles a watch action and returns results. It may choose to
// delegate by indicating handled=false.
React(action Action) (handled bool, ret watch.Interface, err error)
}
// ProxyReactor is an interface to allow the composition of proxy get
// functions.
type ProxyReactor interface {
// Handles indicates whether or not this Reactor deals with a given
// action.
Handles(action Action) bool
// React handles a watch action and returns results. It may choose to
// delegate by indicating handled=false.
React(action Action) (handled bool, ret restclient.ResponseWrapper, err error)
}
// ReactionFunc is a function that returns an object or error for a given
// Action. If "handled" is false, then the test client will ignore the
// results and continue to the next ReactionFunc. A ReactionFunc can describe
// reactions on subresources by testing the result of the action's
// GetSubresource() method.
type ReactionFunc func(action Action) (handled bool, ret runtime.Object, err error)
// WatchReactionFunc is a function that returns a watch interface. If
// "handled" is false, then the test client will ignore the results and
// continue to the next ReactionFunc.
type WatchReactionFunc func(action Action) (handled bool, ret watch.Interface, err error)
// ProxyReactionFunc is a function that returns a ResponseWrapper interface
// for a given Action. If "handled" is false, then the test client will
// ignore the results and continue to the next ProxyReactionFunc.
type ProxyReactionFunc func(action Action) (handled bool, ret restclient.ResponseWrapper, err error)
// AddReactor appends a reactor to the end of the chain.
func (c *Fake) AddReactor(verb, resource string, reaction ReactionFunc) {
c.ReactionChain = append(c.ReactionChain, &SimpleReactor{verb, resource, reaction})
}
// PrependReactor adds a reactor to the beginning of the chain.
func (c *Fake) PrependReactor(verb, resource string, reaction ReactionFunc) {
c.ReactionChain = append([]Reactor{&SimpleReactor{verb, resource, reaction}}, c.ReactionChain...)
}
// AddWatchReactor appends a reactor to the end of the chain.
func (c *Fake) AddWatchReactor(resource string, reaction WatchReactionFunc) {
c.WatchReactionChain = append(c.WatchReactionChain, &SimpleWatchReactor{resource, reaction})
}
// PrependWatchReactor adds a reactor to the beginning of the chain.
func (c *Fake) PrependWatchReactor(resource string, reaction WatchReactionFunc) {
c.WatchReactionChain = append([]WatchReactor{&SimpleWatchReactor{resource, reaction}}, c.WatchReactionChain...)
}
// AddProxyReactor appends a reactor to the end of the chain.
func (c *Fake) AddProxyReactor(resource string, reaction ProxyReactionFunc) {
c.ProxyReactionChain = append(c.ProxyReactionChain, &SimpleProxyReactor{resource, reaction})
}
// PrependProxyReactor adds a reactor to the beginning of the chain.
func (c *Fake) PrependProxyReactor(resource string, reaction ProxyReactionFunc) {
c.ProxyReactionChain = append([]ProxyReactor{&SimpleProxyReactor{resource, reaction}}, c.ProxyReactionChain...)
}
// Invokes records the provided Action and then invokes the ReactionFunc that
// handles the action if one exists. defaultReturnObj is expected to be of the
// same type a normal call would return.
func (c *Fake) Invokes(action Action, defaultReturnObj runtime.Object) (runtime.Object, error) {
c.Lock()
defer c.Unlock()
c.actions = append(c.actions, action)
for _, reactor := range c.ReactionChain {
if !reactor.Handles(action) {
continue
}
handled, ret, err := reactor.React(action)
if !handled {
continue
}
return ret, err
}
return defaultReturnObj, nil
}
// InvokesWatch records the provided Action and then invokes the ReactionFunc
// that handles the action if one exists.
func (c *Fake) InvokesWatch(action Action) (watch.Interface, error) {
c.Lock()
defer c.Unlock()
c.actions = append(c.actions, action)
for _, reactor := range c.WatchReactionChain {
if !reactor.Handles(action) {
continue
}
handled, ret, err := reactor.React(action)
if !handled {
continue
}
return ret, err
}
return nil, fmt.Errorf("unhandled watch: %#v", action)
}
// InvokesProxy records the provided Action and then invokes the ReactionFunc
// that handles the action if one exists.
func (c *Fake) InvokesProxy(action Action) restclient.ResponseWrapper {
c.Lock()
defer c.Unlock()
c.actions = append(c.actions, action)
for _, reactor := range c.ProxyReactionChain {
if !reactor.Handles(action) {
continue
}
handled, ret, err := reactor.React(action)
if !handled || err != nil {
continue
}
return ret
}
return nil
}
// ClearActions clears the history of actions called on the fake client.
func (c *Fake) ClearActions() {
c.Lock()
defer c.Unlock()
c.actions = make([]Action, 0)
}
// Actions returns a chronologically ordered slice fake actions called on the
// fake client.
func (c *Fake) Actions() []Action {
c.RLock()
defer c.RUnlock()
fa := make([]Action, len(c.actions))
copy(fa, c.actions)
return fa
}
// TODO: this probably should be moved to somewhere else.
type FakeDiscovery struct {
*Fake
}
func (c *FakeDiscovery) ServerResourcesForGroupVersion(groupVersion string) (*metav1.APIResourceList, error) {
action := ActionImpl{
Verb: "get",
Resource: schema.GroupVersionResource{Resource: "resource"},
}
c.Invokes(action, nil)
for _, rl := range c.Resources {
if rl.GroupVersion == groupVersion {
return rl, nil
}
}
return nil, fmt.Errorf("GroupVersion %q not found", groupVersion)
}
func (c *FakeDiscovery) ServerResources() ([]*metav1.APIResourceList, error) {
action := ActionImpl{
Verb: "get",
Resource: schema.GroupVersionResource{Resource: "resource"},
}
c.Invokes(action, nil)
return c.Resources, nil
}
func (c *FakeDiscovery) ServerGroups() (*metav1.APIGroupList, error) {
return nil, nil
}
func (c *FakeDiscovery) ServerVersion() (*version.Info, error) {
action := ActionImpl{}
action.Verb = "get"
action.Resource = schema.GroupVersionResource{Resource: "version"}
c.Invokes(action, nil)
versionInfo := kubeversion.Get()
return &versionInfo, nil
}

464
vendor/k8s.io/client-go/testing/fixture.go generated vendored Normal file
View File

@ -0,0 +1,464 @@
/*
Copyright 2015 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 testing
import (
"fmt"
"sync"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/watch"
restclient "k8s.io/client-go/rest"
)
// ObjectTracker keeps track of objects. It is intended to be used to
// fake calls to a server by returning objects based on their kind,
// namespace and name.
type ObjectTracker interface {
// Add adds an object to the tracker. If object being added
// is a list, its items are added separately.
Add(obj runtime.Object) error
// Get retrieves the object by its kind, namespace and name.
Get(gvr schema.GroupVersionResource, ns, name string) (runtime.Object, error)
// Create adds an object to the tracker in the specified namespace.
Create(gvr schema.GroupVersionResource, obj runtime.Object, ns string) error
// Update updates an existing object in the tracker in the specified namespace.
Update(gvr schema.GroupVersionResource, obj runtime.Object, ns string) error
// List retrieves all objects of a given kind in the given
// namespace. Only non-List kinds are accepted.
List(gvr schema.GroupVersionResource, gvk schema.GroupVersionKind, ns string) (runtime.Object, error)
// Delete deletes an existing object from the tracker. If object
// didn't exist in the tracker prior to deletion, Delete returns
// no error.
Delete(gvr schema.GroupVersionResource, ns, name string) error
}
// ObjectScheme abstracts the implementation of common operations on objects.
type ObjectScheme interface {
runtime.ObjectCreater
runtime.ObjectCopier
runtime.ObjectTyper
}
// ObjectReaction returns a ReactionFunc that applies core.Action to
// the given tracker.
func ObjectReaction(tracker ObjectTracker) ReactionFunc {
return func(action Action) (bool, runtime.Object, error) {
ns := action.GetNamespace()
gvr := action.GetResource()
// Here and below we need to switch on implementation types,
// not on interfaces, as some interfaces are identical
// (e.g. UpdateAction and CreateAction), so if we use them,
// updates and creates end up matching the same case branch.
switch action := action.(type) {
case ListActionImpl:
obj, err := tracker.List(gvr, action.GetKind(), ns)
return true, obj, err
case GetActionImpl:
obj, err := tracker.Get(gvr, ns, action.GetName())
return true, obj, err
case CreateActionImpl:
objMeta, err := meta.Accessor(action.GetObject())
if err != nil {
return true, nil, err
}
if action.GetSubresource() == "" {
err = tracker.Create(gvr, action.GetObject(), ns)
} else {
// TODO: Currently we're handling subresource creation as an update
// on the enclosing resource. This works for some subresources but
// might not be generic enough.
err = tracker.Update(gvr, action.GetObject(), ns)
}
if err != nil {
return true, nil, err
}
obj, err := tracker.Get(gvr, ns, objMeta.GetName())
return true, obj, err
case UpdateActionImpl:
objMeta, err := meta.Accessor(action.GetObject())
if err != nil {
return true, nil, err
}
err = tracker.Update(gvr, action.GetObject(), ns)
if err != nil {
return true, nil, err
}
obj, err := tracker.Get(gvr, ns, objMeta.GetName())
return true, obj, err
case DeleteActionImpl:
err := tracker.Delete(gvr, ns, action.GetName())
if err != nil {
return true, nil, err
}
return true, nil, nil
default:
return false, nil, fmt.Errorf("no reaction implemented for %s", action)
}
}
}
type tracker struct {
scheme ObjectScheme
decoder runtime.Decoder
lock sync.RWMutex
objects map[schema.GroupVersionResource][]runtime.Object
}
var _ ObjectTracker = &tracker{}
// NewObjectTracker returns an ObjectTracker that can be used to keep track
// of objects for the fake clientset. Mostly useful for unit tests.
func NewObjectTracker(scheme ObjectScheme, decoder runtime.Decoder) ObjectTracker {
return &tracker{
scheme: scheme,
decoder: decoder,
objects: make(map[schema.GroupVersionResource][]runtime.Object),
}
}
func (t *tracker) List(gvr schema.GroupVersionResource, gvk schema.GroupVersionKind, ns string) (runtime.Object, error) {
// Heuristic for list kind: original kind + List suffix. Might
// not always be true but this tracker has a pretty limited
// understanding of the actual API model.
listGVK := gvk
listGVK.Kind = listGVK.Kind + "List"
// GVK does have the concept of "internal version". The scheme recognizes
// the runtime.APIVersionInternal, but not the empty string.
if listGVK.Version == "" {
listGVK.Version = runtime.APIVersionInternal
}
list, err := t.scheme.New(listGVK)
if err != nil {
return nil, err
}
if !meta.IsListType(list) {
return nil, fmt.Errorf("%q is not a list type", listGVK.Kind)
}
t.lock.RLock()
defer t.lock.RUnlock()
objs, ok := t.objects[gvr]
if !ok {
return list, nil
}
matchingObjs, err := filterByNamespaceAndName(objs, ns, "")
if err != nil {
return nil, err
}
if err := meta.SetList(list, matchingObjs); err != nil {
return nil, err
}
if list, err = t.scheme.Copy(list); err != nil {
return nil, err
}
return list, nil
}
func (t *tracker) Get(gvr schema.GroupVersionResource, ns, name string) (runtime.Object, error) {
errNotFound := errors.NewNotFound(gvr.GroupResource(), name)
t.lock.RLock()
defer t.lock.RUnlock()
objs, ok := t.objects[gvr]
if !ok {
return nil, errNotFound
}
matchingObjs, err := filterByNamespaceAndName(objs, ns, name)
if err != nil {
return nil, err
}
if len(matchingObjs) == 0 {
return nil, errNotFound
}
if len(matchingObjs) > 1 {
return nil, fmt.Errorf("more than one object matched gvr %s, ns: %q name: %q", gvr, ns, name)
}
// Only one object should match in the tracker if it works
// correctly, as Add/Update methods enforce kind/namespace/name
// uniqueness.
obj, err := t.scheme.Copy(matchingObjs[0])
if err != nil {
return nil, err
}
if status, ok := obj.(*metav1.Status); ok {
if status.Status != metav1.StatusSuccess {
return nil, &errors.StatusError{ErrStatus: *status}
}
}
return obj, nil
}
func (t *tracker) Add(obj runtime.Object) error {
if meta.IsListType(obj) {
return t.addList(obj, false)
}
objMeta, err := meta.Accessor(obj)
if err != nil {
return err
}
gvks, _, err := t.scheme.ObjectKinds(obj)
if err != nil {
return err
}
if len(gvks) == 0 {
return fmt.Errorf("no registered kinds for %v", obj)
}
for _, gvk := range gvks {
// NOTE: UnsafeGuessKindToResource is a heuristic and default match. The
// actual registration in apiserver can specify arbitrary route for a
// gvk. If a test uses such objects, it cannot preset the tracker with
// objects via Add(). Instead, it should trigger the Create() function
// of the tracker, where an arbitrary gvr can be specified.
gvr, _ := meta.UnsafeGuessKindToResource(gvk)
// Resource doesn't have the concept of "__internal" version, just set it to "".
if gvr.Version == runtime.APIVersionInternal {
gvr.Version = ""
}
err := t.add(gvr, obj, objMeta.GetNamespace(), false)
if err != nil {
return err
}
}
return nil
}
func (t *tracker) Create(gvr schema.GroupVersionResource, obj runtime.Object, ns string) error {
return t.add(gvr, obj, ns, false)
}
func (t *tracker) Update(gvr schema.GroupVersionResource, obj runtime.Object, ns string) error {
return t.add(gvr, obj, ns, true)
}
func (t *tracker) add(gvr schema.GroupVersionResource, obj runtime.Object, ns string, replaceExisting bool) error {
t.lock.Lock()
defer t.lock.Unlock()
gr := gvr.GroupResource()
// To avoid the object from being accidentally modified by caller
// after it's been added to the tracker, we always store the deep
// copy.
obj, err := t.scheme.Copy(obj)
if err != nil {
return err
}
newMeta, err := meta.Accessor(obj)
if err != nil {
return err
}
// Propagate namespace to the new object if hasn't already been set.
if len(newMeta.GetNamespace()) == 0 {
newMeta.SetNamespace(ns)
}
if ns != newMeta.GetNamespace() {
msg := fmt.Sprintf("request namespace does not match object namespace, request: %q object: %q", ns, newMeta.GetNamespace())
return errors.NewBadRequest(msg)
}
for i, existingObj := range t.objects[gvr] {
oldMeta, err := meta.Accessor(existingObj)
if err != nil {
return err
}
if oldMeta.GetNamespace() == newMeta.GetNamespace() && oldMeta.GetName() == newMeta.GetName() {
if replaceExisting {
t.objects[gvr][i] = obj
return nil
}
return errors.NewAlreadyExists(gr, newMeta.GetName())
}
}
if replaceExisting {
// Tried to update but no matching object was found.
return errors.NewNotFound(gr, newMeta.GetName())
}
t.objects[gvr] = append(t.objects[gvr], obj)
return nil
}
func (t *tracker) addList(obj runtime.Object, replaceExisting bool) error {
list, err := meta.ExtractList(obj)
if err != nil {
return err
}
errs := runtime.DecodeList(list, t.decoder)
if len(errs) > 0 {
return errs[0]
}
for _, obj := range list {
if err := t.Add(obj); err != nil {
return err
}
}
return nil
}
func (t *tracker) Delete(gvr schema.GroupVersionResource, ns, name string) error {
t.lock.Lock()
defer t.lock.Unlock()
found := false
for i, existingObj := range t.objects[gvr] {
objMeta, err := meta.Accessor(existingObj)
if err != nil {
return err
}
if objMeta.GetNamespace() == ns && objMeta.GetName() == name {
t.objects[gvr] = append(t.objects[gvr][:i], t.objects[gvr][i+1:]...)
found = true
break
}
}
if found {
return nil
}
return errors.NewNotFound(gvr.GroupResource(), name)
}
// filterByNamespaceAndName returns all objects in the collection that
// match provided namespace and name. Empty namespace matches
// non-namespaced objects.
func filterByNamespaceAndName(objs []runtime.Object, ns, name string) ([]runtime.Object, error) {
var res []runtime.Object
for _, obj := range objs {
acc, err := meta.Accessor(obj)
if err != nil {
return nil, err
}
if ns != "" && acc.GetNamespace() != ns {
continue
}
if name != "" && acc.GetName() != name {
continue
}
res = append(res, obj)
}
return res, nil
}
func DefaultWatchReactor(watchInterface watch.Interface, err error) WatchReactionFunc {
return func(action Action) (bool, watch.Interface, error) {
return true, watchInterface, err
}
}
// SimpleReactor is a Reactor. Each reaction function is attached to a given verb,resource tuple. "*" in either field matches everything for that value.
// For instance, *,pods matches all verbs on pods. This allows for easier composition of reaction functions
type SimpleReactor struct {
Verb string
Resource string
Reaction ReactionFunc
}
func (r *SimpleReactor) Handles(action Action) bool {
verbCovers := r.Verb == "*" || r.Verb == action.GetVerb()
if !verbCovers {
return false
}
resourceCovers := r.Resource == "*" || r.Resource == action.GetResource().Resource
if !resourceCovers {
return false
}
return true
}
func (r *SimpleReactor) React(action Action) (bool, runtime.Object, error) {
return r.Reaction(action)
}
// SimpleWatchReactor is a WatchReactor. Each reaction function is attached to a given resource. "*" matches everything for that value.
// For instance, *,pods matches all verbs on pods. This allows for easier composition of reaction functions
type SimpleWatchReactor struct {
Resource string
Reaction WatchReactionFunc
}
func (r *SimpleWatchReactor) Handles(action Action) bool {
resourceCovers := r.Resource == "*" || r.Resource == action.GetResource().Resource
if !resourceCovers {
return false
}
return true
}
func (r *SimpleWatchReactor) React(action Action) (bool, watch.Interface, error) {
return r.Reaction(action)
}
// SimpleProxyReactor is a ProxyReactor. Each reaction function is attached to a given resource. "*" matches everything for that value.
// For instance, *,pods matches all verbs on pods. This allows for easier composition of reaction functions.
type SimpleProxyReactor struct {
Resource string
Reaction ProxyReactionFunc
}
func (r *SimpleProxyReactor) Handles(action Action) bool {
resourceCovers := r.Resource == "*" || r.Resource == action.GetResource().Resource
if !resourceCovers {
return false
}
return true
}
func (r *SimpleProxyReactor) React(action Action) (bool, restclient.ResponseWrapper, error) {
return r.Reaction(action)
}

296
vendor/k8s.io/kubernetes/pkg/api/v1/pod/util.go generated vendored Normal file
View File

@ -0,0 +1,296 @@
/*
Copyright 2015 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 pod
import (
"fmt"
"time"
"k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
)
// FindPort locates the container port for the given pod and portName. If the
// targetPort is a number, use that. If the targetPort is a string, look that
// string up in all named ports in all containers in the target pod. If no
// match is found, fail.
func FindPort(pod *v1.Pod, svcPort *v1.ServicePort) (int, error) {
portName := svcPort.TargetPort
switch portName.Type {
case intstr.String:
name := portName.StrVal
for _, container := range pod.Spec.Containers {
for _, port := range container.Ports {
if port.Name == name && port.Protocol == svcPort.Protocol {
return int(port.ContainerPort), nil
}
}
}
case intstr.Int:
return portName.IntValue(), nil
}
return 0, fmt.Errorf("no suitable port for manifest: %s", pod.UID)
}
// Visitor is called with each object name, and returns true if visiting should continue
type Visitor func(name string) (shouldContinue bool)
// VisitPodSecretNames invokes the visitor function with the name of every secret
// referenced by the pod spec. If visitor returns false, visiting is short-circuited.
// Transitive references (e.g. pod -> pvc -> pv -> secret) are not visited.
// Returns true if visiting completed, false if visiting was short-circuited.
func VisitPodSecretNames(pod *v1.Pod, visitor Visitor) bool {
for _, reference := range pod.Spec.ImagePullSecrets {
if !visitor(reference.Name) {
return false
}
}
for i := range pod.Spec.InitContainers {
if !visitContainerSecretNames(&pod.Spec.InitContainers[i], visitor) {
return false
}
}
for i := range pod.Spec.Containers {
if !visitContainerSecretNames(&pod.Spec.Containers[i], visitor) {
return false
}
}
var source *v1.VolumeSource
for i := range pod.Spec.Volumes {
source = &pod.Spec.Volumes[i].VolumeSource
switch {
case source.AzureFile != nil:
if len(source.AzureFile.SecretName) > 0 && !visitor(source.AzureFile.SecretName) {
return false
}
case source.CephFS != nil:
if source.CephFS.SecretRef != nil && !visitor(source.CephFS.SecretRef.Name) {
return false
}
case source.FlexVolume != nil:
if source.FlexVolume.SecretRef != nil && !visitor(source.FlexVolume.SecretRef.Name) {
return false
}
case source.Projected != nil:
for j := range source.Projected.Sources {
if source.Projected.Sources[j].Secret != nil {
if !visitor(source.Projected.Sources[j].Secret.Name) {
return false
}
}
}
case source.RBD != nil:
if source.RBD.SecretRef != nil && !visitor(source.RBD.SecretRef.Name) {
return false
}
case source.Secret != nil:
if !visitor(source.Secret.SecretName) {
return false
}
case source.ScaleIO != nil:
if source.ScaleIO.SecretRef != nil && !visitor(source.ScaleIO.SecretRef.Name) {
return false
}
case source.ISCSI != nil:
if source.ISCSI.SecretRef != nil && !visitor(source.ISCSI.SecretRef.Name) {
return false
}
case source.StorageOS != nil:
if source.StorageOS.SecretRef != nil && !visitor(source.StorageOS.SecretRef.Name) {
return false
}
}
}
return true
}
func visitContainerSecretNames(container *v1.Container, visitor Visitor) bool {
for _, env := range container.EnvFrom {
if env.SecretRef != nil {
if !visitor(env.SecretRef.Name) {
return false
}
}
}
for _, envVar := range container.Env {
if envVar.ValueFrom != nil && envVar.ValueFrom.SecretKeyRef != nil {
if !visitor(envVar.ValueFrom.SecretKeyRef.Name) {
return false
}
}
}
return true
}
// VisitPodConfigmapNames invokes the visitor function with the name of every configmap
// referenced by the pod spec. If visitor returns false, visiting is short-circuited.
// Transitive references (e.g. pod -> pvc -> pv -> secret) are not visited.
// Returns true if visiting completed, false if visiting was short-circuited.
func VisitPodConfigmapNames(pod *v1.Pod, visitor Visitor) bool {
for i := range pod.Spec.InitContainers {
if !visitContainerConfigmapNames(&pod.Spec.InitContainers[i], visitor) {
return false
}
}
for i := range pod.Spec.Containers {
if !visitContainerConfigmapNames(&pod.Spec.Containers[i], visitor) {
return false
}
}
var source *v1.VolumeSource
for i := range pod.Spec.Volumes {
source = &pod.Spec.Volumes[i].VolumeSource
switch {
case source.Projected != nil:
for j := range source.Projected.Sources {
if source.Projected.Sources[j].ConfigMap != nil {
if !visitor(source.Projected.Sources[j].ConfigMap.Name) {
return false
}
}
}
case source.ConfigMap != nil:
if !visitor(source.ConfigMap.Name) {
return false
}
}
}
return true
}
func visitContainerConfigmapNames(container *v1.Container, visitor Visitor) bool {
for _, env := range container.EnvFrom {
if env.ConfigMapRef != nil {
if !visitor(env.ConfigMapRef.Name) {
return false
}
}
}
for _, envVar := range container.Env {
if envVar.ValueFrom != nil && envVar.ValueFrom.ConfigMapKeyRef != nil {
if !visitor(envVar.ValueFrom.ConfigMapKeyRef.Name) {
return false
}
}
}
return true
}
// GetContainerStatus extracts the status of container "name" from "statuses".
// It also returns if "name" exists.
func GetContainerStatus(statuses []v1.ContainerStatus, name string) (v1.ContainerStatus, bool) {
for i := range statuses {
if statuses[i].Name == name {
return statuses[i], true
}
}
return v1.ContainerStatus{}, false
}
// GetExistingContainerStatus extracts the status of container "name" from "statuses",
// and returns empty status if "name" does not exist.
func GetExistingContainerStatus(statuses []v1.ContainerStatus, name string) v1.ContainerStatus {
for i := range statuses {
if statuses[i].Name == name {
return statuses[i]
}
}
return v1.ContainerStatus{}
}
// IsPodAvailable returns true if a pod is available; false otherwise.
// Precondition for an available pod is that it must be ready. On top
// of that, there are two cases when a pod can be considered available:
// 1. minReadySeconds == 0, or
// 2. LastTransitionTime (is set) + minReadySeconds < current time
func IsPodAvailable(pod *v1.Pod, minReadySeconds int32, now metav1.Time) bool {
if !IsPodReady(pod) {
return false
}
c := GetPodReadyCondition(pod.Status)
minReadySecondsDuration := time.Duration(minReadySeconds) * time.Second
if minReadySeconds == 0 || !c.LastTransitionTime.IsZero() && c.LastTransitionTime.Add(minReadySecondsDuration).Before(now.Time) {
return true
}
return false
}
// IsPodReady returns true if a pod is ready; false otherwise.
func IsPodReady(pod *v1.Pod) bool {
return IsPodReadyConditionTrue(pod.Status)
}
// IsPodReady retruns true if a pod is ready; false otherwise.
func IsPodReadyConditionTrue(status v1.PodStatus) bool {
condition := GetPodReadyCondition(status)
return condition != nil && condition.Status == v1.ConditionTrue
}
// Extracts the pod ready condition from the given status and returns that.
// Returns nil if the condition is not present.
func GetPodReadyCondition(status v1.PodStatus) *v1.PodCondition {
_, condition := GetPodCondition(&status, v1.PodReady)
return condition
}
// GetPodCondition extracts the provided condition from the given status and returns that.
// Returns nil and -1 if the condition is not present, and the index of the located condition.
func GetPodCondition(status *v1.PodStatus, conditionType v1.PodConditionType) (int, *v1.PodCondition) {
if status == nil {
return -1, nil
}
for i := range status.Conditions {
if status.Conditions[i].Type == conditionType {
return i, &status.Conditions[i]
}
}
return -1, nil
}
// Updates existing pod condition or creates a new one. Sets LastTransitionTime to now if the
// status has changed.
// Returns true if pod condition has changed or has been added.
func UpdatePodCondition(status *v1.PodStatus, condition *v1.PodCondition) bool {
condition.LastTransitionTime = metav1.Now()
// Try to find this pod condition.
conditionIndex, oldCondition := GetPodCondition(status, condition.Type)
if oldCondition == nil {
// We are adding new pod condition.
status.Conditions = append(status.Conditions, *condition)
return true
} else {
// We are updating an existing condition, so we need to check if it has changed.
if condition.Status == oldCondition.Status {
condition.LastTransitionTime = oldCondition.LastTransitionTime
}
isEqual := condition.Status == oldCondition.Status &&
condition.Reason == oldCondition.Reason &&
condition.Message == oldCondition.Message &&
condition.LastProbeTime.Equal(&oldCondition.LastProbeTime) &&
condition.LastTransitionTime.Equal(&oldCondition.LastTransitionTime)
status.Conditions[conditionIndex] = *condition
// Return true if one of the fields have changed.
return !isEqual
}
}