Repository: knative/client (go)

Issue:
Add serviceaccount & resources config to pingsource source handling
`kn source cronjob` create & update command should support to provide

* seviceAccount name
* requests & limits (like for services)

Pull request:
Add serviceaccount and resources config to cronjob source handling
Fixes #564

## Proposed Changes

* Add flag `service-account` to cronjob commands to handle service account name
* Add flag `requests-cpu`,`requests-memory`,`limits-cpu` and `limits-memory` to cronjob commands to handle resources configuration
* Update `cronjob describe` to include the new flags
* Update unit tests and e2e test accordingly

Patch:
diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc
index 1d542973..e801f8e7 100644
--- a/CHANGELOG.adoc
+++ b/CHANGELOG.adoc
@@ -18,6 +18,10 @@
 |===
 | | Description | PR
 
+| ✨
+| Add serviceaccount and resources config to cronjob source handling
+| https://github.com/knative/client/issues/564[#564]
+
 | ✨
 | Allow configuration sink prefixes
 | https://github.com/knative/client/pull/571[#571]
diff --git a/docs/cmd/kn_source_cronjob_create.md b/docs/cmd/kn_source_cronjob_create.md
index 161eddc7..2fda38fe 100644
--- a/docs/cmd/kn_source_cronjob_create.md
+++ b/docs/cmd/kn_source_cronjob_create.md
@@ -16,16 +16,27 @@ kn source cronjob create NAME --schedule SCHEDULE --sink SINK --data DATA [flags
 
   # Create a crontab scheduler 'my-cron-trigger' which fires every minute and sends 'ping' to service 'mysvc' as a cloudevent
   kn source cronjob create my-cron-trigger --schedule "* * * * */1" --data "ping" --sink svc:mysvc
+  
+  # Create a crontab scheduler 'my-cron-trigger' with ServiceAccount name
+  kn source cronjob create my-cron-trigger1 --schedule "* * * * */1" --data "ping" --sink svc:event-display --service-account myaccount
+
+  # Create a crontab scheduler 'my-cron-trigger' with requested resources
+  kn source cronjob create my-cron-trigger1 --schedule "* * * * */1" --data "ping" --sink svc:event-display --requests-cpu 100m --requests-memory 128Mi
 ```
 
 ### Options
 
 ```
-  -d, --data string        String data to send
-  -h, --help               help for create
-  -n, --namespace string   Specify the namespace to operate in.
-      --schedule string    Schedule specification in crontab format (e.g. '* * * * */2' for every two minutes
-  -s, --sink string        Addressable sink for events
+  -d, --data string              String data to send
+  -h, --help                     help for create
+      --limits-cpu string        The limits on the requested CPU (e.g., 1000m).
+      --limits-memory string     The limits on the requested memory (e.g., 1024Mi).
+  -n, --namespace string         Specify the namespace to operate in.
+      --requests-cpu string      The requested CPU (e.g., 250m).
+      --requests-memory string   The requested memory (e.g., 64Mi).
+      --schedule string          Schedule specification in crontab format (e.g. '* * * * */2' for every two minutes
+      --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_cronjob_update.md b/docs/cmd/kn_source_cronjob_update.md
index caa9767a..ef7d9331 100644
--- a/docs/cmd/kn_source_cronjob_update.md
+++ b/docs/cmd/kn_source_cronjob_update.md
@@ -21,11 +21,16 @@ kn source cronjob update NAME --schedule SCHEDULE --sink SERVICE --data DATA [fl
 ### Options
 
 ```
-  -d, --data string        String data to send
-  -h, --help               help for update
-  -n, --namespace string   Specify the namespace to operate in.
-      --schedule string    Schedule specification in crontab format (e.g. '* * * * */2' for every two minutes
-  -s, --sink string        Addressable sink for events
+  -d, --data string              String data to send
+  -h, --help                     help for update
+      --limits-cpu string        The limits on the requested CPU (e.g., 1000m).
+      --limits-memory string     The limits on the requested memory (e.g., 1024Mi).
+  -n, --namespace string         Specify the namespace to operate in.
+      --requests-cpu string      The requested CPU (e.g., 250m).
+      --requests-memory string   The requested memory (e.g., 64Mi).
+      --schedule string          Schedule specification in crontab format (e.g. '* * * * */2' for every two minutes
+      --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/go.mod b/go.mod
index 09b20bed..6439cff3 100644
--- a/go.mod
+++ b/go.mod
@@ -23,7 +23,7 @@ require (
 	knative.dev/eventing v0.12.1-0.20200206203632-b0a7d8a77cc7
 	knative.dev/pkg v0.0.0-20200206191432-9072f494c940
 	knative.dev/serving v0.12.1-0.20200206201132-525b15d87dc1
-	knative.dev/test-infra v0.0.0-20200206203333-08d3515c0e1f
+	knative.dev/test-infra v0.0.0-20200229011351-4dac123b9a3d
 	sigs.k8s.io/yaml v1.1.0
 )
 
diff --git a/go.sum b/go.sum
index e9cd9100..bad85985 100644
--- a/go.sum
+++ b/go.sum
@@ -593,8 +593,8 @@ knative.dev/pkg v0.0.0-20200206191432-9072f494c940 h1:bbmIixxihewJeoxolF7jRgSzER
 knative.dev/pkg v0.0.0-20200206191432-9072f494c940/go.mod h1:pgODObA1dTyhNoFxPZTTjNWfx6F0aKsKzn+vaT9XO/Q=
 knative.dev/serving v0.12.1-0.20200206201132-525b15d87dc1 h1:fEj1JY8FwjyQ3bB7+nMGrKX/apOwIJOB7UVuLzIYkmA=
 knative.dev/serving v0.12.1-0.20200206201132-525b15d87dc1/go.mod h1:x2n255JS2XBI39tmjZ8CwTxIf9EKNMCrkVuiOttLRm0=
-knative.dev/test-infra v0.0.0-20200206203333-08d3515c0e1f h1:3zlcV5Q15wBr+h82Ys05O14rEQsIX3PBfiZVYrr0JGo=
-knative.dev/test-infra v0.0.0-20200206203333-08d3515c0e1f/go.mod h1:xcdUkMJrLlBswIZqL5zCuBFOC22WIPMQoVX1L35i0vQ=
+knative.dev/test-infra v0.0.0-20200229011351-4dac123b9a3d h1:YlscBzPOL3Rfyl8844/wHhJNL5uiHDdyaRn6IEVvv64=
+knative.dev/test-infra v0.0.0-20200229011351-4dac123b9a3d/go.mod h1:xcdUkMJrLlBswIZqL5zCuBFOC22WIPMQoVX1L35i0vQ=
 modernc.org/cc v1.0.0/go.mod h1:1Sk4//wdnYJiUIxnW8ddKpaOJCF37yAdqYnkxUpaYxw=
 modernc.org/golex v1.0.0/go.mod h1:b/QX9oBD/LhixY6NDh+IdGv17hgB+51fET1i2kPSmvk=
 modernc.org/mathutil v1.0.0/go.mod h1:wU0vUrJsVWBZ4P6e7xtFJEhFSNsfRLJ8H458uRjg03k=
diff --git a/pkg/eventing/legacysources/v1alpha1/cronjob_client.go b/pkg/eventing/legacysources/v1alpha1/cronjob_client.go
index 7069cc80..ec3ed170 100644
--- a/pkg/eventing/legacysources/v1alpha1/cronjob_client.go
+++ b/pkg/eventing/legacysources/v1alpha1/cronjob_client.go
@@ -158,6 +158,31 @@ func (b *CronJobSourceBuilder) Sink(sink *duckv1beta1.Destination) *CronJobSourc
 	return b
 }
 
+func (b *CronJobSourceBuilder) ServiceAccount(sa string) *CronJobSourceBuilder {
+	b.cronjobSource.Spec.ServiceAccountName = sa
+	return b
+}
+
+func (b *CronJobSourceBuilder) ResourceRequestsCPU(cpu string) *CronJobSourceBuilder {
+	b.cronjobSource.Spec.Resources.Requests.ResourceCPU = cpu
+	return b
+}
+
+func (b *CronJobSourceBuilder) ResourceRequestsMemory(memory string) *CronJobSourceBuilder {
+	b.cronjobSource.Spec.Resources.Requests.ResourceMemory = memory
+	return b
+}
+
+func (b *CronJobSourceBuilder) ResourceLimitsCPU(cpu string) *CronJobSourceBuilder {
+	b.cronjobSource.Spec.Resources.Limits.ResourceCPU = cpu
+	return b
+}
+
+func (b *CronJobSourceBuilder) ResourceLimitsMemory(memory string) *CronJobSourceBuilder {
+	b.cronjobSource.Spec.Resources.Limits.ResourceMemory = memory
+	return b
+}
+
 func (b *CronJobSourceBuilder) Build() *v1alpha1.CronJobSource {
 	return b.cronjobSource
 }
diff --git a/pkg/kn/commands/source/cronjob/create.go b/pkg/kn/commands/source/cronjob/create.go
index 1142f4d6..52bbdced 100644
--- a/pkg/kn/commands/source/cronjob/create.go
+++ b/pkg/kn/commands/source/cronjob/create.go
@@ -35,7 +35,13 @@ func NewCronJobCreateCommand(p *commands.KnParams) *cobra.Command {
 		Short: "Create a CronJob source.",
 		Example: `
   # Create a crontab scheduler 'my-cron-trigger' which fires every minute and sends 'ping' to service 'mysvc' as a cloudevent
-  kn source cronjob create my-cron-trigger --schedule "* * * * */1" --data "ping" --sink svc:mysvc`,
+  kn source cronjob create my-cron-trigger --schedule "* * * * */1" --data "ping" --sink svc:mysvc
+  
+  # Create a crontab scheduler 'my-cron-trigger' with ServiceAccount name
+  kn source cronjob create my-cron-trigger1 --schedule "* * * * */1" --data "ping" --sink svc:event-display --service-account myaccount
+
+  # Create a crontab scheduler 'my-cron-trigger' with requested resources
+  kn source cronjob create my-cron-trigger1 --schedule "* * * * */1" --data "ping" --sink svc:event-display --requests-cpu 100m --requests-memory 128Mi`,
 
 		RunE: func(cmd *cobra.Command, args []string) (err error) {
 			if len(args) != 1 {
@@ -68,6 +74,11 @@ func NewCronJobCreateCommand(p *commands.KnParams) *cobra.Command {
 					Schedule(cronUpdateFlags.schedule).
 					Data(cronUpdateFlags.data).
 					Sink(toDuckV1Beta1(destination)).
+					ResourceRequestsCPU(cronUpdateFlags.resourceRequestsCPU).
+					ResourceRequestsMemory(cronUpdateFlags.resourceRequestsMemory).
+					ResourceLimitsCPU(cronUpdateFlags.resourceLimitsCPU).
+					ResourceLimitsMemory(cronUpdateFlags.resourceLimitsMemory).
+					ServiceAccount(cronUpdateFlags.serviceAccountName).
 					Build())
 			if err == nil {
 				fmt.Fprintf(cmd.OutOrStdout(), "CronJob source '%s' created in namespace '%s'.\n", args[0], cronSourceClient.Namespace())
diff --git a/pkg/kn/commands/source/cronjob/describe.go b/pkg/kn/commands/source/cronjob/describe.go
index 86e48acd..da941b81 100644
--- a/pkg/kn/commands/source/cronjob/describe.go
+++ b/pkg/kn/commands/source/cronjob/describe.go
@@ -106,4 +106,12 @@ func writeCronJobSource(dw printers.PrefixWriter, source *v1alpha1.CronJobSource
 	commands.WriteMetadata(dw, &source.ObjectMeta, printDetails)
 	dw.WriteAttribute("Schedule", source.Spec.Schedule)
 	dw.WriteAttribute("Data", source.Spec.Data)
+	dw.WriteAttribute("ServiceAccountName", source.Spec.ServiceAccountName)
+	subWriter1 := dw.WriteAttribute("Resources", "")
+	subWriter2 := subWriter1.WriteAttribute("Requests", "")
+	subWriter2.WriteAttribute("CPU", source.Spec.Resources.Requests.ResourceCPU)
+	subWriter2.WriteAttribute("Memory", source.Spec.Resources.Requests.ResourceMemory)
+	subWriter2 = subWriter1.WriteAttribute("Limits", "")
+	subWriter2.WriteAttribute("CPU", source.Spec.Resources.Limits.ResourceCPU)
+	subWriter2.WriteAttribute("Memory", source.Spec.Resources.Limits.ResourceMemory)
 }
diff --git a/pkg/kn/commands/source/cronjob/flags.go b/pkg/kn/commands/source/cronjob/flags.go
index cb100fb0..b8cb96d8 100644
--- a/pkg/kn/commands/source/cronjob/flags.go
+++ b/pkg/kn/commands/source/cronjob/flags.go
@@ -29,13 +29,24 @@ import (
 )
 
 type cronJobUpdateFlags struct {
-	schedule string
-	data     string
+	schedule               string
+	data                   string
+	resourceRequestsCPU    string
+	resourceRequestsMemory string
+	resourceLimitsCPU      string
+	resourceLimitsMemory   string
+	serviceAccountName     string
 }
 
 func (c *cronJobUpdateFlags) addCronJobFlags(cmd *cobra.Command) {
 	cmd.Flags().StringVar(&c.schedule, "schedule", "", "Schedule specification in crontab format (e.g. '* * * * */2' for every two minutes")
 	cmd.Flags().StringVarP(&c.data, "data", "d", "", "String data to send")
+	cmd.Flags().StringVar(&c.resourceRequestsCPU, "requests-cpu", "", "The requested CPU (e.g., 250m).")
+	cmd.Flags().StringVar(&c.resourceRequestsMemory, "requests-memory", "", "The requested memory (e.g., 64Mi).")
+	cmd.Flags().StringVar(&c.resourceLimitsCPU, "limits-cpu", "", "The limits on the requested CPU (e.g., 1000m).")
+	cmd.Flags().StringVar(&c.resourceLimitsMemory, "limits-memory", "",
+		"The limits on the requested memory (e.g., 1024Mi).")
+	cmd.Flags().StringVar(&c.serviceAccountName, "service-account", "", "Name of the service account to use to run this source")
 }
 
 // CronJobListHandlers handles printing human readable table for `kn source cronjob list` command's output
diff --git a/pkg/kn/commands/source/cronjob/update.go b/pkg/kn/commands/source/cronjob/update.go
index b2c22e3e..e67248df 100644
--- a/pkg/kn/commands/source/cronjob/update.go
+++ b/pkg/kn/commands/source/cronjob/update.go
@@ -76,6 +76,21 @@ func NewCronJobUpdateCommand(p *commands.KnParams) *cobra.Command {
 				}
 				b.Sink(toDuckV1Beta1(destination))
 			}
+			if cmd.Flags().Changed("requests-cpu") {
+				b.ResourceRequestsCPU(cronUpdateFlags.resourceRequestsCPU)
+			}
+			if cmd.Flags().Changed("requests-memory") {
+				b.ResourceRequestsMemory(cronUpdateFlags.resourceRequestsMemory)
+			}
+			if cmd.Flags().Changed("limits-cpu") {
+				b.ResourceLimitsCPU(cronUpdateFlags.resourceLimitsCPU)
+			}
+			if cmd.Flags().Changed("limits-memory") {
+				b.ResourceLimitsMemory(cronUpdateFlags.resourceLimitsMemory)
+			}
+			if cmd.Flags().Changed("service-account") {
+				b.ServiceAccount(cronUpdateFlags.serviceAccountName)
+			}
 			err = cronSourceClient.UpdateCronJobSource(b.Build())
 			if err == nil {
 				fmt.Fprintf(cmd.OutOrStdout(), "CronJob source '%s' updated in namespace '%s'.\n", name, cronSourceClient.Namespace())
diff --git a/vendor/modules.txt b/vendor/modules.txt
index 3a13617a..2d8799f8 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -714,7 +714,7 @@ knative.dev/serving/pkg/client/clientset/versioned/typed/serving/v1/fake
 knative.dev/serving/pkg/gc
 knative.dev/serving/pkg/network
 knative.dev/serving/pkg/reconciler/route/config
-# knative.dev/test-infra v0.0.0-20200206203333-08d3515c0e1f
+# knative.dev/test-infra v0.0.0-20200229011351-4dac123b9a3d
 knative.dev/test-infra/scripts
 # sigs.k8s.io/kustomize v2.0.3+incompatible
 sigs.k8s.io/kustomize/pkg/commands/build


Test patch:
diff --git a/pkg/kn/commands/source/cronjob/create_test.go b/pkg/kn/commands/source/cronjob/create_test.go
index 9c843138..ae99a82e 100644
--- a/pkg/kn/commands/source/cronjob/create_test.go
+++ b/pkg/kn/commands/source/cronjob/create_test.go
@@ -37,9 +37,10 @@ func TestSimpleCreateCronJobSource(t *testing.T) {
 	cronjobClient := clientsourcesv1alpha1.NewMockKnCronJobSourceClient(t)
 
 	cronJobRecorder := cronjobClient.Recorder()
-	cronJobRecorder.CreateCronJobSource(createCronJobSource("testsource", "* * * * */2", "maxwell", "mysvc"), nil)
+	cronJobRecorder.CreateCronJobSource(createCronJobSource("testsource", "* * * * */2", "maxwell", "mysvc", "mysa", "100m", "128Mi", "200m", "256Mi"), nil)
 
-	out, err := executeCronJobSourceCommand(cronjobClient, dynamicClient, "create", "--sink", "svc:mysvc", "--schedule", "* * * * */2", "--data", "maxwell", "testsource")
+	out, err := executeCronJobSourceCommand(cronjobClient, dynamicClient, "create", "--sink", "svc:mysvc", "--schedule", "* * * * */2", "--data", "maxwell",
+		"--service-account", "mysa", "--requests-cpu", "100m", "--requests-memory", "128Mi", "--limits-cpu", "200m", "--limits-memory", "256Mi", "testsource")
 	assert.NilError(t, err, "Source should have been created")
 	util.ContainsAll(out, "created", "default", "testsource")
 
diff --git a/pkg/kn/commands/source/cronjob/cronjob_test.go b/pkg/kn/commands/source/cronjob/cronjob_test.go
index 49546a19..443454f6 100644
--- a/pkg/kn/commands/source/cronjob/cronjob_test.go
+++ b/pkg/kn/commands/source/cronjob/cronjob_test.go
@@ -82,9 +82,18 @@ func cleanupCronJobMockClient() {
 	cronJobSourceClientFactory = nil
 }
 
-func createCronJobSource(name, schedule, data, service string) *v1alpha1.CronJobSource {
+func createCronJobSource(name, schedule, data, service string, sa string, requestcpu string, requestmm string, limitcpu string, limitmm string) *v1alpha1.CronJobSource {
 	sink := &v1beta1.Destination{
 		Ref: &corev1.ObjectReference{Name: service, Kind: "Service", APIVersion: "serving.knative.dev/v1", Namespace: "default"},
 	}
-	return source_client_v1alpha1.NewCronJobSourceBuilder(name).Schedule(schedule).Data(data).Sink(sink).Build()
+	return source_client_v1alpha1.NewCronJobSourceBuilder(name).
+		Schedule(schedule).
+		Data(data).
+		Sink(sink).
+		ResourceRequestsCPU(requestcpu).
+		ResourceRequestsMemory(requestmm).
+		ResourceLimitsCPU(limitcpu).
+		ResourceLimitsMemory(limitmm).
+		ServiceAccount(sa).
+		Build()
 }
diff --git a/pkg/kn/commands/source/cronjob/describe_test.go b/pkg/kn/commands/source/cronjob/describe_test.go
index e54f6c70..e90bf4ee 100644
--- a/pkg/kn/commands/source/cronjob/describe_test.go
+++ b/pkg/kn/commands/source/cronjob/describe_test.go
@@ -37,6 +37,7 @@ func TestSimpleDescribe(t *testing.T) {
 	out, err := executeCronJobSourceCommand(cronjobClient, nil, "describe", "testsource")
 	assert.NilError(t, err)
 	util.ContainsAll(out, "1 2 3 4 5", "honeymoon", "myservicenamespace", "mysvc", "Service", "testsource")
+	util.ContainsAll(out, "myaccount", "100m", "128Mi", "200m", "256Mi")
 
 	cronJobRecorder.Validate()
 
@@ -72,6 +73,17 @@ func getCronJobSource() *v1alpha1.CronJobSource {
 					Name:      "mysvc",
 				},
 			},
+			ServiceAccountName: "myaccount",
+			Resources: v1alpha1.CronJobResourceSpec{
+				Requests: v1alpha1.CronJobRequestsSpec{
+					ResourceCPU:    "100m",
+					ResourceMemory: "128Mi",
+				},
+				Limits: v1alpha1.CronJobLimitsSpec{
+					ResourceCPU:    "200m",
+					ResourceMemory: "256Mi",
+				},
+			},
 		},
 		Status: v1alpha1.CronJobSourceStatus{},
 	}
diff --git a/pkg/kn/commands/source/cronjob/list_test.go b/pkg/kn/commands/source/cronjob/list_test.go
index a99a3ec7..ccf15059 100644
--- a/pkg/kn/commands/source/cronjob/list_test.go
+++ b/pkg/kn/commands/source/cronjob/list_test.go
@@ -28,7 +28,7 @@ func TestListCronJobSource(t *testing.T) {
 	cronjobClient := knsource_v1alpha1.NewMockKnCronJobSourceClient(t)
 
 	cronJobRecorder := cronjobClient.Recorder()
-	cJSource := createCronJobSource("testsource", "* * * * */2", "maxwell", "mysvc")
+	cJSource := createCronJobSource("testsource", "* * * * */2", "maxwell", "mysvc", "mysa", "100m", "128Mi", "200m", "256Mi")
 	cJSourceList := v1alpha1.CronJobSourceList{}
 	cJSourceList.Items = []v1alpha1.CronJobSource{*cJSource}
 
diff --git a/pkg/kn/commands/source/cronjob/update_test.go b/pkg/kn/commands/source/cronjob/update_test.go
index 1d96da2d..5f559090 100644
--- a/pkg/kn/commands/source/cronjob/update_test.go
+++ b/pkg/kn/commands/source/cronjob/update_test.go
@@ -52,9 +52,10 @@ func TestSimpleUpdate(t *testing.T) {
 		},
 		Status: v1alpha1.CronJobSourceStatus{},
 	}, nil)
-	cronJobRecorder.UpdateCronJobSource(createCronJobSource("testsource", "* * * * */3", "maxwell", "mysvc"), nil)
+	cronJobRecorder.UpdateCronJobSource(createCronJobSource("testsource", "* * * * */3", "maxwell", "mysvc", "mysa", "100m", "128Mi", "200m", "256Mi"), nil)
 
-	out, err := executeCronJobSourceCommand(cronjobClient, nil, "update", "--schedule", "* * * * */3", "testsource")
+	out, err := executeCronJobSourceCommand(cronjobClient, nil, "update", "--schedule", "* * * * */3", "--service-account",
+		"mysa", "--requests-cpu", "100m", "--requests-memory", "128Mi", "--limits-cpu", "200m", "--limits-memory", "256Mi", "testsource")
 	assert.NilError(t, err)
 	util.ContainsAll(out, "updated", "default", "testsource")
 
diff --git a/test/e2e/cronjob_test.go b/test/e2e/cronjob_test.go
index 94d04cf9..fd533e12 100644
--- a/test/e2e/cronjob_test.go
+++ b/test/e2e/cronjob_test.go
@@ -57,6 +57,12 @@ func TestSourceCronJob(t *testing.T) {
 	out, err := test.getResourceFieldsWithJSONPath("cronjobsource", "testcronjobsource2", jpSinkRefNameInSpec)
 	assert.NilError(t, err)
 	assert.Equal(t, out, "testsvc1")
+
+	t.Log("create cronJob source with service account and resources")
+	test.cronJobSourceCreateWithResources(t, r, "testcronjobsource3", "* * * * */1", "ping", "svc:testsvc0", "default", "100m", "128Mi", "200m", "256Mi")
+	test.verifyCronJobSourceDescribe(t, r, "testcronjobsource3", "* * * * */1", "ping", "testsvc0", "default", "100m", "128Mi", "200m", "256Mi")
+	test.cronJobSourceUpdateResources(t, r, "testcronjobsource3", "101m", "129Mi", "201m", "257Mi")
+	test.verifyCronJobSourceDescribe(t, r, "testcronjobsource3", "* * * * */1", "ping", "testsvc0", "default", "101m", "129Mi", "201m", "257Mi")
 }
 
 func (test *e2eTest) cronJobSourceCreate(t *testing.T, r *KnRunResultCollector, sourceName string, schedule string, data string, sink string) {
@@ -85,3 +91,24 @@ func (test *e2eTest) cronJobSourceUpdateSink(t *testing.T, r *KnRunResultCollect
 	assert.Check(t, util.ContainsAll(out.Stdout, sourceName, "updated", "namespace", test.kn.namespace))
 	r.AssertNoError(out)
 }
+
+func (test *e2eTest) cronJobSourceCreateWithResources(t *testing.T, r *KnRunResultCollector, sourceName string, schedule string, data string, sink string, sa string, requestcpu string, requestmm string, limitcpu string, limitmm string) {
+	out := test.kn.Run("source", "cronjob", "create", sourceName,
+		"--schedule", schedule, "--data", data, "--sink", sink, "--service-account", sa,
+		"--requests-cpu", requestcpu, "--requests-memory", requestmm, "--limits-cpu", limitcpu, "--limits-memory", limitmm)
+	assert.Check(t, util.ContainsAllIgnoreCase(out.Stdout, "cronjob", "source", sourceName, "created", "namespace", test.kn.namespace))
+	r.AssertNoError(out)
+}
+
+func (test *e2eTest) cronJobSourceUpdateResources(t *testing.T, r *KnRunResultCollector, sourceName string, requestcpu string, requestmm string, limitcpu string, limitmm string) {
+	out := test.kn.Run("source", "cronjob", "update", sourceName,
+		"--requests-cpu", requestcpu, "--requests-memory", requestmm, "--limits-cpu", limitcpu, "--limits-memory", limitmm)
+	assert.Check(t, util.ContainsAllIgnoreCase(out.Stdout, sourceName, "updated", "namespace", test.kn.namespace))
+	r.AssertNoError(out)
+}
+
+func (test *e2eTest) verifyCronJobSourceDescribe(t *testing.T, r *KnRunResultCollector, sourceName string, schedule string, data string, sink string, sa string, requestcpu string, requestmm string, limitcpu string, limitmm string) {
+	out := test.kn.Run("source", "cronjob", "describe", sourceName)
+	assert.Check(t, util.ContainsAllIgnoreCase(out.Stdout, sourceName, schedule, data, sink, sa, requestcpu, requestmm, limitcpu, limitmm))
+	r.AssertNoError(out)
+}
diff --git a/test/presubmit-tests.sh b/test/presubmit-tests.sh
index e6372883..76cb1649 100755
--- a/test/presubmit-tests.sh
+++ b/test/presubmit-tests.sh
@@ -32,8 +32,6 @@
 # Serving / Eventing of this specific version.
 
 export DISABLE_MD_LINTING=1
-# TODO: Enable markdown link check as https://github.com/raviqqe/liche/issues/33 is fixed
-export DISABLE_MD_LINK_CHECK=1
 export PRESUBMIT_TEST_FAIL_FAST=1
 
 export GO111MODULE=on
diff --git a/vendor/knative.dev/test-infra/scripts/README.md b/vendor/knative.dev/test-infra/scripts/README.md
index e6e9f3c7..ca673fb1 100644
--- a/vendor/knative.dev/test-infra/scripts/README.md
+++ b/vendor/knative.dev/test-infra/scripts/README.md
@@ -1,6 +1,6 @@
 # Helper scripts
 
-This directory contains helper scripts used by Prow test jobs, as well and local
+This directory contains helper scripts used by Prow test jobs, as well as local
 development scripts.
 
 ## Using the `presubmit-tests.sh` helper script
@@ -176,6 +176,9 @@ This is a helper script for Knative E2E test scripts. To use it:
    will immediately start the tests against the cluster currently configured for
    `kubectl`.
 
+1. By default `knative_teardown()` and `test_teardown()` will be called after
+   the tests finish, use `--skip-teardowns` if you don't want them to be called.
+
 1. By default Istio is installed on the cluster via Addon, use
    `--skip-istio-addon` if you choose not to have it preinstalled.
 
@@ -307,6 +310,9 @@ This is a helper script for Knative release scripts. To use it:
      if `--release-gcs` was passed, otherwise the default value
      `knative-nightly/<repo>` will be used. It is empty if `--publish` was not
      passed.
+   - `RELEASE_DIR`: contains the directory to store the manifests if
+     `--release-dir` was passed. Defaults to empty value, but if `--nopublish`
+     was passed then points to the repository root directory.
    - `BUILD_COMMIT_HASH`: the commit short hash for the current repo. If the
      current git tree is dirty, it will have `-dirty` appended to it.
    - `BUILD_YYYYMMDD`: current UTC date in `YYYYMMDD` format.
@@ -332,7 +338,7 @@ This is a helper script for Knative release scripts. To use it:
    All environment variables above, except `KO_FLAGS`, are marked read-only once
    `main()` is called (see below).
 
-1. Call the `main()` function passing `$@` (without quotes).
+1. Call the `main()` function passing `"$@"` (with quotes).
 
 ### Sample release script
 
@@ -345,5 +351,5 @@ function build_release() {
   ARTIFACTS_TO_PUBLISH="release.yaml"
 }
 
-main $@
+main "$@"
 ```
diff --git a/vendor/knative.dev/test-infra/scripts/e2e-tests.sh b/vendor/knative.dev/test-infra/scripts/e2e-tests.sh
index 3a2fa89a..a78920dc 100644
--- a/vendor/knative.dev/test-infra/scripts/e2e-tests.sh
+++ b/vendor/knative.dev/test-infra/scripts/e2e-tests.sh
@@ -231,9 +231,12 @@ function create_test_cluster() {
   [[ -n "${GCP_PROJECT}" ]] && test_cmd_args+=" --gcp-project ${GCP_PROJECT}"
   [[ -n "${E2E_SCRIPT_CUSTOM_FLAGS[@]}" ]] && test_cmd_args+=" ${E2E_SCRIPT_CUSTOM_FLAGS[@]}"
   local extra_flags=()
-  if (( IS_BOSKOS )); then # Add arbitrary duration, wait for Boskos projects acquisition before error out
+  if (( IS_BOSKOS )); then
+    # Add arbitrary duration, wait for Boskos projects acquisition before error out
     extra_flags+=(--boskos-wait-duration=20m)
-  else # Only let kubetest tear down the cluster if not using Boskos, it's done by Janitor if using Boskos
+  elif (( ! SKIP_TEARDOWNS )); then
+    # Only let kubetest tear down the cluster if not using Boskos and teardowns are not expected to be skipped,
+    # it's done by Janitor if using Boskos
     extra_flags+=(--down)
   fi
 
@@ -365,7 +368,8 @@ function setup_test_cluster() {
 
   export KO_DATA_PATH="${REPO_ROOT_DIR}/.git"
 
-  trap teardown_test_resources EXIT
+  # Do not run teardowns if we explicitly want to skip them.
+  (( ! SKIP_TEARDOWNS )) && trap teardown_test_resources EXIT
 
   # Handle failures ourselves, so we can dump useful info.
   set +o errexit
@@ -419,6 +423,7 @@ function fail_test() {
 RUN_TESTS=0
 SKIP_KNATIVE_SETUP=0
 SKIP_ISTIO_ADDON=0
+SKIP_TEARDOWNS=0
 GCP_PROJECT=""
 E2E_SCRIPT=""
 E2E_CLUSTER_VERSION=""
@@ -453,6 +458,7 @@ function initialize() {
     case ${parameter} in
       --run-tests) RUN_TESTS=1 ;;
       --skip-knative-setup) SKIP_KNATIVE_SETUP=1 ;;
+      --skip-teardowns) SKIP_TEARDOWNS=1 ;;
       --skip-istio-addon) SKIP_ISTIO_ADDON=1 ;;
       *)
         [[ $# -ge 2 ]] || abort "missing parameter after $1"
@@ -487,6 +493,7 @@ function initialize() {
   readonly EXTRA_CLUSTER_CREATION_FLAGS
   readonly EXTRA_KUBETEST_FLAGS
   readonly SKIP_KNATIVE_SETUP
+  readonly SKIP_TEARDOWNS
   readonly GKE_ADDONS
 
   if (( ! RUN_TESTS )); then
diff --git a/vendor/knative.dev/test-infra/scripts/library.sh b/vendor/knative.dev/test-infra/scripts/library.sh
index 0c921580..d2715650 100644
--- a/vendor/knative.dev/test-infra/scripts/library.sh
+++ b/vendor/knative.dev/test-infra/scripts/library.sh
@@ -135,8 +135,8 @@ function wait_until_pods_running() {
   local failed_pod=""
   for i in {1..150}; do  # timeout after 5 minutes
     local pods="$(kubectl get pods --no-headers -n $1 2>/dev/null)"
-    # All pods must be running
-    local not_running_pods=$(echo "${pods}" | grep -v Running | grep -v Completed)
+    # All pods must be running (ignore ImagePull error to allow the pod to retry)
+    local not_running_pods=$(echo "${pods}" | grep -v Running | grep -v Completed | grep -v ErrImagePull | grep -v ImagePullBackOff)
     if [[ -n "${pods}" ]] && [[ -z "${not_running_pods}" ]]; then
       # All Pods are running or completed. Verify the containers on each Pod.
       local all_ready=1
diff --git a/vendor/knative.dev/test-infra/scripts/presubmit-tests.sh b/vendor/knative.dev/test-infra/scripts/presubmit-tests.sh
index 85897acc..c14f2517 100644
--- a/vendor/knative.dev/test-infra/scripts/presubmit-tests.sh
+++ b/vendor/knative.dev/test-infra/scripts/presubmit-tests.sh
@@ -127,9 +127,9 @@ function report_build_test() {
 # Perform markdown build tests if necessary, unless disabled.
 function markdown_build_tests() {
   (( DISABLE_MD_LINTING && DISABLE_MD_LINK_CHECK )) && return 0
-  # Get changed markdown files (ignore /vendor and deleted files)
+  # Get changed markdown files (ignore /vendor, github templates, and deleted files)
   local mdfiles=""
-  for file in $(echo "${CHANGED_FILES}" | grep \.md$ | grep -v ^vendor/); do
+  for file in $(echo "${CHANGED_FILES}" | grep \.md$ | grep -v ^vendor/ | grep -v ^.github/); do
     [[ -f "${file}" ]] && mdfiles="${mdfiles} ${file}"
   done
   [[ -z "${mdfiles}" ]] && return 0
diff --git a/vendor/knative.dev/test-infra/scripts/release.sh b/vendor/knative.dev/test-infra/scripts/release.sh
index 8e19f2d0..915fc97f 100644
--- a/vendor/knative.dev/test-infra/scripts/release.sh
+++ b/vendor/knative.dev/test-infra/scripts/release.sh
@@ -93,9 +93,9 @@ RELEASE_VERSION=""
 RELEASE_NOTES=""
 RELEASE_BRANCH=""
 RELEASE_GCS_BUCKET="knative-nightly/${REPO_NAME}"
+RELEASE_DIR=""
 KO_FLAGS="-P"
 VALIDATION_TESTS="./test/presubmit-tests.sh"
-YAMLS_TO_PUBLISH=""
 ARTIFACTS_TO_PUBLISH=""
 FROM_NIGHTLY_RELEASE=""
 FROM_NIGHTLY_RELEASE_GCS=""
@@ -334,6 +334,7 @@ function find_latest_nightly() {
 function parse_flags() {
   local has_gcr_flag=0
   local has_gcs_flag=0
+  local has_dir_flag=0
   local is_dot_release=0
   local is_auto_release=0
 
@@ -365,8 +366,14 @@ function parse_flags() {
             ;;
           --release-gcs)
             RELEASE_GCS_BUCKET=$1
+            RELEASE_DIR=""
             has_gcs_flag=1
             ;;
+          --release-dir)
+            RELEASE_DIR=$1
+            RELEASE_GCS_BUCKET=""
+            has_dir_flag=1
+            ;;
           --version)
             [[ $1 =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] || abort "version format must be '[0-9].[0-9].[0-9]'"
             RELEASE_VERSION=$1
@@ -389,6 +396,9 @@ function parse_flags() {
     shift
   done
 
+  (( has_gcs_flag )) && (( has_dir_flag )) && abort "cannot have both --release-gcs and --release-dir set simultaneously"
+  [[ -n "${RELEASE_GCS_BUCKET}" && -n "${RELEASE_DIR}" ]] && abort "cannot have both GCS and release directory set"
+
   # Do auto release unless release is forced
   if (( is_auto_release )); then
     (( is_dot_release )) && abort "cannot have both --dot-release and --auto-release set simultaneously"
@@ -422,8 +432,13 @@ function parse_flags() {
     (( has_gcr_flag )) && echo "Not publishing the release, GCR flag is ignored"
     (( has_gcs_flag )) && echo "Not publishing the release, GCS flag is ignored"
     KO_DOCKER_REPO="ko.local"
-    KO_FLAGS="-L ${KO_FLAGS}"
     RELEASE_GCS_BUCKET=""
+    [[ -z "${RELEASE_DIR}" ]] && RELEASE_DIR="${REPO_ROOT_DIR}"
+  fi
+
+  [[ -z "${RELEASE_GCS_BUCKET}" && -z "${RELEASE_DIR}" ]] && abort "--release-gcs or --release-dir must be used"
+  if [[ -n "${RELEASE_DIR}" ]]; then
+    mkdir -p "${RELEASE_DIR}" || abort "cannot create release dir '${RELEASE_DIR}'"
   fi
 
   # Get the commit, excluding any tags but keeping the "dirty" flag
@@ -450,6 +465,7 @@ function parse_flags() {
   readonly RELEASE_NOTES
   readonly RELEASE_BRANCH
   readonly RELEASE_GCS_BUCKET
+  readonly RELEASE_DIR
   readonly KO_DOCKER_REPO
   readonly VALIDATION_TESTS
   readonly FROM_NIGHTLY_RELEASE
@@ -458,22 +474,24 @@ function parse_flags() {
 # Run tests (unless --skip-tests was passed). Conveniently displays a banner indicating so.
 # Parameters: $1 - executable that runs the tests.
 function run_validation_tests() {
-  if (( ! SKIP_TESTS )); then
-    banner "Running release validation tests"
-    # Run tests.
-    if ! $1; then
-      banner "Release validation tests failed, aborting"
-      exit 1
-    fi
+  (( SKIP_TESTS )) && return
+  banner "Running release validation tests"
+  # Run tests.
+  if ! $1; then
+    banner "Release validation tests failed, aborting"
+    abort "release validation tests failed"
   fi
 }
 
-# Publishes the generated artifacts to GCS, GitHub, etc.
+# Publishes the generated artifacts to directory, GCS, GitHub, etc.
 # Parameters: $1..$n - files to add to the release.
 function publish_artifacts() {
   (( ! PUBLISH_RELEASE )) && return
   tag_images_in_yamls ${ARTIFACTS_TO_PUBLISH}
-  publish_to_gcs ${ARTIFACTS_TO_PUBLISH}
+  if [[ -n "${RELEASE_DIR}" ]]; then
+    cp ${ARTIFACTS_TO_PUBLISH} ${RELEASE_DIR} || abort "cannot copy release to '${RELEASE_DIR}'"
+  fi
+  [[ -n "${RELEASE_GCS_BUCKET}" ]] && publish_to_gcs ${ARTIFACTS_TO_PUBLISH}
   publish_to_github ${ARTIFACTS_TO_PUBLISH}
   banner "New release published successfully"
 }
@@ -482,7 +500,7 @@ function publish_artifacts() {
 function main() {
   function_exists build_release || abort "function 'build_release()' not defined"
   [[ -x ${VALIDATION_TESTS} ]] || abort "test script '${VALIDATION_TESTS}' doesn't exist"
-  parse_flags $@
+  parse_flags "$@"
   # Log what will be done and where.
   banner "Release configuration"
   if which gcloud &>/dev/null ; then
@@ -498,7 +516,9 @@ function main() {
     echo "- Artifacts WILL NOT be tagged"
   fi
   if (( PUBLISH_RELEASE )); then
-    echo "- Release WILL BE published to '${RELEASE_GCS_BUCKET}'"
+    local dst="${RELEASE_DIR}"
+    [[ -z "${dst}" ]] && dst="${RELEASE_GCS_BUCKET}"
+    echo "- Release WILL BE published to '${dst}'"
   else
     echo "- Release will not be published"
   fi
@@ -527,8 +547,6 @@ function main() {
     build_from_source
     set +e +o pipefail
   fi
-  # TODO(adrcunha): Remove once all repos use ARTIFACTS_TO_PUBLISH.
-  [[ -z "${ARTIFACTS_TO_PUBLISH}" ]] && ARTIFACTS_TO_PUBLISH="${YAMLS_TO_PUBLISH}"
   [[ -z "${ARTIFACTS_TO_PUBLISH}" ]] && abort "no artifacts were generated"
   # Ensure no empty file will be published.
   for artifact in ${ARTIFACTS_TO_PUBLISH}; do