mirror of
https://github.com/kubernetes-sigs/kustomize.git
synced 2026-05-18 12:31:48 +00:00
Merge pull request #1983 from mortent/statusCmdTests
Add tests for status cli
This commit is contained in:
@@ -15,7 +15,7 @@ linters:
|
||||
- dogsled
|
||||
- dupl
|
||||
- errcheck
|
||||
- funlen
|
||||
# - funlen
|
||||
# - gochecknoinits
|
||||
- goconst
|
||||
- gocritic
|
||||
|
||||
@@ -3,9 +3,12 @@ module sigs.k8s.io/kustomize/cmd/resource
|
||||
go 1.12
|
||||
|
||||
require (
|
||||
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d
|
||||
github.com/pkg/errors v0.8.1
|
||||
github.com/spf13/cobra v0.0.5
|
||||
github.com/stretchr/testify v1.4.0
|
||||
golang.org/x/net v0.0.0-20191004110552-13f9640d40b9 // indirect
|
||||
k8s.io/api v0.0.0-20190918155943-95b840bb6a1f
|
||||
k8s.io/apimachinery v0.0.0-20190913080033-27d36303b655
|
||||
k8s.io/client-go v0.0.0-20190918160344-1fbdaa4c8d90
|
||||
sigs.k8s.io/controller-runtime v0.4.0
|
||||
|
||||
@@ -18,6 +18,8 @@ github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbt
|
||||
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
|
||||
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpHMqeKTCYkitsPqHNxTmd4SNR5r94FGM8=
|
||||
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat636LX7Bqt5lYEZ27JNDcqxfjdBQuJ/MM4CN/Lzo=
|
||||
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
|
||||
github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
|
||||
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
|
||||
@@ -349,8 +351,10 @@ google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873 h1:nfPFGzJkUDX6uBmpN/pSw7MbOAWegH5QDQuoXFHedLg=
|
||||
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.23.0 h1:AzbTB6ux+okLTzP8Ru1Xs41C303zdcfEht7MQnYJt5A=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
|
||||
@@ -16,7 +16,9 @@ import (
|
||||
|
||||
// GetEventsRunner returns a command EventsRunner.
|
||||
func GetEventsRunner() *EventsRunner {
|
||||
r := &EventsRunner{}
|
||||
r := &EventsRunner{
|
||||
createClientFunc: createClient,
|
||||
}
|
||||
c := &cobra.Command{
|
||||
Use: "events DIR...",
|
||||
Short: commands.EventsShort,
|
||||
@@ -46,13 +48,15 @@ type EventsRunner struct {
|
||||
Interval time.Duration
|
||||
Timeout time.Duration
|
||||
Command *cobra.Command
|
||||
|
||||
createClientFunc createClientFunc
|
||||
}
|
||||
|
||||
func (r *EventsRunner) runE(c *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
|
||||
// Create a client and use it to set up a new resolver.
|
||||
client, err := getClient()
|
||||
client, err := r.createClientFunc()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error creating client")
|
||||
}
|
||||
|
||||
298
cmd/resource/status/cmd/events_test.go
Normal file
298
cmd/resource/status/cmd/events_test.go
Normal file
@@ -0,0 +1,298 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"sigs.k8s.io/kustomize/kstatus/status"
|
||||
"sigs.k8s.io/kustomize/kstatus/wait"
|
||||
)
|
||||
|
||||
func TestEventsNoResources(t *testing.T) {
|
||||
inBuffer := &bytes.Buffer{}
|
||||
outBuffer := &bytes.Buffer{}
|
||||
|
||||
fakeClient := &FakeClient{}
|
||||
|
||||
r := GetEventsRunner()
|
||||
r.createClientFunc = newClientFunc(fakeClient)
|
||||
r.Command.SetArgs([]string{})
|
||||
r.Command.SetIn(inBuffer)
|
||||
r.Command.SetOut(outBuffer)
|
||||
|
||||
err := r.Command.Execute()
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
eventOutput := parseEventOutput(t, outBuffer.String())
|
||||
|
||||
if want, got := 1, len(eventOutput.events); want != got {
|
||||
t.Errorf("expected %d events, but got %d", want, got)
|
||||
}
|
||||
|
||||
event := eventOutput.events[0]
|
||||
if want, got := status.CurrentStatus, event.aggStatus; want != got {
|
||||
t.Errorf("expected agg status %s, but got %s", want, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEventsMultipleUpdates(t *testing.T) {
|
||||
inBuffer := &bytes.Buffer{}
|
||||
outBuffer := &bytes.Buffer{}
|
||||
|
||||
_, err := fmt.Fprint(inBuffer, `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: bar
|
||||
namespace: default
|
||||
`)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
fakeClient := &FakeClient{
|
||||
resourceCallbackMap: map[string]ResourceGetCallback{
|
||||
"Deployment": createDeploymentStatusFunc(),
|
||||
},
|
||||
}
|
||||
|
||||
r := GetEventsRunner()
|
||||
r.createClientFunc = newClientFunc(fakeClient)
|
||||
r.Command.SetArgs([]string{})
|
||||
r.Command.SetIn(inBuffer)
|
||||
r.Command.SetOut(outBuffer)
|
||||
|
||||
err = r.Command.Execute()
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
eventOutput := parseEventOutput(t, outBuffer.String())
|
||||
|
||||
aggStatuses := eventOutput.allAggStatuses()
|
||||
expectedAggStatuses := []status.Status{
|
||||
status.InProgressStatus,
|
||||
status.InProgressStatus,
|
||||
status.InProgressStatus,
|
||||
status.InProgressStatus,
|
||||
status.CurrentStatus,
|
||||
status.CurrentStatus,
|
||||
}
|
||||
if !reflect.DeepEqual(aggStatuses, expectedAggStatuses) {
|
||||
t.Errorf("expected agg statuses to be %s, but got %s", joinStatuses(expectedAggStatuses),
|
||||
joinStatuses(aggStatuses))
|
||||
}
|
||||
|
||||
resources := eventOutput.allResources()
|
||||
if want, got := 1, len(resources); want != got {
|
||||
t.Errorf("expected %d resource, but got %d", want, got)
|
||||
}
|
||||
|
||||
resource := resources[0]
|
||||
resourceStatuses := eventOutput.statusesForResource(resource)
|
||||
expectedResourceStatuses := []status.Status{
|
||||
status.InProgressStatus,
|
||||
status.InProgressStatus,
|
||||
status.InProgressStatus,
|
||||
status.InProgressStatus,
|
||||
status.CurrentStatus,
|
||||
}
|
||||
if !reflect.DeepEqual(resourceStatuses, expectedResourceStatuses) {
|
||||
t.Errorf("expected statuses to be %s, but got %s", joinStatuses(expectedResourceStatuses),
|
||||
joinStatuses(resourceStatuses))
|
||||
}
|
||||
}
|
||||
|
||||
func TestEventsMultipleResources(t *testing.T) {
|
||||
inBuffer := &bytes.Buffer{}
|
||||
outBuffer := &bytes.Buffer{}
|
||||
|
||||
_, err := fmt.Fprint(inBuffer, `
|
||||
apiVersion: v1
|
||||
kind: List
|
||||
items:
|
||||
- apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: foo
|
||||
namespace: default
|
||||
- apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: bar
|
||||
namespace: default
|
||||
`)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
fakeClient := &FakeClient{
|
||||
resourceCallbackMap: map[string]ResourceGetCallback{
|
||||
"Pod": createPodStatusFunc(),
|
||||
"Service": createServiceStatusFunc(),
|
||||
},
|
||||
}
|
||||
|
||||
r := GetEventsRunner()
|
||||
r.createClientFunc = newClientFunc(fakeClient)
|
||||
r.Command.SetArgs([]string{})
|
||||
r.Command.SetIn(inBuffer)
|
||||
r.Command.SetOut(outBuffer)
|
||||
|
||||
err = r.Command.Execute()
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
eventOutput := parseEventOutput(t, outBuffer.String())
|
||||
|
||||
aggStatuses := eventOutput.allAggStatuses()
|
||||
expectedAggStatuses := []status.Status{
|
||||
status.UnknownStatus,
|
||||
status.CurrentStatus,
|
||||
status.CurrentStatus,
|
||||
}
|
||||
if !reflect.DeepEqual(aggStatuses, expectedAggStatuses) {
|
||||
t.Errorf("expected agg statuses to be %s, but got %s", joinStatuses(expectedAggStatuses),
|
||||
joinStatuses(aggStatuses))
|
||||
}
|
||||
|
||||
resources := eventOutput.allResources()
|
||||
if want, got := 2, len(resources); got != want {
|
||||
t.Errorf("expected %d resource, but got %d", want, got)
|
||||
}
|
||||
|
||||
for _, resource := range resources {
|
||||
resourceStatuses := eventOutput.statusesForResource(resource)
|
||||
if want, got := status.CurrentStatus, resourceStatuses[len(resourceStatuses)-1]; want != got {
|
||||
t.Errorf("expected resource %q to have final status %s, but got %s", resource.name, want, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type EventOutput struct {
|
||||
events []EventOutputLine
|
||||
unknownLines []string
|
||||
}
|
||||
|
||||
func (e *EventOutput) allAggStatuses() []status.Status {
|
||||
var aggStatuses []status.Status
|
||||
for _, event := range e.events {
|
||||
aggStatuses = append(aggStatuses, event.aggStatus)
|
||||
}
|
||||
return aggStatuses
|
||||
}
|
||||
|
||||
func (e *EventOutput) allResources() []ResourceIdentifier {
|
||||
var resources []ResourceIdentifier
|
||||
seenResources := make(map[ResourceIdentifier]bool)
|
||||
for _, event := range e.events {
|
||||
if !event.isResourceUpdateEvent() {
|
||||
continue
|
||||
}
|
||||
r := event.identifier
|
||||
if _, found := seenResources[r]; !found {
|
||||
resources = append(resources, r)
|
||||
seenResources[r] = true
|
||||
}
|
||||
}
|
||||
return resources
|
||||
}
|
||||
|
||||
func (e *EventOutput) statusesForResource(resource ResourceIdentifier) []status.Status {
|
||||
var statuses []status.Status
|
||||
for _, event := range e.events {
|
||||
if !event.isResourceUpdateEvent() {
|
||||
continue
|
||||
}
|
||||
if event.identifier.Equals(resource) {
|
||||
statuses = append(statuses, event.status)
|
||||
}
|
||||
}
|
||||
return statuses
|
||||
}
|
||||
|
||||
type EventOutputLine struct {
|
||||
eventType string
|
||||
aggStatus status.Status
|
||||
identifier ResourceIdentifier
|
||||
status status.Status
|
||||
message string
|
||||
}
|
||||
|
||||
func (e *EventOutputLine) isResourceUpdateEvent() bool {
|
||||
return e.eventType == string(wait.ResourceUpdate)
|
||||
}
|
||||
|
||||
var (
|
||||
eventRegex = regexp.MustCompile(`^\s*` +
|
||||
`(?P<eventType>\S+)\s+` +
|
||||
`(?P<aggStatus>\S+)\s+` +
|
||||
`((?P<resourceType>\S+)\s+` +
|
||||
`(?P<namespace>\S+)\s+` +
|
||||
`(?P<name>\S+)\s+` +
|
||||
`(?P<status>\S+)\s+` +
|
||||
`(?P<message>.*\S)){0,1}` +
|
||||
`\s*$`)
|
||||
eventHeaderRegex = regexp.MustCompile(`^\s*` +
|
||||
`EVENT TYPE\s+` +
|
||||
`AGG STATUS\s+` +
|
||||
`TYPE\s+` +
|
||||
`NAMESPACE\s+` +
|
||||
`NAME\s+` +
|
||||
`STATUS\s+` +
|
||||
`MESSAGE` +
|
||||
`\s*$`)
|
||||
)
|
||||
|
||||
func parseEventOutput(_ *testing.T, output string) EventOutput {
|
||||
var eventOutput EventOutput
|
||||
lines := strings.Split(output, "\n")
|
||||
for _, line := range lines {
|
||||
if len(line) == 0 {
|
||||
continue // Ignore empty lines
|
||||
}
|
||||
match := eventHeaderRegex.FindStringSubmatch(line)
|
||||
if match != nil {
|
||||
continue // Ignore headers
|
||||
}
|
||||
match = eventRegex.FindStringSubmatch(line)
|
||||
if match == nil {
|
||||
eventOutput.unknownLines = append(eventOutput.unknownLines, line)
|
||||
continue
|
||||
}
|
||||
|
||||
eventOutputLine := EventOutputLine{
|
||||
eventType: match[1],
|
||||
aggStatus: status.FromStringOrDie(match[2]),
|
||||
}
|
||||
|
||||
if eventOutputLine.eventType == string(wait.ResourceUpdate) {
|
||||
resourceType := match[4]
|
||||
parts := strings.Split(resourceType, "/")
|
||||
var identifier ResourceIdentifier
|
||||
if len(parts) == 2 {
|
||||
identifier.apiVersion = parts[0]
|
||||
identifier.kind = parts[1]
|
||||
} else {
|
||||
identifier.apiVersion = strings.Join(parts[:2], "/")
|
||||
identifier.kind = parts[2]
|
||||
}
|
||||
identifier.namespace = match[5]
|
||||
identifier.name = match[6]
|
||||
eventOutputLine.identifier = identifier
|
||||
eventOutputLine.status = status.FromStringOrDie(match[7])
|
||||
eventOutputLine.message = match[8]
|
||||
}
|
||||
|
||||
eventOutput.events = append(eventOutput.events, eventOutputLine)
|
||||
}
|
||||
return eventOutput
|
||||
}
|
||||
@@ -17,7 +17,9 @@ import (
|
||||
|
||||
// GetFetchRunner returns a command FetchRunner.
|
||||
func GetFetchRunner() *FetchRunner {
|
||||
r := &FetchRunner{}
|
||||
r := &FetchRunner{
|
||||
createClientFunc: createClient,
|
||||
}
|
||||
c := &cobra.Command{
|
||||
Use: "fetch DIR...",
|
||||
Short: commands.FetchShort,
|
||||
@@ -41,18 +43,20 @@ func FetchCommand() *cobra.Command {
|
||||
type FetchRunner struct {
|
||||
IncludeSubpackages bool
|
||||
Command *cobra.Command
|
||||
|
||||
createClientFunc createClientFunc
|
||||
}
|
||||
|
||||
func (r *FetchRunner) runE(c *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
|
||||
// Create a new client and use it to set up a resolver.
|
||||
client, err := getClient()
|
||||
k8sClient, err := r.createClientFunc()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error creating client")
|
||||
return errors.Wrap(err, "error creating k8sClient")
|
||||
}
|
||||
|
||||
resolver := wait.NewResolver(client, time.Minute)
|
||||
resolver := wait.NewResolver(k8sClient, time.Minute)
|
||||
|
||||
// Set up a CaptureIdentifierFilter and run all inputs through the
|
||||
// filter with the pipeline to capture the inventory of resources
|
||||
|
||||
230
cmd/resource/status/cmd/fetch_test.go
Normal file
230
cmd/resource/status/cmd/fetch_test.go
Normal file
@@ -0,0 +1,230 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/acarl005/stripansi"
|
||||
"github.com/stretchr/testify/assert"
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client/fake"
|
||||
"sigs.k8s.io/kustomize/kstatus/status"
|
||||
)
|
||||
|
||||
func TestEmptyManifest(t *testing.T) {
|
||||
inBuffer := &bytes.Buffer{}
|
||||
outBuffer := &bytes.Buffer{}
|
||||
|
||||
fakeClient := fake.NewFakeClientWithScheme(scheme)
|
||||
|
||||
r := GetFetchRunner()
|
||||
r.createClientFunc = newClientFunc(fakeClient)
|
||||
r.Command.SetArgs([]string{})
|
||||
r.Command.SetIn(inBuffer)
|
||||
r.Command.SetOut(outBuffer)
|
||||
|
||||
err := r.Command.Execute()
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
output := outBuffer.String()
|
||||
lines := strings.Split(output, "\n")
|
||||
|
||||
if want, got := 2, len(lines); want != got {
|
||||
t.Errorf("Expected %d lines, but got %d", want, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFetchStatusFromManifestStdIn(t *testing.T) {
|
||||
inBuffer := &bytes.Buffer{}
|
||||
outBuffer := &bytes.Buffer{}
|
||||
|
||||
_, err := fmt.Fprint(inBuffer, `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: bar
|
||||
namespace: default
|
||||
`)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
deployment := createDeployment("bar", "default", 42, appsv1.DeploymentStatus{
|
||||
ObservedGeneration: 1,
|
||||
})
|
||||
|
||||
fakeClient := fake.NewFakeClientWithScheme(scheme, deployment)
|
||||
|
||||
r := GetFetchRunner()
|
||||
r.createClientFunc = newClientFunc(fakeClient)
|
||||
r.Command.SetArgs([]string{})
|
||||
r.Command.SetIn(inBuffer)
|
||||
r.Command.SetOut(outBuffer)
|
||||
|
||||
err = r.Command.Execute()
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
cleanOutput := stripansi.Strip(outBuffer.String())
|
||||
tableOutput := parseTableOutput(t, cleanOutput)
|
||||
|
||||
expectedResource := ResourceIdentifier{
|
||||
apiVersion: "apps/v1",
|
||||
kind: "Deployment",
|
||||
namespace: "default",
|
||||
name: "bar",
|
||||
}
|
||||
expectedStatus := status.InProgressStatus
|
||||
expectedMessage := "Deployment generation is 2, but latest observed generation is 1"
|
||||
|
||||
verifyOutputContains(t, tableOutput, expectedResource, expectedStatus, expectedMessage)
|
||||
}
|
||||
|
||||
//nolint:funlen
|
||||
func TestFetchStatusFromManifestsFiles(t *testing.T) {
|
||||
d, err := ioutil.TempDir("", "status-fetch-test")
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
defer os.RemoveAll(d)
|
||||
|
||||
err = ioutil.WriteFile(filepath.Join(d, "dep.yaml"), []byte(`
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: foo
|
||||
namespace: default
|
||||
`), 0600)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
err = ioutil.WriteFile(filepath.Join(d, "svc.yaml"), []byte(`
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: foo
|
||||
namespace: default
|
||||
`), 0600)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
replicas := int32(42)
|
||||
deployment := createDeployment("foo", "default", replicas, appsv1.DeploymentStatus{
|
||||
ObservedGeneration: 2,
|
||||
Replicas: replicas,
|
||||
ReadyReplicas: replicas,
|
||||
AvailableReplicas: replicas,
|
||||
UpdatedReplicas: replicas,
|
||||
Conditions: []appsv1.DeploymentCondition{
|
||||
{
|
||||
Type: appsv1.DeploymentAvailable,
|
||||
Status: v1.ConditionTrue,
|
||||
},
|
||||
},
|
||||
})
|
||||
service := createService("foo", "default")
|
||||
|
||||
fakeClient := fake.NewFakeClientWithScheme(scheme, deployment, service)
|
||||
|
||||
outBuffer := &bytes.Buffer{}
|
||||
|
||||
r := GetFetchRunner()
|
||||
r.createClientFunc = newClientFunc(fakeClient)
|
||||
r.Command.SetArgs([]string{d})
|
||||
r.Command.SetOut(outBuffer)
|
||||
|
||||
err = r.Command.Execute()
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
cleanOutput := stripansi.Strip(outBuffer.String())
|
||||
tableOutput := parseTableOutput(t, cleanOutput)
|
||||
|
||||
expectedDeploymentResource := ResourceIdentifier{
|
||||
apiVersion: "apps/v1",
|
||||
kind: "Deployment",
|
||||
namespace: "default",
|
||||
name: "foo",
|
||||
}
|
||||
expectedDeploymentStatus := status.CurrentStatus
|
||||
expectedDeploymentMessage := "Deployment is available. Replicas: 42"
|
||||
verifyOutputContains(t, tableOutput, expectedDeploymentResource, expectedDeploymentStatus, expectedDeploymentMessage)
|
||||
|
||||
expectedServiceResource := ResourceIdentifier{
|
||||
apiVersion: "v1",
|
||||
kind: "Service",
|
||||
namespace: "default",
|
||||
name: "foo",
|
||||
}
|
||||
expectedServiceStatus := status.CurrentStatus
|
||||
expectedServiceMessage := "Service is ready"
|
||||
|
||||
verifyOutputContains(t, tableOutput, expectedServiceResource, expectedServiceStatus, expectedServiceMessage)
|
||||
}
|
||||
|
||||
func createDeployment(name, namespace string, replicas int32, status appsv1.DeploymentStatus) *appsv1.Deployment {
|
||||
return &appsv1.Deployment{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: "apps/v1",
|
||||
Kind: "Deployment",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Namespace: namespace,
|
||||
Generation: 2,
|
||||
},
|
||||
Spec: appsv1.DeploymentSpec{
|
||||
Replicas: &replicas,
|
||||
},
|
||||
Status: status,
|
||||
}
|
||||
}
|
||||
|
||||
func createService(name, namespace string) *v1.Service {
|
||||
return &v1.Service{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: "v1",
|
||||
Kind: "Service",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Namespace: namespace,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func verifyOutputContains(t *testing.T, tableOutput TableOutput, resource ResourceIdentifier, status status.Status, message string) {
|
||||
if len(tableOutput.Frames) == 0 {
|
||||
t.Fatalf("expected match for resource %s, but output had no frames", resource.name)
|
||||
}
|
||||
firstFrame := tableOutput.Frames[0]
|
||||
var foundResource ResourceOutput
|
||||
match := false
|
||||
for _, resourceOutput := range firstFrame.Resources {
|
||||
if resourceOutput.identifier.Equals(resource) {
|
||||
foundResource = resourceOutput
|
||||
match = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !match {
|
||||
t.Errorf("expected match for resource %s, but didn't find it", resource.name)
|
||||
}
|
||||
if want, got := status, foundResource.status; want != got {
|
||||
t.Errorf("expected status %s for resource %s, but got %s", want, resource.name, got)
|
||||
}
|
||||
if want, got := message, foundResource.message; !strings.HasPrefix(want, got) {
|
||||
t.Errorf("expected message %s for resource %s, but got %s", want, resource.name, got)
|
||||
}
|
||||
}
|
||||
245
cmd/resource/status/cmd/helpers_test.go
Normal file
245
cmd/resource/status/cmd/helpers_test.go
Normal file
@@ -0,0 +1,245 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/kustomize/kstatus/status"
|
||||
)
|
||||
|
||||
type TableOutput struct {
|
||||
Frames []TableOutputFrame
|
||||
UnknownRows []string
|
||||
}
|
||||
|
||||
func (t *TableOutput) allAggStatuses() []status.Status {
|
||||
var statuses []status.Status
|
||||
for _, frame := range t.Frames {
|
||||
if frame.AggregateStatus != "" {
|
||||
statuses = append(statuses, frame.AggregateStatus)
|
||||
}
|
||||
}
|
||||
return statuses
|
||||
}
|
||||
|
||||
func (t *TableOutput) dedupedAggStatuses() []status.Status {
|
||||
var dedupedStatuses []status.Status
|
||||
statuses := t.allAggStatuses()
|
||||
var previousStatus status.Status
|
||||
for _, s := range statuses {
|
||||
if s != previousStatus {
|
||||
dedupedStatuses = append(dedupedStatuses, s)
|
||||
previousStatus = s
|
||||
}
|
||||
}
|
||||
return dedupedStatuses
|
||||
}
|
||||
|
||||
func (t *TableOutput) resources() []ResourceIdentifier {
|
||||
seenResources := make(map[ResourceIdentifier]bool)
|
||||
var resources []ResourceIdentifier
|
||||
for _, frame := range t.Frames {
|
||||
for _, resource := range frame.Resources {
|
||||
r := resource.identifier
|
||||
_, found := seenResources[r]
|
||||
if !found {
|
||||
seenResources[r] = true
|
||||
resources = append(resources, r)
|
||||
}
|
||||
}
|
||||
}
|
||||
return resources
|
||||
}
|
||||
|
||||
func (t *TableOutput) dedupedStatusesForResource(resource ResourceIdentifier) []status.Status {
|
||||
var dedupedStatuses []status.Status
|
||||
var previousStatus status.Status
|
||||
for _, frame := range t.Frames {
|
||||
for _, r := range frame.Resources {
|
||||
if r.identifier.Equals(resource) {
|
||||
if r.status != previousStatus {
|
||||
previousStatus = r.status
|
||||
dedupedStatuses = append(dedupedStatuses, r.status)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return dedupedStatuses
|
||||
}
|
||||
|
||||
type TableOutputFrame struct {
|
||||
AggregateStatus status.Status
|
||||
Resources []ResourceOutput
|
||||
}
|
||||
|
||||
type ResourceIdentifier struct {
|
||||
apiVersion string
|
||||
kind string
|
||||
name string
|
||||
namespace string
|
||||
}
|
||||
|
||||
func (r ResourceIdentifier) Equals(identifier ResourceIdentifier) bool {
|
||||
return r.apiVersion == identifier.apiVersion &&
|
||||
r.kind == identifier.kind &&
|
||||
r.namespace == identifier.namespace &&
|
||||
r.name == identifier.name
|
||||
}
|
||||
|
||||
type ResourceOutput struct {
|
||||
identifier ResourceIdentifier
|
||||
status status.Status
|
||||
message string
|
||||
}
|
||||
|
||||
var (
|
||||
headerRegex = regexp.MustCompile(`^\s*TYPE\s+NAMESPACE\s+NAME\s+STATUS\s+MESSAGE\s*$`)
|
||||
resourceRegex = regexp.MustCompile(`^(?P<resourceType>\S+)\s+(?P<namespace>\S+)\s+(?P<name>\S+)\s+(?P<status>\S+)\s+(?P<message>.*\S)\s*$`)
|
||||
aggStatusRegex = regexp.MustCompile(`^\s*AggregateStatus: (?P<aggregateStatus>\S+)\s*$`)
|
||||
)
|
||||
|
||||
func parseTableOutput(_ *testing.T, output string) TableOutput {
|
||||
tableOutput := TableOutput{}
|
||||
|
||||
lines := strings.Split(output, "\n")
|
||||
|
||||
hasAggStatus := false
|
||||
var currentFrame TableOutputFrame
|
||||
for i, line := range lines {
|
||||
if len(line) == 0 {
|
||||
continue // We don't care about empty lines.
|
||||
}
|
||||
|
||||
// Check for lines with aggregate status. They are not always present, but if they are,
|
||||
// they always start a new frame of output.
|
||||
match := aggStatusRegex.FindStringSubmatch(line)
|
||||
if match != nil {
|
||||
hasAggStatus = true
|
||||
if i != 0 {
|
||||
tableOutput.Frames = append(tableOutput.Frames, currentFrame)
|
||||
}
|
||||
currentFrame = TableOutputFrame{
|
||||
AggregateStatus: status.FromStringOrDie(match[1]),
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
match = headerRegex.FindStringSubmatch(line)
|
||||
if match != nil {
|
||||
if !hasAggStatus {
|
||||
if i != 0 {
|
||||
tableOutput.Frames = append(tableOutput.Frames, currentFrame)
|
||||
}
|
||||
currentFrame = TableOutputFrame{}
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
match = resourceRegex.FindStringSubmatch(line)
|
||||
if match != nil {
|
||||
var identifier ResourceIdentifier
|
||||
resourceType := match[1]
|
||||
parts := strings.Split(resourceType, "/")
|
||||
if len(parts) == 2 {
|
||||
identifier.apiVersion = parts[0]
|
||||
identifier.kind = parts[1]
|
||||
} else {
|
||||
identifier.apiVersion = strings.Join(parts[:2], "/")
|
||||
identifier.kind = parts[2]
|
||||
}
|
||||
identifier.namespace = match[2]
|
||||
identifier.name = match[3]
|
||||
|
||||
res := ResourceOutput{
|
||||
identifier: identifier,
|
||||
}
|
||||
res.status = status.FromStringOrDie(match[4])
|
||||
res.message = match[5]
|
||||
currentFrame.Resources = append(currentFrame.Resources, res)
|
||||
continue
|
||||
}
|
||||
tableOutput.UnknownRows = append(tableOutput.UnknownRows, line)
|
||||
}
|
||||
tableOutput.Frames = append(tableOutput.Frames, currentFrame)
|
||||
return tableOutput
|
||||
}
|
||||
|
||||
func createDeploymentStatusFunc() func(*unstructured.Unstructured) error {
|
||||
metadataMap := map[string]interface{}{
|
||||
"generation": int64(2),
|
||||
}
|
||||
specMap := map[string]interface{}{
|
||||
"replicas": int64(2),
|
||||
}
|
||||
statusMap := map[string]interface{}{
|
||||
"observedGeneration": int64(2),
|
||||
"replicas": int64(4),
|
||||
"updatedReplicas": int64(4),
|
||||
"readyReplicas": int64(4),
|
||||
}
|
||||
var conditions = make([]interface{}, 0)
|
||||
conditions = append(conditions, map[string]interface{}{
|
||||
"type": "Available",
|
||||
"status": "True",
|
||||
})
|
||||
callbackCount := int64(0)
|
||||
return func(deployment *unstructured.Unstructured) error {
|
||||
_ = unstructured.SetNestedMap(deployment.Object, metadataMap, "metadata")
|
||||
_ = unstructured.SetNestedMap(deployment.Object, specMap, "spec")
|
||||
statusMap["availableReplicas"] = callbackCount
|
||||
_ = unstructured.SetNestedMap(deployment.Object, statusMap, "status")
|
||||
_ = unstructured.SetNestedSlice(deployment.Object, conditions, "status", "conditions")
|
||||
callbackCount++
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func createServiceStatusFunc() func(*unstructured.Unstructured) error {
|
||||
return func(*unstructured.Unstructured) error {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func createPodStatusFunc() func(*unstructured.Unstructured) error {
|
||||
statusMap := map[string]interface{}{
|
||||
"phase": "Succeeded",
|
||||
}
|
||||
return func(pod *unstructured.Unstructured) error {
|
||||
_ = unstructured.SetNestedMap(pod.Object, statusMap, "status")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
type ResourceGetCallback func(resource *unstructured.Unstructured) error
|
||||
|
||||
type FakeClient struct {
|
||||
resourceCallbackMap map[string]ResourceGetCallback
|
||||
}
|
||||
|
||||
func (f *FakeClient) Get(_ context.Context, _ client.ObjectKey, obj runtime.Object) error {
|
||||
kind := obj.GetObjectKind().GroupVersionKind().Kind
|
||||
callbackFunc, found := f.resourceCallbackMap[kind]
|
||||
if !found {
|
||||
return fmt.Errorf("no callback func found for kind %s", kind)
|
||||
}
|
||||
u := obj.(*unstructured.Unstructured)
|
||||
return callbackFunc(u)
|
||||
}
|
||||
|
||||
func (f *FakeClient) List(ctx context.Context, list runtime.Object, opts ...client.ListOption) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func joinStatuses(statuses []status.Status) string {
|
||||
var stringStatuses []string
|
||||
for _, s := range statuses {
|
||||
stringStatuses = append(stringStatuses, s.String())
|
||||
}
|
||||
return strings.Join(stringStatuses, ",")
|
||||
}
|
||||
@@ -179,10 +179,13 @@ func (s *TablePrinter) printTable(data StatusData, deleteUp bool) {
|
||||
}
|
||||
|
||||
func (s *TablePrinter) printTableRow(rowData []RowData) {
|
||||
for _, row := range rowData {
|
||||
for i, row := range rowData {
|
||||
setColor(s.out, row.color)
|
||||
format := fmt.Sprintf("%%-%ds ", row.width)
|
||||
format := fmt.Sprintf("%%-%ds", row.width)
|
||||
printOrDie(s.out, format, trimString(row.content, row.width))
|
||||
if i != len(rowData)-1 {
|
||||
printOrDie(s.out, " ")
|
||||
}
|
||||
setColor(s.out, WHITE)
|
||||
}
|
||||
printOrDie(s.out, "\n")
|
||||
|
||||
@@ -22,9 +22,11 @@ func init() {
|
||||
_ = clientgoscheme.AddToScheme(scheme)
|
||||
}
|
||||
|
||||
// getClient returns a client for talking to a Kubernetes cluster. The client
|
||||
type createClientFunc func() (client.Reader, error)
|
||||
|
||||
// createClient returns a client for talking to a Kubernetes cluster. The client
|
||||
// is from controller-runtime.
|
||||
func getClient() (client.Client, error) {
|
||||
func createClient() (client.Reader, error) {
|
||||
config := ctrl.GetConfigOrDie()
|
||||
mapper, err := apiutil.NewDiscoveryRESTMapper(config)
|
||||
if err != nil {
|
||||
@@ -33,6 +35,12 @@ func getClient() (client.Client, error) {
|
||||
return client.New(config, client.Options{Scheme: scheme, Mapper: mapper})
|
||||
}
|
||||
|
||||
func newClientFunc(c client.Reader) func() (client.Reader, error) {
|
||||
return func() (client.Reader, error) {
|
||||
return c, nil
|
||||
}
|
||||
}
|
||||
|
||||
// CaptureIdentifiersFilter implements the Filter interface in the kio package. It
|
||||
// captures the identifiers for all resources passed through the pipeline.
|
||||
type CaptureIdentifiersFilter struct {
|
||||
|
||||
@@ -18,7 +18,9 @@ import (
|
||||
|
||||
// GetWaitRunner return a command WaitRunner.
|
||||
func GetWaitRunner() *WaitRunner {
|
||||
r := &WaitRunner{}
|
||||
r := &WaitRunner{
|
||||
createClientFunc: createClient,
|
||||
}
|
||||
c := &cobra.Command{
|
||||
Use: "wait DIR...",
|
||||
Short: commands.WaitShort,
|
||||
@@ -48,6 +50,8 @@ type WaitRunner struct {
|
||||
Interval time.Duration
|
||||
Timeout time.Duration
|
||||
Command *cobra.Command
|
||||
|
||||
createClientFunc createClientFunc
|
||||
}
|
||||
|
||||
// runE implements the logic of the command and will call the Wait command in the wait
|
||||
@@ -55,7 +59,7 @@ type WaitRunner struct {
|
||||
// TablePrinter to display the information.
|
||||
func (r *WaitRunner) runE(c *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
client, err := getClient()
|
||||
client, err := r.createClientFunc()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error creating client")
|
||||
}
|
||||
|
||||
176
cmd/resource/status/cmd/wait_test.go
Normal file
176
cmd/resource/status/cmd/wait_test.go
Normal file
@@ -0,0 +1,176 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/acarl005/stripansi"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"sigs.k8s.io/kustomize/kstatus/status"
|
||||
)
|
||||
|
||||
func TestWaitNoResources(t *testing.T) {
|
||||
inBuffer := &bytes.Buffer{}
|
||||
outBuffer := &bytes.Buffer{}
|
||||
|
||||
fakeClient := &FakeClient{}
|
||||
|
||||
r := GetWaitRunner()
|
||||
r.createClientFunc = newClientFunc(fakeClient)
|
||||
r.Command.SetArgs([]string{})
|
||||
r.Command.SetIn(inBuffer)
|
||||
r.Command.SetOut(outBuffer)
|
||||
|
||||
err := r.Command.Execute()
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
cleanOutput := stripansi.Strip(outBuffer.String())
|
||||
tableOutput := parseTableOutput(t, cleanOutput)
|
||||
|
||||
if want, got := 2, len(tableOutput.Frames); want != got {
|
||||
t.Errorf("expected %d frames, but found %d", want, got)
|
||||
}
|
||||
|
||||
aggStatuses := tableOutput.allAggStatuses()
|
||||
expectedAggStatuses := []status.Status{
|
||||
status.CurrentStatus,
|
||||
status.CurrentStatus,
|
||||
}
|
||||
if !reflect.DeepEqual(aggStatuses, expectedAggStatuses) {
|
||||
t.Errorf("expected agg statuses to be %s, but got %s", joinStatuses(expectedAggStatuses),
|
||||
joinStatuses(aggStatuses))
|
||||
}
|
||||
|
||||
resources := tableOutput.resources()
|
||||
if want, got := 0, len(resources); want != got {
|
||||
t.Errorf("expected %d resources, but found %d", want, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWaitMultipleUpdates(t *testing.T) {
|
||||
inBuffer := &bytes.Buffer{}
|
||||
outBuffer := &bytes.Buffer{}
|
||||
|
||||
_, err := fmt.Fprint(inBuffer, `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: bar
|
||||
namespace: default
|
||||
`)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
fakeClient := &FakeClient{
|
||||
resourceCallbackMap: map[string]ResourceGetCallback{
|
||||
"Deployment": createDeploymentStatusFunc(),
|
||||
},
|
||||
}
|
||||
|
||||
r := GetWaitRunner()
|
||||
r.createClientFunc = newClientFunc(fakeClient)
|
||||
r.Command.SetArgs([]string{})
|
||||
r.Command.SetIn(inBuffer)
|
||||
r.Command.SetOut(outBuffer)
|
||||
|
||||
err = r.Command.Execute()
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
cleanOutput := stripansi.Strip(outBuffer.String())
|
||||
tableOutput := parseTableOutput(t, cleanOutput)
|
||||
|
||||
aggStatuses := tableOutput.dedupedAggStatuses()
|
||||
expectedStatuses := []status.Status{
|
||||
status.UnknownStatus,
|
||||
status.InProgressStatus,
|
||||
status.CurrentStatus,
|
||||
}
|
||||
if !reflect.DeepEqual(aggStatuses, expectedStatuses) {
|
||||
t.Errorf("expected deduped agg statuses to be %s, but got %s", joinStatuses(expectedStatuses),
|
||||
joinStatuses(aggStatuses))
|
||||
}
|
||||
|
||||
resources := tableOutput.resources()
|
||||
if want, got := 1, len(resources); got != want {
|
||||
t.Errorf("expected %d resource, but got %d", want, got)
|
||||
}
|
||||
|
||||
resource := resources[0]
|
||||
resourceStatuses := tableOutput.dedupedStatusesForResource(resource)
|
||||
expectedResourceStatuses := []status.Status{
|
||||
status.InProgressStatus,
|
||||
status.CurrentStatus,
|
||||
}
|
||||
if !reflect.DeepEqual(expectedResourceStatuses, resourceStatuses) {
|
||||
t.Errorf("expected resource %q to have statuses %s, but got %s", resource.name,
|
||||
joinStatuses(expectedResourceStatuses), joinStatuses(resourceStatuses))
|
||||
}
|
||||
}
|
||||
|
||||
func TestWaitMultipleResources(t *testing.T) {
|
||||
inBuffer := &bytes.Buffer{}
|
||||
outBuffer := &bytes.Buffer{}
|
||||
|
||||
_, err := fmt.Fprint(inBuffer, `
|
||||
apiVersion: v1
|
||||
kind: List
|
||||
items:
|
||||
- apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: foo
|
||||
namespace: default
|
||||
- apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: bar
|
||||
namespace: default
|
||||
`)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
fakeClient := &FakeClient{
|
||||
resourceCallbackMap: map[string]ResourceGetCallback{
|
||||
"Pod": createPodStatusFunc(),
|
||||
"Service": createServiceStatusFunc(),
|
||||
},
|
||||
}
|
||||
|
||||
r := GetWaitRunner()
|
||||
r.createClientFunc = newClientFunc(fakeClient)
|
||||
r.Command.SetArgs([]string{})
|
||||
r.Command.SetIn(inBuffer)
|
||||
r.Command.SetOut(outBuffer)
|
||||
|
||||
err = r.Command.Execute()
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
cleanOutput := stripansi.Strip(outBuffer.String())
|
||||
tableOutput := parseTableOutput(t, cleanOutput)
|
||||
|
||||
aggStatuses := tableOutput.dedupedAggStatuses()
|
||||
if want, got := status.CurrentStatus, aggStatuses[len(aggStatuses)-1]; want != got {
|
||||
t.Errorf("expected final agg statuses to be %s, but got %s", want, got)
|
||||
}
|
||||
|
||||
resources := tableOutput.resources()
|
||||
if want, got := 2, len(resources); got != want {
|
||||
t.Errorf("expected %d resource, but got %d", want, got)
|
||||
}
|
||||
|
||||
for _, resource := range resources {
|
||||
resourceStatuses := tableOutput.dedupedStatusesForResource(resource)
|
||||
if want, got := status.CurrentStatus, resourceStatuses[len(resourceStatuses)-1]; want != got {
|
||||
t.Errorf("expected resource %q to have final status %s, but got %s", resource.name, want, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,8 @@
|
||||
|
||||
|
||||
// Code generated by "mdtogo"; DO NOT EDIT.
|
||||
package commands
|
||||
|
||||
var EventsShort=`[Alpha] Poll the cluster until all provided resources have become Current and list the status change events.`
|
||||
var EventsLong=`
|
||||
var EventsShort = `[Alpha] Poll the cluster until all provided resources have become Current and list the status change events.`
|
||||
var EventsLong = `
|
||||
[Alpha] Poll the cluster for the state of all the provided resources until either they have all become
|
||||
Current or the timeout is reached. The output will be status change events.
|
||||
|
||||
@@ -14,15 +12,15 @@ on StdIn.
|
||||
DIR:
|
||||
Path to local directory. If not provided, input is expected on StdIn.
|
||||
`
|
||||
var EventsExamples=`
|
||||
var EventsExamples = `
|
||||
# Read resources from the filesystem and wait up to 1 minute for all of them to become Current
|
||||
resource status events my-dir/
|
||||
|
||||
# Fetch all resources in the cluster and wait up to 5 minutes for all of them to become Current
|
||||
kubectl get all --all-namespaces -oyaml | resource status events --timeout=5m`
|
||||
|
||||
var FetchShort=`[Alpha] Fetch the state of the provided resources from the cluster and display status in a table.`
|
||||
var FetchLong=`
|
||||
var FetchShort = `[Alpha] Fetch the state of the provided resources from the cluster and display status in a table.`
|
||||
var FetchLong = `
|
||||
[Alpha] Fetches the state of all provided resources from the cluster and displays the status in
|
||||
a table.
|
||||
|
||||
@@ -31,15 +29,15 @@ The list of resources are provided as manifests either on the filesystem or on S
|
||||
DIR:
|
||||
Path to local directory.
|
||||
`
|
||||
var FetchExamples=`
|
||||
var FetchExamples = `
|
||||
# Read resources from the filesystem and wait up to 1 minute for all of them to become Current
|
||||
resource status fetch my-dir/
|
||||
|
||||
# Fetch all resources in the cluster and wait up to 5 minutes for all of them to become Current
|
||||
kubectl get all --all-namespaces -oyaml | resource status fetch`
|
||||
|
||||
var WaitShort=`[Alpha] Poll the cluster until all provided resources have become Current and display progress in a table. `
|
||||
var WaitLong=`
|
||||
var WaitShort = `[Alpha] Poll the cluster until all provided resources have become Current and display progress in a table. `
|
||||
var WaitLong = `
|
||||
[Alpha] Poll the cluster for the state of all the provided resources until either they have all become
|
||||
Current or the timeout is reached. The output will be presented as a table.
|
||||
|
||||
@@ -49,7 +47,7 @@ on StdIn.
|
||||
DIR:
|
||||
Path to local directory. If not provided, input is expected on StdIn.
|
||||
`
|
||||
var WaitExamples=`
|
||||
var WaitExamples = `
|
||||
# Read resources from the filesystem and wait up to 1 minute for all of them to become Current
|
||||
resource status wait my-dir/
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
package status
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
@@ -26,6 +27,11 @@ const (
|
||||
UnknownStatus Status = "Unknown"
|
||||
)
|
||||
|
||||
var (
|
||||
Statuses = []Status{InProgressStatus, FailedStatus, CurrentStatus, TerminatingStatus, UnknownStatus}
|
||||
ConditionTypes = []ConditionType{ConditionFailed, ConditionInProgress}
|
||||
)
|
||||
|
||||
// ConditionType defines the set of condition types allowed inside a Condition struct.
|
||||
type ConditionType string
|
||||
|
||||
@@ -42,6 +48,18 @@ func (s Status) String() string {
|
||||
return string(s)
|
||||
}
|
||||
|
||||
// StatusFromString turns a string into a Status. Will panic if the provided string is
|
||||
// not a valid status.
|
||||
func FromStringOrDie(text string) Status {
|
||||
s := Status(text)
|
||||
for _, r := range Statuses {
|
||||
if s == r {
|
||||
return s
|
||||
}
|
||||
}
|
||||
panic(fmt.Errorf("string has invalid status: %s", s))
|
||||
}
|
||||
|
||||
// Result contains the results of a call to compute the status of
|
||||
// a resource.
|
||||
type Result struct {
|
||||
|
||||
Reference in New Issue
Block a user