Repository: knative/client (go)

Issue:
Add dataBase64 flag for Ping Source
### Feature request

For parity to be able to create a ping source using the kn cli

### Use case
Use the `dataBase64` for binary data as shown in the [docs](https://knative.dev/docs/eventing/samples/ping-source/)

`kn source ping create test-ping-source --schedule "*/2 * * * *" --dataBase64: "ZGF0YQ==" --sink ksvc:event-display`



Pull request:
Adding encoding flag and support for base64 data
## Description
Adding encoding option to specify base64 or text values. Adding support for base64 data through CLI

<!-- Please add a short summary about what the pull request is going to bring on the table -->

## Changes

<!-- Please add list of more detailed changes. These changes should be reflected also in the commit messages -->

* Adding encoding option to specify the type of encoding of data.
* Adding code to identify automatically (without any encoding option specified) if given data is base64 encoded or not and setting the relevant field in PingSourceSpec.
* Adding a test case to cover these scenarios.

## Reference

<!-- Please add the corresponding issue number which this pull request is about to fix -->
Fixes #1204

<!--
Please add an entry to CHANGELOG.adoc file, too, as part of your Pull Request.

In the following cases, add a short description of PR to the unreleased section in CHANGELOG.adoc:

- 🎁 New feature
- 🐛 Bug fix
- ✨ Feature Update
- 🐣 Refactoring
- 🗑️ Remove feature or internal logic

See other entries in CHANGELOG.adoc as an example for how to add the entry, including a reference to the corresponding issue/PR

PLEASE DON'T ADD THAT LINE HERE IN THE PULL-REQUEST DESCRIPTION BUT DIRECTLY IN CHANGELOG.ADOC AND ADD CHANGELOG.ADOC AS PART OF YOUR PULL-REQUEST.
-->

<!--
To automatically lint go code in this pull request uncomment the line below. You get feedback as comments on your pull request then -->

<!--
/lint
-->


Patch:
diff --git a/docs/cmd/kn_source_ping_create.md b/docs/cmd/kn_source_ping_create.md
index 17917723..0e4c079e 100644
--- a/docs/cmd/kn_source_ping_create.md
+++ b/docs/cmd/kn_source_ping_create.md
@@ -19,6 +19,7 @@ kn source ping create NAME --sink SINK
 ```
       --ce-override stringArray   Cloud Event overrides to apply before sending event to sink. Example: '--ce-override key=value' You may be provide this flag multiple times. To unset, append "-" to the key (e.g. --ce-override key-).
   -d, --data string               Json data to send
+  -e, --encoding string           Preferred encoding format. Valid values: text/base64
   -h, --help                      help for create
   -n, --namespace string          Specify the namespace to operate in.
       --schedule string           Optional schedule specification in crontab format (e.g. '*/2 * * * *' for every two minutes. By default fire every minute.
diff --git a/docs/cmd/kn_source_ping_update.md b/docs/cmd/kn_source_ping_update.md
index 5336da33..354523f7 100644
--- a/docs/cmd/kn_source_ping_update.md
+++ b/docs/cmd/kn_source_ping_update.md
@@ -19,6 +19,7 @@ kn source ping update NAME
 ```
       --ce-override stringArray   Cloud Event overrides to apply before sending event to sink. Example: '--ce-override key=value' You may be provide this flag multiple times. To unset, append "-" to the key (e.g. --ce-override key-).
   -d, --data string               Json data to send
+  -e, --encoding string           Preferred encoding format. Valid values: text/base64
   -h, --help                      help for update
   -n, --namespace string          Specify the namespace to operate in.
       --schedule string           Optional schedule specification in crontab format (e.g. '*/2 * * * *' for every two minutes. By default fire every minute.
diff --git a/pkg/kn/commands/source/ping/create.go b/pkg/kn/commands/source/ping/create.go
index a422b7f9..6a355db3 100644
--- a/pkg/kn/commands/source/ping/create.go
+++ b/pkg/kn/commands/source/ping/create.go
@@ -15,6 +15,7 @@
 package ping
 
 import (
+	"encoding/base64"
 	"errors"
 	"fmt"
 
@@ -26,6 +27,11 @@ import (
 	"knative.dev/client/pkg/util"
 )
 
+const (
+	base64Encoding = "base64"
+	textEncoding   = "text"
+)
+
 // NewPingCreateCommand is for creating Ping source COs
 func NewPingCreateCommand(p *commands.KnParams) *cobra.Command {
 	var updateFlags pingUpdateFlags
@@ -70,9 +76,29 @@ func NewPingCreateCommand(p *commands.KnParams) *cobra.Command {
 			}
 			ceOverridesToRemove := util.ParseMinusSuffix(ceOverridesMap)
 
+			var data, dataBase64 string
+
+			switch updateFlags.encoding {
+			case "":
+				if _, err := base64.StdEncoding.DecodeString(updateFlags.data); err == nil {
+					dataBase64 = updateFlags.data
+				} else {
+					data = updateFlags.data
+				}
+			case base64Encoding:
+				dataBase64 = updateFlags.data
+			case textEncoding:
+				data = updateFlags.data
+			default:
+				err := fmt.Errorf("invalid value: %v", updateFlags.encoding)
+				fmt.Fprintf(cmd.OutOrStderr(), "Ping source creation failed with error: %v", err)
+				return err
+			}
+
 			err = pingSourceClient.CreatePingSource(cmd.Context(), clientsourcesv1beta2.NewPingSourceBuilder(name).
 				Schedule(updateFlags.schedule).
-				Data(updateFlags.data).
+				Data(data).
+				DataBase64(dataBase64).
 				Sink(*destination).
 				CloudEventOverrides(ceOverridesMap, ceOverridesToRemove).
 				Build())
diff --git a/pkg/kn/commands/source/ping/flags.go b/pkg/kn/commands/source/ping/flags.go
index b56feef5..f46005b6 100644
--- a/pkg/kn/commands/source/ping/flags.go
+++ b/pkg/kn/commands/source/ping/flags.go
@@ -31,6 +31,7 @@ import (
 type pingUpdateFlags struct {
 	schedule    string
 	data        string
+	encoding    string
 	ceOverrides []string
 }
 
@@ -42,6 +43,8 @@ func (c *pingUpdateFlags) addFlags(cmd *cobra.Command) {
 
 	cmd.Flags().StringVarP(&c.data, "data", "d", "", "Json data to send")
 
+	cmd.Flags().StringVarP(&c.encoding, "encoding", "e", "", "Preferred encoding format. Valid values: text/base64")
+
 	cmd.Flags().StringArrayVar(&c.ceOverrides,
 		"ce-override",
 		[]string{},
diff --git a/pkg/sources/v1beta2/ping_client.go b/pkg/sources/v1beta2/ping_client.go
index 790f651e..6589d621 100644
--- a/pkg/sources/v1beta2/ping_client.go
+++ b/pkg/sources/v1beta2/ping_client.go
@@ -175,6 +175,11 @@ func (b *PingSourceBuilder) Data(data string) *PingSourceBuilder {
 	return b
 }
 
+func (b *PingSourceBuilder) DataBase64(data string) *PingSourceBuilder {
+	b.pingSource.Spec.DataBase64 = data
+	return b
+}
+
 func (b *PingSourceBuilder) Sink(sink duckv1.Destination) *PingSourceBuilder {
 	b.pingSource.Spec.Sink = sink
 	return b


Test patch:
diff --git a/pkg/kn/commands/source/ping/create_test.go b/pkg/kn/commands/source/ping/create_test.go
index e036d5bb..00add675 100644
--- a/pkg/kn/commands/source/ping/create_test.go
+++ b/pkg/kn/commands/source/ping/create_test.go
@@ -37,7 +37,7 @@ func TestSimpleCreatePingSource(t *testing.T) {
 	pingClient := clientsourcesv1beta2.NewMockKnPingSourceClient(t)
 
 	pingRecorder := pingClient.Recorder()
-	pingRecorder.CreatePingSource(createPingSource("testsource", "* * * * */2", "maxwell", "mysvc", map[string]string{"bla": "blub", "foo": "bar"}), nil)
+	pingRecorder.CreatePingSource(createPingSource("testsource", "* * * * */2", "maxwell", "", "mysvc", map[string]string{"bla": "blub", "foo": "bar"}), nil)
 
 	out, err := executePingSourceCommand(pingClient, dynamicClient, "create", "--sink", "ksvc:mysvc", "--schedule", "* * * * */2", "--data", "maxwell", "testsource", "--ce-override", "bla=blub", "--ce-override", "foo=bar")
 	assert.NilError(t, err, "Source should have been created")
@@ -69,3 +69,35 @@ func TestNoNameGivenError(t *testing.T) {
 	assert.ErrorContains(t, err, "require")
 	assert.Assert(t, util.ContainsAll(out, "Usage", "require", "name"))
 }
+
+func TestDataEncoding(t *testing.T) {
+	base64Val := "ZGF0YQ=="
+	mysvc := &servingv1.Service{
+		TypeMeta:   metav1.TypeMeta{Kind: "Service", APIVersion: "serving.knative.dev/v1"},
+		ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "default"},
+	}
+	dynamicClient := dynamicfake.CreateFakeKnDynamicClient("default", mysvc)
+
+	pingClient := clientsourcesv1beta2.NewMockKnPingSourceClient(t)
+
+	pingRecorder := pingClient.Recorder()
+
+	pingRecorder.CreatePingSource(createPingSource("testsource", "* * * * */2", "", base64Val, "mysvc", nil), nil)
+	out, err := executePingSourceCommand(pingClient, dynamicClient, "create", "--sink", "ksvc:mysvc", "--schedule", "* * * * */2", "--data", base64Val, "testsource")
+	assert.NilError(t, err, "Source should have been created")
+	assert.Assert(t, util.ContainsAll(out, "created", "default", "testsource"))
+
+	pingRecorder.CreatePingSource(createPingSource("testsource", "* * * * */2", "", base64Val, "mysvc", nil), nil)
+	out, err = executePingSourceCommand(pingClient, dynamicClient, "create", "--sink", "ksvc:mysvc", "--schedule", "* * * * */2", "--data", base64Val, "--encoding", "base64", "testsource")
+	assert.NilError(t, err, "Source should have been created")
+	assert.Assert(t, util.ContainsAll(out, "created", "default", "testsource"))
+
+	pingRecorder.CreatePingSource(createPingSource("testsource", "* * * * */2", base64Val, "", "mysvc", nil), nil)
+	out, err = executePingSourceCommand(pingClient, dynamicClient, "create", "--sink", "ksvc:mysvc", "--schedule", "* * * * */2", "--data", base64Val, "--encoding", "text", "testsource")
+	assert.NilError(t, err, "Source should have been created")
+	assert.Assert(t, util.ContainsAll(out, "created", "default", "testsource"))
+
+	out, err = executePingSourceCommand(pingClient, dynamicClient, "create", "--sink", "ksvc:mysvc", "--schedule", "* * * * */2", "--data", base64Val, "--encoding", "baseMock", "testsource")
+	assert.ErrorContains(t, err, "invalid")
+	assert.Assert(t, util.ContainsAll(out, "Usage", "text", "base64"))
+}
diff --git a/pkg/kn/commands/source/ping/describe_test.go b/pkg/kn/commands/source/ping/describe_test.go
index dd75fb2b..e8744a49 100644
--- a/pkg/kn/commands/source/ping/describe_test.go
+++ b/pkg/kn/commands/source/ping/describe_test.go
@@ -33,7 +33,7 @@ func TestDescribeRef(t *testing.T) {
 
 	pingRecorder := pingClient.Recorder()
 	pingRecorder.GetPingSource("testping",
-		createPingSource("testping", "*/2 * * * *", "test", "testsvc", map[string]string{"foo": "bar"}), nil)
+		createPingSource("testping", "*/2 * * * *", "test", "", "testsvc", map[string]string{"foo": "bar"}), nil)
 
 	out, err := executePingSourceCommand(pingClient, nil, "describe", "testping")
 	assert.NilError(t, err)
diff --git a/pkg/kn/commands/source/ping/list_test.go b/pkg/kn/commands/source/ping/list_test.go
index c388bc76..a09ce091 100644
--- a/pkg/kn/commands/source/ping/list_test.go
+++ b/pkg/kn/commands/source/ping/list_test.go
@@ -31,7 +31,7 @@ func TestListPingSource(t *testing.T) {
 	pingClient := clientv1beta2.NewMockKnPingSourceClient(t)
 
 	pingRecorder := pingClient.Recorder()
-	cJSource := createPingSource("testsource", "* * * * */2", "maxwell", "mysvc", nil)
+	cJSource := createPingSource("testsource", "* * * * */2", "maxwell", "", "mysvc", nil)
 	cJSourceList := sourcesv1beta2.PingSourceList{}
 	cJSourceList.Items = []sourcesv1beta2.PingSource{*cJSource}
 
diff --git a/pkg/kn/commands/source/ping/ping_test.go b/pkg/kn/commands/source/ping/ping_test.go
index 6e9a2d6a..5644f78c 100644
--- a/pkg/kn/commands/source/ping/ping_test.go
+++ b/pkg/kn/commands/source/ping/ping_test.go
@@ -81,13 +81,14 @@ func cleanupPingMockClient() {
 	pingSourceClientFactory = nil
 }
 
-func createPingSource(name, schedule, data, service string, ceOverridesMap map[string]string) *sourcesv1beta2.PingSource {
+func createPingSource(name, schedule, data, dataBase64, service string, ceOverridesMap map[string]string) *sourcesv1beta2.PingSource {
 	sink := &duckv1.Destination{
 		Ref: &duckv1.KReference{Name: service, Kind: "Service", APIVersion: "serving.knative.dev/v1", Namespace: "default"},
 	}
 	return clientv1beta2.NewPingSourceBuilder(name).
 		Schedule(schedule).
 		Data(data).
+		DataBase64(dataBase64).
 		Sink(*sink).
 		CloudEventOverrides(ceOverridesMap, []string{}).
 		Build()
diff --git a/pkg/kn/commands/source/ping/update_test.go b/pkg/kn/commands/source/ping/update_test.go
index 6c855513..ccbcbd5d 100644
--- a/pkg/kn/commands/source/ping/update_test.go
+++ b/pkg/kn/commands/source/ping/update_test.go
@@ -36,8 +36,8 @@ func TestSimplePingUpdate(t *testing.T) {
 	dynamicClient := dynamicfake.CreateFakeKnDynamicClient("default", mysvc1)
 	pingSourceClient := sourcesv1beta2.NewMockKnPingSourceClient(t)
 	pingRecorder := pingSourceClient.Recorder()
-	pingRecorder.GetPingSource("testsource", createPingSource("testsource", "* * * * */1", "maxwell", "mysvc", nil), nil)
-	pingRecorder.UpdatePingSource(createPingSource("testsource", "* * * * */3", "maxwell", "mysvc1", nil), nil)
+	pingRecorder.GetPingSource("testsource", createPingSource("testsource", "* * * * */1", "maxwell", "", "mysvc", nil), nil)
+	pingRecorder.UpdatePingSource(createPingSource("testsource", "* * * * */3", "maxwell", "", "mysvc1", nil), nil)
 
 	out, err := executePingSourceCommand(pingSourceClient, dynamicClient, "update", "--schedule", "* * * * */3", "testsource", "--sink", "mysvc1")
 	assert.NilError(t, err)
@@ -52,8 +52,8 @@ func TestSimplePingUpdateCEOverrides(t *testing.T) {
 	pingRecorder := pingSourceClient.Recorder()
 	ceOverrideMap := map[string]string{"bla": "blub", "foo": "bar"}
 	ceOverrideMapUpdated := map[string]string{"foo": "baz", "new": "ceoverride"}
-	pingRecorder.GetPingSource("testsource", createPingSource("testsource", "* * * * */1", "maxwell", "mysvc", ceOverrideMap), nil)
-	pingRecorder.UpdatePingSource(createPingSource("testsource", "* * * * */3", "updated-data", "mysvc", ceOverrideMapUpdated), nil)
+	pingRecorder.GetPingSource("testsource", createPingSource("testsource", "* * * * */1", "maxwell", "", "mysvc", ceOverrideMap), nil)
+	pingRecorder.UpdatePingSource(createPingSource("testsource", "* * * * */3", "updated-data", "", "mysvc", ceOverrideMapUpdated), nil)
 
 	out, err := executePingSourceCommand(pingSourceClient, nil, "update", "--schedule", "* * * * */3", "testsource", "--data", "updated-data", "--ce-override", "bla-", "--ce-override", "foo=baz", "--ce-override", "new=ceoverride")
 	assert.NilError(t, err)
@@ -77,7 +77,7 @@ func TestUpdateError(t *testing.T) {
 
 func TestPingUpdateDeletionTimestampNotNil(t *testing.T) {
 	pingSourceClient := sourcesv1beta2.NewMockKnPingSourceClient(t)
-	present := createPingSource("test", "", "", "", nil)
+	present := createPingSource("test", "", "", "", "", nil)
 	present.DeletionTimestamp = &metav1.Time{Time: time.Now()}
 	pingRecorder := pingSourceClient.Recorder()
 	pingRecorder.GetPingSource("test", present, nil)
@@ -100,7 +100,7 @@ func TestPingUpdateNoSinkError(t *testing.T) {
 	pingClient := sourcesv1beta2.NewMockKnPingSourceClient(t)
 	pingRecorder := pingClient.Recorder()
 
-	pingRecorder.GetPingSource("testsource", createPingSource("testsource", "* * * * */1", "maxwell", "mysvc", nil), nil)
+	pingRecorder.GetPingSource("testsource", createPingSource("testsource", "* * * * */1", "maxwell", "", "mysvc", nil), nil)
 
 	out, err := executePingSourceCommand(pingClient, dynamicClient, "update", "testsource", "--sink", "ksvc1")
 	assert.ErrorContains(t, err, "not found")