Repository: knative/client (go)

Issue:
service update final message is confusing (could be improved)
## In what area(s)?

`kn service update`

Classifications:

> /kind cleanup

## What version of Knative Client?

```bash
➜  client git:(master) ✗ ./kn version
Using kn config file: /Users/maximilien/.kn/config.yaml
Version:      v20191220-local-d03cbf9-dirty
Build Date:   2019-12-20 21:27:38
Git Revision: d03cbf9
Supported APIs:
* Serving
  - serving.knative.dev/v1alpha1 (knative-serving v0.11.0)
* Eventing
  - sources.eventing.knative.dev/v1alpha1 (knative-eventing v0.11.0)
  - eventing.knative.dev/v1alpha1 (knative-eventing v0.11.0)
```
## What version of Knative Serving running on your cluster?

> 0.11.0

## Expected Behavior

When updating services I expect a message that is clear about what changes is shown to the user.

## Actual Behavior

```bash
➜  client git:(master) ./kn service describe summary-fn
Using kn config file: /Users/maximilien/.kn/config.yaml
Name:       summary-fn
Namespace:  default
Labels:     knfun=demo
Age:        39d
URL:        http://summary-fn-default.kndemo-267179.sjc03.containers.appdomain.cloud

Revisions:
   50%  summary-fn-hwkll-31 (current @latest) #async [20] (23d)
        Image:  index.docker.io/drmax/summary-fn@sha256:415f521bac57ae8bac8fd51b09f53a38ef879935283ee1649d40842689db3e2b (at 415f52)
   50%  summary-fn-qwfdb-1 #sync [19] (23d)
        Image:  docker.io/drmax/summary-fn:latest (pinned to 415f52)

Conditions:
  OK TYPE                   AGE REASON
  ++ Ready                   5s
  ++ ConfigurationsReady    23d
  ++ RoutesReady             5s

➜  client git:(master) ./kn service update summary-fn --tag summary-fn-qwfdb-1=sync
Using kn config file: /Users/maximilien/.kn/config.yaml
Updating Service 'summary-fn' in namespace 'default':

  0.090s Ready to serve.

Service 'summary-fn' updated with latest revision 'summary-fn-hwkll-31' (unchanged) and URL:
http://summary-fn-default.kndemo-267179.sjc03.containers.appdomain.cloud
```
The word `updated` in the last message is confusing and conflicts with `unchanged` which I understand applies to the revision but could be made clearer.

## Steps to Reproduce the Problem

above


Pull request description:
fixes(#586): simplifies service update message to user
Fixes #586

## Proposed Changes

* splits final service update to user to make clear what changed and what has not

```bash
➜  client git:(master) ✗ ./kn service update summary-fn --untag sync1
Using kn config file: /Users/maximilien/.kn/config.yaml
Updating Service 'summary-fn' in namespace 'default':

  0.089s Ready to serve.

Service 'summary-fn' with latest revision 'summary-fn-nxqwb-41' (unchanged) is available at URL:
http://summary-fn-default.kndemo-267179.sjc03.containers.appdomain.cloud
```

This is a case when service is updated with tags and revision remained unchanged.

```bash
➜  client git:(master) ✗ ./kn service update summary-fn --image docker.io/drmax/summary-fn:latest
Using kn config file: /Users/maximilien/.kn/config.yaml
Updating Service 'summary-fn' in namespace 'default':

  0.091s The Configuration is still working to reflect the latest desired specification.
  6.317s Ready to serve.

Service 'summary-fn' updated to latest revision 'summary-fn-jfjcy-42' is available at URL:
http://summary-fn-default.kndemo-267179.sjc03.containers.appdomain.cloud
```

This is the case when service updated to new revision.

Release Note:

- 🧽 clarifies service update message


Patch:
diff --git a/pkg/kn/commands/revision/human_readable_flags.go b/pkg/kn/commands/revision/human_readable_flags.go
index b3bb44b9..65257225 100644
--- a/pkg/kn/commands/revision/human_readable_flags.go
+++ b/pkg/kn/commands/revision/human_readable_flags.go
@@ -23,11 +23,18 @@ import (
 	servingv1alpha1 "knative.dev/serving/pkg/apis/serving/v1alpha1"
 )
 
+const (
+	RevisionTrafficAnnotation = "client.knative.dev/traffic"
+	RevisionTagsAnnotation    = "client.knative.dev/tags"
+)
+
 // RevisionListHandlers adds print handlers for revision list command
 func RevisionListHandlers(h hprinters.PrintHandler) {
 	RevisionColumnDefinitions := []metav1beta1.TableColumnDefinition{
 		{Name: "Name", Type: "string", Description: "Name of the revision.", Priority: 1},
 		{Name: "Service", Type: "string", Description: "Name of the Knative service.", Priority: 1},
+		{Name: "Traffic", Type: "string", Description: "Percentage of traffic assigned to this revision.", Priority: 1},
+		{Name: "Tags", Type: "string", Description: "Set of tags assigned to this revision.", Priority: 1},
 		{Name: "Generation", Type: "string", Description: "Generation of the revision", Priority: 1},
 		{Name: "Age", Type: "string", Description: "Age of the revision.", Priority: 1},
 		{Name: "Conditions", Type: "string", Description: "Conditions describing statuses of the revision.", Priority: 1},
@@ -57,6 +64,8 @@ func printRevisionList(revisionList *servingv1alpha1.RevisionList, options hprin
 func printRevision(revision *servingv1alpha1.Revision, options hprinters.PrintOptions) ([]metav1beta1.TableRow, error) {
 	name := revision.Name
 	service := revision.Labels[serving.ServiceLabelKey]
+	traffic := revision.Annotations[RevisionTrafficAnnotation]
+	tags := revision.Annotations[RevisionTagsAnnotation]
 	generation := revision.Labels[serving.ConfigurationGenerationLabelKey]
 	age := commands.TranslateTimestampSince(revision.CreationTimestamp)
 	conditions := commands.ConditionsValue(revision.Status.Conditions)
@@ -68,6 +77,8 @@ func printRevision(revision *servingv1alpha1.Revision, options hprinters.PrintOp
 	row.Cells = append(row.Cells,
 		name,
 		service,
+		traffic,
+		tags,
 		generation,
 		age,
 		conditions,
diff --git a/pkg/kn/commands/revision/list.go b/pkg/kn/commands/revision/list.go
index 52c0738c..a8fb662f 100644
--- a/pkg/kn/commands/revision/list.go
+++ b/pkg/kn/commands/revision/list.go
@@ -18,6 +18,7 @@ import (
 	"fmt"
 	"sort"
 	"strconv"
+	"strings"
 
 	"knative.dev/serving/pkg/apis/serving"
 
@@ -85,6 +86,23 @@ func NewRevisionListCommand(p *commands.KnParams) *cobra.Command {
 				}
 			}
 
+			// Add revision/service info as annotations: traffic and tags
+			var service *v1alpha1.Service
+			var serviceName string
+			for _, revision := range revisionList.Items {
+				if serviceName == "" || revision.Labels[serving.ServiceLabelKey] != serviceName {
+					serviceName = revision.Labels[serving.ServiceLabelKey]
+					service, err = client.GetService(serviceName)
+					if err != nil {
+						return err
+					}
+				}
+
+				traffic, tags := trafficForRevision(revision.Name, service)
+				revision.Annotations[RevisionTrafficAnnotation] = fmt.Sprintf("%d%%", traffic)
+				revision.Annotations[RevisionTagsAnnotation] = strings.Join(tags, ",")
+			}
+
 			// sort revisionList by configuration generation key
 			sort.SliceStable(revisionList.Items, func(i, j int) bool {
 				a := revisionList.Items[i]
@@ -110,6 +128,7 @@ func NewRevisionListCommand(p *commands.KnParams) *cobra.Command {
 			if err != nil {
 				return err
 			}
+
 			err = printer.PrintObj(revisionList, cmd.OutOrStdout())
 			if err != nil {
 				return err
diff --git a/pkg/kn/commands/revision/list_flags.go b/pkg/kn/commands/revision/list_flags.go
index a22dfb33..7d6fd309 100644
--- a/pkg/kn/commands/revision/list_flags.go
+++ b/pkg/kn/commands/revision/list_flags.go
@@ -26,7 +26,7 @@ import (
 type RevisionListFlags struct {
 	GenericPrintFlags  *genericclioptions.PrintFlags
 	HumanReadableFlags *commands.HumanPrintFlags
-	ServiceRefFlags    ServiceReferenceFlags
+	ServiceRefFlags    *ServiceReferenceFlags
 }
 
 // AllowedFormats is the list of formats in which data can be displayed
@@ -70,6 +70,7 @@ func NewRevisionListFlags() *RevisionListFlags {
 	return &RevisionListFlags{
 		GenericPrintFlags:  genericclioptions.NewPrintFlags(""),
 		HumanReadableFlags: commands.NewHumanPrintFlags(),
+		ServiceRefFlags:    &ServiceReferenceFlags{},
 	}
 }
 
diff --git a/pkg/kn/commands/service/service.go b/pkg/kn/commands/service/service.go
index aa93bd32..5b6fa3e5 100644
--- a/pkg/kn/commands/service/service.go
+++ b/pkg/kn/commands/service/service.go
@@ -59,15 +59,18 @@ func showUrl(client serving_kn_v1alpha1.KnServingClient, serviceName string, ori
 	if err != nil {
 		return fmt.Errorf("cannot fetch service '%s' in namespace '%s' for extracting the URL: %v", serviceName, client.Namespace(), err)
 	}
+
 	url := service.Status.URL.String()
 	if url == "" {
 		url = service.Status.DeprecatedDomain
 	}
-	revisionUpdateStatus := ""
+
 	newRevision := service.Status.LatestReadyRevisionName
 	if originalRevision != "" && originalRevision == newRevision {
-		revisionUpdateStatus = " (unchanged)"
+		fmt.Fprintf(out, "Service '%s' with latest revision '%s' (unchanged) is available at URL:\n%s\n", serviceName, newRevision, url)
+	} else {
+		fmt.Fprintf(out, "Service '%s' %s to latest revision '%s' is available at URL:\n%s\n", serviceName, what, newRevision, url)
 	}
-	fmt.Fprintf(out, "Service '%s' %s with latest revision '%s'%s and URL:\n%s\n", serviceName, what, newRevision, revisionUpdateStatus, url)
+
 	return nil
 }


Test patch:
diff --git a/pkg/kn/commands/revision/list_test.go b/pkg/kn/commands/revision/list_test.go
index 48090e40..8a913b8a 100644
--- a/pkg/kn/commands/revision/list_test.go
+++ b/pkg/kn/commands/revision/list_test.go
@@ -29,7 +29,7 @@ import (
 	"knative.dev/client/pkg/util"
 )
 
-var revisionListHeader = []string{"NAME", "SERVICE", "GENERATION", "AGE", "CONDITIONS", "READY", "REASON"}
+var revisionListHeader = []string{"NAME", "SERVICE", "TRAFFIC", "TAGS", "GENERATION", "AGE", "CONDITIONS", "READY", "REASON"}
 
 func fakeRevisionList(args []string, response *v1alpha1.RevisionList) (action client_testing.Action, output []string, err error) {
 	knParams := &commands.KnParams{}
@@ -71,13 +71,13 @@ func TestRevisionListEmptyByName(t *testing.T) {
 }
 
 func TestRevisionListDefaultOutput(t *testing.T) {
-	revision1 := createMockRevisionWithParams("foo-abcd", "foo", "1")
-	revision2 := createMockRevisionWithParams("bar-abcd", "bar", "1")
-	revision3 := createMockRevisionWithParams("foo-wxyz", "foo", "2")
-	revision4 := createMockRevisionWithParams("bar-wxyz", "bar", "2")
+	revision1 := createMockRevisionWithParams("foo-abcd", "foo", "1", "100", "")
+	revision2 := createMockRevisionWithParams("bar-abcd", "bar", "1", "100", "")
+	revision3 := createMockRevisionWithParams("foo-wxyz", "foo", "2", "50", "tag10")
+	revision4 := createMockRevisionWithParams("bar-wxyz", "bar", "2", "0", "tag2")
 	// Validate edge case for catching the sorting issue caused by string comparison
-	revision5 := createMockRevisionWithParams("foo-wxyz", "foo", "10")
-	revision6 := createMockRevisionWithParams("bar-wxyz", "bar", "10")
+	revision5 := createMockRevisionWithParams("foo-wxyz", "foo", "10", "tag1", "tagx")
+	revision6 := createMockRevisionWithParams("bar-wxyz", "bar", "10", "50", "")
 
 	RevisionList := &v1alpha1.RevisionList{Items: []v1alpha1.Revision{
 		*revision1, *revision2, *revision3, *revision4, *revision5, *revision6}}
@@ -98,8 +98,8 @@ func TestRevisionListDefaultOutput(t *testing.T) {
 }
 
 func TestRevisionListDefaultOutputNoHeaders(t *testing.T) {
-	revision1 := createMockRevisionWithParams("foo-abcd", "foo", "2")
-	revision2 := createMockRevisionWithParams("bar-wxyz", "bar", "1")
+	revision1 := createMockRevisionWithParams("foo-abcd", "foo", "2", "100", "")
+	revision2 := createMockRevisionWithParams("bar-wxyz", "bar", "1", "100", "")
 	RevisionList := &v1alpha1.RevisionList{Items: []v1alpha1.Revision{*revision1, *revision2}}
 	action, output, err := fakeRevisionList([]string{"revision", "list", "--no-headers"}, RevisionList)
 	assert.NilError(t, err)
@@ -116,10 +116,10 @@ func TestRevisionListDefaultOutputNoHeaders(t *testing.T) {
 }
 
 func TestRevisionListForService(t *testing.T) {
-	revision1 := createMockRevisionWithParams("foo-abcd", "svc1", "1")
-	revision2 := createMockRevisionWithParams("bar-wxyz", "svc1", "2")
-	revision3 := createMockRevisionWithParams("foo-abcd", "svc2", "1")
-	revision4 := createMockRevisionWithParams("bar-wxyz", "svc2", "2")
+	revision1 := createMockRevisionWithParams("foo-abcd", "svc1", "1", "50", "")
+	revision2 := createMockRevisionWithParams("bar-wxyz", "svc1", "2", "50", "")
+	revision3 := createMockRevisionWithParams("foo-abcd", "svc2", "1", "0", "")
+	revision4 := createMockRevisionWithParams("bar-wxyz", "svc2", "2", "100", "")
 	RevisionList := &v1alpha1.RevisionList{Items: []v1alpha1.Revision{*revision1, *revision2, *revision3, *revision4}}
 	action, output, err := fakeRevisionList([]string{"revision", "list", "-s", "svc1"}, RevisionList)
 	assert.NilError(t, err)
@@ -156,7 +156,7 @@ func TestRevisionListForService(t *testing.T) {
 }
 
 func TestRevisionListOneOutput(t *testing.T) {
-	revision := createMockRevisionWithParams("foo-abcd", "foo", "1")
+	revision := createMockRevisionWithParams("foo-abcd", "foo", "1", "100", "")
 	RevisionList := &v1alpha1.RevisionList{Items: []v1alpha1.Revision{*revision}}
 	action, output, err := fakeRevisionList([]string{"revision", "list", "foo-abcd"}, RevisionList)
 	assert.NilError(t, err)
@@ -176,7 +176,7 @@ func TestRevisionListOutputWithTwoRevName(t *testing.T) {
 	assert.ErrorContains(t, err, "'kn revision list' accepts maximum 1 argument")
 }
 
-func createMockRevisionWithParams(name, svcName, generation string) *v1alpha1.Revision {
+func createMockRevisionWithParams(name, svcName, generation, traffic, tags string) *v1alpha1.Revision {
 	revision := &v1alpha1.Revision{
 		TypeMeta: metav1.TypeMeta{
 			Kind:       "Revision",
@@ -189,6 +189,10 @@ func createMockRevisionWithParams(name, svcName, generation string) *v1alpha1.Re
 				serving.ServiceLabelKey:                 svcName,
 				serving.ConfigurationGenerationLabelKey: generation,
 			},
+			Annotations: map[string]string{
+				"client.knative.dev/traffic": traffic,
+				"client.knative.dev/tags":    tags,
+			},
 		},
 	}
 	return revision
diff --git a/test/e2e/source_apiserver_test.go b/test/e2e/source_apiserver_test.go
index d8360c23..1cbacd1c 100644
--- a/test/e2e/source_apiserver_test.go
+++ b/test/e2e/source_apiserver_test.go
@@ -18,23 +18,30 @@
 package e2e
 
 import (
-	"fmt"
+	"strings"
 	"testing"
 
 	"gotest.tools/assert"
 	"knative.dev/client/pkg/util"
 )
 
+const (
+	testServiceAccount = "apiserver-sa"
+	// use following prefix + current namespace to generate ClusterRole and ClusterRoleBinding names
+	clusterRolePrefix        = "apiserver-role-"
+	clusterRoleBindingPrefix = "apiserver-binding-"
+)
+
 func TestSourceApiServer(t *testing.T) {
 	t.Parallel()
 	test := NewE2eTest(t)
 	test.Setup(t)
 	defer func() {
-		test.deleteServiceAccountForApiserver(t, "testsa")
+		test.tearDownForSourceApiServer(t)
 		test.Teardown(t)
 	}()
 
-	test.setupServiceAccountForApiserver(t, "testsa")
+	test.setupForSourceApiServer(t)
 	test.serviceCreate(t, "testsvc0")
 
 	t.Run("create apiserver sources with a sink to a service", func(t *testing.T) {
@@ -82,37 +89,47 @@ func (test *e2eTest) apiServerSourceDelete(t *testing.T, sourceName string) {
 	assert.Check(t, util.ContainsAllIgnoreCase(out, "apiserver", "source", sourceName, "deleted", "namespace", test.kn.namespace))
 }
 
-func (test *e2eTest) setupServiceAccountForApiserver(t *testing.T, name string) {
+func (test *e2eTest) setupForSourceApiServer(t *testing.T) {
 	kubectl := kubectl{t, Logger{}}
 
-	_, err := kubectl.RunWithOpts([]string{"create", "serviceaccount", name, "--namespace", test.kn.namespace}, runOpts{})
+	saCmd := []string{"create", "serviceaccount", testServiceAccount, "--namespace", test.kn.namespace}
+	_, err := kubectl.RunWithOpts(saCmd, runOpts{})
 	if err != nil {
-		t.Fatalf(fmt.Sprintf("Error executing 'kubectl create serviceaccount test-sa'. Error: %s", err.Error()))
+		t.Fatalf("Error executing '%s'. Error: %v", strings.Join(saCmd, " "), err.Error())
 	}
-	_, err = kubectl.RunWithOpts([]string{"create", "clusterrole", "testsa-role", "--verb=get,list,watch", "--resource=events,namespaces"}, runOpts{})
+
+	crCmd := []string{"create", "clusterrole", clusterRolePrefix + test.kn.namespace, "--verb=get,list,watch", "--resource=events,namespaces"}
+	_, err = kubectl.RunWithOpts(crCmd, runOpts{})
 	if err != nil {
-		t.Fatalf(fmt.Sprintf("Error executing 'kubectl create clusterrole testsa-role'. Error: %s", err.Error()))
+		t.Fatalf("Error executing '%s'. Error: %v", strings.Join(crCmd, " "), err.Error())
 	}
-	_, err = kubectl.RunWithOpts([]string{"create", "clusterrolebinding", "testsa-binding", "--clusterrole=testsa-role", "--serviceaccount=" + test.kn.namespace + ":" + name}, runOpts{})
+
+	crbCmd := []string{"create", "clusterrolebinding", clusterRoleBindingPrefix + test.kn.namespace, "--clusterrole=" + clusterRolePrefix + test.kn.namespace, "--serviceaccount=" + test.kn.namespace + ":" + testServiceAccount}
+	_, err = kubectl.RunWithOpts(crbCmd, runOpts{})
 	if err != nil {
-		t.Fatalf(fmt.Sprintf("Error executing 'kubectl create clusterrolebinding testsa-binding'. Error: %s", err.Error()))
+		t.Fatalf("Error executing '%s'. Error: %v", strings.Join(crbCmd, " "), err.Error())
 	}
 }
 
-func (test *e2eTest) deleteServiceAccountForApiserver(t *testing.T, name string) {
+func (test *e2eTest) tearDownForSourceApiServer(t *testing.T) {
 	kubectl := kubectl{t, Logger{}}
 
-	_, err := kubectl.RunWithOpts([]string{"delete", "serviceaccount", name, "--namespace", test.kn.namespace}, runOpts{})
+	saCmd := []string{"delete", "serviceaccount", testServiceAccount, "--namespace", test.kn.namespace}
+	_, err := kubectl.RunWithOpts(saCmd, runOpts{})
 	if err != nil {
-		t.Fatalf(fmt.Sprintf("Error executing 'kubectl delete serviceaccount test-sa'. Error: %s", err.Error()))
+		t.Fatalf("Error executing '%s'. Error: %v", strings.Join(saCmd, " "), err.Error())
 	}
-	_, err = kubectl.RunWithOpts([]string{"delete", "clusterrole", "testsa-role"}, runOpts{})
+
+	crCmd := []string{"delete", "clusterrole", clusterRolePrefix + test.kn.namespace}
+	_, err = kubectl.RunWithOpts(crCmd, runOpts{})
 	if err != nil {
-		t.Fatalf(fmt.Sprintf("Error executing 'kubectl delete clusterrole testsa-role'. Error: %s", err.Error()))
+		t.Fatalf("Error executing '%s'. Error: %v", strings.Join(crCmd, " "), err.Error())
 	}
-	_, err = kubectl.RunWithOpts([]string{"delete", "clusterrolebinding", "testsa-binding"}, runOpts{})
+
+	crbCmd := []string{"delete", "clusterrolebinding", clusterRoleBindingPrefix + test.kn.namespace}
+	_, err = kubectl.RunWithOpts(crbCmd, runOpts{})
 	if err != nil {
-		t.Fatalf(fmt.Sprintf("Error executing 'kubectl delete clusterrolebinding testsa-binding'. Error: %s", err.Error()))
+		t.Fatalf("Error executing '%s'. Error: %v", strings.Join(crbCmd, " "), err.Error())
 	}
 }
 
diff --git a/test/e2e/tekton_test.go b/test/e2e/tekton_test.go
index 37905301..60f1efc1 100644
--- a/test/e2e/tekton_test.go
+++ b/test/e2e/tekton_test.go
@@ -50,7 +50,7 @@ func TestTektonPipeline(t *testing.T) {
 	_, err = kubectl.RunWithOpts([]string{"apply", "-n", test.env.Namespace, "-f", basedir + "/kn-deployer-rbac.yaml"}, runOpts{})
 	assert.NilError(t, err)
 
-	_, err = kubectl.RunWithOpts([]string{"apply", "-n", test.env.Namespace, "-f", "https://raw.githubusercontent.com/tektoncd/catalog/master/buildah/buildah.yaml"}, runOpts{})
+	_, err = kubectl.RunWithOpts([]string{"apply", "-n", test.env.Namespace, "-f", basedir + "/buildah.yaml"}, runOpts{})
 	assert.NilError(t, err)
 
 	_, err = kubectl.RunWithOpts([]string{"apply", "-n", test.env.Namespace, "-f", "https://raw.githubusercontent.com/tektoncd/catalog/master/kn/kn.yaml"}, runOpts{})
diff --git a/test/resources/tekton/buildah.yaml b/test/resources/tekton/buildah.yaml
new file mode 100644
index 00000000..1e9635b2
--- /dev/null
+++ b/test/resources/tekton/buildah.yaml
@@ -0,0 +1,66 @@
+# 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.
+# Define a ServiceAccount named kn-deployer-account that has permission to
+# manage Knative services.
+---
+apiVersion: tekton.dev/v1alpha1
+kind: Task
+metadata:
+  name: buildah
+spec:
+  inputs:
+    params:
+    - name: BUILDER_IMAGE
+      description: The location of the buildah builder image.
+      default: quay.io/buildah/stable:v1.11.0
+    - name: DOCKERFILE
+      description: Path to the Dockerfile to build.
+      default: ./Dockerfile
+    - name: TLSVERIFY
+      description: Verify the TLS on the registry endpoint (for push/pull to a non-TLS registry)
+      default: "true"
+
+    resources:
+    - name: source
+      type: git
+
+  outputs:
+    resources:
+    - name: image
+      type: image
+
+  steps:
+  - name: build
+    image: $(inputs.params.BUILDER_IMAGE)
+    workingDir: /workspace/source
+    command: ['buildah', 'bud', '--format=docker', '--tls-verify=$(inputs.params.TLSVERIFY)', '--layers', '-f', '$(inputs.params.DOCKERFILE)', '-t', '$(outputs.resources.image.url)', '.']
+    volumeMounts:
+    - name: varlibcontainers
+      mountPath: /var/lib/containers
+    securityContext:
+      privileged: true
+
+  - name: push
+    image: $(inputs.params.BUILDER_IMAGE)
+    workingDir: /workspace/source
+    command: ['buildah', 'push', '--tls-verify=$(inputs.params.TLSVERIFY)', '$(outputs.resources.image.url)', 'docker://$(outputs.resources.image.url)']
+    volumeMounts:
+    - name: varlibcontainers
+      mountPath: /var/lib/containers
+    securityContext:
+      privileged: true
+
+  volumes:
+  - name: varlibcontainers
+    emptyDir: {}
diff --git a/test/resources/tekton/kn-pipeline-run.yaml b/test/resources/tekton/kn-pipeline-run.yaml
index f08eec27..9a5b671c 100644
--- a/test/resources/tekton/kn-pipeline-run.yaml
+++ b/test/resources/tekton/kn-pipeline-run.yaml
@@ -16,7 +16,7 @@ kind: PipelineRun
 metadata:
   generateName: buildah-build-kn-create-
 spec:
-  serviceAccount: kn-deployer-account
+  serviceAccountName: kn-deployer-account
   pipelineRef:
     name: buildah-build-kn-create
   resources:
@@ -35,4 +35,4 @@ spec:
         - "--force"
         - "--service-account=kn-deployer-account"
         - "--image=$(inputs.resources.image.url)"
-        - "--env=TARGET=Tekton"
\ No newline at end of file
+        - "--env=TARGET=Tekton"
diff --git a/test/resources/tekton/kn-pipeline.yaml b/test/resources/tekton/kn-pipeline.yaml
index 70c583fe..6e1d8bbd 100644
--- a/test/resources/tekton/kn-pipeline.yaml
+++ b/test/resources/tekton/kn-pipeline.yaml
@@ -41,6 +41,8 @@ spec:
     params:
     - name: DOCKERFILE
       value: ./test/test_images/helloworld/Dockerfile
+    - name: BUILDER_IMAGE
+      value: "quay.io/buildah/stable:latest"
   - name: kn-service-create
     taskRef:
       name: kn
@@ -57,4 +59,4 @@ spec:
       value: "gcr.io/knative-nightly/knative.dev/client/cmd/kn"
     - name: ARGS
       value:
-        - "$(params.ARGS)"
\ No newline at end of file
+        - "$(params.ARGS)"
diff --git a/test/tekton-tests.sh b/test/tekton-tests.sh
index 0b79d775..2ccfc375 100755
--- a/test/tekton-tests.sh
+++ b/test/tekton-tests.sh
@@ -28,7 +28,7 @@ export PATH=$PATH:${REPO_ROOT_DIR}
 # Script entry point.
 initialize $@
 
-export TEKTON_VERSION=${TEKTON_VERSION:-v0.8.0}
+export TEKTON_VERSION=${TEKTON_VERSION:-v0.9.2}
 export KN_E2E_NAMESPACE=tkn-kn
 
 header "Running integration tests for Tekton"
diff --git a/test/test_images/helloworld/Dockerfile b/test/test_images/helloworld/Dockerfile
index c59fb767..5aa9bd40 100644
--- a/test/test_images/helloworld/Dockerfile
+++ b/test/test_images/helloworld/Dockerfile
@@ -1,23 +1,26 @@
-# Use the offical Golang image to create a build artifact.
+# Use the official Golang image to create a build artifact.
 # This is based on Debian and sets the GOPATH to /go.
 # https://hub.docker.com/_/golang
-FROM golang:1.12 as builder
+FROM golang:1.13 as builder
+
+# Create and change to the app directory.
+WORKDIR /app
 
 # Copy local code to the container image.
-WORKDIR /go/src/knative.dev/client/test/test_images/helloworld
-COPY ./test/test_images/helloworld/helloworld.go .
+COPY ./test/test_images/helloworld/ .
 
-# Build the command inside the container.
-# (You may fetch or manage dependencies here,
-# either manually or with a tool like "godep".)
-RUN CGO_ENABLED=0 GOOS=linux go build -v -o helloworld
+# Build the binary.
+# -mod=readonly ensures immutable go.mod and go.sum in container builds.
+RUN CGO_ENABLED=0 GOOS=linux go build -mod=readonly -v -o server
 
-# Use a Docker multi-stage build to create a lean production image.
+# Use the official Alpine image for a lean production container.
+# https://hub.docker.com/_/alpine
 # https://docs.docker.com/develop/develop-images/multistage-build/#use-multi-stage-builds
-FROM scratch
+FROM alpine:3
+RUN apk add --no-cache ca-certificates
 
 # Copy the binary to the production image from the builder stage.
-COPY --from=builder /go/src/knative.dev/client/test/test_images/helloworld/helloworld /helloworld
+COPY --from=builder /app/server /server
 
 # Run the web service on container startup.
-CMD ["/helloworld"]
\ No newline at end of file
+CMD ["/server"]
diff --git a/test/test_images/helloworld/go.mod b/test/test_images/helloworld/go.mod
new file mode 100644
index 00000000..1c89f351
--- /dev/null
+++ b/test/test_images/helloworld/go.mod
@@ -0,0 +1,3 @@
+module knative.dev/client/test/test_images/helloworld
+
+go 1.13
diff --git a/test/test_images/helloworld/helloworld.go b/test/test_images/helloworld/helloworld.go
index 11030351..6d0f4bcc 100644
--- a/test/test_images/helloworld/helloworld.go
+++ b/test/test_images/helloworld/helloworld.go
@@ -22,7 +22,7 @@ import (
 )
 
 func handler(w http.ResponseWriter, r *http.Request) {
-	log.Print("Hello world received a request.")
+	log.Print("helloworld: received a request")
 	target := os.Getenv("TARGET")
 	if target == "" {
 		target = "World"
@@ -31,7 +31,7 @@ func handler(w http.ResponseWriter, r *http.Request) {
 }
 
 func main() {
-	log.Print("Hello world sample started.")
+	log.Print("helloworld: starting server...")
 
 	http.HandleFunc("/", handler)
 
@@ -40,5 +40,6 @@ func main() {
 		port = "8080"
 	}
 
+	log.Printf("helloworld: listening on port %s", port)
 	log.Fatal(http.ListenAndServe(fmt.Sprintf(":%s", port), nil))
 }