Repository: knative/client (go)

Issue:
Support `--ce-override` on `ping` and `apiserver` sources
### Feature request

All `Source`-ducktyped objects include a `CloudEventOverride`, which serializes as `extensions`. In `kn`, this is exposed as an option for `source binding create` and `source binding update`, but not for comparable commands on `ping` and `apiserver` command groups.

Also print the cloud event overrides extensions in `describe` output for sources if present.

The request is to add identical support to these other sources for consistency.

Pull request description:
Add --ce-override flag for apiserver and ping sources
## Description
- Add --ce-override flag for apiserver, ping sources and update it for SinkBinding.
- Show ce-override extensions for apiserver and ping sources, in describe output if present.

## Changes
- Updated map handling utilities, cloud events extensions builder method, flag description and related unit tests for SinkBinding
- Added --ce-override flag and unit tests for APIServer and Ping sources

## Reference
Fixes #849 

/lint


Patch:
diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc
index c2d5e480..00aef229 100644
--- a/CHANGELOG.adoc
+++ b/CHANGELOG.adoc
@@ -16,6 +16,10 @@
 |===
 | | Description | PR
 
+| 🎁
+| Add --ce-override flag for apiserver and ping sources
+| https://github.com/knative/client/pull/865[#865]
+
 | 🎁
 | Add --requests and --limits flags for resource requirements
 | https://github.com/knative/client/pull/859[#859]
diff --git a/docs/cmd/kn_source_apiserver_create.md b/docs/cmd/kn_source_apiserver_create.md
index 39511d54..f13c76ba 100644
--- a/docs/cmd/kn_source_apiserver_create.md
+++ b/docs/cmd/kn_source_apiserver_create.md
@@ -21,15 +21,16 @@ kn source apiserver create NAME --resource RESOURCE --service-account ACCOUNTNAM
 ### Options
 
 ```
-  -h, --help                     help for create
-      --mode string              The mode the receive adapter controller runs under:,
-                                 "Reference" sends only the reference to the resource,
-                                 "Resource" send the full resource. (default "Reference")
-  -n, --namespace string         Specify the namespace to operate in.
-      --resource stringArray     Specification for which events to listen, in the format Kind:APIVersion:LabelSelector, e.g. "Event:v1:key=value".
-                                 "LabelSelector" is a list of comma separated key value pairs. "LabelSelector" can be omitted, e.g. "Event:v1".
-      --service-account string   Name of the service account to use to run this source
-  -s, --sink string              Addressable sink for events
+      --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-).
+  -h, --help                      help for create
+      --mode string               The mode the receive adapter controller runs under:,
+                                  "Reference" sends only the reference to the resource,
+                                  "Resource" send the full resource. (default "Reference")
+  -n, --namespace string          Specify the namespace to operate in.
+      --resource stringArray      Specification for which events to listen, in the format Kind:APIVersion:LabelSelector, e.g. "Event:v1:key=value".
+                                  "LabelSelector" is a list of comma separated key value pairs. "LabelSelector" can be omitted, e.g. "Event:v1".
+      --service-account string    Name of the service account to use to run this source
+  -s, --sink string               Addressable sink for events
 ```
 
 ### Options inherited from parent commands
diff --git a/docs/cmd/kn_source_apiserver_update.md b/docs/cmd/kn_source_apiserver_update.md
index 8d4acebc..d238a835 100644
--- a/docs/cmd/kn_source_apiserver_update.md
+++ b/docs/cmd/kn_source_apiserver_update.md
@@ -21,15 +21,16 @@ kn source apiserver update NAME --resource RESOURCE --service-account ACCOUNTNAM
 ### Options
 
 ```
-  -h, --help                     help for update
-      --mode string              The mode the receive adapter controller runs under:,
-                                 "Reference" sends only the reference to the resource,
-                                 "Resource" send the full resource. (default "Reference")
-  -n, --namespace string         Specify the namespace to operate in.
-      --resource stringArray     Specification for which events to listen, in the format Kind:APIVersion:LabelSelector, e.g. "Event:v1:key=value".
-                                 "LabelSelector" is a list of comma separated key value pairs. "LabelSelector" can be omitted, e.g. "Event:v1".
-      --service-account string   Name of the service account to use to run this source
-  -s, --sink string              Addressable sink for events
+      --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-).
+  -h, --help                      help for update
+      --mode string               The mode the receive adapter controller runs under:,
+                                  "Reference" sends only the reference to the resource,
+                                  "Resource" send the full resource. (default "Reference")
+  -n, --namespace string          Specify the namespace to operate in.
+      --resource stringArray      Specification for which events to listen, in the format Kind:APIVersion:LabelSelector, e.g. "Event:v1:key=value".
+                                  "LabelSelector" is a list of comma separated key value pairs. "LabelSelector" can be omitted, e.g. "Event:v1".
+      --service-account string    Name of the service account to use to run this source
+  -s, --sink string               Addressable sink for events
 ```
 
 ### Options inherited from parent commands
diff --git a/docs/cmd/kn_source_binding_create.md b/docs/cmd/kn_source_binding_create.md
index ed5e0296..93ccbccd 100644
--- a/docs/cmd/kn_source_binding_create.md
+++ b/docs/cmd/kn_source_binding_create.md
@@ -21,7 +21,7 @@ kn source binding create NAME --subject SUBJECT --sink SINK --ce-override KEY=VA
 ### Options
 
 ```
-      --ce-override stringArray   Cloud Event overrides to apply before sending event to sink in the format '--ce-override key=value'. --ce-override can be provide multiple times
+      --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-).
   -h, --help                      help for create
   -n, --namespace string          Specify the namespace to operate in.
   -s, --sink string               Addressable sink for events
diff --git a/docs/cmd/kn_source_binding_update.md b/docs/cmd/kn_source_binding_update.md
index d889047c..a620d4b7 100644
--- a/docs/cmd/kn_source_binding_update.md
+++ b/docs/cmd/kn_source_binding_update.md
@@ -21,7 +21,7 @@ kn source binding update NAME --subject SCHEDULE --sink SINK --ce-override OVERR
 ### Options
 
 ```
-      --ce-override stringArray   Cloud Event overrides to apply before sending event to sink in the format '--ce-override key=value'. --ce-override can be provide multiple times
+      --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-).
   -h, --help                      help for update
   -n, --namespace string          Specify the namespace to operate in.
   -s, --sink string               Addressable sink for events
diff --git a/docs/cmd/kn_source_ping_create.md b/docs/cmd/kn_source_ping_create.md
index b563b8de..0869c9e6 100644
--- a/docs/cmd/kn_source_ping_create.md
+++ b/docs/cmd/kn_source_ping_create.md
@@ -21,11 +21,12 @@ kn source ping create NAME --schedule SCHEDULE --sink SINK --data DATA [flags]
 ### Options
 
 ```
-  -d, --data string        Json data to send
-  -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.
-  -s, --sink string        Addressable sink for events
+      --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
+  -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.
+  -s, --sink string               Addressable sink for events
 ```
 
 ### Options inherited from parent commands
diff --git a/docs/cmd/kn_source_ping_update.md b/docs/cmd/kn_source_ping_update.md
index a9bf38fd..c2c84455 100644
--- a/docs/cmd/kn_source_ping_update.md
+++ b/docs/cmd/kn_source_ping_update.md
@@ -21,11 +21,12 @@ kn source ping update NAME --schedule SCHEDULE --sink SERVICE --data DATA [flags
 ### Options
 
 ```
-  -d, --data string        Json data to send
-  -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.
-  -s, --sink string        Addressable sink for events
+      --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
+  -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.
+  -s, --sink string               Addressable sink for events
 ```
 
 ### Options inherited from parent commands
diff --git a/pkg/kn/commands/source/apiserver/create.go b/pkg/kn/commands/source/apiserver/create.go
index fcd4c647..928ef00b 100644
--- a/pkg/kn/commands/source/apiserver/create.go
+++ b/pkg/kn/commands/source/apiserver/create.go
@@ -23,6 +23,7 @@ import (
 	"knative.dev/client/pkg/kn/commands"
 	"knative.dev/client/pkg/kn/commands/flags"
 	"knative.dev/client/pkg/sources/v1alpha2"
+	"knative.dev/client/pkg/util"
 )
 
 // NewAPIServerCreateCommand for creating source
@@ -62,16 +63,23 @@ func NewAPIServerCreateCommand(p *commands.KnParams) *cobra.Command {
 						"because: %s", name, namespace, err)
 			}
 
-			b := v1alpha2.NewAPIServerSourceBuilder(name).
-				ServiceAccount(updateFlags.ServiceAccountName).
-				EventMode(updateFlags.Mode).
-				Sink(*objectRef)
-
 			resources, err := updateFlags.getAPIServerVersionKindSelector()
 			if err != nil {
 				return err
 			}
-			b.Resources(resources)
+
+			ceOverridesMap, err := util.MapFromArrayAllowingSingles(updateFlags.ceOverrides, "=")
+			if err != nil {
+				return err
+			}
+			ceOverridesToRemove := util.ParseMinusSuffix(ceOverridesMap)
+
+			b := v1alpha2.NewAPIServerSourceBuilder(name).
+				ServiceAccount(updateFlags.ServiceAccountName).
+				EventMode(updateFlags.Mode).
+				Sink(*objectRef).
+				Resources(resources).
+				CloudEventOverrides(ceOverridesMap, ceOverridesToRemove)
 
 			err = apiSourceClient.CreateAPIServerSource(b.Build())
 
diff --git a/pkg/kn/commands/source/apiserver/describe.go b/pkg/kn/commands/source/apiserver/describe.go
index 5605cb61..a691da4a 100644
--- a/pkg/kn/commands/source/apiserver/describe.go
+++ b/pkg/kn/commands/source/apiserver/describe.go
@@ -17,6 +17,7 @@ package apiserver
 import (
 	"errors"
 	"fmt"
+	"sort"
 
 	"github.com/spf13/cobra"
 	v1alpha2 "knative.dev/eventing/pkg/apis/sources/v1alpha2"
@@ -71,6 +72,10 @@ func NewAPIServerDescribeCommand(p *commands.KnParams) *cobra.Command {
 				return err
 			}
 
+			if apiSource.Spec.CloudEventOverrides != nil && apiSource.Spec.CloudEventOverrides.Extensions != nil {
+				writeCeOverrides(dw, apiSource.Spec.CloudEventOverrides.Extensions)
+			}
+
 			writeResources(dw, apiSource.Spec.Resources)
 			dw.WriteLine()
 			if err := dw.Flush(); err != nil {
@@ -122,3 +127,15 @@ func writeAPIServerSource(dw printers.PrefixWriter, source *v1alpha2.ApiServerSo
 	dw.WriteAttribute("ServiceAccountName", source.Spec.ServiceAccountName)
 	dw.WriteAttribute("EventMode", source.Spec.EventMode)
 }
+
+func writeCeOverrides(dw printers.PrefixWriter, ceOverrides map[string]string) {
+	subDw := dw.WriteAttribute("CloudEvent Overrides", "")
+	var keys []string
+	for k := range ceOverrides {
+		keys = append(keys, k)
+	}
+	sort.Strings(keys)
+	for _, k := range keys {
+		subDw.WriteAttribute(k, ceOverrides[k])
+	}
+}
diff --git a/pkg/kn/commands/source/apiserver/flags.go b/pkg/kn/commands/source/apiserver/flags.go
index b576753a..7dc2023c 100644
--- a/pkg/kn/commands/source/apiserver/flags.go
+++ b/pkg/kn/commands/source/apiserver/flags.go
@@ -45,6 +45,7 @@ type APIServerSourceUpdateFlags struct {
 	ServiceAccountName string
 	Mode               string
 	Resources          []string
+	ceOverrides        []string
 }
 
 // getAPIServerVersionKindSelector is to construct an array of resources.
@@ -161,6 +162,13 @@ func (f *APIServerSourceUpdateFlags) Add(cmd *cobra.Command) {
 		[]string{},
 		`Specification for which events to listen, in the format Kind:APIVersion:LabelSelector, e.g. "Event:v1:key=value".
 "LabelSelector" is a list of comma separated key value pairs. "LabelSelector" can be omitted, e.g. "Event:v1".`)
+	cmd.Flags().StringArrayVar(&f.ceOverrides,
+		"ce-override",
+		[]string{},
+		"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-).")
 }
 
 // APIServerSourceListHandlers handles printing human readable table for `kn source apiserver list` command's output
diff --git a/pkg/kn/commands/source/apiserver/update.go b/pkg/kn/commands/source/apiserver/update.go
index 6ec97bc0..785b5819 100644
--- a/pkg/kn/commands/source/apiserver/update.go
+++ b/pkg/kn/commands/source/apiserver/update.go
@@ -23,11 +23,12 @@ import (
 	"knative.dev/client/pkg/kn/commands"
 	"knative.dev/client/pkg/kn/commands/flags"
 	"knative.dev/client/pkg/sources/v1alpha2"
+	"knative.dev/client/pkg/util"
 )
 
 // NewAPIServerUpdateCommand for managing source update
 func NewAPIServerUpdateCommand(p *commands.KnParams) *cobra.Command {
-	var apiServerUpdateFlags APIServerSourceUpdateFlags
+	var updateFlags APIServerSourceUpdateFlags
 	var sinkFlags flags.SinkFlags
 
 	cmd := &cobra.Command{
@@ -69,15 +70,15 @@ func NewAPIServerUpdateCommand(p *commands.KnParams) *cobra.Command {
 
 			b := v1alpha2.NewAPIServerSourceBuilderFromExisting(source)
 			if cmd.Flags().Changed("service-account") {
-				b.ServiceAccount(apiServerUpdateFlags.ServiceAccountName)
+				b.ServiceAccount(updateFlags.ServiceAccountName)
 			}
 
 			if cmd.Flags().Changed("mode") {
-				b.EventMode(apiServerUpdateFlags.Mode)
+				b.EventMode(updateFlags.Mode)
 			}
 
 			if cmd.Flags().Changed("resource") {
-				updateExisting, err := apiServerUpdateFlags.updateExistingAPIVersionKindSelectorArray(source.Spec.Resources)
+				updateExisting, err := updateFlags.updateExistingAPIVersionKindSelectorArray(source.Spec.Resources)
 				if err != nil {
 					return err
 				}
@@ -92,6 +93,15 @@ func NewAPIServerUpdateCommand(p *commands.KnParams) *cobra.Command {
 				b.Sink(*objectRef)
 			}
 
+			if cmd.Flags().Changed("ce-override") {
+				ceOverridesMap, err := util.MapFromArrayAllowingSingles(updateFlags.ceOverrides, "=")
+				if err != nil {
+					return err
+				}
+				ceOverridesToRemove := util.ParseMinusSuffix(ceOverridesMap)
+				b.CloudEventOverrides(ceOverridesMap, ceOverridesToRemove)
+			}
+
 			err = sourcesClient.UpdateAPIServerSource(b.Build())
 			if err == nil {
 				fmt.Fprintf(cmd.OutOrStdout(), "ApiServer source '%s' updated in namespace '%s'.\n", args[0], namespace)
@@ -101,7 +111,7 @@ func NewAPIServerUpdateCommand(p *commands.KnParams) *cobra.Command {
 		},
 	}
 	commands.AddNamespaceFlags(cmd.Flags(), false)
-	apiServerUpdateFlags.Add(cmd)
+	updateFlags.Add(cmd)
 	sinkFlags.Add(cmd)
 	return cmd
 }
diff --git a/pkg/kn/commands/source/binding/binding.go b/pkg/kn/commands/source/binding/binding.go
index 05455cd5..3a6e2e62 100644
--- a/pkg/kn/commands/source/binding/binding.go
+++ b/pkg/kn/commands/source/binding/binding.go
@@ -27,7 +27,6 @@ import (
 
 	"knative.dev/client/pkg/kn/commands"
 	clientsourcesv1alpha1 "knative.dev/client/pkg/sources/v1alpha2"
-	"knative.dev/client/pkg/util"
 )
 
 // NewBindingCommand is the root command for all binding related commands
@@ -131,15 +130,3 @@ func subjectToString(ref tracker.Reference) string {
 	}
 	return ret
 }
-
-// updateCeOverrides updates the values of the --ce-override flags if given
-func updateCeOverrides(bindingFlags bindingUpdateFlags, bindingBuilder *clientsourcesv1alpha1.SinkBindingBuilder) error {
-	if bindingFlags.ceOverrides != nil {
-		ceOverrideMap, err := util.MapFromArray(bindingFlags.ceOverrides, "=")
-		if err != nil {
-			return err
-		}
-		bindingBuilder.AddCloudEventOverrides(ceOverrideMap)
-	}
-	return nil
-}
diff --git a/pkg/kn/commands/source/binding/create.go b/pkg/kn/commands/source/binding/create.go
index 89177056..ea2c96bf 100644
--- a/pkg/kn/commands/source/binding/create.go
+++ b/pkg/kn/commands/source/binding/create.go
@@ -23,6 +23,7 @@ import (
 	"knative.dev/client/pkg/kn/commands"
 	"knative.dev/client/pkg/kn/commands/flags"
 	v1alpha12 "knative.dev/client/pkg/sources/v1alpha2"
+	"knative.dev/client/pkg/util"
 )
 
 // NewBindingCreateCommand is for creating sink bindings
@@ -68,15 +69,18 @@ func NewBindingCreateCommand(p *commands.KnParams) *cobra.Command {
 				return err
 			}
 
+			ceOverridesMap, err := util.MapFromArrayAllowingSingles(bindingFlags.ceOverrides, "=")
+			if err != nil {
+				return err
+			}
+			ceOverridesToRemove := util.ParseMinusSuffix(ceOverridesMap)
+
 			bindingBuilder := v1alpha12.NewSinkBindingBuilder(name).
 				Sink(destination).
 				Subject(reference).
-				Namespace(namespace)
+				Namespace(namespace).
+				CloudEventOverrides(ceOverridesMap, ceOverridesToRemove)
 
-			err = updateCeOverrides(bindingFlags, bindingBuilder)
-			if err != nil {
-				return err
-			}
 			binding, err := bindingBuilder.Build()
 			if err != nil {
 				return err
diff --git a/pkg/kn/commands/source/binding/flags.go b/pkg/kn/commands/source/binding/flags.go
index cb7069a0..013b4bb3 100644
--- a/pkg/kn/commands/source/binding/flags.go
+++ b/pkg/kn/commands/source/binding/flags.go
@@ -32,8 +32,17 @@ type bindingUpdateFlags struct {
 }
 
 func (b *bindingUpdateFlags) addBindingFlags(cmd *cobra.Command) {
-	cmd.Flags().StringVar(&b.subject, "subject", "", "Subject which emits cloud events. This argument takes format kind:apiVersion:name for named resources or kind:apiVersion:labelKey1=value1,labelKey2=value2 for matching via a label selector")
-	cmd.Flags().StringArrayVar(&b.ceOverrides, "ce-override", nil, "Cloud Event overrides to apply before sending event to sink in the format '--ce-override key=value'. --ce-override can be provide multiple times")
+	cmd.Flags().StringVar(&b.subject,
+		"subject",
+		"",
+		"Subject which emits cloud events. This argument takes format kind:apiVersion:name for named resources or kind:apiVersion:labelKey1=value1,labelKey2=value2 for matching via a label selector")
+	cmd.Flags().StringArrayVar(&b.ceOverrides,
+		"ce-override",
+		[]string{},
+		"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-).")
 }
 
 func BindingListHandlers(h hprinters.PrintHandler) {
diff --git a/pkg/kn/commands/source/binding/update.go b/pkg/kn/commands/source/binding/update.go
index b8b8526f..e7e6e7ea 100644
--- a/pkg/kn/commands/source/binding/update.go
+++ b/pkg/kn/commands/source/binding/update.go
@@ -23,6 +23,7 @@ import (
 	"knative.dev/client/pkg/kn/commands"
 	"knative.dev/client/pkg/kn/commands/flags"
 	v1alpha12 "knative.dev/client/pkg/sources/v1alpha2"
+	"knative.dev/client/pkg/util"
 )
 
 // NewBindingUpdateCommand prepares the command for a sink binding update
@@ -80,11 +81,14 @@ func NewBindingUpdateCommand(p *commands.KnParams) *cobra.Command {
 				}
 				b.Subject(reference)
 			}
-			err = updateCeOverrides(bindingFlags, b)
-			if err != nil {
-				return err
+			if cmd.Flags().Changed("ce-override") {
+				ceOverridesMap, err := util.MapFromArrayAllowingSingles(bindingFlags.ceOverrides, "=")
+				if err != nil {
+					return err
+				}
+				ceOverridesToRemove := util.ParseMinusSuffix(ceOverridesMap)
+				b.CloudEventOverrides(ceOverridesMap, ceOverridesToRemove)
 			}
-
 			binding, err := b.Build()
 			if err != nil {
 				return err
diff --git a/pkg/kn/commands/source/ping/create.go b/pkg/kn/commands/source/ping/create.go
index 20a47ea8..997c1463 100644
--- a/pkg/kn/commands/source/ping/create.go
+++ b/pkg/kn/commands/source/ping/create.go
@@ -23,11 +23,12 @@ import (
 	"knative.dev/client/pkg/kn/commands"
 	"knative.dev/client/pkg/kn/commands/flags"
 	"knative.dev/client/pkg/sources/v1alpha2"
+	"knative.dev/client/pkg/util"
 )
 
 // NewPingCreateCommand is for creating Ping source COs
 func NewPingCreateCommand(p *commands.KnParams) *cobra.Command {
-	var pingUpdateFlags pingUpdateFlags
+	var updateFlags pingUpdateFlags
 	var sinkFlags flags.SinkFlags
 
 	cmd := &cobra.Command{
@@ -63,11 +64,18 @@ func NewPingCreateCommand(p *commands.KnParams) *cobra.Command {
 				return err
 			}
 
+			ceOverridesMap, err := util.MapFromArrayAllowingSingles(updateFlags.ceOverrides, "=")
+			if err != nil {
+				return err
+			}
+			ceOverridesToRemove := util.ParseMinusSuffix(ceOverridesMap)
+
 			err = pingSourceClient.CreatePingSource(
 				v1alpha2.NewPingSourceBuilder(name).
-					Schedule(pingUpdateFlags.schedule).
-					JsonData(pingUpdateFlags.data).
+					Schedule(updateFlags.schedule).
+					JsonData(updateFlags.data).
 					Sink(*destination).
+					CloudEventOverrides(ceOverridesMap, ceOverridesToRemove).
 					Build())
 			if err == nil {
 				fmt.Fprintf(cmd.OutOrStdout(), "Ping source '%s' created in namespace '%s'.\n", args[0], pingSourceClient.Namespace())
@@ -76,7 +84,7 @@ func NewPingCreateCommand(p *commands.KnParams) *cobra.Command {
 		},
 	}
 	commands.AddNamespaceFlags(cmd.Flags(), false)
-	pingUpdateFlags.addPingFlags(cmd)
+	updateFlags.addFlags(cmd)
 	sinkFlags.Add(cmd)
 	cmd.MarkFlagRequired("sink")
 
diff --git a/pkg/kn/commands/source/ping/describe.go b/pkg/kn/commands/source/ping/describe.go
index d0500892..88b9b5cd 100644
--- a/pkg/kn/commands/source/ping/describe.go
+++ b/pkg/kn/commands/source/ping/describe.go
@@ -17,6 +17,7 @@ package ping
 import (
 	"errors"
 	"fmt"
+	"sort"
 
 	"github.com/spf13/cobra"
 	v1alpha2 "knative.dev/eventing/pkg/apis/sources/v1alpha2"
@@ -46,7 +47,7 @@ func NewPingDescribeCommand(p *commands.KnParams) *cobra.Command {
 				return err
 			}
 
-			cjSource, err := pingSourceClient.GetPingSource(name)
+			pingSource, err := pingSourceClient.GetPingSource(name)
 			if err != nil {
 				return err
 			}
@@ -59,21 +60,29 @@ func NewPingDescribeCommand(p *commands.KnParams) *cobra.Command {
 				return err
 			}
 
-			writePingSource(dw, cjSource, printDetails)
+			writePingSource(dw, pingSource, printDetails)
 			dw.WriteLine()
 			if err := dw.Flush(); err != nil {
 				return err
 			}
 
 			// Revisions summary info
-			writeSink(dw, &cjSource.Spec.Sink)
+			writeSink(dw, &pingSource.Spec.Sink)
 			dw.WriteLine()
 			if err := dw.Flush(); err != nil {
 				return err
 			}
 
+			if pingSource.Spec.CloudEventOverrides != nil && pingSource.Spec.CloudEventOverrides.Extensions != nil {
+				writeCeOverrides(dw, pingSource.Spec.CloudEventOverrides.Extensions)
+				dw.WriteLine()
+				if err := dw.Flush(); err != nil {
+					return err
+				}
+			}
+
 			// Condition info
-			commands.WriteConditions(dw, cjSource.Status.Conditions, printDetails)
+			commands.WriteConditions(dw, pingSource.Status.Conditions, printDetails)
 			if err := dw.Flush(); err != nil {
 				return err
 			}
@@ -107,3 +116,15 @@ func writePingSource(dw printers.PrefixWriter, source *v1alpha2.PingSource, prin
 	dw.WriteAttribute("Schedule", source.Spec.Schedule)
 	dw.WriteAttribute("Data", source.Spec.JsonData)
 }
+
+func writeCeOverrides(dw printers.PrefixWriter, ceOverrides map[string]string) {
+	subDw := dw.WriteAttribute("CloudEvent Overrides", "")
+	var keys []string
+	for k := range ceOverrides {
+		keys = append(keys, k)
+	}
+	sort.Strings(keys)
+	for _, k := range keys {
+		subDw.WriteAttribute(k, ceOverrides[k])
+	}
+}
diff --git a/pkg/kn/commands/source/ping/flags.go b/pkg/kn/commands/source/ping/flags.go
index 7c91bad9..6f590be5 100644
--- a/pkg/kn/commands/source/ping/flags.go
+++ b/pkg/kn/commands/source/ping/flags.go
@@ -29,13 +29,26 @@ import (
 )
 
 type pingUpdateFlags struct {
-	schedule string
-	data     string
+	schedule    string
+	data        string
+	ceOverrides []string
 }
 
-func (c *pingUpdateFlags) addPingFlags(cmd *cobra.Command) {
-	cmd.Flags().StringVar(&c.schedule, "schedule", "", "Optional schedule specification in crontab format (e.g. '*/2 * * * *' for every two minutes. By default fire every minute.")
+func (c *pingUpdateFlags) addFlags(cmd *cobra.Command) {
+	cmd.Flags().StringVar(&c.schedule,
+		"schedule",
+		"",
+		"Optional schedule specification in crontab format (e.g. '*/2 * * * *' for every two minutes. By default fire every minute.")
+
 	cmd.Flags().StringVarP(&c.data, "data", "d", "", "Json data to send")
+
+	cmd.Flags().StringArrayVar(&c.ceOverrides,
+		"ce-override",
+		[]string{},
+		"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-).")
 }
 
 // PingListHandlers handles printing human readable table for `kn source ping list` command's output
diff --git a/pkg/kn/commands/source/ping/update.go b/pkg/kn/commands/source/ping/update.go
index 80ce941d..9c254ec4 100644
--- a/pkg/kn/commands/source/ping/update.go
+++ b/pkg/kn/commands/source/ping/update.go
@@ -23,11 +23,12 @@ import (
 	"knative.dev/client/pkg/kn/commands"
 	"knative.dev/client/pkg/kn/commands/flags"
 	"knative.dev/client/pkg/sources/v1alpha2"
+	"knative.dev/client/pkg/util"
 )
 
 // NewPingUpdateCommand prepares the command for a PingSource update
 func NewPingUpdateCommand(p *commands.KnParams) *cobra.Command {
-	var pingUpdateFlags pingUpdateFlags
+	var updateFlags pingUpdateFlags
 	var sinkFlags flags.SinkFlags
 
 	cmd := &cobra.Command{
@@ -67,10 +68,10 @@ func NewPingUpdateCommand(p *commands.KnParams) *cobra.Command {
 
 			b := v1alpha2.NewPingSourceBuilderFromExisting(source)
 			if cmd.Flags().Changed("schedule") {
-				b.Schedule(pingUpdateFlags.schedule)
+				b.Schedule(updateFlags.schedule)
 			}
 			if cmd.Flags().Changed("data") {
-				b.JsonData(pingUpdateFlags.data)
+				b.JsonData(updateFlags.data)
 			}
 			if cmd.Flags().Changed("sink") {
 				destination, err := sinkFlags.ResolveSink(dynamicClient, namespace)
@@ -79,6 +80,16 @@ func NewPingUpdateCommand(p *commands.KnParams) *cobra.Command {
 				}
 				b.Sink(*destination)
 			}
+
+			if cmd.Flags().Changed("ce-override") {
+				ceOverridesMap, err := util.MapFromArrayAllowingSingles(updateFlags.ceOverrides, "=")
+				if err != nil {
+					return err
+				}
+				ceOverridesToRemove := util.ParseMinusSuffix(ceOverridesMap)
+				b.CloudEventOverrides(ceOverridesMap, ceOverridesToRemove)
+			}
+
 			err = pingSourceClient.UpdatePingSource(b.Build())
 			if err == nil {
 				fmt.Fprintf(cmd.OutOrStdout(), "Ping source '%s' updated in namespace '%s'.\n", name, pingSourceClient.Namespace())
@@ -87,7 +98,7 @@ func NewPingUpdateCommand(p *commands.KnParams) *cobra.Command {
 		},
 	}
 	commands.AddNamespaceFlags(cmd.Flags(), false)
-	pingUpdateFlags.addPingFlags(cmd)
+	updateFlags.addFlags(cmd)
 	sinkFlags.Add(cmd)
 
 	return cmd
diff --git a/pkg/sources/v1alpha2/apiserver_client.go b/pkg/sources/v1alpha2/apiserver_client.go
index 8fafb343..3f66511c 100644
--- a/pkg/sources/v1alpha2/apiserver_client.go
+++ b/pkg/sources/v1alpha2/apiserver_client.go
@@ -183,6 +183,27 @@ func (b *APIServerSourceBuilder) Sink(sink duckv1.Destination) *APIServerSourceB
 	return b
 }
 
+// CloudEventOverrides adds given Cloud Event override extensions map to source spec
+func (b *APIServerSourceBuilder) CloudEventOverrides(ceo map[string]string, toRemove []string) *APIServerSourceBuilder {
+	if ceo == nil && len(toRemove) == 0 {
+		return b
+	}
+
+	ceOverrides := b.apiServerSource.Spec.CloudEventOverrides
+	if ceOverrides == nil {
+		ceOverrides = &duckv1.CloudEventOverrides{Extensions: map[string]string{}}
+		b.apiServerSource.Spec.CloudEventOverrides = ceOverrides
+	}
+	for k, v := range ceo {
+		ceOverrides.Extensions[k] = v
+	}
+	for _, r := range toRemove {
+		delete(ceOverrides.Extensions, r)
+	}
+
+	return b
+}
+
 // Build the ApiServerSource object
 func (b *APIServerSourceBuilder) Build() *v1alpha2.ApiServerSource {
 	return b.apiServerSource
diff --git a/pkg/sources/v1alpha2/binding_client.go b/pkg/sources/v1alpha2/binding_client.go
index d004246f..96e67a35 100644
--- a/pkg/sources/v1alpha2/binding_client.go
+++ b/pkg/sources/v1alpha2/binding_client.go
@@ -204,7 +204,12 @@ func (b *SinkBindingBuilder) Sink(sink *duckv1.Destination) *SinkBindingBuilder
 	return b
 }
 
-func (b *SinkBindingBuilder) AddCloudEventOverrides(ceo map[string]string) *SinkBindingBuilder {
+// CloudEventOverrides adds given Cloud Event override extensions map to source spec
+func (b *SinkBindingBuilder) CloudEventOverrides(ceo map[string]string, toRemove []string) *SinkBindingBuilder {
+	if ceo == nil && len(toRemove) == 0 {
+		return b
+	}
+
 	ceOverrides := b.binding.Spec.CloudEventOverrides
 	if ceOverrides == nil {
 		ceOverrides = &duckv1.CloudEventOverrides{Extensions: map[string]string{}}
@@ -213,6 +218,10 @@ func (b *SinkBindingBuilder) AddCloudEventOverrides(ceo map[string]string) *Sink
 	for k, v := range ceo {
 		ceOverrides.Extensions[k] = v
 	}
+	for _, r := range toRemove {
+		delete(ceOverrides.Extensions, r)
+	}
+
 	return b
 }
 
diff --git a/pkg/sources/v1alpha2/ping_client.go b/pkg/sources/v1alpha2/ping_client.go
index b8d307af..daf84efd 100644
--- a/pkg/sources/v1alpha2/ping_client.go
+++ b/pkg/sources/v1alpha2/ping_client.go
@@ -151,6 +151,27 @@ func (b *PingSourceBuilder) Sink(sink duckv1.Destination) *PingSourceBuilder {
 	return b
 }
 
+// CloudEventOverrides adds given Cloud Event override extensions map to source spec
+func (b *PingSourceBuilder) CloudEventOverrides(ceo map[string]string, toRemove []string) *PingSourceBuilder {
+	if ceo == nil && len(toRemove) == 0 {
+		return b
+	}
+
+	ceOverrides := b.pingSource.Spec.CloudEventOverrides
+	if ceOverrides == nil {
+		ceOverrides = &duckv1.CloudEventOverrides{Extensions: map[string]string{}}
+		b.pingSource.Spec.CloudEventOverrides = ceOverrides
+	}
+	for k, v := range ceo {
+		ceOverrides.Extensions[k] = v
+	}
+	for _, r := range toRemove {
+		delete(ceOverrides.Extensions, r)
+	}
+
+	return b
+}
+
 func (b *PingSourceBuilder) Build() *v1alpha2.PingSource {
 	return b.pingSource
 }
diff --git a/pkg/util/parsing_helper.go b/pkg/util/parsing_helper.go
index 0837a4f8..c800be7c 100644
--- a/pkg/util/parsing_helper.go
+++ b/pkg/util/parsing_helper.go
@@ -108,6 +108,10 @@ func AddedAndRemovalListsFromArray(m []string) ([]string, []string) {
 // separated by a delimiter and returns a map where keys are mapped to their respective values.
 // If allowSingles is true, values without a delimiter will be added as keys pointing to empty strings
 func mapFromArray(arr []string, delimiter string, allowSingles bool) (map[string]string, error) {
+	if len(arr) == 0 {
+		return nil, nil
+	}
+
 	returnMap := map[string]string{}
 	for _, pairStr := range arr {
 		pairSlice := strings.SplitN(pairStr, delimiter, 2)


Test patch:
diff --git a/pkg/kn/commands/source/apiserver/apiserver_test.go b/pkg/kn/commands/source/apiserver/apiserver_test.go
index 8e9d2478..0f7d21c9 100644
--- a/pkg/kn/commands/source/apiserver/apiserver_test.go
+++ b/pkg/kn/commands/source/apiserver/apiserver_test.go
@@ -83,7 +83,7 @@ func cleanupAPIServerMockClient() {
 	apiServerSourceClientFactory = nil
 }
 
-func createAPIServerSource(name, resourceKind, resourceVersion, serviceAccount, mode, service string) *v1alpha2.ApiServerSource {
+func createAPIServerSource(name, resourceKind, resourceVersion, serviceAccount, mode, service string, ceOverrides map[string]string) *v1alpha2.ApiServerSource {
 	resources := []v1alpha2.APIVersionKindSelector{{
 		APIVersion: resourceVersion,
 		Kind:       resourceKind,
@@ -102,5 +102,6 @@ func createAPIServerSource(name, resourceKind, resourceVersion, serviceAccount,
 		ServiceAccount(serviceAccount).
 		EventMode(mode).
 		Sink(sink).
+		CloudEventOverrides(ceOverrides, []string{}).
 		Build()
 }
diff --git a/pkg/kn/commands/source/apiserver/create_test.go b/pkg/kn/commands/source/apiserver/create_test.go
index 619cd1cf..e0bc78d4 100644
--- a/pkg/kn/commands/source/apiserver/create_test.go
+++ b/pkg/kn/commands/source/apiserver/create_test.go
@@ -35,9 +35,9 @@ func TestCreateApiServerSource(t *testing.T) {
 	apiServerClient := v1alpha2.NewMockKnAPIServerSourceClient(t)
 
 	apiServerRecorder := apiServerClient.Recorder()
-	apiServerRecorder.CreateAPIServerSource(createAPIServerSource("testsource", "Event", "v1", "testsa", "Reference", "testsvc"), nil)
+	apiServerRecorder.CreateAPIServerSource(createAPIServerSource("testsource", "Event", "v1", "testsa", "Reference", "testsvc", map[string]string{"bla": "blub", "foo": "bar"}), nil)
 
-	out, err := executeAPIServerSourceCommand(apiServerClient, dynamicClient, "create", "testsource", "--resource", "Event:v1", "--service-account", "testsa", "--sink", "svc:testsvc", "--mode", "Reference")
+	out, err := executeAPIServerSourceCommand(apiServerClient, dynamicClient, "create", "testsource", "--resource", "Event:v1", "--service-account", "testsa", "--sink", "svc:testsvc", "--mode", "Reference", "--ce-override", "bla=blub", "--ce-override", "foo=bar")
 	assert.NilError(t, err, "ApiServer source should be created")
 	util.ContainsAll(out, "created", "default", "testsource")
 
diff --git a/pkg/kn/commands/source/apiserver/describe_test.go b/pkg/kn/commands/source/apiserver/describe_test.go
index ffa7ae6e..8abfca2e 100644
--- a/pkg/kn/commands/source/apiserver/describe_test.go
+++ b/pkg/kn/commands/source/apiserver/describe_test.go
@@ -28,12 +28,12 @@ func TestSimpleDescribe(t *testing.T) {
 	apiServerClient := v1alpha2.NewMockKnAPIServerSourceClient(t, "mynamespace")
 
 	apiServerRecorder := apiServerClient.Recorder()
-	sampleSource := createAPIServerSource("testsource", "Event", "v1", "testsa", "Reference", "testsvc")
+	sampleSource := createAPIServerSource("testsource", "Event", "v1", "testsa", "Reference", "testsvc", map[string]string{"foo": "bar"})
 	apiServerRecorder.GetAPIServerSource("testsource", sampleSource, nil)
 
 	out, err := executeAPIServerSourceCommand(apiServerClient, nil, "describe", "testsource")
 	assert.NilError(t, err)
-	util.ContainsAll(out, "testsource", "testsa", "Reference", "testsvc", "Service", "Resources", "Event", "v1", "false", "Conditions")
+	util.ContainsAll(out, "testsource", "testsa", "Reference", "testsvc", "Service", "Resources", "Event", "v1", "false", "Conditions", "foo", "bar")
 
 	apiServerRecorder.Validate()
 }
diff --git a/pkg/kn/commands/source/apiserver/list_test.go b/pkg/kn/commands/source/apiserver/list_test.go
index 8aae3cf2..fbc63ad6 100644
--- a/pkg/kn/commands/source/apiserver/list_test.go
+++ b/pkg/kn/commands/source/apiserver/list_test.go
@@ -29,7 +29,7 @@ func TestListAPIServerSource(t *testing.T) {
 	apiServerClient := v1alpha22.NewMockKnAPIServerSourceClient(t)
 
 	apiServerRecorder := apiServerClient.Recorder()
-	sampleSource := createAPIServerSource("testsource", "Event", "v1", "testsa", "Reference", "testsvc")
+	sampleSource := createAPIServerSource("testsource", "Event", "v1", "testsa", "Reference", "testsvc", nil)
 	sampleSourceList := v1alpha2.ApiServerSourceList{}
 	sampleSourceList.Items = []v1alpha2.ApiServerSource{*sampleSource}
 
diff --git a/pkg/kn/commands/source/apiserver/update_test.go b/pkg/kn/commands/source/apiserver/update_test.go
index 932704a3..7dac5589 100644
--- a/pkg/kn/commands/source/apiserver/update_test.go
+++ b/pkg/kn/commands/source/apiserver/update_test.go
@@ -37,13 +37,13 @@ func TestApiServerSourceUpdate(t *testing.T) {
 
 	apiServerRecorder := apiServerClient.Recorder()
 
-	present := createAPIServerSource("testsource", "Event", "v1", "testsa1", "Reference", "svc1")
+	present := createAPIServerSource("testsource", "Event", "v1", "testsa1", "Reference", "svc1", map[string]string{"bla": "blub", "foo": "bar"})
 	apiServerRecorder.GetAPIServerSource("testsource", present, nil)
 
-	updated := createAPIServerSource("testsource", "Event", "v1", "testsa2", "Reference", "svc2")
+	updated := createAPIServerSource("testsource", "Event", "v1", "testsa2", "Reference", "svc2", map[string]string{"foo": "baz"})
 	apiServerRecorder.UpdateAPIServerSource(updated, nil)
 
-	output, err := executeAPIServerSourceCommand(apiServerClient, dynamicClient, "update", "testsource", "--service-account", "testsa2", "--sink", "svc:svc2")
+	output, err := executeAPIServerSourceCommand(apiServerClient, dynamicClient, "update", "testsource", "--service-account", "testsa2", "--sink", "svc:svc2", "--ce-override", "bla-", "--ce-override", "foo=baz")
 	assert.NilError(t, err)
 	assert.Assert(t, util.ContainsAll(output, "testsource", "updated", "default"))
 
@@ -54,7 +54,7 @@ func TestApiServerSourceUpdateDeletionTimestampNotNil(t *testing.T) {
 	apiServerClient := v1alpha2.NewMockKnAPIServerSourceClient(t)
 	apiServerRecorder := apiServerClient.Recorder()
 
-	present := createAPIServerSource("testsource", "Event", "v1", "testsa1", "Ref", "svc1")
+	present := createAPIServerSource("testsource", "Event", "v1", "testsa1", "Ref", "svc1", nil)
 	present.DeletionTimestamp = &metav1.Time{Time: time.Now()}
 	apiServerRecorder.GetAPIServerSource("testsource", present, nil)
 
diff --git a/pkg/kn/commands/source/binding/binding_test.go b/pkg/kn/commands/source/binding/binding_test.go
index a4e28b27..2c814048 100644
--- a/pkg/kn/commands/source/binding/binding_test.go
+++ b/pkg/kn/commands/source/binding/binding_test.go
@@ -92,10 +92,9 @@ func createSinkBinding(name, service string, subjectGvk schema.GroupVersionKind,
 		Sink(&sink).
 		SubjectGVK(&subjectGvk).
 		SubjectName(subjectName).
-		SubjectNamespace("default")
-	if ceOverrides != nil {
-		builder.AddCloudEventOverrides(ceOverrides)
-	}
+		SubjectNamespace("default").
+		CloudEventOverrides(ceOverrides, []string{})
+
 	binding, _ := builder.Build()
 	return binding
 }
diff --git a/pkg/kn/commands/source/binding/update_test.go b/pkg/kn/commands/source/binding/update_test.go
index fba37abc..ccf63a03 100644
--- a/pkg/kn/commands/source/binding/update_test.go
+++ b/pkg/kn/commands/source/binding/update_test.go
@@ -38,10 +38,11 @@ func TestSimpleBindingUpdate(t *testing.T) {
 
 	bindingRecorder := sinkBindingClient.Recorder()
 	ceOverrideMap := map[string]string{"bla": "blub", "foo": "bar"}
+	ceOverrideMapUpdated := map[string]string{"foo": "baz", "new": "ceoverride"}
 	bindingRecorder.GetSinkBinding("testbinding", createSinkBinding("testbinding", "mysvc", deploymentGvk, "mydeploy", ceOverrideMap), nil)
-	bindingRecorder.UpdateSinkBinding(createSinkBinding("testbinding", "othersvc", deploymentGvk, "mydeploy", ceOverrideMap), nil)
+	bindingRecorder.UpdateSinkBinding(createSinkBinding("testbinding", "othersvc", deploymentGvk, "mydeploy", ceOverrideMapUpdated), nil)
 
-	out, err := executeSinkBindingCommand(sinkBindingClient, dynamicClient, "update", "testbinding", "--sink", "svc:othersvc", "--ce-override", "bla=blub", "--ce-override", "foo=bar")
+	out, err := executeSinkBindingCommand(sinkBindingClient, dynamicClient, "update", "testbinding", "--sink", "svc:othersvc", "--ce-override", "bla-", "--ce-override", "foo=baz", "--ce-override", "new=ceoverride")
 	assert.NilError(t, err)
 	util.ContainsAll(out, "updated", "default", "testbinding", "foo", "bar")
 
diff --git a/pkg/kn/commands/source/ping/create_test.go b/pkg/kn/commands/source/ping/create_test.go
index c331b84f..783bcc29 100644
--- a/pkg/kn/commands/source/ping/create_test.go
+++ b/pkg/kn/commands/source/ping/create_test.go
@@ -37,9 +37,9 @@ func TestSimpleCreatePingSource(t *testing.T) {
 	pingClient := v1alpha2.NewMockKnPingSourceClient(t)
 
 	pingRecorder := pingClient.Recorder()
-	pingRecorder.CreatePingSource(createPingSource("testsource", "* * * * */2", "maxwell", "mysvc"), nil)
+	pingRecorder.CreatePingSource(createPingSource("testsource", "* * * * */2", "maxwell", "mysvc", map[string]string{"bla": "blub", "foo": "bar"}), nil)
 
-	out, err := executePingSourceCommand(pingClient, dynamicClient, "create", "--sink", "svc:mysvc", "--schedule", "* * * * */2", "--data", "maxwell", "testsource")
+	out, err := executePingSourceCommand(pingClient, dynamicClient, "create", "--sink", "svc:mysvc", "--schedule", "* * * * */2", "--data", "maxwell", "testsource", "--ce-override", "bla=blub", "--ce-override", "foo=bar")
 	assert.NilError(t, err, "Source should have been created")
 	util.ContainsAll(out, "created", "default", "testsource")
 
diff --git a/pkg/kn/commands/source/ping/describe_test.go b/pkg/kn/commands/source/ping/describe_test.go
index 1c57e8ab..e226cd05 100644
--- a/pkg/kn/commands/source/ping/describe_test.go
+++ b/pkg/kn/commands/source/ping/describe_test.go
@@ -32,11 +32,12 @@ func TestDescribeRef(t *testing.T) {
 	pingClient := clientv1alpha2.NewMockKnPingSourceClient(t, "mynamespace")
 
 	pingRecorder := pingClient.Recorder()
-	pingRecorder.GetPingSource("testsource-ref", getPingSourceSinkRef(), nil)
+	pingRecorder.GetPingSource("testping",
+		createPingSource("testping", "*/2 * * * *", "test", "testsvc", map[string]string{"foo": "bar"}), nil)
 
-	out, err := executePingSourceCommand(pingClient, nil, "describe", "testsource-ref")
+	out, err := executePingSourceCommand(pingClient, nil, "describe", "testping")
 	assert.NilError(t, err)
-	assert.Assert(t, util.ContainsAll(out, "1 2 3 4 5", "honeymoon", "myservicenamespace", "mysvc", "Service", "testsource-ref"))
+	assert.Assert(t, util.ContainsAll(out, "*/2 * * * *", "test", "testsvc", "Service", "Overrides", "foo", "bar", "Conditions"))
 
 	pingRecorder.Validate()
 }
@@ -68,29 +69,6 @@ func TestDescribeError(t *testing.T) {
 
 }
 
-func getPingSourceSinkRef() *v1alpha2.PingSource {
-	return &v1alpha2.PingSource{
-		TypeMeta: metav1.TypeMeta{},
-		ObjectMeta: metav1.ObjectMeta{
-			Name: "testsource-ref",
-		},
-		Spec: v1alpha2.PingSourceSpec{
-			Schedule: "1 2 3 4 5",
-			JsonData: "honeymoon",
-			SourceSpec: duckv1.SourceSpec{
-				Sink: duckv1.Destination{
-					Ref: &duckv1.KReference{
-						Kind:      "Service",
-						Namespace: "myservicenamespace",
-						Name:      "mysvc",
-					},
-				},
-			},
-		},
-		Status: v1alpha2.PingSourceStatus{},
-	}
-}
-
 func getPingSourceSinkURI() *v1alpha2.PingSource {
 	return &v1alpha2.PingSource{
 		TypeMeta: metav1.TypeMeta{},
diff --git a/pkg/kn/commands/source/ping/list_test.go b/pkg/kn/commands/source/ping/list_test.go
index 4663829b..d770f51e 100644
--- a/pkg/kn/commands/source/ping/list_test.go
+++ b/pkg/kn/commands/source/ping/list_test.go
@@ -29,7 +29,7 @@ func TestListPingSource(t *testing.T) {
 	pingClient := clientv1alpha2.NewMockKnPingSourceClient(t)
 
 	pingRecorder := pingClient.Recorder()
-	cJSource := createPingSource("testsource", "* * * * */2", "maxwell", "mysvc")
+	cJSource := createPingSource("testsource", "* * * * */2", "maxwell", "mysvc", nil)
 	cJSourceList := v1alpha2.PingSourceList{}
 	cJSourceList.Items = []v1alpha2.PingSource{*cJSource}
 
diff --git a/pkg/kn/commands/source/ping/ping_test.go b/pkg/kn/commands/source/ping/ping_test.go
index 2543a2c0..9bc71740 100644
--- a/pkg/kn/commands/source/ping/ping_test.go
+++ b/pkg/kn/commands/source/ping/ping_test.go
@@ -81,7 +81,7 @@ func cleanupPingMockClient() {
 	pingSourceClientFactory = nil
 }
 
-func createPingSource(name, schedule, data, service string) *v1alpha2.PingSource {
+func createPingSource(name, schedule, data, service string, ceOverridesMap map[string]string) *v1alpha2.PingSource {
 	sink := &v1.Destination{
 		Ref: &v1.KReference{Name: service, Kind: "Service", APIVersion: "serving.knative.dev/v1", Namespace: "default"},
 	}
@@ -89,5 +89,6 @@ func createPingSource(name, schedule, data, service string) *v1alpha2.PingSource
 		Schedule(schedule).
 		JsonData(data).
 		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 59db0b9a..1b210263 100644
--- a/pkg/kn/commands/source/ping/update_test.go
+++ b/pkg/kn/commands/source/ping/update_test.go
@@ -21,40 +21,16 @@ import (
 
 	"gotest.tools/assert"
 	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-	v1alpha2 "knative.dev/eventing/pkg/apis/sources/v1alpha2"
-	duckv1 "knative.dev/pkg/apis/duck/v1"
 
 	clientv1alpha2 "knative.dev/client/pkg/sources/v1alpha2"
 	"knative.dev/client/pkg/util"
 )
 
 func TestSimplePingUpdate(t *testing.T) {
-
 	pingSourceClient := clientv1alpha2.NewMockKnPingSourceClient(t)
-
 	pingRecorder := pingSourceClient.Recorder()
-	pingRecorder.GetPingSource("testsource", &v1alpha2.PingSource{
-		TypeMeta: v1.TypeMeta{},
-		ObjectMeta: v1.ObjectMeta{
-			Name: "testsource",
-		},
-		Spec: v1alpha2.PingSourceSpec{
-			Schedule: "1 2 3 4 5",
-			JsonData: "maxwell",
-			SourceSpec: duckv1.SourceSpec{
-				Sink: duckv1.Destination{
-					Ref: &duckv1.KReference{
-						Kind:       "Service",
-						Name:       "mysvc",
-						APIVersion: "serving.knative.dev/v1",
-						Namespace:  "default",
-					},
-				},
-			},
-		},
-		Status: v1alpha2.PingSourceStatus{},
-	}, nil)
-	pingRecorder.UpdatePingSource(createPingSource("testsource", "* * * * */3", "maxwell", "mysvc"), nil)
+	pingRecorder.GetPingSource("testsource", createPingSource("testsource", "* * * * */1", "maxwell", "mysvc", nil), nil)
+	pingRecorder.UpdatePingSource(createPingSource("testsource", "* * * * */3", "maxwell", "mysvc", nil), nil)
 
 	out, err := executePingSourceCommand(pingSourceClient, nil, "update", "--schedule", "* * * * */3", "testsource")
 	assert.NilError(t, err)
@@ -63,6 +39,21 @@ func TestSimplePingUpdate(t *testing.T) {
 	pingRecorder.Validate()
 }
 
+func TestSimplePingUpdateCEOverrides(t *testing.T) {
+	pingSourceClient := clientv1alpha2.NewMockKnPingSourceClient(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", "maxwell", "mysvc", ceOverrideMapUpdated), nil)
+
+	out, err := executePingSourceCommand(pingSourceClient, nil, "update", "--schedule", "* * * * */3", "testsource", "--ce-override", "bla-", "--ce-override", "foo=baz", "--ce-override", "new=ceoverride")
+	assert.NilError(t, err)
+	util.ContainsAll(out, "updated", "default", "testsource")
+
+	pingRecorder.Validate()
+}
+
 func TestUpdateError(t *testing.T) {
 	pingClient := clientv1alpha2.NewMockKnPingSourceClient(t, "mynamespace")
 
@@ -78,7 +69,7 @@ func TestUpdateError(t *testing.T) {
 
 func TestPingUpdateDeletionTimestampNotNil(t *testing.T) {
 	pingSourceClient := clientv1alpha2.NewMockKnPingSourceClient(t)
-	present := createPingSource("test", "", "", "")
+	present := createPingSource("test", "", "", "", nil)
 	present.DeletionTimestamp = &v1.Time{Time: time.Now()}
 	pingRecorder := pingSourceClient.Recorder()
 	pingRecorder.GetPingSource("test", present, nil)
diff --git a/pkg/sources/v1alpha2/apiserver_client_test.go b/pkg/sources/v1alpha2/apiserver_client_test.go
index ad62142f..3e18dbbf 100644
--- a/pkg/sources/v1alpha2/apiserver_client_test.go
+++ b/pkg/sources/v1alpha2/apiserver_client_test.go
@@ -130,13 +130,16 @@ func TestListAPIServerSource(t *testing.T) {
 }
 
 func newAPIServerSource(name, resource string) *v1alpha2.ApiServerSource {
-	b := NewAPIServerSourceBuilder(name).ServiceAccount("testsa").EventMode("Reference")
-	b.Sink(duckv1.Destination{
-		Ref: &duckv1.KReference{
-			Kind:      "Service",
-			Name:      "foosvc",
-			Namespace: "default",
-		}})
+	b := NewAPIServerSourceBuilder(name).
+		ServiceAccount("testsa").
+		EventMode("Reference").
+		CloudEventOverrides(map[string]string{"type": "foo"}, []string{}).
+		Sink(duckv1.Destination{
+			Ref: &duckv1.KReference{
+				Kind:      "Service",
+				Name:      "foosvc",
+				Namespace: "default",
+			}})
 
 	if resource != "" {
 		b.Resources([]v1alpha2.APIVersionKindSelector{{
diff --git a/pkg/sources/v1alpha2/binding_client_test.go b/pkg/sources/v1alpha2/binding_client_test.go
index 34803d11..2677268d 100644
--- a/pkg/sources/v1alpha2/binding_client_test.go
+++ b/pkg/sources/v1alpha2/binding_client_test.go
@@ -134,9 +134,9 @@ func TestListSinkBinding(t *testing.T) {
 	})
 }
 
-func TestSinkBindingBuilderAddCloudEventOverrides(t *testing.T) {
+func TestSinkBindingBuilderCloudEventOverrides(t *testing.T) {
 	aBuilder := NewSinkBindingBuilder("testsinkbinding")
-	aBuilder.AddCloudEventOverrides(map[string]string{"type": "foo"})
+	aBuilder.CloudEventOverrides(map[string]string{"type": "foo"}, []string{})
 	a, err := aBuilder.Build()
 	assert.NilError(t, err)
 
@@ -145,7 +145,7 @@ func TestSinkBindingBuilderAddCloudEventOverrides(t *testing.T) {
 		b, err := bBuilder.Build()
 		assert.NilError(t, err)
 		assert.DeepEqual(t, b, a)
-		bBuilder.AddCloudEventOverrides(map[string]string{"type": "new"})
+		bBuilder.CloudEventOverrides(map[string]string{"type": "new"}, []string{})
 		expected := &duckv1.CloudEventOverrides{
 			Extensions: map[string]string{
 				"type": "new",
@@ -160,7 +160,7 @@ func TestSinkBindingBuilderAddCloudEventOverrides(t *testing.T) {
 		b, err := bBuilder.Build()
 		assert.NilError(t, err)
 		assert.DeepEqual(t, b, a)
-		bBuilder.AddCloudEventOverrides(map[string]string{"source": "bar"})
+		bBuilder.CloudEventOverrides(map[string]string{"source": "bar"}, []string{})
 		expected := &duckv1.CloudEventOverrides{
 			Extensions: map[string]string{
 				"type":   "foo",
@@ -169,6 +169,21 @@ func TestSinkBindingBuilderAddCloudEventOverrides(t *testing.T) {
 		}
 		assert.DeepEqual(t, expected, b.Spec.CloudEventOverrides)
 	})
+
+	t.Run("update bindings with new entry and removed entry", func(t *testing.T) {
+		bBuilder := NewSinkBindingBuilderFromExisting(a)
+		c, err := bBuilder.Build()
+		assert.NilError(t, err)
+		assert.DeepEqual(t, c, a)
+		bBuilder.CloudEventOverrides(map[string]string{"new": "entry"}, []string{"type"})
+		expected := &duckv1.CloudEventOverrides{
+			Extensions: map[string]string{
+				"new": "entry",
+			},
+		}
+		assert.DeepEqual(t, expected, c.Spec.CloudEventOverrides)
+	})
+
 }
 
 func TestSinkBindingBuilderForSubjectError(t *testing.T) {
@@ -246,7 +261,7 @@ func newSinkBinding(name, sinkService, pingName string) *v1alpha2.SinkBinding {
 		Sink(sink).
 		SubjectGVK(&schema.GroupVersionKind{"batch", "v1beta1", "CronJob"}).
 		SubjectName(pingName).
-		AddCloudEventOverrides(map[string]string{"type": "foo"}).
+		CloudEventOverrides(map[string]string{"type": "foo"}, []string{}).
 		Build()
 	return b
 }
diff --git a/pkg/sources/v1alpha2/ping_client_test.go b/pkg/sources/v1alpha2/ping_client_test.go
index 1998afd7..c33352f6 100644
--- a/pkg/sources/v1alpha2/ping_client_test.go
+++ b/pkg/sources/v1alpha2/ping_client_test.go
@@ -135,7 +135,8 @@ func TestListPingSource(t *testing.T) {
 func newPingSource(name string, sink string) *v1alpha2.PingSource {
 	b := NewPingSourceBuilder(name).
 		Schedule("* * * * *").
-		JsonData("mydata")
+		JsonData("mydata").
+		CloudEventOverrides(map[string]string{"type": "foo"}, []string{})
 
 	if sink != "" {
 		b.Sink(