Repository: knative/client (go)

Issue:
Add to "kn source list" test E2E tests cases
<!-- If you need to report a security issue with Knative, send an email to knative-security@googlegroups.com. -->

We should add additional E2E test cases for `kn source list` for checking:

* Invalid filter options
* Heterogenous list of different types with `-o yaml` (would involve to combine a pingsource + apiserver source creation)

/kind feature

<!--
Optional classifications (remove the '> ' to select)
> /kind question
> /kind good-first-issue
> /kind usage
> /kind doc
-->


Pull request description:
Add E2E test cases for "kn source list".
## Description

Add E2E test cases for "kn source list"

- Test cases for valid & Invalid filter option.
- Source list output in YAML format.

## Reference

<!-- Please add the corresponding issue number which this pull request is about to fix -->
Fixes #722 




Patch:
diff --git a/docs/cmd/kn_service_create.md b/docs/cmd/kn_service_create.md
index 2dc5aa97..e358c7c0 100644
--- a/docs/cmd/kn_service_create.md
+++ b/docs/cmd/kn_service_create.md
@@ -64,6 +64,7 @@ kn service create NAME --image IMAGE
       --concurrency-utilization int   Percentage of concurrent requests utilization before scaling up. (default 70)
   -e, --env stringArray               Environment variable to set. NAME=value; you may provide this flag any number of times to set multiple environment variables. To unset, specify the environment variable name followed by a "-" (e.g., NAME-).
       --env-from stringArray          Add environment variables from a ConfigMap (prefix cm: or config-map:) or a Secret (prefix secret:). Example: --env-from cm:myconfigmap or --env-from secret:mysecret. You can use this flag multiple times. To unset a ConfigMap/Secret reference, append "-" to the name, e.g. --env-from cm:myconfigmap-.
+  -f, --filename string               Create a service from file.
       --force                         Create service forcefully, replaces existing service if any.
   -h, --help                          help for create
       --image string                  Image to run.
@@ -87,6 +88,7 @@ kn service create NAME --image IMAGE
       --requests-cpu string           DEPRECATED: please use --request instead. The requested CPU (e.g., 250m).
       --requests-memory string        DEPRECATED: please use --request instead. The requested memory (e.g., 64Mi).
       --revision-name string          The revision name to set. Must start with the service name and a dash as a prefix. Empty revision name will result in the server generating a name for the revision. Accepts golang templates, allowing {{.Service}} for the service name, {{.Generation}} for the generation, and {{.Random [n]}} for n random consonants. (default "{{.Service}}-{{.Random 5}}-{{.Generation}}")
+      --scale int                     Minimum and maximum number of replicas.
       --service-account string        Service account name to set. An empty argument ("") clears the service account. The referenced service account must exist in the service's namespace.
       --user int                      The user ID to run the container (e.g., 1001).
       --volume stringArray            Add a volume from a ConfigMap (prefix cm: or config-map:) or a Secret (prefix secret: or sc:). Example: --volume myvolume=cm:myconfigmap or --volume myvolume=secret:mysecret. You can use this flag multiple times. To unset a ConfigMap/Secret reference, append "-" to the name, e.g. --volume myvolume-.
diff --git a/docs/cmd/kn_service_update.md b/docs/cmd/kn_service_update.md
index 87e4c80b..f8a1dd64 100644
--- a/docs/cmd/kn_service_update.md
+++ b/docs/cmd/kn_service_update.md
@@ -71,6 +71,7 @@ kn service update NAME
       --requests-cpu string           DEPRECATED: please use --request instead. The requested CPU (e.g., 250m).
       --requests-memory string        DEPRECATED: please use --request instead. The requested memory (e.g., 64Mi).
       --revision-name string          The revision name to set. Must start with the service name and a dash as a prefix. Empty revision name will result in the server generating a name for the revision. Accepts golang templates, allowing {{.Service}} for the service name, {{.Generation}} for the generation, and {{.Random [n]}} for n random consonants. (default "{{.Service}}-{{.Random 5}}-{{.Generation}}")
+      --scale int                     Minimum and maximum number of replicas.
       --service-account string        Service account name to set. An empty argument ("") clears the service account. The referenced service account must exist in the service's namespace.
       --tag strings                   Set tag (format: --tag revisionRef=tagName) where revisionRef can be a revision or '@latest' string representing latest ready revision. This flag can be specified multiple times.
       --traffic strings               Set traffic distribution (format: --traffic revisionRef=percent) where revisionRef can be a revision or a tag or '@latest' string representing latest ready revision. This flag can be given multiple times with percent summing up to 100%.
diff --git a/pkg/kn/commands/service/configuration_edit_flags.go b/pkg/kn/commands/service/configuration_edit_flags.go
index 36ea73d3..6321e9a8 100644
--- a/pkg/kn/commands/service/configuration_edit_flags.go
+++ b/pkg/kn/commands/service/configuration_edit_flags.go
@@ -45,6 +45,7 @@ type ConfigurationEditFlags struct {
 
 	RequestsFlags, LimitsFlags ResourceFlags // TODO: Flag marked deprecated in release v0.15.0, remove in release v0.18.0
 	Resources                  knflags.ResourceOptions
+	Scale                      int
 	MinScale                   int
 	MaxScale                   int
 	ConcurrencyTarget          int
@@ -68,6 +69,8 @@ type ConfigurationEditFlags struct {
 	GenerateRevisionName bool
 	ForceCreate          bool
 
+	Filename string
+
 	// Bookkeeping
 	flags []string
 }
@@ -185,6 +188,9 @@ func (p *ConfigurationEditFlags) addSharedFlags(command *cobra.Command) {
 	command.Flags().IntVar(&p.MaxScale, "max-scale", 0, "Maximal number of replicas.")
 	p.markFlagMakesRevision("max-scale")
 
+	command.Flags().IntVar(&p.Scale, "scale", 0, "Minimum and maximum number of replicas.")
+	p.markFlagMakesRevision("scale")
+
 	command.Flags().StringVar(&p.AutoscaleWindow, "autoscale-window", "", "Duration to look back for making auto-scaling decisions. The service is scaled to zero if no request was received in during that time. (eg: 10s)")
 	p.markFlagMakesRevision("autoscale-window")
 
@@ -272,7 +278,8 @@ func (p *ConfigurationEditFlags) AddCreateFlags(command *cobra.Command) {
 	p.addSharedFlags(command)
 	command.Flags().BoolVar(&p.ForceCreate, "force", false,
 		"Create service forcefully, replaces existing service if any.")
-	command.MarkFlagRequired("image")
+	command.Flags().StringVarP(&p.Filename, "filename", "f", "", "Create a service from file.")
+	command.MarkFlagFilename("filename")
 }
 
 // Apply mutates the given service according to the flags in the command.
@@ -434,6 +441,23 @@ func (p *ConfigurationEditFlags) Apply(
 		}
 	}
 
+	if cmd.Flags().Changed("scale") {
+		if cmd.Flags().Changed("max-scale") {
+			return fmt.Errorf("only --scale or --max-scale can be specified")
+		} else if cmd.Flags().Changed("min-scale") {
+			return fmt.Errorf("only --scale or --min-scale can be specified")
+		} else {
+			err = servinglib.UpdateMaxScale(template, p.Scale)
+			if err != nil {
+				return err
+			}
+			err = servinglib.UpdateMinScale(template, p.Scale)
+			if err != nil {
+				return err
+			}
+		}
+	}
+
 	if cmd.Flags().Changed("autoscale-window") {
 		err = servinglib.UpdateAutoscaleWindow(template, p.AutoscaleWindow)
 		if err != nil {
diff --git a/pkg/kn/commands/service/create.go b/pkg/kn/commands/service/create.go
index 0dfae39f..a2095d8c 100644
--- a/pkg/kn/commands/service/create.go
+++ b/pkg/kn/commands/service/create.go
@@ -18,6 +18,7 @@ import (
 	"errors"
 	"fmt"
 	"io"
+	"os"
 
 	"knative.dev/client/pkg/kn/commands"
 	servinglib "knative.dev/client/pkg/serving"
@@ -28,6 +29,8 @@ import (
 	corev1 "k8s.io/api/core/v1"
 	apierrors "k8s.io/apimachinery/pkg/api/errors"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/util/yaml"
+
 	servingv1 "knative.dev/serving/pkg/apis/serving/v1"
 
 	clientservingv1 "knative.dev/client/pkg/serving/v1"
@@ -78,11 +81,14 @@ func NewServiceCreateCommand(p *commands.KnParams) *cobra.Command {
 		Short:   "Create a service",
 		Example: create_example,
 		RunE: func(cmd *cobra.Command, args []string) (err error) {
-			if len(args) != 1 {
+			if len(args) != 1 && editFlags.Filename == "" {
 				return errors.New("'service create' requires the service name given as single argument")
 			}
-			name := args[0]
-			if editFlags.Image == "" {
+			name := ""
+			if len(args) == 1 {
+				name = args[0]
+			}
+			if editFlags.Image == "" && editFlags.Filename == "" {
 				return errors.New("'service create' requires the image name to run provided with the --image option")
 			}
 
@@ -91,7 +97,12 @@ func NewServiceCreateCommand(p *commands.KnParams) *cobra.Command {
 				return err
 			}
 
-			service, err := constructService(cmd, editFlags, name, namespace)
+			var service *servingv1.Service
+			if editFlags.Filename == "" {
+				service, err = constructService(cmd, editFlags, name, namespace)
+			} else {
+				service, err = constructServiceFromFile(cmd, editFlags, name, namespace)
+			}
 			if err != nil {
 				return err
 			}
@@ -100,8 +111,7 @@ func NewServiceCreateCommand(p *commands.KnParams) *cobra.Command {
 			if err != nil {
 				return err
 			}
-
-			serviceExists, err := serviceExists(client, name)
+			serviceExists, err := serviceExists(client, service.Name)
 			if err != nil {
 				return err
 			}
@@ -111,7 +121,7 @@ func NewServiceCreateCommand(p *commands.KnParams) *cobra.Command {
 				if !editFlags.ForceCreate {
 					return fmt.Errorf(
 						"cannot create service '%s' in namespace '%s' "+
-							"because the service already exists and no --force option was given", name, namespace)
+							"because the service already exists and no --force option was given", service.Name, namespace)
 				}
 				err = replaceService(client, service, waitFlags, out)
 			} else {
@@ -263,3 +273,42 @@ func constructService(cmd *cobra.Command, editFlags ConfigurationEditFlags, name
 	}
 	return &service, nil
 }
+
+// constructServiceFromFile creates struct from provided file
+func constructServiceFromFile(cmd *cobra.Command, editFlags ConfigurationEditFlags, name, namespace string) (*servingv1.Service, error) {
+	var service servingv1.Service
+	file, err := os.Open(editFlags.Filename)
+	if err != nil {
+		return nil, err
+	}
+	decoder := yaml.NewYAMLOrJSONDecoder(file, 512)
+
+	err = decoder.Decode(&service)
+	if err != nil {
+		return nil, err
+	}
+	if name == "" && service.Name != "" {
+		// keep provided service.Name if name param is empty
+	} else if name != "" && service.Name == "" {
+		service.Name = name
+	} else if name != "" && service.Name != "" {
+		// throw error if names differ, otherwise use already set value
+		if name != service.Name {
+			return nil, fmt.Errorf("provided service name '%s' doesn't match name from file '%s'", name, service.Name)
+		}
+	} else {
+		return nil, fmt.Errorf("no service name provided in command parameter or file")
+	}
+
+	// Set namespace in case it's specified as --namespace
+	service.ObjectMeta.Namespace = namespace
+
+	// We need to generate revision to have --force replace working
+	revName, err := servinglib.GenerateRevisionName(editFlags.RevisionName, &service)
+	if err != nil {
+		return nil, err
+	}
+	service.Spec.Template.Name = revName
+
+	return &service, nil
+}


Test patch:
diff --git a/pkg/kn/commands/service/create_test.go b/pkg/kn/commands/service/create_test.go
index f056267c..575d38b0 100644
--- a/pkg/kn/commands/service/create_test.go
+++ b/pkg/kn/commands/service/create_test.go
@@ -17,6 +17,9 @@ package service
 import (
 	"errors"
 	"fmt"
+	"io/ioutil"
+	"os"
+	"path/filepath"
 	"reflect"
 	"strings"
 	"testing"
@@ -544,6 +547,76 @@ func TestServiceCreateMaxMinScale(t *testing.T) {
 	}
 }
 
+func TestServiceCreateScale(t *testing.T) {
+	action, created, _, err := fakeServiceCreate([]string{
+		"service", "create", "foo", "--image", "gcr.io/foo/bar:baz",
+		"--scale", "5", "--no-wait"}, false)
+
+	if err != nil {
+		t.Fatal(err)
+	} else if !action.Matches("create", "services") {
+		t.Fatalf("Bad action %v", action)
+	}
+
+	template := &created.Spec.Template
+
+	actualAnnos := template.Annotations
+	expectedAnnos := []string{
+		"autoscaling.knative.dev/minScale", "5",
+		"autoscaling.knative.dev/maxScale", "5",
+	}
+
+	for i := 0; i < len(expectedAnnos); i += 2 {
+		anno := expectedAnnos[i]
+		if actualAnnos[anno] != expectedAnnos[i+1] {
+			t.Fatalf("Unexpected annotation value for %s : %s (actual) != %s (expected)",
+				anno, actualAnnos[anno], expectedAnnos[i+1])
+		}
+	}
+}
+
+func TestServiceCreateScaleWithNegativeValue(t *testing.T) {
+	_, _, _, err := fakeServiceCreate([]string{
+		"service", "create", "foo", "--image", "gcr.io/foo/bar:baz",
+		"--scale", "-1", "--no-wait"}, true)
+	if err == nil {
+		t.Fatal(err)
+	}
+	expectedErrMsg := "expected 0 <= -1 <= 2147483647: autoscaling.knative.dev/maxScale"
+	if !strings.Contains(err.Error(), expectedErrMsg) {
+		t.Errorf("Invalid error output, expected: %s, got : '%s'", expectedErrMsg, err)
+	}
+
+}
+
+func TestServiceCreateScaleWithMaxScaleSet(t *testing.T) {
+	_, _, _, err := fakeServiceCreate([]string{
+		"service", "create", "foo", "--image", "gcr.io/foo/bar:baz",
+		"--scale", "5", "--max-scale", "2", "--no-wait"}, true)
+	if err == nil {
+		t.Fatal(err)
+	}
+	expectedErrMsg := "only --scale or --max-scale can be specified"
+	if !strings.Contains(err.Error(), expectedErrMsg) {
+		t.Errorf("Invalid error output, expected: %s, got : '%s'", expectedErrMsg, err)
+	}
+
+}
+
+func TestServiceCreateScaleWithMinScaleSet(t *testing.T) {
+	_, _, _, err := fakeServiceCreate([]string{
+		"service", "create", "foo", "--image", "gcr.io/foo/bar:baz",
+		"--scale", "5", "--min-scale", "2", "--no-wait"}, true)
+	if err == nil {
+		t.Fatal(err)
+	}
+	expectedErrMsg := "only --scale or --min-scale can be specified"
+	if !strings.Contains(err.Error(), expectedErrMsg) {
+		t.Errorf("Invalid error output, expected: %s, got : '%s'", expectedErrMsg, err)
+	}
+
+}
+
 func TestServiceCreateRequestsLimitsCPUMemory(t *testing.T) {
 	action, created, _, err := fakeServiceCreate([]string{
 		"service", "create", "foo", "--image", "gcr.io/foo/bar:baz",
@@ -707,3 +780,196 @@ func TestServiceCreateWithClusterLocal(t *testing.T) {
 		t.Fatalf("Incorrect VisibilityClusterLocal value '%s'", labelValue)
 	}
 }
+
+var serviceYAML = `
+apiVersion: serving.knative.dev/v1
+kind: Service
+metadata:
+  name: foo
+spec:
+  template:
+    spec:
+      containers:
+        - image: gcr.io/foo/bar:baz
+          env:
+            - name: TARGET
+              value: "Go Sample v1"
+`
+
+var serviceJSON = `
+{
+  "apiVersion": "serving.knative.dev/v1",
+  "kind": "Service",
+  "metadata": {
+    "name": "foo"
+  },
+  "spec": {
+    "template": {
+		"spec": {
+		"containers": [
+			{
+			"image": "gcr.io/foo/bar:baz",
+			"env": [
+				{
+				"name": "TARGET",
+				"value": "Go Sample v1"
+				}
+			]
+		  }
+		]
+	  }
+	}
+  }
+}`
+
+func TestServiceCreateFromYAML(t *testing.T) {
+	tempDir, err := ioutil.TempDir("", "kn-file")
+	defer os.RemoveAll(tempDir)
+	assert.NilError(t, err)
+
+	tempFile := filepath.Join(tempDir, "service.yaml")
+	err = ioutil.WriteFile(tempFile, []byte(serviceYAML), os.FileMode(0666))
+	assert.NilError(t, err)
+
+	action, created, _, err := fakeServiceCreate([]string{
+		"service", "create", "foo", "--filename", tempFile}, false)
+	assert.NilError(t, err)
+	assert.Assert(t, action.Matches("create", "services"))
+
+	assert.Equal(t, created.Name, "foo")
+	assert.Equal(t, created.Spec.Template.Spec.GetContainer().Image, "gcr.io/foo/bar:baz")
+}
+
+func TestServiceCreateFromJSON(t *testing.T) {
+	tempDir, err := ioutil.TempDir("", "kn-file")
+	defer os.RemoveAll(tempDir)
+	assert.NilError(t, err)
+
+	tempFile := filepath.Join(tempDir, "service.json")
+	err = ioutil.WriteFile(tempFile, []byte(serviceJSON), os.FileMode(0666))
+	assert.NilError(t, err)
+
+	action, created, _, err := fakeServiceCreate([]string{
+		"service", "create", "foo", "--filename", tempFile}, false)
+	assert.NilError(t, err)
+	assert.Assert(t, action.Matches("create", "services"))
+
+	assert.Equal(t, created.Name, "foo")
+	assert.Equal(t, created.Spec.Template.Spec.GetContainer().Image, "gcr.io/foo/bar:baz")
+}
+
+func TestServiceCreateFromFileWithName(t *testing.T) {
+	tempDir, err := ioutil.TempDir("", "kn-file")
+	defer os.RemoveAll(tempDir)
+	assert.NilError(t, err)
+
+	tempFile := filepath.Join(tempDir, "service.yaml")
+	err = ioutil.WriteFile(tempFile, []byte(serviceYAML), os.FileMode(0666))
+	assert.NilError(t, err)
+
+	t.Log("no NAME param provided")
+	action, created, _, err := fakeServiceCreate([]string{
+		"service", "create", "--filename", tempFile}, false)
+	assert.NilError(t, err)
+	assert.Assert(t, action.Matches("create", "services"))
+
+	assert.Equal(t, created.Name, "foo")
+	assert.Equal(t, created.Spec.Template.Spec.GetContainer().Image, "gcr.io/foo/bar:baz")
+
+	t.Log("no service.Name provided in file")
+	err = ioutil.WriteFile(tempFile, []byte(strings.ReplaceAll(serviceYAML, "name: foo", "")), os.FileMode(0666))
+	assert.NilError(t, err)
+	action, created, _, err = fakeServiceCreate([]string{
+		"service", "create", "cli-foo", "--filename", tempFile}, false)
+	assert.NilError(t, err)
+	assert.Assert(t, action.Matches("create", "services"))
+
+	assert.Equal(t, created.Name, "cli-foo")
+	assert.Equal(t, created.Spec.Template.Spec.GetContainer().Image, "gcr.io/foo/bar:baz")
+}
+
+func TestServiceCreateFileNameMismatch(t *testing.T) {
+	tempDir, err := ioutil.TempDir("", "kn-file")
+	assert.NilError(t, err)
+
+	tempFile := filepath.Join(tempDir, "service.json")
+	err = ioutil.WriteFile(tempFile, []byte(serviceJSON), os.FileMode(0666))
+	assert.NilError(t, err)
+
+	t.Log("NAME param nad service.Name differ")
+	_, _, _, err = fakeServiceCreate([]string{
+		"service", "create", "anotherFoo", "--filename", tempFile}, false)
+	assert.Assert(t, err != nil)
+	assert.Assert(t, util.ContainsAllIgnoreCase(err.Error(), "provided", "'anotherFoo'", "name", "match", "from", "file", "'foo'"))
+
+	t.Log("no NAME param & no service.Name provided in file")
+	err = ioutil.WriteFile(tempFile, []byte(strings.ReplaceAll(serviceYAML, "name: foo", "")), os.FileMode(0666))
+	assert.NilError(t, err)
+	_, _, _, err = fakeServiceCreate([]string{
+		"service", "create", "--filename", tempFile}, false)
+	assert.Assert(t, err != nil)
+	assert.Assert(t, util.ContainsAllIgnoreCase(err.Error(), "no", "service", "name", "provided", "parameter", "file"))
+}
+
+func TestServiceCreateFileError(t *testing.T) {
+	_, _, _, err := fakeServiceCreate([]string{
+		"service", "create", "foo", "--filename", "filepath"}, false)
+
+	assert.Assert(t, util.ContainsAll(err.Error(), "no", "such", "file", "directory", "filepath"))
+}
+
+func TestServiceCreateInvalidDataJSON(t *testing.T) {
+	tempDir, err := ioutil.TempDir("", "kn-file")
+	defer os.RemoveAll(tempDir)
+	assert.NilError(t, err)
+	tempFile := filepath.Join(tempDir, "invalid.json")
+
+	// Double curly bracket at the beginning of file
+	invalidData := strings.Replace(serviceJSON, "{\n", "{{\n", 1)
+	err = ioutil.WriteFile(tempFile, []byte(invalidData), os.FileMode(0666))
+	assert.NilError(t, err)
+	_, _, _, err = fakeServiceCreate([]string{"service", "create", "foo", "--filename", tempFile}, false)
+	assert.Assert(t, util.ContainsAll(err.Error(), "invalid", "character", "'{'", "beginning"))
+
+	// Remove closing quote on key
+	invalidData = strings.Replace(serviceJSON, "metadata\"", "metadata", 1)
+	err = ioutil.WriteFile(tempFile, []byte(invalidData), os.FileMode(0666))
+	assert.NilError(t, err)
+	_, _, _, err = fakeServiceCreate([]string{"service", "create", "foo", "--filename", tempFile}, false)
+	assert.Assert(t, util.ContainsAll(err.Error(), "invalid", "character", "'\\n'", "string", "literal"))
+
+	// Remove opening square bracket
+	invalidData = strings.Replace(serviceJSON, " [", "", 1)
+	err = ioutil.WriteFile(tempFile, []byte(invalidData), os.FileMode(0666))
+	assert.NilError(t, err)
+	_, _, _, err = fakeServiceCreate([]string{"service", "create", "foo", "--filename", tempFile}, false)
+	assert.Assert(t, util.ContainsAll(err.Error(), "invalid", "character", "']'", "after", "key:value"))
+}
+
+func TestServiceCreateInvalidDataYAML(t *testing.T) {
+	tempDir, err := ioutil.TempDir("", "kn-file")
+	defer os.RemoveAll(tempDir)
+	assert.NilError(t, err)
+	tempFile := filepath.Join(tempDir, "invalid.yaml")
+
+	// Remove dash
+	invalidData := strings.Replace(serviceYAML, "- image", "image", 1)
+	err = ioutil.WriteFile(tempFile, []byte(invalidData), os.FileMode(0666))
+	assert.NilError(t, err)
+	_, _, _, err = fakeServiceCreate([]string{"service", "create", "foo", "--filename", tempFile}, false)
+	assert.Assert(t, util.ContainsAll(err.Error(), "mapping", "values", "not", "allowed"))
+
+	// Remove name key
+	invalidData = strings.Replace(serviceYAML, "name:", "", 1)
+	err = ioutil.WriteFile(tempFile, []byte(invalidData), os.FileMode(0666))
+	assert.NilError(t, err)
+	_, _, _, err = fakeServiceCreate([]string{"service", "create", "foo", "--filename", tempFile}, false)
+	assert.Assert(t, util.ContainsAll(err.Error(), "cannot", "unmarshal", "Go", "struct", "Service.metadata"))
+
+	// Remove opening square bracket
+	invalidData = strings.Replace(serviceYAML, "env", "\tenv", 1)
+	err = ioutil.WriteFile(tempFile, []byte(invalidData), os.FileMode(0666))
+	assert.NilError(t, err)
+	_, _, _, err = fakeServiceCreate([]string{"service", "create", "foo", "--filename", tempFile}, false)
+	assert.Assert(t, util.ContainsAll(err.Error(), "found", "tab", "violates", "indentation"))
+}
diff --git a/pkg/kn/commands/service/update_test.go b/pkg/kn/commands/service/update_test.go
index 832fb0ee..f02c21f1 100644
--- a/pkg/kn/commands/service/update_test.go
+++ b/pkg/kn/commands/service/update_test.go
@@ -355,6 +355,96 @@ func TestServiceUpdateMaxMinScale(t *testing.T) {
 
 }
 
+func TestServiceUpdateScale(t *testing.T) {
+	original := newEmptyService()
+
+	action, updated, _, err := fakeServiceUpdate(original, []string{
+		"service", "update", "foo",
+		"--scale", "5", "--no-wait"})
+
+	if err != nil {
+		t.Fatal(err)
+	} else if !action.Matches("update", "services") {
+		t.Fatalf("Bad action %v", action)
+	}
+
+	template := updated.Spec.Template
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	actualAnnos := template.Annotations
+	expectedAnnos := []string{
+		"autoscaling.knative.dev/minScale", "5",
+		"autoscaling.knative.dev/maxScale", "5",
+	}
+
+	for i := 0; i < len(expectedAnnos); i += 2 {
+		anno := expectedAnnos[i]
+		if actualAnnos[anno] != expectedAnnos[i+1] {
+			t.Fatalf("Unexpected annotation value for %s : %s (actual) != %s (expected)",
+				anno, actualAnnos[anno], expectedAnnos[i+1])
+		}
+	}
+
+}
+
+func TestServiceUpdateScaleWithNegativeValue(t *testing.T) {
+	original := newEmptyService()
+
+	_, _, _, err := fakeServiceUpdate(original, []string{
+		"service", "update", "foo",
+		"--scale", "-1", "--no-wait"})
+
+	if err == nil {
+		t.Fatal("Expected error, got nil")
+	}
+
+	expectedErrMsg := "expected 0 <= -1 <= 2147483647: autoscaling.knative.dev/maxScale"
+
+	if !strings.Contains(err.Error(), expectedErrMsg) {
+		t.Errorf("Invalid error output, expected: %s, got : '%s'", expectedErrMsg, err)
+	}
+
+}
+
+func TestServiceUpdateScaleWithMaxScaleSet(t *testing.T) {
+	original := newEmptyService()
+
+	_, _, _, err := fakeServiceUpdate(original, []string{
+		"service", "update", "foo",
+		"--scale", "5", "--max-scale", "2", "--no-wait"})
+
+	if err == nil {
+		t.Fatal("Expected error, got nil")
+	}
+
+	expectedErrMsg := "only --scale or --max-scale can be specified"
+
+	if !strings.Contains(err.Error(), expectedErrMsg) {
+		t.Errorf("Invalid error output, expected: %s, got : '%s'", expectedErrMsg, err)
+	}
+
+}
+
+func TestServiceUpdateScaleWithMinScaleSet(t *testing.T) {
+	original := newEmptyService()
+
+	_, _, _, err := fakeServiceUpdate(original, []string{
+		"service", "update", "foo",
+		"--scale", "5", "--min-scale", "2", "--no-wait"})
+
+	if err == nil {
+		t.Fatal("Expected error, got nil")
+	}
+
+	expectedErrMsg := "only --scale or --min-scale can be specified"
+
+	if !strings.Contains(err.Error(), expectedErrMsg) {
+		t.Errorf("Invalid error output, expected: %s, got : '%s'", expectedErrMsg, err)
+	}
+
+}
 func TestServiceUpdateEnv(t *testing.T) {
 	orig := newEmptyService()
 
diff --git a/test/e2e/service_file_test.go b/test/e2e/service_file_test.go
new file mode 100644
index 00000000..e5a0f441
--- /dev/null
+++ b/test/e2e/service_file_test.go
@@ -0,0 +1,133 @@
+// Copyright 2020 The Knative Authors
+
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+
+//     http://www.apache.org/licenses/LICENSE-2.0
+
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// +build e2e
+// +build !eventing
+
+package e2e
+
+import (
+	"fmt"
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"testing"
+
+	"gotest.tools/assert"
+
+	"knative.dev/client/lib/test"
+	"knative.dev/client/pkg/util"
+)
+
+const (
+	ServiceYAML string = `
+apiVersion: serving.knative.dev/v1
+kind: Service
+metadata:
+  name: foo-yaml
+spec:
+  template:
+    spec:
+      containers:
+        - image: %s
+          env:
+            - name: TARGET
+              value: "Go Sample v1"`
+
+	ServiceJSON string = `
+{
+  "apiVersion": "serving.knative.dev/v1",
+  "kind": "Service",
+  "metadata": {
+    "name": "foo-json"
+  },
+  "spec": {
+    "template": {
+		"spec": {
+		"containers": [
+			{
+			"image": "%s",
+			"env": [
+				{
+				"name": "TARGET",
+				"value": "Go Sample v1"
+				}
+			]
+		  }
+		]
+	  }
+	}
+  }
+}`
+)
+
+func TestServiceCreateFromFile(t *testing.T) {
+	t.Parallel()
+	it, err := test.NewKnTest()
+	assert.NilError(t, err)
+	defer func() {
+		assert.NilError(t, it.Teardown())
+	}()
+
+	r := test.NewKnRunResultCollector(t, it)
+	defer r.DumpIfFailed()
+
+	tempDir, err := ioutil.TempDir("", "kn-file")
+	defer os.RemoveAll(tempDir)
+	assert.NilError(t, err)
+
+	test.CreateFile("foo.json", fmt.Sprintf(ServiceJSON, test.KnDefaultTestImage), tempDir, test.FileModeReadWrite)
+	test.CreateFile("foo.yaml", fmt.Sprintf(ServiceYAML, test.KnDefaultTestImage), tempDir, test.FileModeReadWrite)
+
+	t.Log("create foo-json service from JSON file")
+	serviceCreateFromFile(r, "foo-json", filepath.Join(tempDir, "foo.json"), true)
+
+	t.Log("create foo-yaml service from YAML file")
+	serviceCreateFromFile(r, "foo-yaml", filepath.Join(tempDir, "foo.yaml"), false)
+
+	t.Log("error message for non-existing file")
+	serviceCreateFromFileError(r, "foo", filepath.Join(tempDir, "fake-foo.json"))
+	serviceCreateFromFileError(r, "foo", filepath.Join(tempDir, "fake-foo.yaml"))
+
+	t.Log("error message for mismatch names")
+	serviceCreateFromFileNameMismatch(r, "foo", filepath.Join(tempDir, "foo.json"))
+	serviceCreateFromFileNameMismatch(r, "foo", filepath.Join(tempDir, "foo.yaml"))
+}
+
+func serviceCreateFromFile(r *test.KnRunResultCollector, serviceName, filePath string, useName bool) {
+	var out test.KnRunResult
+	if useName {
+		out = r.KnTest().Kn().Run("service", "create", serviceName, "-f", filePath)
+	} else {
+		out = r.KnTest().Kn().Run("service", "create", "-f", filePath)
+	}
+	r.AssertNoError(out)
+	assert.Check(r.T(), util.ContainsAllIgnoreCase(out.Stdout, "service", serviceName, "creating", "namespace", r.KnTest().Kn().Namespace(), "ready"))
+
+	out = r.KnTest().Kn().Run("service", "describe", serviceName, "--verbose")
+	r.AssertNoError(out)
+	assert.Check(r.T(), util.ContainsAllIgnoreCase(out.Stdout, serviceName))
+}
+
+func serviceCreateFromFileError(r *test.KnRunResultCollector, serviceName, filePath string) {
+	out := r.KnTest().Kn().Run("service", "create", serviceName, "--filename", filePath)
+	r.AssertError(out)
+	assert.Check(r.T(), util.ContainsAllIgnoreCase(out.Stderr, "no", "such", "file", "directory", filePath))
+}
+
+func serviceCreateFromFileNameMismatch(r *test.KnRunResultCollector, serviceName, filePath string) {
+	out := r.KnTest().Kn().Run("service", "create", serviceName, "--filename", filePath)
+	r.AssertError(out)
+	assert.Check(r.T(), util.ContainsAllIgnoreCase(out.Stderr, "provided", "'"+serviceName+"'", "name", "match", "from", "file"))
+}
diff --git a/test/e2e/service_options_test.go b/test/e2e/service_options_test.go
index 71164bb9..6d071a5a 100644
--- a/test/e2e/service_options_test.go
+++ b/test/e2e/service_options_test.go
@@ -67,7 +67,7 @@ func TestServiceOptions(t *testing.T) {
 	t.Log("delete service")
 	test.ServiceDelete(r, "svc1")
 
-	t.Log("create and validate service with min/max scale options ")
+	t.Log("create and validate service with min/max scale options")
 	serviceCreateWithOptions(r, "svc2", "--min-scale", "1", "--max-scale", "3")
 	validateServiceMinScale(r, "svc2", "1")
 	validateServiceMaxScale(r, "svc2", "3")
@@ -76,6 +76,16 @@ func TestServiceOptions(t *testing.T) {
 	test.ServiceUpdate(r, "svc2", "--max-scale", "2")
 	validateServiceMaxScale(r, "svc2", "2")
 
+	t.Log("create and validate service with scale options")
+	serviceCreateWithOptions(r, "svc2a", "--scale", "5")
+	validateServiceMinScale(r, "svc2a", "5")
+	validateServiceMaxScale(r, "svc2a", "5")
+
+	t.Log("update and validate service with scale option")
+	test.ServiceUpdate(r, "svc2a", "--scale", "2")
+	validateServiceMaxScale(r, "svc2a", "2")
+	validateServiceMinScale(r, "svc2a", "2")
+
 	t.Log("delete service")
 	test.ServiceDelete(r, "svc2")
 
diff --git a/test/e2e/source_list_test.go b/test/e2e/source_list_test.go
index 7d82c62d..3c6d248a 100644
--- a/test/e2e/source_list_test.go
+++ b/test/e2e/source_list_test.go
@@ -52,17 +52,55 @@ func TestSourceList(t *testing.T) {
 	it, err := test.NewKnTest()
 	assert.NilError(t, err)
 	defer func() {
+		assert.NilError(t, tearDownForSourceAPIServer(t, it))
 		assert.NilError(t, it.Teardown())
 	}()
 
 	r := test.NewKnRunResultCollector(t, it)
+
 	defer r.DumpIfFailed()
+	setupForSourceAPIServer(t, it)
+	test.ServiceCreate(r, "testsvc0")
 
 	t.Log("List sources empty case")
 	output := sourceList(r)
 	assert.Check(t, util.ContainsAll(output, "No", "sources", "found", "namespace"))
 	assert.Check(t, util.ContainsNone(output, "NAME", "TYPE", "RESOURCE", "SINK", "READY"))
 
+	t.Log("Create API Server")
+	apiServerSourceCreate(r, "testapisource0", "Event:v1:key1=value1", "testsa", "svc:testsvc0")
+	apiServerSourceListOutputName(r, "testapisource0")
+
+	t.Log("Create source binding")
+	sourceBindingCreate(r, "my-binding0", "Deployment:apps/v1:myapp", "svc:testsvc0")
+	sourceBindingListOutputName(r, "my-binding0")
+
+	t.Log("Create ping source")
+	pingSourceCreate(r, "testpingsource0", "* * * * */1", "ping", "svc:testsvc0")
+	pingSourceListOutputName(r, "testpingsource0")
+
+	t.Log("List sources filter valid case")
+	output = sourceList(r, "--type", "PingSource")
+	assert.Check(t, util.ContainsAll(output, "NAME", "TYPE", "RESOURCE", "SINK", "READY"))
+	assert.Check(t, util.ContainsAll(output, "testpingsource0", "PingSource", "pingsources.sources.knative.dev", "svc:testsvc0"))
+
+	t.Log("List sources filter invalid case")
+	output = sourceList(r, "--type", "testapisource0")
+	assert.Check(t, util.ContainsAll(output, "No", "sources", "found", "namespace"))
+	output = sourceList(r, "--type", "TestSource", "-oyaml")
+	assert.Check(t, util.ContainsAll(output, "No", "sources", "found", "namespace"))
+
+	t.Log("List available source in YAML format")
+	output = sourceList(r, "--type", "PingSource,ApiServerSource", "-oyaml")
+	assert.Check(t, util.ContainsAll(output, "testpingsource0", "PingSource", "Service", "testsvc0"))
+	assert.Check(t, util.ContainsAll(output, "testapisource0", "ApiServerSource", "Service", "testsvc0"))
+
+	t.Log("Delete apiserver sources")
+	apiServerSourceDelete(r, "testapisource0")
+	t.Log("Delete source binding")
+	sourceBindingDelete(r, "my-binding0")
+	t.Log("Delete Ping sources")
+	pingSourceDelete(r, "testpingsource0")
 	// non empty list case is tested in test/e2e/source_apiserver_it.go where source setup is present
 }