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:
Navid Shaikh 2020-07-02 05:36:42 +05:30 committed by GitHub
parent 875247a8b1
commit 4b62f2008b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 249 additions and 9 deletions

View File

@ -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)
}
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)
}
// TODO: Find out how to find ready conditions for untyped sources
return "<unknown>"
}

View File

@ -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{}{},
},
}
}

View File

@ -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",
},
},
},
},
}
}