mirror of https://github.com/knative/client.git
Find sink and ready conditions for untyped source (#911)
* Find sink and ready conditions for untyped source Fixes #909 - Look up spec.sink field in untyped source unstructured object and try to convert it to duck Destination object - Look up status.conditions field in untyped source unstructured object and try to convert it to duck Conditions object - Upon not finding the data at expected fields, return "<unknown>" * Add unit tests * Update LICENSE header
This commit is contained in:
parent
875247a8b1
commit
4b62f2008b
|
|
@ -15,6 +15,7 @@
|
|||
package duck
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
|
|
@ -24,9 +25,10 @@ import (
|
|||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
sourcesv1alpha2 "knative.dev/eventing/pkg/apis/sources/v1alpha2"
|
||||
duck "knative.dev/pkg/apis/duck"
|
||||
duckv1 "knative.dev/pkg/apis/duck/v1"
|
||||
|
||||
"knative.dev/client/pkg/kn/commands"
|
||||
"knative.dev/client/pkg/kn/commands/flags"
|
||||
knflags "knative.dev/client/pkg/kn/commands/flags"
|
||||
)
|
||||
|
||||
// Source struct holds common properties between different eventing sources
|
||||
|
|
@ -103,25 +105,77 @@ func getSourceTypeName(source *unstructured.Unstructured) string {
|
|||
)
|
||||
}
|
||||
|
||||
func sinkFromUnstructured(u *unstructured.Unstructured) (*duckv1.Destination, error) {
|
||||
content := u.UnstructuredContent()
|
||||
sink, found, err := unstructured.NestedFieldCopy(content, "spec", "sink")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cant find sink in given unstructured object at spec.sink field: %v", err)
|
||||
}
|
||||
|
||||
if !found {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
sinkM, err := json.Marshal(sink)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error marshaling sink %v: %v", sink, err)
|
||||
}
|
||||
|
||||
var sinkD duckv1.Destination
|
||||
if err := json.Unmarshal(sinkM, &sinkD); err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshal source sink: %v", err)
|
||||
}
|
||||
|
||||
return &sinkD, nil
|
||||
}
|
||||
|
||||
func conditionsFromUnstructured(u *unstructured.Unstructured) (*duckv1.Conditions, error) {
|
||||
content := u.UnstructuredContent()
|
||||
conds, found, err := unstructured.NestedFieldCopy(content, "status", "conditions")
|
||||
if !found || err != nil {
|
||||
return nil, fmt.Errorf("cant find conditions in given unstructured object at status.conditions field: %v", err)
|
||||
}
|
||||
|
||||
condsM, err := json.Marshal(conds)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error marshaling conditions %v: %v", conds, err)
|
||||
}
|
||||
|
||||
var condsD duckv1.Conditions
|
||||
if err := json.Unmarshal(condsM, &condsD); err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshal source status conditions: %v", err)
|
||||
}
|
||||
|
||||
return &condsD, nil
|
||||
}
|
||||
|
||||
func findSink(source *unstructured.Unstructured) string {
|
||||
switch source.GetKind() {
|
||||
case "ApiServerSource":
|
||||
var apiSource sourcesv1alpha2.ApiServerSource
|
||||
if err := duck.FromUnstructured(source, &apiSource); err == nil {
|
||||
return flags.SinkToString(apiSource.Spec.Sink)
|
||||
return knflags.SinkToString(apiSource.Spec.Sink)
|
||||
}
|
||||
case "SinkBinding":
|
||||
var binding sourcesv1alpha2.SinkBinding
|
||||
if err := duck.FromUnstructured(source, &binding); err == nil {
|
||||
return flags.SinkToString(binding.Spec.Sink)
|
||||
return knflags.SinkToString(binding.Spec.Sink)
|
||||
}
|
||||
case "PingSource":
|
||||
var pingSource sourcesv1alpha2.PingSource
|
||||
if err := duck.FromUnstructured(source, &pingSource); err == nil {
|
||||
return flags.SinkToString(pingSource.Spec.Sink)
|
||||
return knflags.SinkToString(pingSource.Spec.Sink)
|
||||
}
|
||||
default:
|
||||
sink, err := sinkFromUnstructured(source)
|
||||
if err != nil {
|
||||
return "<unknown>"
|
||||
}
|
||||
if sink == nil {
|
||||
return ""
|
||||
}
|
||||
return knflags.SinkToString(*sink)
|
||||
}
|
||||
// TODO: Find out how to find sink in untyped sources
|
||||
return "<unknown>"
|
||||
}
|
||||
|
||||
|
|
@ -142,7 +196,13 @@ func isReady(source *unstructured.Unstructured) string {
|
|||
if err := duck.FromUnstructured(source, &tSource); err == nil {
|
||||
return commands.ReadyCondition(tSource.Status.Conditions)
|
||||
}
|
||||
}
|
||||
// TODO: Find out how to find ready conditions for untyped sources
|
||||
default:
|
||||
conds, err := conditionsFromUnstructured(source)
|
||||
if err != nil {
|
||||
// dont throw error in listing: if it cant find the status, return unknown
|
||||
return "<unknown>"
|
||||
}
|
||||
return commands.ReadyCondition(*conds)
|
||||
}
|
||||
return "<unknown>"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,160 @@
|
|||
/*
|
||||
Copyright 2020 The Knative 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 duck
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"gotest.tools/assert"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
//"knative.dev/client/pkg/util"
|
||||
)
|
||||
|
||||
func TestToSource(t *testing.T) {
|
||||
s := toSource(newSourceUnstructuredObjWithSink("a1",
|
||||
"sources.knative.dev/v1alpha1", "ApiServerSource"))
|
||||
assert.Check(t, s.Name == "a1")
|
||||
s = toSource(newSourceUnstructuredObjWithSink("s1",
|
||||
"sources.knative.dev/v1alpha1", "SinkBinding"))
|
||||
assert.Check(t, s.SourceKind == "SinkBinding")
|
||||
s = toSource(newSourceUnstructuredObjWithSink("p1",
|
||||
"sources.knative.dev/v1alpha1", "PingSource"))
|
||||
assert.Check(t, s.Sink == "svc:foo")
|
||||
s = toSource(newSourceUnstructuredObjWithSink("k1",
|
||||
"sources.knative.dev/v1alpha1", "KafkaSource"))
|
||||
assert.Check(t, s.Sink == "svc:foo")
|
||||
s = toSource(newSourceUnstructuredObjWithoutSink("k1",
|
||||
"sources.knative.dev/v1alpha1", "KafkaSource"))
|
||||
assert.Check(t, s.Sink == "")
|
||||
}
|
||||
|
||||
func TestSinkFromUnstructured(t *testing.T) {
|
||||
s, e := sinkFromUnstructured(newSourceUnstructuredObjWithSink("k1",
|
||||
"sources.knative.dev/v1alpha1", "KafkaSource"))
|
||||
assert.NilError(t, e)
|
||||
assert.Check(t, s != nil)
|
||||
|
||||
s, e = sinkFromUnstructured(newSourceUnstructuredObjWithoutSink("k1",
|
||||
"sources.knative.dev/v1alpha1", "KafkaSource"))
|
||||
assert.NilError(t, e)
|
||||
assert.Check(t, s == nil)
|
||||
|
||||
s, e = sinkFromUnstructured(newSourceUnstructuredObjWithIncorrectSink("k1",
|
||||
"sources.knative.dev/v1alpha1", "KafkaSource"))
|
||||
assert.Check(t, e != nil)
|
||||
assert.Check(t, s == nil)
|
||||
|
||||
}
|
||||
|
||||
func TestConditionsFromUnstructured(t *testing.T) {
|
||||
_, e := conditionsFromUnstructured(newSourceUnstructuredObjWithSink("k1",
|
||||
"sources.knative.dev/v1alpha1", "KafkaSource"))
|
||||
assert.NilError(t, e)
|
||||
|
||||
_, e = conditionsFromUnstructured(newSourceUnstructuredObjWithoutConditions("k1",
|
||||
"sources.knative.dev/v1alpha1", "KafkaSource"))
|
||||
assert.Check(t, e != nil)
|
||||
|
||||
}
|
||||
|
||||
func newSourceUnstructuredObjWithSink(name, apiVersion, kind string) *unstructured.Unstructured {
|
||||
return &unstructured.Unstructured{
|
||||
Object: map[string]interface{}{
|
||||
"apiVersion": apiVersion,
|
||||
"kind": kind,
|
||||
"metadata": map[string]interface{}{
|
||||
"namespace": "current",
|
||||
"name": name,
|
||||
},
|
||||
"spec": map[string]interface{}{
|
||||
"sink": map[string]interface{}{
|
||||
"ref": map[string]interface{}{
|
||||
"kind": "Service",
|
||||
"name": "foo",
|
||||
},
|
||||
},
|
||||
},
|
||||
"status": map[string]interface{}{
|
||||
"conditions": []interface{}{
|
||||
map[string]interface{}{
|
||||
"Type": "Ready",
|
||||
"Status": "True",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func newSourceUnstructuredObjWithoutSink(name, apiVersion, kind string) *unstructured.Unstructured {
|
||||
return &unstructured.Unstructured{
|
||||
Object: map[string]interface{}{
|
||||
"apiVersion": apiVersion,
|
||||
"kind": kind,
|
||||
"metadata": map[string]interface{}{
|
||||
"namespace": "current",
|
||||
"name": name,
|
||||
},
|
||||
"spec": map[string]interface{}{},
|
||||
"status": map[string]interface{}{
|
||||
"conditions": []interface{}{
|
||||
map[string]interface{}{
|
||||
"Type": "Ready",
|
||||
"Status": "False",
|
||||
"Reason": "SinkMissing",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func newSourceUnstructuredObjWithIncorrectSink(name, apiVersion, kind string) *unstructured.Unstructured {
|
||||
return &unstructured.Unstructured{
|
||||
Object: map[string]interface{}{
|
||||
"apiVersion": apiVersion,
|
||||
"kind": kind,
|
||||
"metadata": map[string]interface{}{
|
||||
"namespace": "current",
|
||||
"name": name,
|
||||
},
|
||||
"spec": map[string]interface{}{"sink": "incorrect"},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func newSourceUnstructuredObjWithoutConditions(name, apiVersion, kind string) *unstructured.Unstructured {
|
||||
return &unstructured.Unstructured{
|
||||
Object: map[string]interface{}{
|
||||
"apiVersion": apiVersion,
|
||||
"kind": kind,
|
||||
"metadata": map[string]interface{}{
|
||||
"namespace": "current",
|
||||
"name": name,
|
||||
},
|
||||
"spec": map[string]interface{}{
|
||||
"sink": map[string]interface{}{
|
||||
"ref": map[string]interface{}{
|
||||
"kind": "Service",
|
||||
"name": "foo",
|
||||
},
|
||||
},
|
||||
},
|
||||
"status": map[string]interface{}{},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
@ -82,9 +82,21 @@ func TestSourceList(t *testing.T) {
|
|||
)
|
||||
assert.NilError(t, err)
|
||||
assert.Check(t, util.ContainsAll(output[0], "NAME", "TYPE", "RESOURCE", "SINK", "READY"))
|
||||
assert.Check(t, util.ContainsAll(output[1], "a1", "ApiServerSource", "apiserversources.sources.knative.dev", "svc:foo", "<unknown>"))
|
||||
assert.Check(t, util.ContainsAll(output[2], "p1", "PingSource", "pingsources.sources.knative.dev", "svc:foo", "<unknown>"))
|
||||
assert.Check(t, util.ContainsAll(output[3], "s1", "SinkBinding", "sinkbindings.sources.knative.dev", "svc:foo", "<unknown>"))
|
||||
assert.Check(t, util.ContainsAll(output[1], "a1", "ApiServerSource", "apiserversources.sources.knative.dev", "svc:foo", "True"))
|
||||
assert.Check(t, util.ContainsAll(output[2], "p1", "PingSource", "pingsources.sources.knative.dev", "svc:foo", "True"))
|
||||
assert.Check(t, util.ContainsAll(output[3], "s1", "SinkBinding", "sinkbindings.sources.knative.dev", "svc:foo", "True"))
|
||||
}
|
||||
|
||||
func TestSourceListUntyped(t *testing.T) {
|
||||
output, err := sourceFakeCmd([]string{"source", "list"},
|
||||
newSourceCRDObjWithSpec("kafkasources", "sources.knative.dev", "v1alpha1", "KafkaSource"),
|
||||
newSourceUnstructuredObj("k1", "sources.knative.dev/v1alpha1", "KafkaSource"),
|
||||
newSourceUnstructuredObj("k2", "sources.knative.dev/v1alpha1", "KafkaSource"),
|
||||
)
|
||||
assert.NilError(t, err)
|
||||
assert.Check(t, util.ContainsAll(output[0], "NAME", "TYPE", "RESOURCE", "SINK", "READY"))
|
||||
assert.Check(t, util.ContainsAll(output[1], "k1", "KafkaSource", "kafkasources.sources.knative.dev", "svc:foo", "True"))
|
||||
assert.Check(t, util.ContainsAll(output[2], "k2", "KafkaSource", "kafkasources.sources.knative.dev", "svc:foo", "True"))
|
||||
}
|
||||
|
||||
func TestSourceListNoHeaders(t *testing.T) {
|
||||
|
|
@ -137,6 +149,14 @@ func newSourceUnstructuredObj(name, apiVersion, kind string) *unstructured.Unstr
|
|||
},
|
||||
},
|
||||
},
|
||||
"status": map[string]interface{}{
|
||||
"conditions": []interface{}{
|
||||
map[string]interface{}{
|
||||
"Type": "Ready",
|
||||
"Status": "True",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue