Adding status command

This commit is contained in:
Barni S 2019-06-03 16:04:20 -04:00 committed by jingfangliu
parent 3021f2f069
commit 26d681d633
16 changed files with 1352 additions and 93 deletions

View File

@ -15,11 +15,11 @@ package status
import (
"fmt"
"sigs.k8s.io/cli-experimental/internal/pkg/util"
//"os"
"github.com/spf13/cobra"
"sigs.k8s.io/cli-experimental/internal/pkg/clik8s"
"sigs.k8s.io/cli-experimental/internal/pkg/util"
"sigs.k8s.io/cli-experimental/internal/pkg/wirecli/wirestatus"
)
@ -34,11 +34,18 @@ func GetApplyStatusCommand(a util.Args) *cobra.Command {
cmd.RunE = func(cmd *cobra.Command, args []string) error {
for i := range args {
r, err := wirestatus.DoStatus(clik8s.ResourceConfigPath(args[i]), cmd.OutOrStdout(), a)
result, err := wirestatus.DoStatus(clik8s.ResourceConfigPath(args[i]), cmd.OutOrStdout(), a)
for i := range result.Resources {
u := result.Resources[i].Resource
fmt.Fprintf(cmd.OutOrStdout(), "%s/%s %s", u.GetKind(), u.GetName(), result.Resources[i].Status)
if result.Resources[i].Error != nil {
fmt.Fprintf(cmd.OutOrStdout(), "(err: %s)", result.Resources[i].Error)
}
fmt.Fprintf(cmd.OutOrStdout(), "\n")
}
if err != nil {
return err
}
fmt.Fprintf(cmd.OutOrStdout(), "Resources: %v\n", len(r.Resources))
}
return nil
}

7
go.mod
View File

@ -7,22 +7,21 @@ require (
github.com/Azure/go-autorest v11.7.0+incompatible // indirect
github.com/dgrijalva/jwt-go v3.2.0+incompatible // indirect
github.com/evanphx/json-patch v4.1.0+incompatible
github.com/ghodss/yaml v1.0.0
github.com/go-logr/logr v0.1.0 // indirect
github.com/go-logr/zapr v0.1.1 // indirect
github.com/go-openapi/spec v0.19.0 // indirect
github.com/gogo/protobuf v1.2.1 // indirect
github.com/golang/mock v1.2.0 // indirect
github.com/google/btree v1.0.0 // indirect
github.com/google/go-cmp v0.3.0 // indirect
github.com/google/wire v0.2.2-0.20190423202733-d079521b6f51
github.com/googleapis/gnostic v0.2.0 // indirect
github.com/gophercloud/gophercloud v0.0.0-20190328150603-33e54f40ffcf // indirect
github.com/gregjones/httpcache v0.0.0-20190212212710-3befbb6ad0cc // indirect
github.com/grpc-ecosystem/grpc-gateway v1.8.5 // indirect
github.com/grpc-ecosystem/grpc-gateway v1.6.2 // indirect
github.com/imdario/mergo v0.3.7 // indirect
github.com/json-iterator/go v1.1.6 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/modern-go/reflect2 v1.0.1 // indirect
github.com/onsi/ginkgo v1.8.0
github.com/onsi/gomega v1.5.0
github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
@ -45,7 +44,7 @@ require (
k8s.io/apiextensions-apiserver v0.0.0-20190328030136-8ada4fd07db4
k8s.io/apimachinery v0.0.0-20190326224424-4ceb6b6c5db5
k8s.io/client-go v11.0.0+incompatible
k8s.io/klog v0.2.0 // indirect
k8s.io/klog v0.3.2 // indirect
k8s.io/kube-openapi v0.0.0-20190320154901-5e45bb682580 // indirect
k8s.io/utils v0.0.0-20190308190857-21c4ce38f2a7 // indirect
sigs.k8s.io/controller-runtime v0.1.10

17
go.sum
View File

@ -86,8 +86,6 @@ github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf h1:+RRA9JqSOZFfKrOeqr2z77+8R2RKyh8PG66dcu1V0ck=
github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI=
github.com/google/wire v0.2.2-0.20190423202733-d079521b6f51 h1:oah2osnQk2JwZUU9BasNK201Ea2oNDY/3is4GYtnR7k=
@ -101,9 +99,8 @@ github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gregjones/httpcache v0.0.0-20190212212710-3befbb6ad0cc h1:f8eY6cV/x1x+HLjOp4r72s/31/V2aTUtg5oKRRPf8/Q=
github.com/gregjones/httpcache v0.0.0-20190212212710-3befbb6ad0cc/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
github.com/grpc-ecosystem/grpc-gateway v1.6.2 h1:8KyC64BiO8ndiGHY5DlFWWdangUPC9QHPakFRre/Ud0=
github.com/grpc-ecosystem/grpc-gateway v1.6.2/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw=
github.com/grpc-ecosystem/grpc-gateway v1.8.5 h1:2+KSC78XiO6Qy0hIjfc1OD9H+hsaJdJlb8Kqsd41CTE=
github.com/grpc-ecosystem/grpc-gateway v1.8.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/hashicorp/golang-lru v0.5.0 h1:CL2msUPvZTLb5O648aiLNJw3hnBxN2+1Jq8rCOH9wdo=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
@ -141,9 +138,8 @@ github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180228065516-1df9eeb2bb81 h1:ImOHKpmdLPXWX5KSYquUWXKaopEPuY7TPPUo18u9aOI=
github.com/modern-go/reflect2 v0.0.0-20180228065516-1df9eeb2bb81/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs=
@ -175,7 +171,6 @@ github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y8
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
@ -225,7 +220,6 @@ golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73r
golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@ -252,7 +246,6 @@ golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20180903190138-2b024373dcd9/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181218192612-074acd46bca6/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@ -303,7 +296,6 @@ gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/src-d/go-billy.v4 v4.2.1 h1:omN5CrMrMcQ+4I8bJ0wEhOBPanIRWzFC953IiXKdYzo=
gopkg.in/src-d/go-billy.v4 v4.2.1/go.mod h1:tm33zBoOwxjYHZIE+OV8bxTWFMJLrconzFMd38aARFk=
gopkg.in/src-d/go-git-fixtures.v3 v3.1.1 h1:XWW/s5W18RaJpmo1l0IYGqXKuJITWRFuA45iOf1dKJs=
@ -314,7 +306,6 @@ gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkep
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
@ -334,8 +325,8 @@ k8s.io/client-go v7.0.0+incompatible h1:kiH+Y6hn+pc78QS/mtBfMJAMIIaWevHi++JvOGEE
k8s.io/client-go v7.0.0+incompatible/go.mod h1:7vJpHMYJwNQCWgzmNV+VYUl1zCObLyodBc8nIyt8L5s=
k8s.io/client-go v11.0.0+incompatible h1:LBbX2+lOwY9flffWlJM7f1Ct8V2SRNiMRDFeiwnJo9o=
k8s.io/client-go v11.0.0+incompatible/go.mod h1:7vJpHMYJwNQCWgzmNV+VYUl1zCObLyodBc8nIyt8L5s=
k8s.io/klog v0.2.0 h1:0ElL0OHzF3N+OhoJTL0uca20SxtYt4X4+bzHeqrB83c=
k8s.io/klog v0.2.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
k8s.io/klog v0.3.2 h1:qvP/U6CcZ6qyi/qSHlJKdlAboCzo3mT0DAm0XAarpz4=
k8s.io/klog v0.3.2/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
k8s.io/kube-openapi v0.0.0-20180510204742-b3f03f553288/go.mod h1:BXM9ceUBTj2QnfH2MK1odQs778ajze1RxcmP6S8RVVc=
k8s.io/kube-openapi v0.0.0-20190320154901-5e45bb682580 h1:fq0ZXW/BAIFZH+dazlups6JTVdwzRo5d9riFA103yuQ=
k8s.io/kube-openapi v0.0.0-20190320154901-5e45bb682580/go.mod h1:BXM9ceUBTj2QnfH2MK1odQs778ajze1RxcmP6S8RVVc=

View File

@ -0,0 +1,106 @@
/*
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 unstructured
import (
"fmt"
api_unstructured "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"strings"
)
func jsonPath(fields []string) string {
return "." + strings.Join(fields, ".")
}
// NestedInt returns the int value of a nested field.
// Returns false if value is not found and an error if not an int
func NestedInt(obj map[string]interface{}, fields ...string) (int, bool, error) {
var i int
var i32 int32
var i64 int64
var ok bool
val, found, err := api_unstructured.NestedFieldNoCopy(obj, fields...)
if !found || err != nil {
return 0, found, err
}
i, ok = val.(int)
if !ok {
i32, ok = val.(int32)
if ok {
i = int(i32)
}
}
if !ok {
i64, ok = val.(int64)
if ok {
i = int(i64)
}
}
if !ok {
return 0, true, fmt.Errorf("%v accessor error: %v is of the type %T, expected int", jsonPath(fields), val, val)
}
return i, true, nil
}
// NestedMapSlice returns the value of a nested field.
// Returns false if value is not found and an error if not an slice of maps.
func NestedMapSlice(obj map[string]interface{}, fields ...string) ([]map[string]interface{}, bool, error) {
val, found, err := api_unstructured.NestedFieldNoCopy(obj, fields...)
if !found || err != nil {
return nil, found, err
}
array, ok := val.([]interface{})
if !ok {
return nil, true, fmt.Errorf("%v accessor error: %v is of the type %T, expected []interface{}", jsonPath(fields), val, val)
}
conditions := []map[string]interface{}{}
for i := range array {
entry, ok := array[i].(map[string]interface{})
if !ok {
return nil, true, fmt.Errorf("%v accessor error: %v[%d] is of the type %T, expected map[string]interface{}", jsonPath(fields), i, val, val)
}
conditions = append(conditions, entry)
}
return conditions, true, nil
}
// GetStringField - return field as string defaulting to value if not found
func GetStringField(obj map[string]interface{}, field, defaultValue string) string {
value := defaultValue
fieldV, ok := obj[field]
if ok {
stringV, ok := fieldV.(string)
if ok {
value = stringV
}
}
return value
}
// GetConditions - return conditions array as []map[string]interface{}
func GetConditions(obj map[string]interface{}) []map[string]interface{} {
conditions, ok, err := NestedMapSlice(obj, "status", "conditions")
if err != nil {
fmt.Printf("err: %s", err)
return []map[string]interface{}{}
}
if !ok {
return []map[string]interface{}{}
}
return conditions
}

View File

@ -0,0 +1,118 @@
/*
Copyright 2019 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 unstructured_test
import (
"testing"
"github.com/stretchr/testify/assert"
helperu "sigs.k8s.io/cli-experimental/internal/pkg/client/unstructured"
)
var emptyObj = map[string]interface{}{}
var testObj = map[string]interface{}{
"f1": map[string]interface{}{
"f1f2": map[string]interface{}{
"f1f2i32": int32(32),
"f1f2i64": int64(64),
"f1f2float": 64.02,
"f1f2ms": []interface{}{
map[string]interface{}{"f1f2ms0f1": 22},
map[string]interface{}{"f1f2ms1f1": "index1"},
},
"f1f2msbad": []interface{}{
map[string]interface{}{"f1f2ms0f1": 22},
32,
},
},
},
"f2": map[string]interface{}{
"f2f2": map[string]interface{}{},
},
"ride": "dragon",
"status": map[string]interface{}{
"conditions": []interface{}{
map[string]interface{}{"f1f2ms0f1": 22},
map[string]interface{}{"f1f2ms1f1": "index1"},
},
},
}
func TestNestedInt(t *testing.T) {
v, found, err := helperu.NestedInt(testObj, "f1", "f1f2", "f1f2i32")
assert.NoError(t, err)
assert.Equal(t, found, true)
assert.Equal(t, int(32), v)
v, found, err = helperu.NestedInt(testObj, "f1", "f1f2", "wrongname")
assert.NoError(t, err)
assert.Equal(t, found, false)
assert.Equal(t, int(0), v)
v, found, err = helperu.NestedInt(testObj, "f1", "f1f2", "f1f2i64")
assert.NoError(t, err)
assert.Equal(t, found, true)
assert.Equal(t, int(64), v)
v, found, err = helperu.NestedInt(testObj, "f1", "f1f2", "f1f2float")
assert.Error(t, err)
assert.Equal(t, found, true)
assert.Equal(t, int(0), v)
}
func TestGetStringField(t *testing.T) {
v := helperu.GetStringField(testObj, "ride", "horse")
assert.Equal(t, v, "dragon")
v = helperu.GetStringField(testObj, "destination", "north")
assert.Equal(t, v, "north")
}
func TestNestedMapSlice(t *testing.T) {
v, found, err := helperu.NestedMapSlice(testObj, "f1", "f1f2", "f1f2ms")
assert.NoError(t, err)
assert.Equal(t, found, true)
assert.Equal(t, []map[string]interface{}{
map[string]interface{}{"f1f2ms0f1": 22},
map[string]interface{}{"f1f2ms1f1": "index1"},
}, v)
v, found, err = helperu.NestedMapSlice(testObj, "f1", "f1f2", "f1f2msbad")
assert.Error(t, err)
assert.Equal(t, found, true)
assert.Equal(t, []map[string]interface{}(nil), v)
v, found, err = helperu.NestedMapSlice(testObj, "f1", "f1f2", "wrongname")
assert.NoError(t, err)
assert.Equal(t, found, false)
assert.Equal(t, []map[string]interface{}(nil), v)
v, found, err = helperu.NestedMapSlice(testObj, "f1", "f1f2", "f1f2i64")
assert.Error(t, err)
assert.Equal(t, found, true)
assert.Equal(t, []map[string]interface{}(nil), v)
}
func TestGetConditions(t *testing.T) {
v := helperu.GetConditions(emptyObj)
assert.Equal(t, []map[string]interface{}{}, v)
v = helperu.GetConditions(testObj)
assert.Equal(t, []map[string]interface{}{
map[string]interface{}{"f1f2ms0f1": 22},
map[string]interface{}{"f1f2ms1f1": "index1"},
}, v)
}

View File

@ -0,0 +1,18 @@
/*
Copyright 2019 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 status
/*
Status
*/

View File

@ -0,0 +1,34 @@
/*
Copyright 2019 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 status
import (
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
clientu "sigs.k8s.io/cli-experimental/internal/pkg/client/unstructured"
)
func readyConditionReader(u *unstructured.Unstructured) (bool, error) {
conditions := clientu.GetConditions(u.UnstructuredContent())
for _, c := range conditions {
if clientu.GetStringField(c, "type", "") == "Ready" && clientu.GetStringField(c, "status", "") == "False" {
return false, nil
}
}
return true, nil
}
// GetGenericReadyFn - True if we handle it as a known type
func GetGenericReadyFn(u *unstructured.Unstructured) IsReadyFn {
return readyConditionReader
}

View File

@ -0,0 +1,238 @@
/*
Copyright 2019 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 status
import (
"fmt"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
clientu "sigs.k8s.io/cli-experimental/internal/pkg/client/unstructured"
)
// IsReadyFn - status getter
type IsReadyFn func(*unstructured.Unstructured) (bool, error)
var legacyTypes = map[string]map[string]IsReadyFn{
"": map[string]IsReadyFn{
"Service": alwaysReady,
"Pod": podReady,
"PersistentVolumeClaim": pvcReady,
},
"apps": map[string]IsReadyFn{
"StatefulSet": stsReady,
"DaemonSet": daemonsetReady,
"Deployment": deploymentReady,
"ReplicaSet": replicasetReady,
},
"policy": map[string]IsReadyFn{
"PodDisruptionBudget": pdbReady,
},
"batch": map[string]IsReadyFn{
"CronJob": cronjobReady,
"Job": jobReady,
},
}
// GetLegacyReadyFn - True if we handle it as a known type
func GetLegacyReadyFn(u *unstructured.Unstructured) IsReadyFn {
gvk := u.GroupVersionKind()
g := gvk.Group
k := gvk.Kind
if _, ok := legacyTypes[g]; ok {
if fn, ok := legacyTypes[g][k]; ok {
return fn
}
}
return nil
}
func alwaysReady(u *unstructured.Unstructured) (bool, error) { return true, nil }
func compareIntFields(u *unstructured.Unstructured, field1, field2 []string, checkFuncs ...func(int, int) bool) (bool, error) {
v1, ok, err := clientu.NestedInt(u.UnstructuredContent(), field1...)
if err != nil {
return true, err
}
if !ok {
return false, fmt.Errorf("%v not found", field1)
}
v2, ok, err := clientu.NestedInt(u.UnstructuredContent(), field2...)
if err != nil {
return true, err
}
if !ok {
return false, fmt.Errorf("%v not found", field2)
}
rv := true
for _, fn := range checkFuncs {
rv = rv && fn(v1, v2)
}
return rv, nil
}
func equalInt(v1, v2 int) bool { return v1 == v2 }
func geInt(v1, v2 int) bool { return v1 >= v2 }
// Statefulset
func stsReady(u *unstructured.Unstructured) (bool, error) {
c1, err := compareIntFields(u, []string{"status", "readyReplicas"}, []string{"spec", "replicas"}, equalInt)
if err != nil {
return c1, err
}
c2, err := compareIntFields(u, []string{"status", "currentReplicas"}, []string{"spec", "replicas"}, equalInt)
if err != nil {
return c2, err
}
return c1 && c2, nil
}
// Deployment
func deploymentReady(u *unstructured.Unstructured) (bool, error) {
progress := true
available := true
conditions := clientu.GetConditions(u.UnstructuredContent())
if len(conditions) == 0 {
return false, fmt.Errorf("no conditions in object")
}
for _, c := range conditions {
switch clientu.GetStringField(c, "type", "") {
case "Progressing": //appsv1.DeploymentProgressing:
// https://github.com/kubernetes/kubernetes/blob/a3ccea9d8743f2ff82e41b6c2af6dc2c41dc7b10/pkg/controller/deployment/progress.go#L52
status := clientu.GetStringField(c, "status", "")
reason := clientu.GetStringField(c, "reason", "")
if status != "True" || reason != "NewReplicaSetAvailable" {
progress = false
}
case "Available": //appsv1.DeploymentAvailable:
status := clientu.GetStringField(c, "status", "")
if status == "False" {
available = false
}
}
}
return progress && available, nil
}
// Replicaset
func replicasetReady(u *unstructured.Unstructured) (bool, error) {
failure := false
conditions := clientu.GetConditions(u.UnstructuredContent())
for _, c := range conditions {
switch clientu.GetStringField(c, "type", "") {
// https://github.com/kubernetes/kubernetes/blob/a3ccea9d8743f2ff82e41b6c2af6dc2c41dc7b10/pkg/controller/replicaset/replica_set_utils.go
case "ReplicaFailure": //appsv1.ReplicaSetReplicaFailure
status := clientu.GetStringField(c, "status", "")
if status == "True" {
failure = true
break
}
}
}
c1, err := compareIntFields(u, []string{"status", "replicas"}, []string{"status", "readyReplicas"}, equalInt)
if err != nil {
return c1, err
}
c2, err := compareIntFields(u, []string{"status", "replicas"}, []string{"status", "availableReplicas"}, equalInt)
if err != nil {
return c2, err
}
return !failure && c1 && c2, nil
}
// Daemonset
func daemonsetReady(u *unstructured.Unstructured) (bool, error) {
c1, err := compareIntFields(u, []string{"status", "desiredNumberScheduled"}, []string{"status", "numberAvailable"}, equalInt)
if err != nil {
return c1, err
}
c2, err := compareIntFields(u, []string{"status", "desiredNumberScheduled"}, []string{"status", "numberReady"}, equalInt)
if err != nil {
return c2, err
}
return c1 && c2, nil
}
// PVC
func pvcReady(u *unstructured.Unstructured) (bool, error) {
val, found, err := unstructured.NestedString(u.UnstructuredContent(), "status", "phase")
if err != nil {
return false, err
}
if !found {
return false, fmt.Errorf(".status.phase not found")
}
return val == "Bound", nil // corev1.ClaimBound
}
// Pod
func podReady(u *unstructured.Unstructured) (bool, error) {
conditions := clientu.GetConditions(u.UnstructuredContent())
for _, c := range conditions {
if clientu.GetStringField(c, "type", "") == "Ready" && (clientu.GetStringField(c, "status", "") == "True" || clientu.GetStringField(c, "reason", "") == "PodCompleted") {
return true, nil
}
}
return false, nil
}
// PodDisruptionBudget
func pdbReady(u *unstructured.Unstructured) (bool, error) {
return compareIntFields(u, []string{"status", "currentHealthy"}, []string{"status", "desiredHealthy"}, geInt)
}
// Cronjob
func cronjobReady(u *unstructured.Unstructured) (bool, error) {
obj := u.UnstructuredContent()
_, ok := obj["status"]
if !ok {
return false, nil
}
return true, nil
}
// Job
func jobReady(u *unstructured.Unstructured) (bool, error) {
complete := false
failed := false
conditions := clientu.GetConditions(u.UnstructuredContent())
// https://github.com/kubernetes/kubernetes/blob/master/pkg/controller/job/utils.go#L24
for _, c := range conditions {
status := clientu.GetStringField(c, "status", "")
switch clientu.GetStringField(c, "type", "") {
case "Complete":
if status == "True" {
complete = true
}
case "Failed":
if status == "True" {
failed = true
}
}
}
return complete || failed, nil
}

View File

@ -14,41 +14,92 @@ limitations under the License.
package status
import (
"context"
"fmt"
"io"
"gopkg.in/src-d/go-git.v4/plumbing/object"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/errors"
"sigs.k8s.io/cli-experimental/internal/pkg/client"
"sigs.k8s.io/cli-experimental/internal/pkg/clik8s"
)
// Status returns the status for rollouts
type Status struct {
// DynamicClient is the client used to talk
// with the cluster
DynamicClient client.Client
// Out stores the output
Out io.Writer
// Resources is a list of resource configurations
Resources clik8s.ResourceConfigs
Out io.Writer
Clientset *kubernetes.Clientset
Commit *object.Commit
// Commit is a git commit object
Commit *object.Commit
}
// ResourceStatus - resource status
type ResourceStatus struct {
Resource *unstructured.Unstructured
Status string
Error error
}
// Result contains the Status Result
type Result struct {
Resources clik8s.ResourceConfigs
Ready bool
Resources []ResourceStatus
}
// Do executes the apply
func (s *Status) Do() (Result, error) {
fmt.Fprintf(s.Out, "Doing `cli-experimental apply status`\n")
if s.Commit != nil {
fmt.Fprintf(s.Out, "Commit %s\n", s.Commit.Hash.String())
}
pods, err := s.Clientset.CoreV1().Pods("default").List(metav1.ListOptions{})
if err != nil {
return Result{}, err
}
for _, p := range pods.Items {
fmt.Fprintf(s.Out, "Pod %s\n", p.Name)
// Do executes the status
func (a *Status) Do() (Result, error) {
ready := true
var errs []error
var rs = []ResourceStatus{}
fmt.Fprintf(a.Out, "Doing `cli-experimental apply status`\n")
ctx := context.Background()
for _, u := range a.Resources {
err := a.DynamicClient.Get(ctx,
types.NamespacedName{Namespace: u.GetNamespace(), Name: u.GetName()}, u)
if err != nil {
rs = append(rs, ResourceStatus{Resource: u, Status: "GET_ERROR", Error: err})
errs = append(errs, err)
continue
}
// Ready indicator is a simple ANDing of all the individual resource readiness
uReady, err := IsReady(u)
if err != nil {
rs = append(rs, ResourceStatus{Resource: u, Status: "ERROR", Error: err})
errs = append(errs, err)
continue
}
status := "Ready"
if !ready {
status = "InProgress"
}
rs = append(rs, ResourceStatus{Resource: u, Status: status, Error: nil})
ready = ready && uReady
}
return Result{Resources: s.Resources}, nil
if len(errs) != 0 {
return Result{Ready: ready, Resources: rs}, errors.NewAggregate(errs)
}
return Result{Ready: ready, Resources: rs}, nil
}
// IsReady - return true if object is ready
func IsReady(u *unstructured.Unstructured) (bool, error) {
fn := GetLegacyReadyFn(u)
if fn == nil {
fn = GetGenericReadyFn(u)
}
if fn != nil {
return fn(u)
}
return true, nil
}

View File

@ -17,19 +17,663 @@ import (
"bytes"
"testing"
"github.com/ghodss/yaml"
"github.com/stretchr/testify/assert"
"gopkg.in/src-d/go-git.v4/plumbing/object"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"sigs.k8s.io/cli-experimental/internal/pkg/clik8s"
"sigs.k8s.io/cli-experimental/internal/pkg/status"
"sigs.k8s.io/cli-experimental/internal/pkg/wirecli/wiretest"
)
func TestStatus(t *testing.T) {
func noitems() clik8s.ResourceConfigs {
return clik8s.ResourceConfigs(nil)
}
func y2u(t *testing.T, spec string) *unstructured.Unstructured {
j, err := yaml.YAMLToJSON([]byte(spec))
assert.NoError(t, err)
u, _, err := unstructured.UnstructuredJSONScheme.Decode(j, nil, nil)
assert.NoError(t, err)
return u.(*unstructured.Unstructured)
}
func TestEmptyStatus(t *testing.T) {
buf := new(bytes.Buffer)
a, done, err := wiretest.InitializeStatus(clik8s.ResourceConfigs(nil), &object.Commit{}, buf)
a, done, err := wiretest.InitializeStatus(noitems(), &object.Commit{}, buf)
defer done()
assert.NoError(t, err)
r, err := a.Do()
assert.NoError(t, err)
assert.Equal(t, status.Result{}, r)
assert.Equal(t, status.Result{Ready: true, Resources: []status.ResourceStatus{}}, r)
}
var podNoStatus = `
apiVersion: v1
kind: Pod
metadata:
name: test
`
var podReady = `
apiVersion: v1
kind: Pod
metadata:
name: test
namespace: qual
status:
conditions:
- type: Ready
status: "True"
`
var podCompletedOK = `
apiVersion: v1
kind: Pod
metadata:
name: test
namespace: qual
status:
phase: Succeeded
conditions:
- type: Ready
status: "False"
reason: PodCompleted
`
var podCompletedFail = `
apiVersion: v1
kind: Pod
metadata:
name: test
namespace: qual
status:
phase: Failed
conditions:
- type: Ready
status: "False"
reason: PodCompleted
`
// Test coverage using IsReady
func TestPodStatus(t *testing.T) {
r, err := status.IsReady(y2u(t, podNoStatus))
assert.NoError(t, err)
assert.Equal(t, false, r)
for _, spec := range []string{podReady, podCompletedOK, podCompletedFail} {
r, err = status.IsReady(y2u(t, spec))
assert.NoError(t, err)
assert.Equal(t, true, r)
}
}
var pvcNoStatus = `
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: test
`
var pvcBound = `
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: test
namespace: qual
status:
phase: Bound
`
var pvcUnBound = `
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: test
namespace: qual
status:
phase: UnBound
`
func TestPVCStatus(t *testing.T) {
r, err := status.IsReady(y2u(t, pvcNoStatus))
assert.Error(t, err)
assert.Equal(t, false, r)
r, err = status.IsReady(y2u(t, pvcBound))
assert.NoError(t, err)
assert.Equal(t, true, r)
r, err = status.IsReady(y2u(t, pvcUnBound))
assert.NoError(t, err)
assert.Equal(t, false, r)
}
var stsNoStatus = `
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: test
`
var stsBadStatus = `
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: test
namespace: qual
status:
currentReplicas: 1
`
var stsOK = `
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: test
namespace: qual
spec:
replicas: 4
status:
currentReplicas: 4
readyReplicas: 4
`
var stsLessReady = `
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: test
namespace: qual
spec:
replicas: 4
status:
currentReplicas: 4
readyReplicas: 2
`
var stsLessCurrent = `
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: test
namespace: qual
spec:
replicas: 4
status:
currentReplicas: 2
readyReplicas: 4
`
func TestStsStatus(t *testing.T) {
r, err := status.IsReady(y2u(t, stsNoStatus))
assert.Error(t, err)
assert.Equal(t, false, r)
r, err = status.IsReady(y2u(t, stsBadStatus))
assert.Error(t, err)
assert.Equal(t, false, r)
r, err = status.IsReady(y2u(t, stsOK))
assert.NoError(t, err)
assert.Equal(t, true, r)
r, err = status.IsReady(y2u(t, stsLessReady))
assert.NoError(t, err)
assert.Equal(t, false, r)
r, err = status.IsReady(y2u(t, stsLessCurrent))
assert.NoError(t, err)
assert.Equal(t, false, r)
}
var dsNoStatus = `
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: test
`
var dsBadStatus = `
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: test
namespace: qual
status:
currentReplicas: 1
`
var dsOK = `
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: test
namespace: qual
status:
desiredNumberScheduled: 4
numberAvailable: 4
numberReady: 4
`
var dsLessReady = `
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: test
namespace: qual
status:
desiredNumberScheduled: 4
numberAvailable: 4
numberReady: 2
`
var dsLessAvailable = `
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: test
namespace: qual
status:
desiredNumberScheduled: 4
numberAvailable: 2
numberReady: 4
`
func TestDaemonsetStatus(t *testing.T) {
r, err := status.IsReady(y2u(t, dsNoStatus))
assert.Error(t, err)
assert.Equal(t, false, r)
r, err = status.IsReady(y2u(t, dsBadStatus))
assert.Error(t, err)
assert.Equal(t, false, r)
r, err = status.IsReady(y2u(t, dsOK))
assert.NoError(t, err)
assert.Equal(t, true, r)
r, err = status.IsReady(y2u(t, dsLessReady))
assert.NoError(t, err)
assert.Equal(t, false, r)
r, err = status.IsReady(y2u(t, dsLessAvailable))
assert.NoError(t, err)
assert.Equal(t, false, r)
}
var depNoStatus = `
apiVersion: apps/v1
kind: Deployment
metadata:
name: test
`
var depOK = `
apiVersion: apps/v1
kind: Deployment
metadata:
name: test
namespace: qual
status:
conditions:
- type: Progressing
status: "True"
reason: NewReplicaSetAvailable
- type: Available
status: "True"
`
var depNotProgressing = `
apiVersion: apps/v1
kind: Deployment
metadata:
name: test
namespace: qual
status:
conditions:
- type: Progressing
status: "False"
reason: Some reason
- type: Available
status: "True"
`
var depNotAvailable = `
apiVersion: apps/v1
kind: Deployment
metadata:
name: test
namespace: qual
status:
conditions:
- type: Progressing
status: "True"
reason: NewReplicaSetAvailable
- type: Available
status: "False"
`
func TestDeploymentStatus(t *testing.T) {
r, err := status.IsReady(y2u(t, depNoStatus))
assert.Error(t, err)
assert.Equal(t, false, r)
r, err = status.IsReady(y2u(t, depOK))
assert.NoError(t, err)
assert.Equal(t, true, r)
r, err = status.IsReady(y2u(t, depNotProgressing))
assert.NoError(t, err)
assert.Equal(t, false, r)
r, err = status.IsReady(y2u(t, depNotAvailable))
assert.NoError(t, err)
assert.Equal(t, false, r)
}
var rsNoStatus = `
apiVersion: apps/v1
kind: ReplicaSet
metadata:
name: test
`
var rsOK1 = `
apiVersion: apps/v1
kind: ReplicaSet
metadata:
name: test
namespace: qual
status:
replicas: 2
readyReplicas: 2
availableReplicas: 2
conditions:
- type: ReplicaFailure
status: "False"
`
var rsOK2 = `
apiVersion: apps/v1
kind: ReplicaSet
metadata:
name: test
namespace: qual
status:
replicas: 2
readyReplicas: 2
availableReplicas: 2
`
var rsLessReady = `
apiVersion: apps/v1
kind: ReplicaSet
metadata:
name: test
namespace: qual
status:
replicas: 4
readyReplicas: 2
availableReplicas: 4
`
var rsLessAvailable = `
apiVersion: apps/v1
kind: ReplicaSet
metadata:
name: test
namespace: qual
status:
replicas: 4
readyReplicas: 4
availableReplicas: 2
`
var rsReplicaFailure = `
apiVersion: apps/v1
kind: ReplicaSet
metadata:
name: test
namespace: qual
status:
replicas: 4
readyReplicas: 4
availableReplicas: 4
conditions:
- type: ReplicaFailure
status: "True"
`
func TestReplicasetStatus(t *testing.T) {
r, err := status.IsReady(y2u(t, rsNoStatus))
assert.Error(t, err)
assert.Equal(t, false, r)
r, err = status.IsReady(y2u(t, rsOK1))
assert.NoError(t, err)
assert.Equal(t, true, r)
r, err = status.IsReady(y2u(t, rsOK2))
assert.NoError(t, err)
assert.Equal(t, true, r)
r, err = status.IsReady(y2u(t, rsLessAvailable))
assert.NoError(t, err)
assert.Equal(t, false, r)
r, err = status.IsReady(y2u(t, rsLessReady))
assert.NoError(t, err)
assert.Equal(t, false, r)
r, err = status.IsReady(y2u(t, rsReplicaFailure))
assert.NoError(t, err)
assert.Equal(t, false, r)
}
var pdbNoStatus = `
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: test
`
var pdbOK1 = `
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: test
namespace: qual
status:
currentHealthy: 2
desiredHealthy: 2
`
var pdbMoreHealthy = `
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: test
namespace: qual
status:
currentHealthy: 4
desiredHealthy: 2
`
var pdbLessHealthy = `
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: test
namespace: qual
status:
currentHealthy: 2
desiredHealthy: 4
`
func TestPDBStatus(t *testing.T) {
r, err := status.IsReady(y2u(t, pdbNoStatus))
assert.Error(t, err)
assert.Equal(t, false, r)
r, err = status.IsReady(y2u(t, pdbOK1))
assert.NoError(t, err)
assert.Equal(t, true, r)
r, err = status.IsReady(y2u(t, pdbMoreHealthy))
assert.NoError(t, err)
assert.Equal(t, true, r)
r, err = status.IsReady(y2u(t, pdbLessHealthy))
assert.NoError(t, err)
assert.Equal(t, false, r)
}
var crdNoStatus = `
apiVersion: something/v1
kind: MyCR
metadata:
name: test
namespace: qual
`
var crdReady = `
apiVersion: something/v1
kind: MyCR
metadata:
name: test
namespace: qual
status:
conditions:
- type: Ready
status: "True"
`
var crdNotReady = `
apiVersion: something/v1
kind: MyCR
metadata:
name: test
namespace: qual
status:
conditions:
- type: Ready
status: "False"
`
var crdNoCondition = `
apiVersion: something/v1
kind: MyCR
metadata:
name: test
namespace: qual
status:
conditions:
- type: SomeCondition
status: "False"
`
func TestCRDGenericStatus(t *testing.T) {
r, err := status.IsReady(y2u(t, crdNoStatus))
assert.NoError(t, err)
assert.Equal(t, true, r)
r, err = status.IsReady(y2u(t, crdReady))
assert.NoError(t, err)
assert.Equal(t, true, r)
r, err = status.IsReady(y2u(t, crdNotReady))
assert.NoError(t, err)
assert.Equal(t, false, r)
r, err = status.IsReady(y2u(t, crdNoCondition))
assert.NoError(t, err)
assert.Equal(t, true, r)
}
var jobNoStatus = `
apiVersion: batch/v1
kind: Job
metadata:
name: test
namespace: qual
`
var jobComplete = `
apiVersion: batch/v1
kind: Job
metadata:
name: test
namespace: qual
status:
conditions:
- type: Complete
status: "True"
`
var jobFailed = `
apiVersion: batch/v1
kind: Job
metadata:
name: test
namespace: qual
status:
conditions:
- type: Failed
status: "True"
`
var jobInProgress = `
apiVersion: batch/v1
kind: Job
metadata:
name: test
namespace: qual
status:
conditions:
- type: Failed
status: "False"
- type: Complete
status: "False"
`
func TestJobStatus(t *testing.T) {
r, err := status.IsReady(y2u(t, jobNoStatus))
assert.NoError(t, err)
assert.Equal(t, false, r)
r, err = status.IsReady(y2u(t, jobComplete))
assert.NoError(t, err)
assert.Equal(t, true, r)
r, err = status.IsReady(y2u(t, jobFailed))
assert.NoError(t, err)
assert.Equal(t, true, r)
r, err = status.IsReady(y2u(t, jobInProgress))
assert.NoError(t, err)
assert.Equal(t, false, r)
}
var cronjobNoStatus = `
apiVersion: batch/v1
kind: CronJob
metadata:
name: test
namespace: qual
`
var cronjobWithStatus = `
apiVersion: batch/v1
kind: CronJob
metadata:
name: test
namespace: qual
status:
`
func TestCronJobStatus(t *testing.T) {
r, err := status.IsReady(y2u(t, cronjobNoStatus))
assert.NoError(t, err)
assert.Equal(t, false, r)
r, err = status.IsReady(y2u(t, cronjobWithStatus))
assert.NoError(t, err)
assert.Equal(t, true, r)
}

View File

@ -18,6 +18,26 @@ import (
// Injectors from wire.go:
func InitializeStatus(resourceConfigPath clik8s.ResourceConfigPath, writer io.Writer, args util.Args) (*status.Status, error) {
configFlags, err := wirek8s.NewConfigFlags(args)
if err != nil {
return nil, err
}
config, err := wirek8s.NewRestConfig(configFlags)
if err != nil {
return nil, err
}
dynamicInterface, err := wirek8s.NewDynamicClient(config)
if err != nil {
return nil, err
}
restMapper, err := wirek8s.NewRestMapper(config)
if err != nil {
return nil, err
}
client, err := wirek8s.NewClient(dynamicInterface, restMapper)
if err != nil {
return nil, err
}
pluginConfig := wireconfig.NewPluginConfig()
factory := wireconfig.NewResMapFactory(pluginConfig)
fileSystem := wireconfig.NewFileSystem()
@ -27,31 +47,39 @@ func InitializeStatus(resourceConfigPath clik8s.ResourceConfigPath, writer io.Wr
if err != nil {
return nil, err
}
configFlags, err := wirek8s.NewConfigFlags(args)
if err != nil {
return nil, err
}
config, err := wirek8s.NewRestConfig(configFlags)
if err != nil {
return nil, err
}
clientset, err := wirek8s.NewKubernetesClientSet(config)
if err != nil {
return nil, err
}
repository := wiregit.NewOptionalRepository(resourceConfigPath)
commitIter := wiregit.NewOptionalCommitIter(repository)
commit := wiregit.NewOptionalCommit(commitIter)
statusStatus := &status.Status{
Resources: resourceConfigs,
Out: writer,
Clientset: clientset,
Commit: commit,
DynamicClient: client,
Out: writer,
Resources: resourceConfigs,
Commit: commit,
}
return statusStatus, nil
}
func DoStatus(resourceConfigPath clik8s.ResourceConfigPath, writer io.Writer, args util.Args) (status.Result, error) {
configFlags, err := wirek8s.NewConfigFlags(args)
if err != nil {
return status.Result{}, err
}
config, err := wirek8s.NewRestConfig(configFlags)
if err != nil {
return status.Result{}, err
}
dynamicInterface, err := wirek8s.NewDynamicClient(config)
if err != nil {
return status.Result{}, err
}
restMapper, err := wirek8s.NewRestMapper(config)
if err != nil {
return status.Result{}, err
}
client, err := wirek8s.NewClient(dynamicInterface, restMapper)
if err != nil {
return status.Result{}, err
}
pluginConfig := wireconfig.NewPluginConfig()
factory := wireconfig.NewResMapFactory(pluginConfig)
fileSystem := wireconfig.NewFileSystem()
@ -61,26 +89,14 @@ func DoStatus(resourceConfigPath clik8s.ResourceConfigPath, writer io.Writer, ar
if err != nil {
return status.Result{}, err
}
configFlags, err := wirek8s.NewConfigFlags(args)
if err != nil {
return status.Result{}, err
}
config, err := wirek8s.NewRestConfig(configFlags)
if err != nil {
return status.Result{}, err
}
clientset, err := wirek8s.NewKubernetesClientSet(config)
if err != nil {
return status.Result{}, err
}
repository := wiregit.NewOptionalRepository(resourceConfigPath)
commitIter := wiregit.NewOptionalCommitIter(repository)
commit := wiregit.NewOptionalCommit(commitIter)
statusStatus := &status.Status{
Resources: resourceConfigs,
Out: writer,
Clientset: clientset,
Commit: commit,
DynamicClient: client,
Out: writer,
Resources: resourceConfigs,
Commit: commit,
}
result, err := NewStatusCommandResult(statusStatus, writer)
if err != nil {

View File

@ -33,16 +33,26 @@ func InitializeStatus(resourceConfigs clik8s.ResourceConfigs, commit *object.Com
if err != nil {
return nil, nil, err
}
clientset, err := wirek8s.NewKubernetesClientSet(config)
dynamicInterface, err := wirek8s.NewDynamicClient(config)
if err != nil {
cleanup()
return nil, nil, err
}
restMapper, err := wirek8s.NewRestMapper(config)
if err != nil {
cleanup()
return nil, nil, err
}
client, err := wirek8s.NewClient(dynamicInterface, restMapper)
if err != nil {
cleanup()
return nil, nil, err
}
statusStatus := &status.Status{
Resources: resourceConfigs,
Out: writer,
Clientset: clientset,
Commit: commit,
DynamicClient: client,
Out: writer,
Resources: resourceConfigs,
Commit: commit,
}
return statusStatus, func() {
cleanup()

View File

@ -19,15 +19,17 @@ import (
"sigs.k8s.io/cli-experimental/internal/pkg/delete"
"sigs.k8s.io/cli-experimental/internal/pkg/prune"
"sigs.k8s.io/cli-experimental/internal/pkg/resourceconfig"
"sigs.k8s.io/cli-experimental/internal/pkg/status"
)
// Cmd is a wrapper for different structs:
// apply, prune and delete
// These structs share the same client
type Cmd struct {
Applier *apply.Apply
Pruner *prune.Prune
Deleter *delete.Delete
Applier *apply.Apply
Pruner *prune.Prune
Deleter *delete.Delete
StatusGetter *status.Status
}
// Apply applies resources given the input as a slice of unstructured resources
@ -54,3 +56,10 @@ func (a *Cmd) Delete(resources []*unstructured.Unstructured) error {
_, err := a.Deleter.Do()
return err
}
// Status returns the status given the input as a slice of unstructured resources
func (a *Cmd) Status(resources []*unstructured.Unstructured) error {
a.StatusGetter.Resources = resources
_, err := a.StatusGetter.Do()
return err
}

View File

@ -61,8 +61,7 @@ func setupResourcesV1() []*unstructured.Unstructured {
r2.SetName("inventory")
r2.SetNamespace("default")
r2.SetAnnotations(map[string]string{
inventory.InventoryAnnotation:
"{\"current\":{\"~G_v1_ConfigMap|default|cm1\":null}}",
inventory.InventoryAnnotation: "{\"current\":{\"~G_v1_ConfigMap|default|cm1\":null}}",
inventory.InventoryHashAnnotation: "1234567",
})
return []*unstructured.Unstructured{r1, r2}
@ -88,8 +87,7 @@ func setupResourcesV2() []*unstructured.Unstructured {
r2.SetName("inventory")
r2.SetNamespace("default")
r2.SetAnnotations(map[string]string{
inventory.InventoryAnnotation:
"{\"current\":{\"~G_v1_ConfigMap|default|cm2\":null}}",
inventory.InventoryAnnotation: "{\"current\":{\"~G_v1_ConfigMap|default|cm2\":null}}",
inventory.InventoryHashAnnotation: "7654321",
})
return []*unstructured.Unstructured{r1, r2}
@ -157,6 +155,12 @@ func TestCmd(t *testing.T) {
assert.NoError(t, err)
assert.Equal(t, len(cmList.Items), 2)
err = cmd.Status(resources)
assert.NoError(t, err)
err = c.List(context.Background(), cmList, "default", nil)
assert.NoError(t, err)
assert.Equal(t, len(cmList.Items), 2)
err = cmd.Delete(resources)
assert.NoError(t, err)
err = c.List(context.Background(), cmList, "default", nil)

View File

@ -10,6 +10,7 @@ import (
"sigs.k8s.io/cli-experimental/internal/pkg/apply"
delete2 "sigs.k8s.io/cli-experimental/internal/pkg/delete"
"sigs.k8s.io/cli-experimental/internal/pkg/prune"
"sigs.k8s.io/cli-experimental/internal/pkg/status"
"sigs.k8s.io/cli-experimental/internal/pkg/util"
"sigs.k8s.io/cli-experimental/internal/pkg/wirecli/wirek8s"
"sigs.k8s.io/cli-experimental/internal/pkg/wirecli/wiretest"
@ -50,10 +51,15 @@ func InitializeCmd(writer io.Writer, args util.Args) (*Cmd, error) {
DynamicClient: client,
Out: writer,
}
statusStatus := &status.Status{
DynamicClient: client,
Out: writer,
}
cmd := &Cmd{
Applier: applyApply,
Pruner: prunePrune,
Deleter: deleteDelete,
Applier: applyApply,
Pruner: prunePrune,
Deleter: deleteDelete,
StatusGetter: statusStatus,
}
return cmd, nil
}
@ -90,10 +96,15 @@ func InitializeFakeCmd(writer io.Writer, args util.Args) (*Cmd, func(), error) {
DynamicClient: client,
Out: writer,
}
statusStatus := &status.Status{
DynamicClient: client,
Out: writer,
}
cmd := &Cmd{
Applier: applyApply,
Pruner: prunePrune,
Deleter: deleteDelete,
Applier: applyApply,
Pruner: prunePrune,
Deleter: deleteDelete,
StatusGetter: statusStatus,
}
return cmd, func() {
cleanup()

View File

@ -18,6 +18,7 @@ import (
"sigs.k8s.io/cli-experimental/internal/pkg/apply"
"sigs.k8s.io/cli-experimental/internal/pkg/delete"
"sigs.k8s.io/cli-experimental/internal/pkg/prune"
"sigs.k8s.io/cli-experimental/internal/pkg/status"
"sigs.k8s.io/cli-experimental/internal/pkg/wirecli/wirek8s"
"sigs.k8s.io/cli-experimental/internal/pkg/wirecli/wiretest"
)
@ -27,6 +28,7 @@ var ProviderSet = wire.NewSet(
wire.Struct(new(apply.Apply), "DynamicClient", "Out"),
wire.Struct(new(prune.Prune), "DynamicClient", "Out"),
wire.Struct(new(delete.Delete), "DynamicClient", "Out"),
wire.Struct(new(status.Status), "DynamicClient", "Out"),
wire.Struct(new(Cmd), "*"),
wirek8s.ProviderSet,
)
@ -36,6 +38,7 @@ var ProviderSetForTesting = wire.NewSet(
wiretest.NewRestConfig, wirek8s.NewClient, wirek8s.NewRestMapper,
wire.Struct(new(apply.Apply), "DynamicClient", "Out"),
wire.Struct(new(prune.Prune), "DynamicClient", "Out"),
wire.Struct(new(status.Status), "DynamicClient", "Out"),
wire.Struct(new(delete.Delete), "DynamicClient", "Out"),
wire.Struct(new(Cmd), "*"),
)