diff --git a/pkg/kn/commands/source/duck/multisourcelist.go b/pkg/kn/commands/source/duck/multisourcelist.go index 707064bdd8..12ce37380b 100644 --- a/pkg/kn/commands/source/duck/multisourcelist.go +++ b/pkg/kn/commands/source/duck/multisourcelist.go @@ -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 "" } + if sink == nil { + return "" + } + return knflags.SinkToString(*sink) } - // TODO: Find out how to find sink in untyped sources return "" } @@ -142,7 +196,13 @@ func isReady(source *unstructured.Unstructured) string { if err := duck.FromUnstructured(source, &tSource); err == nil { return commands.ReadyCondition(tSource.Status.Conditions) } + default: + conds, err := conditionsFromUnstructured(source) + if err != nil { + // dont throw error in listing: if it cant find the status, return unknown + return "" + } + return commands.ReadyCondition(*conds) } - // TODO: Find out how to find ready conditions for untyped sources return "" } diff --git a/pkg/kn/commands/source/duck/multisourcelist_test.go b/pkg/kn/commands/source/duck/multisourcelist_test.go new file mode 100644 index 0000000000..c615e1fdd0 --- /dev/null +++ b/pkg/kn/commands/source/duck/multisourcelist_test.go @@ -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{}{}, + }, + } +} diff --git a/pkg/kn/commands/source/list_test.go b/pkg/kn/commands/source/list_test.go index 48d732ee0b..4f945a85ca 100644 --- a/pkg/kn/commands/source/list_test.go +++ b/pkg/kn/commands/source/list_test.go @@ -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", "")) - assert.Check(t, util.ContainsAll(output[2], "p1", "PingSource", "pingsources.sources.knative.dev", "svc:foo", "")) - assert.Check(t, util.ContainsAll(output[3], "s1", "SinkBinding", "sinkbindings.sources.knative.dev", "svc:foo", "")) + 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", + }, + }, + }, }, } }