Repository: knative/client
Pull request #964 (2020-08-03 07:26:59)

Feature: "kn service apply"
This implementation reuses the `kubectl.kubernetes.io/last-applied-configuration` for storing the original configuration so that kubectl tooling can be applied, too.

Only client-side patching is implemented as it's not clear to me whether server-side patching is implemented as part of the Knative API contract. 

An initial `kn service apply` for creating a service works as expected, but a subsequent `kn service apply` fails with a patch conflict error

The following challenges are still open:

* As we are using a structured approach when building up a `KService`, when serializing this so a JSON string to be added to the annotation mentioned above, fields that are typed to a struct (not a pointer to struct) are always present. This includes `createdTimestamp` or `resources` in the container spec. `kubectl` which uses always `Unstructured` doesn't suffer from this problem.
* The `name` of a container in the `PodSpec` is marked as mandatory for Kubernetes, but is used optional in Knative (it will be autonamed "user-container" if not provided. So it is always present (even as empty string) when deserializing to JSON from a `Service` struct, indicating the intention of the user to set it to an empty string (which actually is not the case as we never specify that name). That very likely is one of the causes of the patch conflict (as the backend set it to "user-container").

The idea is that we converted a Service to an `Unstructured` object and remove all map entries from which we know that they have not been specified. This work is still on-going.

I added the PR nevertheless as `Draft` as I'm now on PTO for the next two weeks and very likely can't continue on this PR until then. If someone feels fancy and might take a look, feel free to work on this PR.  

Also no tests have been implemented yet.

knative-prow-robot: Skipping CI for Draft Pull Request.
If you want CI signal for your change, please convert it to an actual PR.
You can still manually trigger a test run with `/test all`
knative-prow-robot: [APPROVALNOTIFIER] This PR is **APPROVED**

This pull-request has been approved by: *<a href="https://github.com/knative/client/pull/964#" title="Author self-approved">rhuss</a>*

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=knative%2Fclient).

The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process)

<details >
Needs approval from an approver in each of these files:

- ~~[OWNERS](https://github.com/knative/client/blob/master/OWNERS)~~ [rhuss]

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":[]} -->
duglin: @rhuss what's the diff between this and `kn service create --force`? They both seem to want to update an existing service with new metadata
kwiesmueller: Hey there,
this came up in a GitHub search for Server-Side Apply.
I see you are using the `kubectl.kubernetes.io/last-applied-configuration` annotation which is provided by client-side apply.
We are working on Server-Side Apply which might resolve some of the issues i read in the description above.
If you need any help or got questions around apply, feel free to get in touch with us (wg-api-expression) on Slack or ping me here.
https://kubernetes.slack.com/archives/C0123CNN8F3
wslyln: @duglin 'kn service create --force' will completely override the existing service resource. (akin to git push --force) while 'kn service apply' will do a three way merge of the configuration (aka git merge).
rhuss: @kwiesmueller Thanks for the offer ! I'm just back from PTO and will continue on this issue soon.
knative-prow-robot: @rhuss: PR needs rebase.

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository.
</details>
googlebot: All (the pull request submitter and all commit authors) CLAs are signed, **but** one or more commits were authored or co-authored by someone other than the pull request submitter.

We need to confirm that all authors are ok with their commits being contributed to this project.  Please have them confirm that by leaving a comment that contains only `@googlebot I consent.` in this pull request.

*Note to project maintainer:* There may be cases where the author cannot leave a comment, or the comment is not properly detected as consent.  In those cases, you can manually confirm consent of the commit author(s), and set the `cla` label to `yes` (if enabled on your project).

ℹ️ **Googlers: [Go here](https://goto.google.com/prinfo/https%3A%2F%2Fgithub.com%2Fknative%2Fclient%2Fpull%2F964) for more info**.

<!-- need_author_consent -->
googlebot: CLAs look good, thanks!

ℹ️ **Googlers: [Go here](https://goto.google.com/prinfo/https%3A%2F%2Fgithub.com%2Fknative%2Fclient%2Fpull%2F964) for more info**.

<!-- ok -->
rhuss: @kwiesmueller Finally I got around to continue on working on this PR. I am still trying to get client side patching to work, before switching to server side, but there are still some stumbling block with defaulting, serializing and also Knative backend handling. At the moment I'm a bit stuck to understand the following error message:

```
✦ at 20:00 ❯ kn service apply random --revision-name "" --image rhuss/random:2.0
Error: patch:
metadata:
  annotations:
    kubectl.kubernetes.io/last-applied-configuration: |
      {"apiVersion":"serving.knative.dev/v1","kind":"Service","metadata":{"name":"random","namespace":"default"},"spec":{"template":{"metadata":{"annotations":{"client.knative.dev/user-image":"rhuss/random:2.0"}},"spec":{"containers":[{"image":"rhuss/random:2.0","name":"user-container"}]}}}}
spec:
  template:
    metadata:
      annotations:
        client.knative.dev/user-image: rhuss/random:2.0
    spec:
      $setElementOrder/containers:
      - name: user-container
      containers:
      - image: rhuss/random:2.0
        name: user-container

conflicts with changes made from original to current:
metadata:
  annotations:
    kubectl.kubernetes.io/last-applied-configuration: |
      {"apiVersion":"serving.knative.dev/v1","kind":"Service","metadata":{"name":"random","namespace":"default"},"spec":{"template":{"metadata":{"annotations":{"client.knative.dev/user-image":"rhuss/random:1.0"}},"spec":{"containers":[{"image":"rhuss/random:1.0","name":"user-container"}]}}}}
    serving.knative.dev/creator: minikube-user
    serving.knative.dev/lastModifier: minikube-user
  generation: 1
  managedFields:
  - apiVersion: serving.knative.dev/v1
    fieldsType: FieldsV1
    fieldsV1:
      f:metadata:
        f:annotations:
          .: {}
          f:kubectl.kubernetes.io/last-applied-configuration: {}
      f:spec:
        .: {}
        f:template:
          .: {}
          f:metadata:
            .: {}
            f:annotations:
              .: {}
              f:client.knative.dev/user-image: {}
            f:creationTimestamp: {}
          f:spec:
            .: {}
            f:containers: {}
    manager: kn
    operation: Update
    time: "2020-09-09T18:00:46Z"
  - apiVersion: serving.knative.dev/v1
    fieldsType: FieldsV1
    fieldsV1:
      f:status:
        .: {}
        f:address:
          .: {}
          f:url: {}
        f:conditions: {}
        f:latestCreatedRevisionName: {}
        f:latestReadyRevisionName: {}
        f:observedGeneration: {}
        f:traffic: {}
        f:url: {}
    manager: controller
    operation: Update
    time: "2020-09-09T18:00:54Z"
  resourceVersion: "1473596"
  selfLink: /apis/serving.knative.dev/v1/namespaces/default/services/random
  uid: a37d3619-302e-4a5f-98c5-c4889faf6654
spec:
  template:
    spec:
      containerConcurrency: 0
      containers:
      - name: user-container
        readinessProbe:
          successThreshold: 1
          tcpSocket:
            port: 0
      timeoutSeconds: 300
  traffic:
  - latestRevision: true
    percent: 100
```

@kwiesmueller can you see, where the actual conflict is ? I though I fixed everything, but can't really see what is still conflicting.
kwiesmueller: If you're using SSA then the conflict error should give you the exact fields you get a conflict with.

Did I read correctly, that it's not SSA yet?
If not, I'm not yet sure what the conflict could be. Will take another look once I have time tomorrow.
kwiesmueller: It could be the CSA annotation, we should have a fix for that in the latest release.
rhuss: > Did I read correctly, that it's not SSA yet?

true, it's a true client side patch for now. But happy to explore SSA, too (thought client-side patch was easier to start with)
rhuss: @kwiesmueller do you have a good entrypoint/sample showing how to use SSA so that I can find out what's going on ? Currently I'm a bit lost.
rhuss: /retest

knative-metrics-robot: The following is the coverage report on the affected files.
Say `/test pull-knative-client-go-coverage` to re-run this coverage report

File | Old Coverage | New Coverage | Delta
---- |:------------:|:------------:|:-----:
[pkg/kn/commands/service/apply.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_client/964/pull-knative-client-go-coverage/1305504175362150402/artifacts/line-cov.html#file0) | Do not exist | 3.9% | 
[pkg/kn/commands/service/service.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_client/964/pull-knative-client-go-coverage/1305504175362150402/artifacts/line-cov.html#file1) | 86.4% | 87.0% | 0.6
[pkg/serving/v1/client.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_client/964/pull-knative-client-go-coverage/1305504175362150402/artifacts/line-cov.html#file2) | 66.5% | 64.7% | -1.8
[pkg/serving/v1/client_mock.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_client/964/pull-knative-client-go-coverage/1305504175362150402/artifacts/line-cov.html#file3) | 93.3% | 88.9% | -4.4

knative-prow-robot: @rhuss: The following test **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-knative-client-go-coverage | 22ffd28cf873ddae72847eedb815b93f96906f4a | [link](https://prow.knative.dev/view/gs/knative-prow/pr-logs/pull/knative_client/964/pull-knative-client-go-coverage/1305504175362150402) | `/test pull-knative-client-go-coverage`

[Full PR test history](https://gubernator.knative.dev/pr/knative_client/964). [Your PR dashboard](https://gubernator.knative.dev/pr/rhuss).

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
knative-prow-robot: @rhuss: The following tests **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-knative-client-go-coverage | 22ffd28cf873ddae72847eedb815b93f96906f4a | [link](https://prow.knative.dev/view/gs/knative-prow/pr-logs/pull/knative_client/964/pull-knative-client-go-coverage/1305504175362150402) | `/test pull-knative-client-go-coverage`
pull-knative-client-integration-tests | 22ffd28cf873ddae72847eedb815b93f96906f4a | [link](https://prow.knative.dev/view/gs/knative-prow/pr-logs/pull/knative_client/964/pull-knative-client-integration-tests/1305504175357956097) | `/test pull-knative-client-integration-tests`

[Full PR test history](https://gubernator.knative.dev/pr/knative_client/964). [Your PR dashboard](https://gubernator.knative.dev/pr/rhuss).

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
kwiesmueller: I think it makes sense to also support CSA as SSA is still in beta and only available since v1.16.
That said, to find out more about using SSA we have a bit of documentation here: https://kubernetes.io/docs/reference/using-api/api-concepts/#server-side-apply

On explicit usage examples, I recommend looking at our integration tests. A very basic one being this one: https://github.com/kubernetes/kubernetes/blob/master/test/integration/apiserver/apply/apply_test.go#L803

We have a lot of improvements in the works right now as well, especially working on a good client implementation.
If you have input on your use-case or need dedicated help with this, feel free to stop by on [Slack](https://kubernetes.slack.com/archives/C0123CNN8F3) or our [Bi-Weekly tomorrow (15. Sept 2020 @ 06:30pm GMT+2)](https://docs.google.com/document/d/1CSpNaicbEqKJoW306qaQEBIkwC1mGIcKl3yiB_C0HZk/edit).
knative-prow-robot: @rhuss: The following test **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-knative-client-integration-tests | 22ffd28cf873ddae72847eedb815b93f96906f4a | [link](https://prow.knative.dev/view/gs/knative-prow/pr-logs/pull/knative_client/964/pull-knative-client-integration-tests/1312133214856286209) | `/test pull-knative-client-integration-tests`

[Full PR test history](https://gubernator.knative.dev/pr/knative_client/964). [Your PR dashboard](https://gubernator.knative.dev/pr/rhuss).

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
googlebot: All (the pull request submitter and all commit authors) CLAs are signed, **but** one or more commits were authored or co-authored by someone other than the pull request submitter.

We need to confirm that all authors are ok with their commits being contributed to this project.  Please have them confirm that by leaving a comment that contains only `@googlebot I consent.` in this pull request.

*Note to project maintainer:* There may be cases where the author cannot leave a comment, or the comment is not properly detected as consent.  In those cases, you can manually confirm consent of the commit author(s), and set the `cla` label to `yes` (if enabled on your project).

ℹ️ **Googlers: [Go here](https://goto.google.com/prinfo/https%3A%2F%2Fgithub.com%2Fknative%2Fclient%2Fpull%2F964) for more info**.

<!-- need_author_consent -->
lgtm-com[bot]: This pull request **introduces 1 alert** when merging cb4b24e6eb71dc4759597cd51506507b1c48bbd5 into adb3793b98edb499b9182385d208aee3f1ca1dba - [view on LGTM.com](https://lgtm.com/projects/g/knative/client/rev/pr-11247d659f1e10413656f05b6068986e891bb71c)

**new alerts:**

* 1 for Useless assignment to local variable
knative-prow-robot: @rhuss: PR needs rebase.

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository.
</details>
lgtm-com[bot]: This pull request **introduces 1 alert** when merging 4b075be8b08da07313082d7de5fa26aab8741659 into 4ebd7ed6042c1344e724e0c58b7460c54af91865 - [view on LGTM.com](https://lgtm.com/projects/g/knative/client/rev/pr-2bbaf4243728b7c6f8b7491a82e5ab46a3c2162d)

**new alerts:**

* 1 for Useless assignment to local variable
rhuss: let me squash the whole PR, looks like that a rebase messed that up.
knative-prow-robot: @rhuss: PR needs rebase.

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository.
</details>
google-cla[bot]: CLAs look good, thanks!

ℹ️ **Googlers: [Go here](https://goto.google.com/prinfo/https%3A%2F%2Fgithub.com%2Fknative%2Fclient%2Fpull%2F964) for more info**.

<!-- ok -->
rhuss: @maximilien the tests are missing, I will work on that in this week. But beside this the PR is considered to be complete, so if you like you can already start to review. I will add the tests as extra commits to make an incremental review easier.
knative-metrics-robot: The following is the coverage report on the affected files.
Say `/test pull-knative-client-go-coverage` to re-run this coverage report

File | Old Coverage | New Coverage | Delta
---- |:------------:|:------------:|:-----:
[pkg/kn/commands/service/apply.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_client/964/pull-knative-client-go-coverage/1315643783571836928/artifacts/line-cov.html#file0) | Do not exist | 3.7% | 
[pkg/kn/commands/service/service.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_client/964/pull-knative-client-go-coverage/1315643783571836928/artifacts/line-cov.html#file1) | 86.4% | 87.0% | 0.6
[pkg/serving/v1/client.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_client/964/pull-knative-client-go-coverage/1315643783571836928/artifacts/line-cov.html#file2) | 66.8% | 65.1% | -1.8
[pkg/serving/v1/client_mock.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_client/964/pull-knative-client-go-coverage/1315643783571836928/artifacts/line-cov.html#file3) | 93.3% | 88.9% | -4.4

knative-prow-robot: @rhuss: The following tests **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-knative-client-integration-tests | 22ffd28cf873ddae72847eedb815b93f96906f4a | [link](https://prow.knative.dev/view/gs/knative-prow/pr-logs/pull/knative_client/964/pull-knative-client-integration-tests/1312133214856286209) | `/test pull-knative-client-integration-tests`
pull-knative-client-go-coverage | a95023e6ffecc854b65f2a9a23dd0f065678c94e | [link](https://prow.knative.dev/view/gs/knative-prow/pr-logs/pull/knative_client/964/pull-knative-client-go-coverage/1315643783571836928) | `/test pull-knative-client-go-coverage`

[Full PR test history](https://gubernator.knative.dev/pr/knative_client/964). [Your PR dashboard](https://gubernator.knative.dev/pr/rhuss).

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
google-cla[bot]: CLAs look good, thanks!

ℹ️ **Googlers: [Go here](https://goto.google.com/prinfo/https%3A%2F%2Fgithub.com%2Fknative%2Fclient%2Fpull%2F964) for more info**.

<!-- ok -->
knative-metrics-robot: The following is the coverage report on the affected files.
Say `/test pull-knative-client-go-coverage` to re-run this coverage report

File | Old Coverage | New Coverage | Delta
---- |:------------:|:------------:|:-----:
[pkg/kn/commands/service/apply.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_client/964/pull-knative-client-go-coverage/1315645545137573890/artifacts/line-cov.html#file0) | Do not exist | 3.7% | 
[pkg/kn/commands/service/service.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_client/964/pull-knative-client-go-coverage/1315645545137573890/artifacts/line-cov.html#file1) | 86.4% | 87.0% | 0.6
[pkg/serving/v1/client.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_client/964/pull-knative-client-go-coverage/1315645545137573890/artifacts/line-cov.html#file2) | 66.8% | 65.1% | -1.8
[pkg/serving/v1/client_mock.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_client/964/pull-knative-client-go-coverage/1315645545137573890/artifacts/line-cov.html#file3) | 93.3% | 88.9% | -4.4

knative-prow-robot: @rhuss: The following tests **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-knative-client-integration-tests | 22ffd28cf873ddae72847eedb815b93f96906f4a | [link](https://prow.knative.dev/view/gs/knative-prow/pr-logs/pull/knative_client/964/pull-knative-client-integration-tests/1312133214856286209) | `/test pull-knative-client-integration-tests`
pull-knative-client-go-coverage | 24ca0826a649ccb5d61da1a978cef0ef56b81d72 | [link](https://prow.knative.dev/view/gs/knative-prow/pr-logs/pull/knative_client/964/pull-knative-client-go-coverage/1315645545137573890) | `/test pull-knative-client-go-coverage`

[Full PR test history](https://gubernator.knative.dev/pr/knative_client/964). [Your PR dashboard](https://gubernator.knative.dev/pr/rhuss).

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
lgtm-com[bot]: This pull request **introduces 1 alert** when merging 24ca0826a649ccb5d61da1a978cef0ef56b81d72 into f7ddef522fbce9313c8a4637d3f869b84ee5ecf4 - [view on LGTM.com](https://lgtm.com/projects/g/knative/client/rev/pr-23c07402e8c2d263ee4db222b5ce01cf43c69441)

**new alerts:**

* 1 for Useless assignment to local variable
knative-prow-robot: @rhuss: The following tests **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-knative-client-integration-tests | 22ffd28cf873ddae72847eedb815b93f96906f4a | [link](https://prow.knative.dev/view/gs/knative-prow/pr-logs/pull/knative_client/964/pull-knative-client-integration-tests/1312133214856286209) | `/test pull-knative-client-integration-tests`
pull-knative-client-go-coverage | 24ca0826a649ccb5d61da1a978cef0ef56b81d72 | [link](https://prow.knative.dev/view/gs/knative-prow/pr-logs/pull/knative_client/964/pull-knative-client-go-coverage/1315645545137573890) | `/test pull-knative-client-go-coverage`
pull-knative-client-integration-tests-latest-release | 24ca0826a649ccb5d61da1a978cef0ef56b81d72 | [link](https://prow.knative.dev/view/gs/knative-prow/pr-logs/pull/knative_client/964/pull-knative-client-integration-tests-latest-release/1315645545137573889) | `/test pull-knative-client-integration-tests-latest-release`

[Full PR test history](https://gubernator.knative.dev/pr/knative_client/964). [Your PR dashboard](https://gubernator.knative.dev/pr/rhuss).

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
rhuss: /retest
knative-metrics-robot: The following is the coverage report on the affected files.
Say `/test pull-knative-client-go-coverage` to re-run this coverage report

File | Old Coverage | New Coverage | Delta
---- |:------------:|:------------:|:-----:
[pkg/kn/commands/service/apply.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_client/964/pull-knative-client-go-coverage/1315669075086544896/artifacts/line-cov.html#file0) | Do not exist | 3.7% | 
[pkg/kn/commands/service/service.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_client/964/pull-knative-client-go-coverage/1315669075086544896/artifacts/line-cov.html#file1) | 86.4% | 87.0% | 0.6
[pkg/serving/v1/client.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_client/964/pull-knative-client-go-coverage/1315669075086544896/artifacts/line-cov.html#file2) | 66.8% | 65.1% | -1.8
[pkg/serving/v1/client_mock.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_client/964/pull-knative-client-go-coverage/1315669075086544896/artifacts/line-cov.html#file3) | 93.3% | 88.9% | -4.4

knative-prow-robot: @rhuss: The following tests **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-knative-client-integration-tests-latest-release | 24ca0826a649ccb5d61da1a978cef0ef56b81d72 | [link](https://prow.knative.dev/view/gs/knative-prow/pr-logs/pull/knative_client/964/pull-knative-client-integration-tests-latest-release/1315645545137573889) | `/test pull-knative-client-integration-tests-latest-release`
pull-knative-client-go-coverage | 24ca0826a649ccb5d61da1a978cef0ef56b81d72 | [link](https://prow.knative.dev/view/gs/knative-prow/pr-logs/pull/knative_client/964/pull-knative-client-go-coverage/1315669075086544896) | `/test pull-knative-client-go-coverage`

[Full PR test history](https://gubernator.knative.dev/pr/knative_client/964). [Your PR dashboard](https://gubernator.knative.dev/pr/rhuss).

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
rhuss: the next steps are now to add test to complete this initial implementation and continue to work on a more sophisticated way for a CSA that merges also arrays properly.
google-cla[bot]: CLAs look good, thanks!

ℹ️ **Googlers: [Go here](https://goto.google.com/prinfo/https%3A%2F%2Fgithub.com%2Fknative%2Fclient%2Fpull%2F964) for more info**.

<!-- ok -->
knative-metrics-robot: The following is the coverage report on the affected files.
Say `/test pull-knative-client-go-coverage` to re-run this coverage report

File | Old Coverage | New Coverage | Delta
---- |:------------:|:------------:|:-----:
[pkg/kn/commands/service/apply.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_client/964/pull-knative-client-go-coverage/1316000942981648386/artifacts/line-cov.html#file0) | Do not exist | 3.7% | 
[pkg/kn/commands/service/service.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_client/964/pull-knative-client-go-coverage/1316000942981648386/artifacts/line-cov.html#file1) | 86.4% | 87.0% | 0.6
[pkg/serving/v1/client.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_client/964/pull-knative-client-go-coverage/1316000942981648386/artifacts/line-cov.html#file2) | 66.8% | 65.1% | -1.8
[pkg/serving/v1/client_mock.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_client/964/pull-knative-client-go-coverage/1316000942981648386/artifacts/line-cov.html#file3) | 93.3% | 88.9% | -4.4

knative-prow-robot: @rhuss: The following test **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-knative-client-go-coverage | d8f3b1400508c902482783d037baf1d7777c5f20 | [link](https://prow.knative.dev/view/gs/knative-prow/pr-logs/pull/knative_client/964/pull-knative-client-go-coverage/1316000942981648386) | `/test pull-knative-client-go-coverage`

[Full PR test history](https://gubernator.knative.dev/pr/knative_client/964). [Your PR dashboard](https://gubernator.knative.dev/pr/rhuss).

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
knative-prow-robot: @rhuss: The following tests **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-knative-client-go-coverage | d8f3b1400508c902482783d037baf1d7777c5f20 | [link](https://prow.knative.dev/view/gs/knative-prow/pr-logs/pull/knative_client/964/pull-knative-client-go-coverage/1316000942981648386) | `/test pull-knative-client-go-coverage`
pull-knative-client-build-tests | d8f3b1400508c902482783d037baf1d7777c5f20 | [link](https://prow.knative.dev/view/gs/knative-prow/pr-logs/pull/knative_client/964/pull-knative-client-build-tests/1316000942981648387) | `/test pull-knative-client-build-tests`

[Full PR test history](https://gubernator.knative.dev/pr/knative_client/964). [Your PR dashboard](https://gubernator.knative.dev/pr/rhuss).

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
lgtm-com[bot]: This pull request **introduces 1 alert** when merging d8f3b1400508c902482783d037baf1d7777c5f20 into 4939c5ede3415cf4b7b0c19c4b41762b2739c441 - [view on LGTM.com](https://lgtm.com/projects/g/knative/client/rev/pr-8416a0dd082590bea00977d522163ecf37fb9dac)

**new alerts:**

* 1 for Useless assignment to local variable
google-cla[bot]: CLAs look good, thanks!

ℹ️ **Googlers: [Go here](https://goto.google.com/prinfo/https%3A%2F%2Fgithub.com%2Fknative%2Fclient%2Fpull%2F964) for more info**.

<!-- ok -->
knative-prow-robot: @rhuss: The following tests **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-knative-client-go-coverage | d8f3b1400508c902482783d037baf1d7777c5f20 | [link](https://prow.knative.dev/view/gs/knative-prow/pr-logs/pull/knative_client/964/pull-knative-client-go-coverage/1316000942981648386) | `/test pull-knative-client-go-coverage`
pull-knative-client-build-tests | c89a75bf97b78521c7905452c6226e604e0af935 | [link](https://prow.knative.dev/view/gs/knative-prow/pr-logs/pull/knative_client/964/pull-knative-client-build-tests/1316688706798620672) | `/test pull-knative-client-build-tests`

[Full PR test history](https://gubernator.knative.dev/pr/knative_client/964). [Your PR dashboard](https://gubernator.knative.dev/pr/rhuss).

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
knative-metrics-robot: The following is the coverage report on the affected files.
Say `/test pull-knative-client-go-coverage` to re-run this coverage report

File | Old Coverage | New Coverage | Delta
---- |:------------:|:------------:|:-----:
[pkg/kn/commands/service/apply.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_client/964/pull-knative-client-go-coverage/1316688706802814978/artifacts/line-cov.html#file0) | Do not exist | 16.3% | 
[pkg/kn/commands/service/service.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_client/964/pull-knative-client-go-coverage/1316688706802814978/artifacts/line-cov.html#file2) | 86.4% | 87.0% | 0.6
[pkg/serving/v1/apply.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_client/964/pull-knative-client-go-coverage/1316688706802814978/artifacts/line-cov.html#file3) | Do not exist | 0.0% | 
[pkg/serving/v1/client.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_client/964/pull-knative-client-go-coverage/1316688706802814978/artifacts/line-cov.html#file4) | 66.8% | 60.9% | -6.0
[pkg/serving/v1/client_mock.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_client/964/pull-knative-client-go-coverage/1316688706802814978/artifacts/line-cov.html#file5) | 93.3% | 84.8% | -8.5

knative-prow-robot: @rhuss: The following tests **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-knative-client-build-tests | c89a75bf97b78521c7905452c6226e604e0af935 | [link](https://prow.knative.dev/view/gs/knative-prow/pr-logs/pull/knative_client/964/pull-knative-client-build-tests/1316688706798620672) | `/test pull-knative-client-build-tests`
pull-knative-client-go-coverage | c89a75bf97b78521c7905452c6226e604e0af935 | [link](https://prow.knative.dev/view/gs/knative-prow/pr-logs/pull/knative_client/964/pull-knative-client-go-coverage/1316688706802814978) | `/test pull-knative-client-go-coverage`

[Full PR test history](https://gubernator.knative.dev/pr/knative_client/964). [Your PR dashboard](https://gubernator.knative.dev/pr/rhuss).

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
lgtm-com[bot]: This pull request **introduces 2 alerts** when merging c89a75bf97b78521c7905452c6226e604e0af935 into 880aa1ae1945e16e9384f679a78cbaeda766f915 - [view on LGTM.com](https://lgtm.com/projects/g/knative/client/rev/pr-3b471495f54afc20cd12d88148b1408ba654af1b)

**new alerts:**

* 2 for Useless assignment to local variable
knative-prow-robot: @rhuss: The following tests **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-knative-client-build-tests | c89a75bf97b78521c7905452c6226e604e0af935 | [link](https://prow.knative.dev/view/gs/knative-prow/pr-logs/pull/knative_client/964/pull-knative-client-build-tests/1316688706798620672) | `/test pull-knative-client-build-tests`
pull-knative-client-go-coverage | c89a75bf97b78521c7905452c6226e604e0af935 | [link](https://prow.knative.dev/view/gs/knative-prow/pr-logs/pull/knative_client/964/pull-knative-client-go-coverage/1316688706802814978) | `/test pull-knative-client-go-coverage`
pull-knative-client-integration-tests | c89a75bf97b78521c7905452c6226e604e0af935 | [link](https://prow.knative.dev/view/gs/knative-prow/pr-logs/pull/knative_client/964/pull-knative-client-integration-tests/1316688706802814977) | `/test pull-knative-client-integration-tests`

[Full PR test history](https://gubernator.knative.dev/pr/knative_client/964). [Your PR dashboard](https://gubernator.knative.dev/pr/rhuss).

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
google-cla[bot]: CLAs look good, thanks!

ℹ️ **Googlers: [Go here](https://goto.google.com/prinfo/https%3A%2F%2Fgithub.com%2Fknative%2Fclient%2Fpull%2F964) for more info**.

<!-- ok -->
knative-metrics-robot: The following is the coverage report on the affected files.
Say `/test pull-knative-client-go-coverage` to re-run this coverage report

File | Old Coverage | New Coverage | Delta
---- |:------------:|:------------:|:-----:
[pkg/kn/commands/service/apply.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_client/964/pull-knative-client-go-coverage/1318228605276262406/artifacts/line-cov.html#file0) | Do not exist | 16.3% | 
[pkg/kn/commands/service/service.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_client/964/pull-knative-client-go-coverage/1318228605276262406/artifacts/line-cov.html#file2) | 86.4% | 87.0% | 0.6
[pkg/serving/v1/apply.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_client/964/pull-knative-client-go-coverage/1318228605276262406/artifacts/line-cov.html#file3) | Do not exist | 72.2% | 
[pkg/serving/v1/client.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_client/964/pull-knative-client-go-coverage/1318228605276262406/artifacts/line-cov.html#file4) | 66.8% | 68.3% | 1.5
[pkg/serving/v1/client_mock.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_client/964/pull-knative-client-go-coverage/1318228605276262406/artifacts/line-cov.html#file5) | 93.3% | 84.8% | -8.5

knative-prow-robot: @rhuss: The following tests **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-knative-client-build-tests | c89a75bf97b78521c7905452c6226e604e0af935 | [link](https://prow.knative.dev/view/gs/knative-prow/pr-logs/pull/knative_client/964/pull-knative-client-build-tests/1316688706798620672) | `/test pull-knative-client-build-tests`
pull-knative-client-integration-tests | c89a75bf97b78521c7905452c6226e604e0af935 | [link](https://prow.knative.dev/view/gs/knative-prow/pr-logs/pull/knative_client/964/pull-knative-client-integration-tests/1316688706802814977) | `/test pull-knative-client-integration-tests`
pull-knative-client-go-coverage | 0d1b93640a65712221f3e3386ffc9fd42c7550d5 | [link](https://prow.knative.dev/view/gs/knative-prow/pr-logs/pull/knative_client/964/pull-knative-client-go-coverage/1318228605276262406) | `/test pull-knative-client-go-coverage`

[Full PR test history](https://gubernator.knative.dev/pr/knative_client/964). [Your PR dashboard](https://gubernator.knative.dev/pr/rhuss).

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
lgtm-com[bot]: This pull request **introduces 2 alerts** when merging 0d1b93640a65712221f3e3386ffc9fd42c7550d5 into 25b537b18718b520056f552d10d87cc3b9ec90d9 - [view on LGTM.com](https://lgtm.com/projects/g/knative/client/rev/pr-2a00fe149f1d742e94477b32d12cce18650dee9a)

**new alerts:**

* 2 for Useless assignment to local variable
google-cla[bot]: CLAs look good, thanks!

ℹ️ **Googlers: [Go here](https://goto.google.com/prinfo/https%3A%2F%2Fgithub.com%2Fknative%2Fclient%2Fpull%2F964) for more info**.

<!-- ok -->
knative-metrics-robot: The following is the coverage report on the affected files.
Say `/test pull-knative-client-go-coverage` to re-run this coverage report

File | Old Coverage | New Coverage | Delta
---- |:------------:|:------------:|:-----:
[pkg/kn/commands/service/apply.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_client/964/pull-knative-client-go-coverage/1318870428961214474/artifacts/line-cov.html#file0) | Do not exist | 84.4% | 
[pkg/kn/commands/service/service.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_client/964/pull-knative-client-go-coverage/1318870428961214474/artifacts/line-cov.html#file2) | 86.4% | 87.0% | 0.6
[pkg/serving/v1/apply.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_client/964/pull-knative-client-go-coverage/1318870428961214474/artifacts/line-cov.html#file3) | Do not exist | 72.2% | 
[pkg/serving/v1/client.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_client/964/pull-knative-client-go-coverage/1318870428961214474/artifacts/line-cov.html#file4) | 66.8% | 68.3% | 1.5
[pkg/serving/v1/client_mock.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_client/964/pull-knative-client-go-coverage/1318870428961214474/artifacts/line-cov.html#file5) | 93.3% | 84.8% | -8.5

lgtm-com[bot]: This pull request **introduces 2 alerts** when merging ccc8764d055c5b6793ebd4e3ce38171a9a94d43f into 25b537b18718b520056f552d10d87cc3b9ec90d9 - [view on LGTM.com](https://lgtm.com/projects/g/knative/client/rev/pr-a9eb3045bc70789660f5bf64b01ee178169a7bc1)

**new alerts:**

* 2 for Useless assignment to local variable
google-cla[bot]: CLAs look good, thanks!

ℹ️ **Googlers: [Go here](https://goto.google.com/prinfo/https%3A%2F%2Fgithub.com%2Fknative%2Fclient%2Fpull%2F964) for more info**.

<!-- ok -->
knative-metrics-robot: The following is the coverage report on the affected files.
Say `/test pull-knative-client-go-coverage` to re-run this coverage report

File | Old Coverage | New Coverage | Delta
---- |:------------:|:------------:|:-----:
[pkg/kn/commands/service/apply.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_client/964/pull-knative-client-go-coverage/1318905283589705728/artifacts/line-cov.html#file0) | Do not exist | 83.0% | 
[pkg/kn/commands/service/service.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_client/964/pull-knative-client-go-coverage/1318905283589705728/artifacts/line-cov.html#file2) | 86.4% | 87.0% | 0.6
[pkg/serving/v1/apply.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_client/964/pull-knative-client-go-coverage/1318905283589705728/artifacts/line-cov.html#file3) | Do not exist | 72.2% | 
[pkg/serving/v1/client.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_client/964/pull-knative-client-go-coverage/1318905283589705728/artifacts/line-cov.html#file4) | 66.8% | 68.3% | 1.5
[pkg/serving/v1/client_mock.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_client/964/pull-knative-client-go-coverage/1318905283589705728/artifacts/line-cov.html#file5) | 93.3% | 84.8% | -8.5

google-cla[bot]: CLAs look good, thanks!

ℹ️ **Googlers: [Go here](https://goto.google.com/prinfo/https%3A%2F%2Fgithub.com%2Fknative%2Fclient%2Fpull%2F964) for more info**.

<!-- ok -->
knative-metrics-robot: The following is the coverage report on the affected files.
Say `/test pull-knative-client-go-coverage` to re-run this coverage report

File | Old Coverage | New Coverage | Delta
---- |:------------:|:------------:|:-----:
[pkg/kn/commands/service/apply.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_client/964/pull-knative-client-go-coverage/1318907045511958537/artifacts/line-cov.html#file0) | Do not exist | 83.0% | 
[pkg/kn/commands/service/service.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_client/964/pull-knative-client-go-coverage/1318907045511958537/artifacts/line-cov.html#file2) | 86.4% | 87.0% | 0.6
[pkg/serving/v1/apply.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_client/964/pull-knative-client-go-coverage/1318907045511958537/artifacts/line-cov.html#file3) | Do not exist | 72.2% | 
[pkg/serving/v1/client.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_client/964/pull-knative-client-go-coverage/1318907045511958537/artifacts/line-cov.html#file4) | 66.8% | 68.3% | 1.5
[pkg/serving/v1/client_mock.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_client/964/pull-knative-client-go-coverage/1318907045511958537/artifacts/line-cov.html#file5) | 93.3% | 89.4% | -3.9

google-cla[bot]: CLAs look good, thanks!

ℹ️ **Googlers: [Go here](https://goto.google.com/prinfo/https%3A%2F%2Fgithub.com%2Fknative%2Fclient%2Fpull%2F964) for more info**.

<!-- ok -->
google-cla[bot]: CLAs look good, thanks!

ℹ️ **Googlers: [Go here](https://goto.google.com/prinfo/https%3A%2F%2Fgithub.com%2Fknative%2Fclient%2Fpull%2F964) for more info**.

<!-- ok -->
knative-metrics-robot: The following is the coverage report on the affected files.
Say `/test pull-knative-client-go-coverage` to re-run this coverage report

File | Old Coverage | New Coverage | Delta
---- |:------------:|:------------:|:-----:
[pkg/kn/commands/service/apply.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_client/964/pull-knative-client-go-coverage/1319194313766211589/artifacts/line-cov.html#file0) | Do not exist | 83.0% | 
[pkg/kn/commands/service/service.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_client/964/pull-knative-client-go-coverage/1319194313766211589/artifacts/line-cov.html#file2) | 86.4% | 87.0% | 0.6
[pkg/serving/v1/apply.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_client/964/pull-knative-client-go-coverage/1319194313766211589/artifacts/line-cov.html#file3) | Do not exist | 72.5% | 
[pkg/serving/v1/client.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_client/964/pull-knative-client-go-coverage/1319194313766211589/artifacts/line-cov.html#file4) | 66.8% | 68.0% | 1.2
[pkg/serving/v1/client_mock.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_client/964/pull-knative-client-go-coverage/1319194313766211589/artifacts/line-cov.html#file5) | 93.3% | 93.7% | 0.3

knative-metrics-robot: The following is the coverage report on the affected files.
Say `/test pull-knative-client-go-coverage` to re-run this coverage report

File | Old Coverage | New Coverage | Delta
---- |:------------:|:------------:|:-----:
[pkg/kn/commands/service/apply.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_client/964/pull-knative-client-go-coverage/1319195194242895876/artifacts/line-cov.html#file0) | Do not exist | 83.0% | 
[pkg/kn/commands/service/service.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_client/964/pull-knative-client-go-coverage/1319195194242895876/artifacts/line-cov.html#file2) | 86.4% | 87.0% | 0.6
[pkg/serving/v1/apply.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_client/964/pull-knative-client-go-coverage/1319195194242895876/artifacts/line-cov.html#file3) | Do not exist | 72.5% | 
[pkg/serving/v1/client.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_client/964/pull-knative-client-go-coverage/1319195194242895876/artifacts/line-cov.html#file4) | 66.8% | 68.0% | 1.2
[pkg/serving/v1/client_mock.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_client/964/pull-knative-client-go-coverage/1319195194242895876/artifacts/line-cov.html#file5) | 93.3% | 93.7% | 0.3

google-cla[bot]: CLAs look good, thanks!

ℹ️ **Googlers: [Go here](https://goto.google.com/prinfo/https%3A%2F%2Fgithub.com%2Fknative%2Fclient%2Fpull%2F964) for more info**.

<!-- ok -->
google-cla[bot]: CLAs look good, thanks!

ℹ️ **Googlers: [Go here](https://goto.google.com/prinfo/https%3A%2F%2Fgithub.com%2Fknative%2Fclient%2Fpull%2F964) for more info**.

<!-- ok -->
knative-metrics-robot: The following is the coverage report on the affected files.
Say `/test pull-knative-client-go-coverage` to re-run this coverage report

File | Old Coverage | New Coverage | Delta
---- |:------------:|:------------:|:-----:
[pkg/kn/commands/service/apply.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_client/964/pull-knative-client-go-coverage/1319200227172814850/artifacts/line-cov.html#file0) | Do not exist | 82.2% | 
[pkg/kn/commands/service/service.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_client/964/pull-knative-client-go-coverage/1319200227172814850/artifacts/line-cov.html#file2) | 86.4% | 91.3% | 4.9
[pkg/serving/v1/apply.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_client/964/pull-knative-client-go-coverage/1319200227172814850/artifacts/line-cov.html#file3) | Do not exist | 72.5% | 
[pkg/serving/v1/client.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_client/964/pull-knative-client-go-coverage/1319200227172814850/artifacts/line-cov.html#file4) | 66.8% | 68.0% | 1.2
[pkg/serving/v1/client_mock.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_client/964/pull-knative-client-go-coverage/1319200227172814850/artifacts/line-cov.html#file5) | 93.3% | 93.7% | 0.3

knative-prow-robot: @rhuss: The following test **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-knative-client-integration-tests-latest-release | 6a6622bd183245e5d0bba3c9e74c62b395b7f1d1 | [link](https://prow.knative.dev/view/gs/knative-prow/pr-logs/pull/knative_client/964/pull-knative-client-integration-tests-latest-release/1319200227172814851) | `/test pull-knative-client-integration-tests-latest-release`

[Full PR test history](https://gubernator.knative.dev/pr/knative_client/964). [Your PR dashboard](https://gubernator.knative.dev/pr/rhuss).

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
knative-prow-robot: @rhuss: The following tests **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-knative-client-integration-tests-latest-release | 6a6622bd183245e5d0bba3c9e74c62b395b7f1d1 | [link](https://prow.knative.dev/view/gs/knative-prow/pr-logs/pull/knative_client/964/pull-knative-client-integration-tests-latest-release/1319200227172814851) | `/test pull-knative-client-integration-tests-latest-release`
pull-knative-client-integration-tests | 6a6622bd183245e5d0bba3c9e74c62b395b7f1d1 | [link](https://prow.knative.dev/view/gs/knative-prow/pr-logs/pull/knative_client/964/pull-knative-client-integration-tests/1319200227172814849) | `/test pull-knative-client-integration-tests`

[Full PR test history](https://gubernator.knative.dev/pr/knative_client/964). [Your PR dashboard](https://gubernator.knative.dev/pr/rhuss).

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
knative-metrics-robot: The following is the coverage report on the affected files.
Say `/test pull-knative-client-go-coverage` to re-run this coverage report

File | Old Coverage | New Coverage | Delta
---- |:------------:|:------------:|:-----:
[pkg/kn/commands/service/apply.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_client/964/pull-knative-client-go-coverage/1319261883244482565/artifacts/line-cov.html#file0) | Do not exist | 82.2% | 
[pkg/kn/commands/service/service.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_client/964/pull-knative-client-go-coverage/1319261883244482565/artifacts/line-cov.html#file2) | 86.4% | 91.3% | 4.9
[pkg/serving/v1/apply.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_client/964/pull-knative-client-go-coverage/1319261883244482565/artifacts/line-cov.html#file3) | Do not exist | 72.5% | 
[pkg/serving/v1/client.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_client/964/pull-knative-client-go-coverage/1319261883244482565/artifacts/line-cov.html#file4) | 66.8% | 68.0% | 1.2
[pkg/serving/v1/client_mock.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_client/964/pull-knative-client-go-coverage/1319261883244482565/artifacts/line-cov.html#file5) | 93.3% | 93.7% | 0.3

knative-prow-robot: @rhuss: The following tests **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-knative-client-integration-tests | 6a6622bd183245e5d0bba3c9e74c62b395b7f1d1 | [link](https://prow.knative.dev/view/gs/knative-prow/pr-logs/pull/knative_client/964/pull-knative-client-integration-tests/1319200227172814849) | `/test pull-knative-client-integration-tests`
pull-knative-client-integration-tests-latest-release | 3ef4943017c71762ebcc2f436e3092b2dd751634 | [link](https://prow.knative.dev/view/gs/knative-prow/pr-logs/pull/knative_client/964/pull-knative-client-integration-tests-latest-release/1319261883244482563) | `/test pull-knative-client-integration-tests-latest-release`

[Full PR test history](https://gubernator.knative.dev/pr/knative_client/964). [Your PR dashboard](https://gubernator.knative.dev/pr/rhuss).

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
knative-prow-robot: @rhuss: The following tests **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-knative-client-integration-tests-latest-release | 3ef4943017c71762ebcc2f436e3092b2dd751634 | [link](https://prow.knative.dev/view/gs/knative-prow/pr-logs/pull/knative_client/964/pull-knative-client-integration-tests-latest-release/1319261883244482563) | `/test pull-knative-client-integration-tests-latest-release`
pull-knative-client-integration-tests | 3ef4943017c71762ebcc2f436e3092b2dd751634 | [link](https://prow.knative.dev/view/gs/knative-prow/pr-logs/pull/knative_client/964/pull-knative-client-integration-tests/1319261883244482562) | `/test pull-knative-client-integration-tests`

[Full PR test history](https://gubernator.knative.dev/pr/knative_client/964). [Your PR dashboard](https://gubernator.knative.dev/pr/rhuss).

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
google-cla[bot]: CLAs look good, thanks!

ℹ️ **Googlers: [Go here](https://goto.google.com/prinfo/https%3A%2F%2Fgithub.com%2Fknative%2Fclient%2Fpull%2F964) for more info**.

<!-- ok -->
knative-metrics-robot: The following is the coverage report on the affected files.
Say `/test pull-knative-client-go-coverage` to re-run this coverage report

File | Old Coverage | New Coverage | Delta
---- |:------------:|:------------:|:-----:
[pkg/kn/commands/service/apply.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_client/964/pull-knative-client-go-coverage/1319320897114345474/artifacts/line-cov.html#file0) | Do not exist | 82.2% | 
[pkg/kn/commands/service/service.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_client/964/pull-knative-client-go-coverage/1319320897114345474/artifacts/line-cov.html#file2) | 86.4% | 91.3% | 4.9
[pkg/serving/v1/apply.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_client/964/pull-knative-client-go-coverage/1319320897114345474/artifacts/line-cov.html#file3) | Do not exist | 72.5% | 
[pkg/serving/v1/client.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_client/964/pull-knative-client-go-coverage/1319320897114345474/artifacts/line-cov.html#file4) | 66.8% | 68.0% | 1.2
[pkg/serving/v1/client_mock.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_client/964/pull-knative-client-go-coverage/1319320897114345474/artifacts/line-cov.html#file5) | 93.3% | 93.7% | 0.3

kwiesmueller: @rhuss I'm still reading along. If you need anything from me regarding apply, chat me up :-)
google-cla[bot]: CLAs look good, thanks!

ℹ️ **Googlers: [Go here](https://goto.google.com/prinfo/https%3A%2F%2Fgithub.com%2Fknative%2Fclient%2Fpull%2F964) for more info**.

<!-- ok -->
rhuss: @maximilien thanks for the review, I adjusted the code according to your suggestions. You might want to have a look to the latest commit where I added my changes fixing the issues that you have pointed out
knative-metrics-robot: The following is the coverage report on the affected files.
Say `/test pull-knative-client-go-coverage` to re-run this coverage report

File | Old Coverage | New Coverage | Delta
---- |:------------:|:------------:|:-----:
[pkg/kn/commands/service/apply.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_client/964/pull-knative-client-go-coverage/1320680606622814212/artifacts/line-cov.html#file0) | Do not exist | 84.4% | 
[pkg/kn/commands/service/create.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_client/964/pull-knative-client-go-coverage/1320680606622814212/artifacts/line-cov.html#file1) | 84.1% | 82.6% | -1.5
[pkg/kn/commands/service/service.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_client/964/pull-knative-client-go-coverage/1320680606622814212/artifacts/line-cov.html#file2) | 86.4% | 91.3% | 4.9
[pkg/serving/v1/apply.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_client/964/pull-knative-client-go-coverage/1320680606622814212/artifacts/line-cov.html#file3) | Do not exist | 80.5% | 
[pkg/serving/v1/client.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_client/964/pull-knative-client-go-coverage/1320680606622814212/artifacts/line-cov.html#file4) | 66.8% | 68.0% | 1.2
[pkg/serving/v1/client_mock.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_client/964/pull-knative-client-go-coverage/1320680606622814212/artifacts/line-cov.html#file5) | 93.3% | 93.7% | 0.3

google-cla[bot]: CLAs look good, thanks!

ℹ️ **Googlers: [Go here](https://goto.google.com/prinfo/https%3A%2F%2Fgithub.com%2Fknative%2Fclient%2Fpull%2F964) for more info**.

<!-- ok -->
knative-metrics-robot: The following is the coverage report on the affected files.
Say `/test pull-knative-client-go-coverage` to re-run this coverage report

File | Old Coverage | New Coverage | Delta
---- |:------------:|:------------:|:-----:
[pkg/kn/commands/service/apply.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_client/964/pull-knative-client-go-coverage/1320687653858840576/artifacts/line-cov.html#file0) | Do not exist | 84.4% | 
[pkg/kn/commands/service/create.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_client/964/pull-knative-client-go-coverage/1320687653858840576/artifacts/line-cov.html#file1) | 84.1% | 82.6% | -1.5
[pkg/kn/commands/service/service.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_client/964/pull-knative-client-go-coverage/1320687653858840576/artifacts/line-cov.html#file2) | 86.4% | 91.3% | 4.9
[pkg/serving/v1/apply.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_client/964/pull-knative-client-go-coverage/1320687653858840576/artifacts/line-cov.html#file3) | Do not exist | 80.5% | 
[pkg/serving/v1/client.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_client/964/pull-knative-client-go-coverage/1320687653858840576/artifacts/line-cov.html#file4) | 66.8% | 68.0% | 1.2
[pkg/serving/v1/client_mock.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_client/964/pull-knative-client-go-coverage/1320687653858840576/artifacts/line-cov.html#file5) | 93.3% | 93.7% | 0.3

knative-prow-robot: @rhuss: The following test **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-knative-client-build-tests | bc8f7c251882bb3bef14ff2690933d2203a3f178 | [link](https://prow.knative.dev/view/gs/knative-prow/pr-logs/pull/knative_client/964/pull-knative-client-build-tests/1320687653858840580) | `/test pull-knative-client-build-tests`

[Full PR test history](https://gubernator.knative.dev/pr/knative_client/964). [Your PR dashboard](https://gubernator.knative.dev/pr/rhuss).

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
google-cla[bot]: CLAs look good, thanks!

ℹ️ **Googlers: [Go here](https://goto.google.com/prinfo/https%3A%2F%2Fgithub.com%2Fknative%2Fclient%2Fpull%2F964) for more info**.

<!-- ok -->
knative-metrics-robot: The following is the coverage report on the affected files.
Say `/test pull-knative-client-go-coverage` to re-run this coverage report

File | Old Coverage | New Coverage | Delta
---- |:------------:|:------------:|:-----:
[pkg/kn/commands/service/apply.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_client/964/pull-knative-client-go-coverage/1321007341641928707/artifacts/line-cov.html#file0) | Do not exist | 84.4% | 
[pkg/kn/commands/service/create.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_client/964/pull-knative-client-go-coverage/1321007341641928707/artifacts/line-cov.html#file1) | 84.1% | 82.6% | -1.5
[pkg/kn/commands/service/service.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_client/964/pull-knative-client-go-coverage/1321007341641928707/artifacts/line-cov.html#file2) | 86.4% | 91.3% | 4.9
[pkg/serving/v1/apply.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_client/964/pull-knative-client-go-coverage/1321007341641928707/artifacts/line-cov.html#file3) | Do not exist | 80.5% | 
[pkg/serving/v1/client.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_client/964/pull-knative-client-go-coverage/1321007341641928707/artifacts/line-cov.html#file4) | 66.8% | 68.0% | 1.2
[pkg/serving/v1/client_mock.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_client/964/pull-knative-client-go-coverage/1321007341641928707/artifacts/line-cov.html#file5) | 93.3% | 93.7% | 0.3

knative-prow-robot: [APPROVALNOTIFIER] This PR is **APPROVED**

This pull-request has been approved by: *<a href="https://github.com/knative/client/pull/964#pullrequestreview-519124717" title="Approved">maximilien</a>*, *<a href="https://github.com/knative/client/pull/964#" title="Author self-approved">rhuss</a>*

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=knative%2Fclient).

The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process)

<details >
Needs approval from an approver in each of these files:

- ~~[OWNERS](https://github.com/knative/client/blob/master/OWNERS)~~ [maximilien,rhuss]

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":[]} -->
google-cla[bot]: CLAs look good, thanks!

ℹ️ **Googlers: [Go here](https://goto.google.com/prinfo/https%3A%2F%2Fgithub.com%2Fknative%2Fclient%2Fpull%2F964) for more info**.

<!-- ok -->
knative-prow-robot: @rhuss: The following test **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-knative-client-build-tests | 22cd016398ab9a928bed2583ff06151c9c8bd309 | [link](https://prow.knative.dev/view/gs/knative-prow/pr-logs/pull/knative_client/964/pull-knative-client-build-tests/1322203817617395712) | `/test pull-knative-client-build-tests`

[Full PR test history](https://gubernator.knative.dev/pr/knative_client/964). [Your PR dashboard](https://gubernator.knative.dev/pr/rhuss).

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
knative-metrics-robot: The following is the coverage report on the affected files.
Say `/test pull-knative-client-go-coverage` to re-run this coverage report

File | Old Coverage | New Coverage | Delta
---- |:------------:|:------------:|:-----:
[pkg/kn/commands/service/apply.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_client/964/pull-knative-client-go-coverage/1322203817667727360/artifacts/line-cov.html#file0) | Do not exist | 84.4% | 
[pkg/kn/commands/service/create.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_client/964/pull-knative-client-go-coverage/1322203817667727360/artifacts/line-cov.html#file1) | 84.1% | 82.6% | -1.5
[pkg/kn/commands/service/service.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_client/964/pull-knative-client-go-coverage/1322203817667727360/artifacts/line-cov.html#file2) | 86.4% | 91.3% | 4.9
[pkg/serving/v1/apply.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_client/964/pull-knative-client-go-coverage/1322203817667727360/artifacts/line-cov.html#file3) | Do not exist | 80.5% | 
[pkg/serving/v1/client.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_client/964/pull-knative-client-go-coverage/1322203817667727360/artifacts/line-cov.html#file4) | 66.8% | 68.0% | 1.2
[pkg/serving/v1/client_mock.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_client/964/pull-knative-client-go-coverage/1322203817667727360/artifacts/line-cov.html#file5) | 93.3% | 93.7% | 0.3

rhuss: @navidshaikh I think we are good to merge if you don't have any objections.
knative-prow-robot: @rhuss: PR needs rebase.

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository.
</details>
google-cla[bot]: CLAs look good, thanks!

ℹ️ **Googlers: [Go here](https://goto.google.com/prinfo/https%3A%2F%2Fgithub.com%2Fknative%2Fclient%2Fpull%2F964) for more info**.

<!-- ok -->
knative-metrics-robot: The following is the coverage report on the affected files.
Say `/test pull-knative-client-go-coverage` to re-run this coverage report

File | Old Coverage | New Coverage | Delta
---- |:------------:|:------------:|:-----:
[pkg/kn/commands/service/apply.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_client/964/pull-knative-client-go-coverage/1322248163070840832/artifacts/line-cov.html#file0) | Do not exist | 84.4% | 
[pkg/kn/commands/service/create.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_client/964/pull-knative-client-go-coverage/1322248163070840832/artifacts/line-cov.html#file1) | 84.1% | 82.6% | -1.5
[pkg/kn/commands/service/service.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_client/964/pull-knative-client-go-coverage/1322248163070840832/artifacts/line-cov.html#file2) | 86.4% | 91.3% | 4.9
[pkg/serving/v1/apply.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_client/964/pull-knative-client-go-coverage/1322248163070840832/artifacts/line-cov.html#file3) | Do not exist | 80.5% | 
[pkg/serving/v1/client.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_client/964/pull-knative-client-go-coverage/1322248163070840832/artifacts/line-cov.html#file4) | 66.8% | 68.0% | 1.2
[pkg/serving/v1/client_mock.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_client/964/pull-knative-client-go-coverage/1322248163070840832/artifacts/line-cov.html#file5) | 93.3% | 93.7% | 0.3

knative-prow-robot: @rhuss: The following test **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-knative-client-build-tests | 11b937760c384c1e537c77682a717352865c9bd4 | [link](https://prow.knative.dev/view/gs/knative-prow/pr-logs/pull/knative_client/964/pull-knative-client-build-tests/1322248163024703488) | `/test pull-knative-client-build-tests`

[Full PR test history](https://gubernator.knative.dev/pr/knative_client/964). [Your PR dashboard](https://gubernator.knative.dev/pr/rhuss).

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
google-cla[bot]: CLAs look good, thanks!

ℹ️ **Googlers: [Go here](https://goto.google.com/prinfo/https%3A%2F%2Fgithub.com%2Fknative%2Fclient%2Fpull%2F964) for more info**.

<!-- ok -->
knative-metrics-robot: The following is the coverage report on the affected files.
Say `/test pull-knative-client-go-coverage` to re-run this coverage report

File | Old Coverage | New Coverage | Delta
---- |:------------:|:------------:|:-----:
[pkg/kn/commands/service/apply.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_client/964/pull-knative-client-go-coverage/1322389561589895168/artifacts/line-cov.html#file0) | Do not exist | 84.4% | 
[pkg/kn/commands/service/create.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_client/964/pull-knative-client-go-coverage/1322389561589895168/artifacts/line-cov.html#file1) | 84.1% | 82.6% | -1.5
[pkg/kn/commands/service/service.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_client/964/pull-knative-client-go-coverage/1322389561589895168/artifacts/line-cov.html#file2) | 86.4% | 91.3% | 4.9
[pkg/serving/v1/apply.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_client/964/pull-knative-client-go-coverage/1322389561589895168/artifacts/line-cov.html#file3) | Do not exist | 80.5% | 
[pkg/serving/v1/client.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_client/964/pull-knative-client-go-coverage/1322389561589895168/artifacts/line-cov.html#file4) | 66.8% | 68.0% | 1.2
[pkg/serving/v1/client_mock.go](https://storage.cloud.google.com/knative-prow/pr-logs/pull/knative_client/964/pull-knative-client-go-coverage/1322389561589895168/artifacts/line-cov.html#file5) | 93.3% | 93.7% | 0.3

knative-prow-robot: [APPROVALNOTIFIER] This PR is **APPROVED**

This pull-request has been approved by: *<a href="https://github.com/knative/client/pull/964#pullrequestreview-519124717" title="Approved">maximilien</a>*, *<a href="https://github.com/knative/client/pull/964#pullrequestreview-521343314" title="LGTM">navidshaikh</a>*, *<a href="https://github.com/knative/client/pull/964#" title="Author self-approved">rhuss</a>*

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=knative%2Fclient).

The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process)

<details >
Needs approval from an approver in each of these files:

- ~~[OWNERS](https://github.com/knative/client/blob/master/OWNERS)~~ [maximilien,navidshaikh,rhuss]

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":[]} -->

Patch:
diff --git a/docs/cmd/kn_service.md b/docs/cmd/kn_service.md
index 7243d7e9..27ff8f53 100644
--- a/docs/cmd/kn_service.md
+++ b/docs/cmd/kn_service.md
@@ -27,6 +27,7 @@ kn service
 ### SEE ALSO
 
 * [kn](kn.md)	 - kn manages Knative Serving and Eventing resources
+* [kn service apply](kn_service_apply.md)	 - Apply a service declaration
 * [kn service create](kn_service_create.md)	 - Create a service
 * [kn service delete](kn_service_delete.md)	 - Delete services
 * [kn service describe](kn_service_describe.md)	 - Show details of a service
diff --git a/docs/cmd/kn_service_apply.md b/docs/cmd/kn_service_apply.md
new file mode 100644
index 00000000..8aa6c4cc
--- /dev/null
+++ b/docs/cmd/kn_service_apply.md
@@ -0,0 +1,93 @@
+## kn service apply
+
+Apply a service declaration
+
+### Synopsis
+
+Apply a service declaration
+
+```
+kn service apply NAME
+```
+
+### Examples
+
+```
+
+# Create an initial service with using 'kn service apply', if the service has not
+# been already created
+kn service apply s0 --image knativesamples/helloworld
+
+# Apply the service again which is a no-operation if none of the options changed
+kn service apply s0 --image knativesamples/helloworld
+
+# Add an environment variable to your service. Note, that you have to always fully
+# specify all parameters (in contrast to 'kn service update')
+kn service apply s0 --image knativesamples/helloworld --env foo=bar
+
+# Read the service declaration from a file
+kn service apply s0 --filename my-svc.yml
+
+```
+
+### Options
+
+```
+  -a, --annotation stringArray            Annotations to set for both Service and Revision. name=value; you may provide this flag any number of times to set multiple annotations. To unset, specify the annotation name followed by a "-" (e.g., name-).
+      --annotation-revision stringArray   Revision annotation to set. name=value; you may provide this flag any number of times to set multiple annotations. To unset, specify the annotation name followed by a "-" (e.g., name-). This flag takes precedence over the "annotation" flag.
+      --annotation-service stringArray    Service annotation to set. name=value; you may provide this flag any number of times to set multiple annotations. To unset, specify the annotation name followed by a "-" (e.g., name-). This flag takes precedence over the "annotation" flag.
+      --arg stringArray                   Add argument to the container command. Example: --arg myArg1 --arg --myArg2 --arg myArg3=3. You can use this flag multiple times.
+      --async                             DEPRECATED: please use --no-wait instead. Do not wait for 'service apply' operation to be completed.
+      --autoscale-window string           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)
+      --cluster-local                     Specify that the service be private. (--no-cluster-local will make the service publicly available)
+      --cmd string                        Specify command to be used as entrypoint instead of default one. Example: --cmd /app/start or --cmd /app/start --arg myArg to pass additional arguments.
+      --concurrency-limit int             Hard Limit of concurrent requests to be processed by a single replica.
+      --concurrency-target int            Recommendation for when to scale up based on the concurrent number of incoming request. Defaults to --concurrency-limit when given.
+      --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. The created service can be further modified by combining with other options. For example, -f /path/to/file --env NAME=value adds also an environment variable.
+      --force                             Create service forcefully, replaces existing service if any.
+  -h, --help                              help for apply
+      --image string                      Image to run.
+  -l, --label stringArray                 Labels to set for both Service and Revision. name=value; you may provide this flag any number of times to set multiple labels. To unset, specify the label name followed by a "-" (e.g., name-).
+      --label-revision stringArray        Revision label to set. name=value; you may provide this flag any number of times to set multiple labels. To unset, specify the label name followed by a "-" (e.g., name-). This flag takes precedence over the "label" flag.
+      --label-service stringArray         Service label to set. name=value; you may provide this flag any number of times to set multiple labels. To unset, specify the label name followed by a "-" (e.g., name-). This flag takes precedence over the "label" flag.
+      --limit strings                     The resource requirement limits for this Service. For example, 'cpu=100m,memory=256Mi'. You can use this flag multiple times. To unset a resource limit, append "-" to the resource name, e.g. '--limit memory-'.
+      --limits-cpu string                 DEPRECATED: please use --limit instead. The limits on the requested CPU (e.g., 1000m).
+      --limits-memory string              DEPRECATED: please use --limit instead. The limits on the requested memory (e.g., 1024Mi).
+      --lock-to-digest                    Keep the running image for the service constant when not explicitly specifying the image. (--no-lock-to-digest pulls the image tag afresh with each new revision) (default true)
+      --mount stringArray                 Mount a ConfigMap (prefix cm: or config-map:), a Secret (prefix secret: or sc:), or an existing Volume (without any prefix) on the specified directory. Example: --mount /mydir=cm:myconfigmap, --mount /mydir=secret:mysecret, or --mount /mydir=myvolume. When a configmap or a secret is specified, a corresponding volume is automatically generated. You can use this flag multiple times. For unmounting a directory, append "-", e.g. --mount /mydir-, which also removes any auto-generated volume.
+  -n, --namespace string                  Specify the namespace to operate in.
+      --no-cluster-local                  Do not specify that the service be private. (--no-cluster-local will make the service publicly available) (default true)
+      --no-lock-to-digest                 Do not keep the running image for the service constant when not explicitly specifying the image. (--no-lock-to-digest pulls the image tag afresh with each new revision)
+      --no-wait                           Do not wait for 'service apply' operation to be completed.
+  -p, --port string                       The port where application listens on, in the format 'NAME:PORT', where 'NAME' is optional. Examples: '--port h2c:8080' , '--port 8080'.
+      --pull-secret string                Image pull secret to set. An empty argument ("") clears the pull secret. The referenced secret must exist in the service's namespace.
+      --request strings                   The resource requirement requests for this Service. For example, 'cpu=100m,memory=256Mi'. You can use this flag multiple times. To unset a resource request, append "-" to the resource name, e.g. '--request cpu-'.
+      --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.
+      --scale-init int                    Initial number of replicas with which a service starts. Can be 0 or a positive integer.
+      --scale-max int                     Maximum number of replicas.
+      --scale-min int                     Minimum 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-.
+      --wait                              Wait for 'service apply' operation to be completed. (default true)
+      --wait-timeout int                  Seconds to wait before giving up on waiting for service to be ready. (default 600)
+```
+
+### Options inherited from parent commands
+
+```
+      --config string       kn configuration file (default: ~/.config/kn/config.yaml)
+      --kubeconfig string   kubectl configuration file (default: ~/.kube/config)
+      --log-http            log http traffic
+```
+
+### SEE ALSO
+
+* [kn service](kn_service.md)	 - Manage Knative services
+
diff --git a/go.mod b/go.mod
index 49832dca..f5efc238 100644
--- a/go.mod
+++ b/go.mod
@@ -3,6 +3,7 @@ module knative.dev/client
 go 1.14
 
 require (
+	github.com/google/go-cmp v0.5.2
 	github.com/mitchellh/go-homedir v1.1.0
 	github.com/pkg/errors v0.9.1
 	github.com/spf13/cobra v1.0.1-0.20200715031239-b95db644ed1c
@@ -27,7 +28,6 @@ require (
 // ----------------------------------------------------------------------------------------------
 replace (
 	k8s.io/api => k8s.io/api v0.18.8
-	k8s.io/apiextensions-apiserver => k8s.io/apiextensions-apiserver v0.18.8
 	k8s.io/apimachinery => k8s.io/apimachinery v0.18.8
 	k8s.io/cli-runtime => k8s.io/cli-runtime v0.18.8
 	k8s.io/client-go => k8s.io/client-go v0.18.8
diff --git a/go.sum b/go.sum
index a52bd094..d946011c 100644
--- a/go.sum
+++ b/go.sum
@@ -124,6 +124,7 @@ github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6L
 github.com/Azure/go-autorest/tracing v0.1.0/go.mod h1:ROEEAFwXycQw7Sn3DXNtEedEvdeRAgDr0izn4z5Ij88=
 github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk=
 github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=
+github.com/BurntSushi/toml v0.3.0/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
 github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
@@ -296,11 +297,16 @@ github.com/containerd/fifo v0.0.0-20190226154929-a9fb20d87448/go.mod h1:ODA38xgv
 github.com/containerd/go-runc v0.0.0-20180907222934-5a6d9f37cfa3/go.mod h1:IV7qH3hrUgRmyYrtgEeGWJfWbgcHL9CSRruz2Vqcph0=
 github.com/containerd/ttrpc v0.0.0-20190828154514-0e0f228740de/go.mod h1:PvCDdDGpgqzQIzDW1TphrGLssLDZp2GuS+X5DkEJB8o=
 github.com/containerd/typeurl v0.0.0-20180627222232-a93fcdb778cd/go.mod h1:Cm3kwCdlkCfMSHURc+r6fwoGH6/F1hH3S4sg0rLFWPc=
+github.com/coreos/bbolt v1.3.1-coreos.6/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
 github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
+github.com/coreos/bbolt v1.3.3/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
 github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
 github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
+github.com/coreos/etcd v3.3.17+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
 github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
+github.com/coreos/go-oidc v0.0.0-20180117170138-065b426bd416/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc=
 github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc=
+github.com/coreos/go-semver v0.0.0-20180108230905-e214231b295a/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
 github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
 github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
 github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
@@ -375,6 +381,7 @@ github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.m
 github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
 github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
 github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0=
+github.com/evanphx/json-patch v0.0.0-20190203023257-5858425f7550/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
 github.com/evanphx/json-patch v0.0.0-20200808040245-162e5629780b/go.mod h1:NAJj0yf/KaRKURN6nyi7A9IZydMivZEm9oQLWNjfKDc=
 github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
 github.com/evanphx/json-patch v4.5.0+incompatible h1:ouOWdg56aJriqS0huScTkVXPC5IcNrDCXZ6OoTAWu7M=
@@ -397,6 +404,7 @@ github.com/fsouza/fake-gcs-server v0.0.0-20180612165233-e85be23bdaa8/go.mod h1:1
 github.com/fsouza/fake-gcs-server v1.19.4/go.mod h1:I0/88nHCASqJJ5M7zVF0zKODkYTcuXFW5J5yajsNJnE=
 github.com/garyburd/redigo v0.0.0-20150301180006-535138d7bcd7/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY=
 github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
+github.com/ghodss/yaml v0.0.0-20180820084758-c7ce16629ff4/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
 github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
 github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
 github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
@@ -425,34 +433,41 @@ github.com/go-logr/zapr v0.1.1/go.mod h1:tabnROwaDl0UNxkVeFRbY8bwB37GwRv0P8lg6aA
 github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8=
 github.com/go-openapi/analysis v0.0.0-20180825180245-b006789cd277/go.mod h1:k70tL6pCuVxPJOHXQ+wIac1FUrvNkHolPie/cLEU6hI=
 github.com/go-openapi/analysis v0.17.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik=
+github.com/go-openapi/analysis v0.17.2/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik=
 github.com/go-openapi/analysis v0.18.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik=
 github.com/go-openapi/analysis v0.19.2/go.mod h1:3P1osvZa9jKjb8ed2TPng3f0i/UY9snX6gxi44djMjk=
 github.com/go-openapi/analysis v0.19.5/go.mod h1:hkEAkxagaIvIP7VTn8ygJNkd4kAYON2rCu0v0ObL0AU=
 github.com/go-openapi/errors v0.17.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0=
+github.com/go-openapi/errors v0.17.2/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0=
 github.com/go-openapi/errors v0.18.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0=
 github.com/go-openapi/errors v0.19.2/go.mod h1:qX0BLWsyaKfvhluLejVpVNwNRdXZhEbTA4kxxpKBC94=
 github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0=
 github.com/go-openapi/jsonpointer v0.17.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M=
 github.com/go-openapi/jsonpointer v0.18.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M=
+github.com/go-openapi/jsonpointer v0.19.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M=
 github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg=
 github.com/go-openapi/jsonpointer v0.19.3 h1:gihV7YNZK1iK6Tgwwsxo2rJbD1GTbdm72325Bq8FI3w=
 github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
 github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg=
 github.com/go-openapi/jsonreference v0.17.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I=
 github.com/go-openapi/jsonreference v0.18.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I=
+github.com/go-openapi/jsonreference v0.19.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I=
 github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc=
 github.com/go-openapi/jsonreference v0.19.3 h1:5cxNfTy0UVC3X8JL5ymxzyoUZmo8iZb+jeTWn7tUa8o=
 github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8=
 github.com/go-openapi/loads v0.17.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU=
+github.com/go-openapi/loads v0.17.2/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU=
 github.com/go-openapi/loads v0.18.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU=
 github.com/go-openapi/loads v0.19.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU=
 github.com/go-openapi/loads v0.19.2/go.mod h1:QAskZPMX5V0C2gvfkGZzJlINuP7Hx/4+ix5jWFxsNPs=
 github.com/go-openapi/loads v0.19.4/go.mod h1:zZVHonKd8DXyxyw4yfnVjPzBjIQcLt0CCsn0N0ZrQsk=
 github.com/go-openapi/runtime v0.0.0-20180920151709-4f900dc2ade9/go.mod h1:6v9a6LTXWQCdL8k1AO3cvqx5OtZY/Y9wKTgaoP6YRfA=
+github.com/go-openapi/runtime v0.17.2/go.mod h1:QO936ZXeisByFmZEO1IS1Dqhtf4QV1sYYFtIq6Ld86Q=
 github.com/go-openapi/runtime v0.19.0/go.mod h1:OwNfisksmmaZse4+gpV3Ne9AyMOlP1lt4sK4FXt0O64=
 github.com/go-openapi/runtime v0.19.4/go.mod h1:X277bwSUBxVlCYR3r7xgZZGKVvBd/29gLDlFGtJ8NL4=
 github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc=
 github.com/go-openapi/spec v0.17.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI=
+github.com/go-openapi/spec v0.17.2/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI=
 github.com/go-openapi/spec v0.18.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI=
 github.com/go-openapi/spec v0.19.2/go.mod h1:sCxk3jxKgioEJikev4fgkNmwS+3kuYdJtcsZsD5zxMY=
 github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo=
@@ -465,11 +480,13 @@ github.com/go-openapi/strfmt v0.19.0/go.mod h1:+uW+93UVvGGq2qGaZxdDeJqSAqBqBdl+Z
 github.com/go-openapi/strfmt v0.19.3/go.mod h1:0yX7dbo8mKIvc3XSKp7MNfxw4JytCfCD6+bY1AVL9LU=
 github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I=
 github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg=
+github.com/go-openapi/swag v0.17.2/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg=
 github.com/go-openapi/swag v0.18.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg=
 github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
 github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
 github.com/go-openapi/swag v0.19.7 h1:VRuXN2EnMSsZdauzdss6JBC29YotDqG59BZ+tdlIL1s=
 github.com/go-openapi/swag v0.19.7/go.mod h1:ao+8BpOPyKdpQz3AOJfbeEVpLmWAvlT1IfTe5McPyhY=
+github.com/go-openapi/validate v0.17.0/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+MYsct2VUrAJ4=
 github.com/go-openapi/validate v0.18.0/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+MYsct2VUrAJ4=
 github.com/go-openapi/validate v0.19.2/go.mod h1:1tRCw7m3jtI8eNWEEliiAqUIcBztB2KDnRCRMUi7GTA=
 github.com/go-openapi/validate v0.19.5/go.mod h1:8DJv2CVJQ6kGNpFW6eV9N3JviE1C85nY1c2z52x1Gk4=
@@ -503,6 +520,7 @@ github.com/godbus/dbus v0.0.0-20190422162347-ade71ed3457e/go.mod h1:bBOAhwG1umN6
 github.com/gofrs/flock v0.0.0-20190320160742-5135e617513b/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=
 github.com/gofrs/flock v0.7.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=
 github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s=
+github.com/gogo/protobuf v0.0.0-20171007142547-342cbe0a0415/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
 github.com/gogo/protobuf v1.0.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
 github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
 github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
@@ -682,9 +700,12 @@ github.com/gotestyourself/gotestyourself v2.2.0+incompatible/go.mod h1:zZKM6oeNM
 github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
 github.com/gregjones/httpcache v0.0.0-20190212212710-3befbb6ad0cc h1:f8eY6cV/x1x+HLjOp4r72s/31/V2aTUtg5oKRRPf8/Q=
 github.com/gregjones/httpcache v0.0.0-20190212212710-3befbb6ad0cc/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
+github.com/grpc-ecosystem/go-grpc-middleware v0.0.0-20190222133341-cfaf5686ec79/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
 github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
 github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
+github.com/grpc-ecosystem/go-grpc-prometheus v0.0.0-20170330212424-2500245aa611/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
 github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
+github.com/grpc-ecosystem/grpc-gateway v1.3.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw=
 github.com/grpc-ecosystem/grpc-gateway v1.4.1/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw=
 github.com/grpc-ecosystem/grpc-gateway v1.8.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
 github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
@@ -775,6 +796,7 @@ github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeY
 github.com/jmoiron/sqlx v1.2.1-0.20190826204134-d7d95172beb5/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks=
 github.com/joefitzgerald/rainbow-reporter v0.1.0/go.mod h1:481CNgqmVHQZzdIbN52CupLJyoVwB10FQ/IQlF1pdL8=
 github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
+github.com/jonboulle/clockwork v0.0.0-20141017032234-72f9bd7c4e0c/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
 github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
 github.com/jpillora/backoff v0.0.0-20180909062703-3050d21c67d7/go.mod h1:2iMrUgbbvHEiQClaW2NsSzMyGHqN+rDFqY705q49KG0=
 github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
@@ -919,6 +941,7 @@ github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRW
 github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
 github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
 github.com/nakabonne/nestif v0.3.0/go.mod h1:dI314BppzXjJ4HsCnbo7XzrJHPszZsjnk5wEBSYHI2c=
+github.com/natefinch/lumberjack v2.0.0+incompatible/go.mod h1:Wi9p2TTF5DG5oU+6YfsmYQpsTIOm0B1VNzQg9Mw6nPk=
 github.com/nats-io/gnatsd v1.4.1/go.mod h1:nqco77VO78hLCJpIcVfygDP2rPGfsEHkGTUk94uh5DQ=
 github.com/nats-io/go-nats v1.7.0/go.mod h1:+t7RHT5ApZebkrQdnn6AhQJmhJJiKAvJUio1PiiCtj0=
 github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg=
@@ -1147,6 +1170,7 @@ github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337/go.mod h1:s
 github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
 github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
 github.com/smartystreets/gunit v1.0.0/go.mod h1:qwPWnhz6pn0NnRBP++URONOVyNkPyr4SauJk4cUOwJs=
+github.com/soheilhy/cmux v0.1.3/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
 github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
 github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=
 github.com/sourcegraph/go-diff v0.5.1/go.mod h1:j2dHj3m8aZgQO8lMTcTnBcXkRRRqi34cd2MNlA9u1mE=
@@ -1158,6 +1182,7 @@ github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTd
 github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
 github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng=
 github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
+github.com/spf13/cobra v0.0.0-20180319062004-c439c4fa0937/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
 github.com/spf13/cobra v0.0.2-0.20171109065643-2da4a54c5cee/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
 github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
 github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
@@ -1293,6 +1318,7 @@ go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
 go.opencensus.io v0.22.5 h1:dntmOdLpSpHlVqbW5Eay97DelsZHe+55D+xC6i0dDS0=
 go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
 go.opentelemetry.io/otel v0.2.3/go.mod h1:OgNpQOjrlt33Ew6Ds0mGjmcTQg/rhUctsbkRdk/g1fw=
+go.uber.org/atomic v0.0.0-20181018215023-8dc6146f7569/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
 go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
 go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
 go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
@@ -1303,6 +1329,7 @@ go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
 go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
 go.uber.org/automaxprocs v1.3.0/go.mod h1:9CWT6lKIep8U41DDaPiH6eFscnTyjfTANNQNx6LrIcA=
 go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A=
+go.uber.org/multierr v0.0.0-20180122172545-ddea229ff1df/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
 go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
 go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
 go.uber.org/multierr v1.4.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
@@ -1312,6 +1339,7 @@ go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4=
 go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
 go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4=
 go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
+go.uber.org/zap v0.0.0-20180814183419-67bc79d13d15/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
 go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
 go.uber.org/zap v1.9.2-0.20180814183419-67bc79d13d15/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
 go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
@@ -1326,6 +1354,7 @@ golang.org/x/crypto v0.0.0-20171113213409-9f005a07e0d3/go.mod h1:6SG95UA2DQfeDnf
 golang.org/x/crypto v0.0.0-20180608092829-8ac0e0d97ce4/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
 golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
 golang.org/x/crypto v0.0.0-20181015023909-0c41d7ab0a0e/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
+golang.org/x/crypto v0.0.0-20181025213731-e84da0312774/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
 golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
 golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
 golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
@@ -1419,6 +1448,7 @@ golang.org/x/net v0.0.0-20190619014844-b5b0513f8c1b/go.mod h1:z5CRVTTTmAJ677TzLL
 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20190812203447-cdfb69ac37fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20190912160710-24e19bdeb0f2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
@@ -1711,6 +1741,7 @@ google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCID
 google.golang.org/appengine v1.6.6 h1:lMO5rYAqUxkmaj76jAkRUvt5JZgFymx/+Q5Mzfivuhc=
 google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
 google.golang.org/cloud v0.0.0-20151119220103-975617b05ea8/go.mod h1:0H1ncTHf11KCFhTc/+EFRbzSCOZx+VUbRMk55Yv5MYk=
+google.golang.org/genproto v0.0.0-20170731182057-09f6ed296fc6/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
 google.golang.org/genproto v0.0.0-20180608181217-32ee49c4dd80/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
 google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
 google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
@@ -1762,6 +1793,7 @@ google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6D
 google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d h1:92D1fum1bJLKSdr11OJ+54YeCMCGYIygTA7R/YZxH5M=
 google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 google.golang.org/grpc v0.0.0-20160317175043-d3ddb4469d5a/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
+google.golang.org/grpc v1.13.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
 google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
 google.golang.org/grpc v1.15.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
 google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
@@ -1829,9 +1861,11 @@ gopkg.in/jcmturner/dnsutils.v1 v1.0.1/go.mod h1:m3v+5svpVOhtFAP/wSz+yzh4Mc0Fg7eR
 gopkg.in/jcmturner/gokrb5.v7 v7.2.3/go.mod h1:l8VISx+WGYp+Fp7KRbsiUuXTTOnxIc3Tuvyavf11/WM=
 gopkg.in/jcmturner/gokrb5.v7 v7.3.0/go.mod h1:l8VISx+WGYp+Fp7KRbsiUuXTTOnxIc3Tuvyavf11/WM=
 gopkg.in/jcmturner/rpc.v1 v1.1.0/go.mod h1:YIdkC4XfD6GXbzje11McwsDuOlZQSb9W4vfLvuNnlv8=
+gopkg.in/natefinch/lumberjack.v2 v2.0.0-20150622162204-20b71e5b60d7/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
 gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
 gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
 gopkg.in/robfig/cron.v2 v2.0.0-20150107220207-be2e0b0deed5/go.mod h1:hiOFpYm0ZJbusNj2ywpbrXowU3G8U6GIQzqn2mw1UIE=
+gopkg.in/square/go-jose.v2 v2.0.0-20180411045311-89060dee6a84/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
 gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
 gopkg.in/src-d/go-billy.v4 v4.3.2/go.mod h1:nDjArDMp+XMs1aFAESLRjfGSgfvoYN0hDfzEk0GjC98=
 gopkg.in/src-d/go-git-fixtures.v3 v3.5.0/go.mod h1:dLBcvytrw/TYZsNTWCnkNF2DSIlzWYqTe3rJR56Ac7g=
@@ -1840,6 +1874,7 @@ gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkep
 gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
 gopkg.in/warnings.v0 v0.1.1/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
 gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
+gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg=
 gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
 gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
@@ -1868,11 +1903,21 @@ honnef.co/go/tools v0.0.1-2020.1.5 h1:nI5egYTGJakVyOryqLs1cQO5dO0ksin5XXs2pspk75
 honnef.co/go/tools v0.0.1-2020.1.5/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
 k8s.io/api v0.18.8 h1:aIKUzJPb96f3fKec2lxtY7acZC9gQNDLVhfSGpxBAC4=
 k8s.io/api v0.18.8/go.mod h1:d/CXqwWv+Z2XEG1LgceeDmHQwpUJhROPx16SlxJgERY=
+k8s.io/apiextensions-apiserver v0.0.0-20190918201827-3de75813f604/go.mod h1:7H8sjDlWQu89yWB3FhZfsLyRCRLuoXoCoY5qtwW1q6I=
+k8s.io/apiextensions-apiserver v0.16.4/go.mod h1:HYQwjujEkXmQNhap2C9YDdIVOSskGZ3et0Mvjcyjbto=
+k8s.io/apiextensions-apiserver v0.17.2/go.mod h1:4KdMpjkEjjDI2pPfBA15OscyNldHWdBCfsWMDWAmSTs=
+k8s.io/apiextensions-apiserver v0.17.6/go.mod h1:Z3CHLP3Tha+Rbav7JR3S+ye427UaJkHBomK2c4XtZ3A=
+k8s.io/apiextensions-apiserver v0.18.4/go.mod h1:NYeyeYq4SIpFlPxSAB6jHPIdvu3hL0pc36wuRChybio=
 k8s.io/apiextensions-apiserver v0.18.8/go.mod h1:7f4ySEkkvifIr4+BRrRWriKKIJjPyg9mb/p63dJKnlM=
 k8s.io/apimachinery v0.18.8 h1:jimPrycCqgx2QPearX3to1JePz7wSbVLq+7PdBTTwQ0=
 k8s.io/apimachinery v0.18.8/go.mod h1:6sQd+iHEqmOtALqOFjSWp2KZ9F0wlU/nWm0ZgsYWMig=
+k8s.io/apiserver v0.0.0-20190918200908-1e17798da8c1/go.mod h1:4FuDU+iKPjdsdQSN3GsEKZLB/feQsj1y9dhhBDVV2Ns=
+k8s.io/apiserver v0.16.4/go.mod h1:kbLJOak655g6W7C+muqu1F76u9wnEycfKMqbVaXIdAc=
 k8s.io/apiserver v0.17.0/go.mod h1:ABM+9x/prjINN6iiffRVNCBR2Wk7uY4z+EtEGZD48cg=
+k8s.io/apiserver v0.17.2/go.mod h1:lBmw/TtQdtxvrTk0e2cgtOxHizXI+d0mmGQURIHQZlo=
 k8s.io/apiserver v0.17.4/go.mod h1:5ZDQ6Xr5MNBxyi3iUZXS84QOhZl+W7Oq2us/29c0j9I=
+k8s.io/apiserver v0.17.6/go.mod h1:sAYqm8hUDNA9aj/TzqwsJoExWrxprKv0tqs/z88qym0=
+k8s.io/apiserver v0.18.4/go.mod h1:q+zoFct5ABNnYkGIaGQ3bcbUNdmPyOCoEBcg51LChY8=
 k8s.io/apiserver v0.18.8/go.mod h1:12u5FuGql8Cc497ORNj79rhPdiXQC4bf53X/skR/1YM=
 k8s.io/cli-runtime v0.18.8 h1:ycmbN3hs7CfkJIYxJAOB10iW7BVPmXGXkfEyiV9NJ+k=
 k8s.io/cli-runtime v0.18.8/go.mod h1:7EzWiDbS9PFd0hamHHVoCY4GrokSTPSL32MA4rzIu0M=
@@ -1883,9 +1928,13 @@ k8s.io/cloud-provider v0.17.4/go.mod h1:XEjKDzfD+b9MTLXQFlDGkk6Ho8SGMpaU8Uugx/KN
 k8s.io/cloud-provider v0.18.8/go.mod h1:cn9AlzMPVIXA4HHLVbgGUigaQlZyHSZ7WAwDEFNrQSs=
 k8s.io/code-generator v0.18.8 h1:lgO1P1wjikEtzNvj7ia+x1VC4svJ28a/r0wnOLhhOTU=
 k8s.io/code-generator v0.18.8/go.mod h1:TgNEVx9hCyPGpdtCWA34olQYLkh3ok9ar7XfSsr8b6c=
+k8s.io/component-base v0.0.0-20190918200425-ed2f0867c778/go.mod h1:DFWQCXgXVLiWtzFaS17KxHdlUeUymP7FLxZSkmL9/jU=
+k8s.io/component-base v0.16.4/go.mod h1:GYQ+4hlkEwdlpAp59Ztc4gYuFhdoZqiAJD1unYDJ3FM=
 k8s.io/component-base v0.17.0/go.mod h1:rKuRAokNMY2nn2A6LP/MiwpoaMRHpfRnrPaUJJj1Yoc=
 k8s.io/component-base v0.17.2/go.mod h1:zMPW3g5aH7cHJpKYQ/ZsGMcgbsA/VyhEugF3QT1awLs=
 k8s.io/component-base v0.17.4/go.mod h1:5BRqHMbbQPm2kKu35v3G+CpVq4K0RJKC7TRioF0I9lE=
+k8s.io/component-base v0.17.6/go.mod h1:jgRLWl0B0rOzFNtxQ9E4BphPmDqoMafujdau6AdG2Xo=
+k8s.io/component-base v0.18.4/go.mod h1:7jr/Ef5PGmKwQhyAz/pjByxJbC58mhKAhiaDu0vXfPk=
 k8s.io/component-base v0.18.8/go.mod h1:00frPRDas29rx58pPCxNkhUfPbwajlyyvu8ruNgSErU=
 k8s.io/csi-translation-lib v0.17.0/go.mod h1:HEF7MEz7pOLJCnxabi45IPkhSsE/KmxPQksuCrHKWls=
 k8s.io/csi-translation-lib v0.17.4/go.mod h1:CsxmjwxEI0tTNMzffIAcgR9lX4wOh6AKHdxQrT7L0oo=
@@ -1898,12 +1947,15 @@ k8s.io/gengo v0.0.0-20200205140755-e0e292d8aa12 h1:pZzawYyz6VRNPVYpqGv61LWCimQv1
 k8s.io/gengo v0.0.0-20200205140755-e0e292d8aa12/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
 k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
 k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
+k8s.io/klog v0.3.1/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
 k8s.io/klog v0.3.3/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
+k8s.io/klog v0.4.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I=
 k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8=
 k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I=
 k8s.io/klog/v2 v2.0.0 h1:Foj74zO6RbjjP4hBEKjnYtjjAhGg4jNynUdYF6fJrok=
 k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE=
 k8s.io/kube-openapi v0.0.0-20180731170545-e3762e86a74c/go.mod h1:BXM9ceUBTj2QnfH2MK1odQs778ajze1RxcmP6S8RVVc=
+k8s.io/kube-openapi v0.0.0-20190228160746-b3a7cee44a30/go.mod h1:BXM9ceUBTj2QnfH2MK1odQs778ajze1RxcmP6S8RVVc=
 k8s.io/kube-openapi v0.0.0-20190816220812-743ec37842bf/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E=
 k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E=
 k8s.io/kube-openapi v0.0.0-20200410145947-61e04a5be9a6/go.mod h1:GRQhZsXIAJ1xR0C9bd8UpWHZ5plfAS9fzPjJuQ6JL3E=
@@ -1925,7 +1977,9 @@ k8s.io/test-infra v0.0.0-20200617221206-ea73eaeab7ff/go.mod h1:L3+cRvwftUq8IW1Tr
 k8s.io/test-infra v0.0.0-20200630233406-1dca6122872e/go.mod h1:L3+cRvwftUq8IW1TrHji5m3msnc4uck/7LsE/GR/aZk=
 k8s.io/test-infra v0.0.0-20200803112140-d8aa4e063646/go.mod h1:rtUd2cOFwT0aBma1ld6W40F7PuVVw4ELLSFlz9ZEmv8=
 k8s.io/utils v0.0.0-20181019225348-5e321f9a457c/go.mod h1:8k8uAuAQ0rXslZKaEWd0c3oVhZz7sSzSiPnVZayjIX0=
+k8s.io/utils v0.0.0-20190221042446-c2654d5206da/go.mod h1:8k8uAuAQ0rXslZKaEWd0c3oVhZz7sSzSiPnVZayjIX0=
 k8s.io/utils v0.0.0-20190506122338-8fab8cb257d5/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew=
+k8s.io/utils v0.0.0-20190801114015-581e00157fb1/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew=
 k8s.io/utils v0.0.0-20190907131718-3d4f5b7dea0b/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew=
 k8s.io/utils v0.0.0-20191114184206-e782cd3c129f/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew=
 k8s.io/utils v0.0.0-20200124190032-861946025e34/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew=
@@ -1985,9 +2039,11 @@ sigs.k8s.io/controller-runtime v0.5.4/go.mod h1:JZUwSMVbxDupo0lTJSSFP5pimEyxGynR
 sigs.k8s.io/controller-runtime v0.6.1/go.mod h1:XRYBPdbf5XJu9kpS84VJiZ7h/u1hF3gEORz0efEja7A=
 sigs.k8s.io/kustomize v2.0.3+incompatible h1:JUufWFNlI44MdtnjUqVnvh29rR37PQFzPbLXqhyOyX0=
 sigs.k8s.io/kustomize v2.0.3+incompatible/go.mod h1:MkjgH3RdOWrievjo6c9T245dYlB5QeXV4WCbnt/PEpU=
+sigs.k8s.io/structured-merge-diff v0.0.0-20190302045857-e85c7b244fd2/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI=
 sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI=
-sigs.k8s.io/structured-merge-diff v1.0.1-0.20191108220359-b1b620dd3f06 h1:zD2IemQ4LmOcAumeiyDWXKUI2SO0NYDe3H6QGvPOVgU=
 sigs.k8s.io/structured-merge-diff v1.0.1-0.20191108220359-b1b620dd3f06/go.mod h1:/ULNhyfzRopfcjskuui0cTITekDduZ7ycKN3oUT9R18=
+sigs.k8s.io/structured-merge-diff v1.0.1 h1:LOs1LZWMsz1xs77Phr/pkB4LFaavH7IVq/3+WTN9XTA=
+sigs.k8s.io/structured-merge-diff v1.0.1/go.mod h1:IIgPezJWb76P0hotTxzDbWsMYB8APh18qZnxkomBpxA=
 sigs.k8s.io/structured-merge-diff/v2 v2.0.1/go.mod h1:Wb7vfKAodbKgf6tn1Kl0VvGj7mRH6DGaRcixXEJXTsE=
 sigs.k8s.io/structured-merge-diff/v3 v3.0.0-20200116222232-67a7b8c61874/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw=
 sigs.k8s.io/structured-merge-diff/v3 v3.0.0/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw=
diff --git a/pkg/kn/commands/service/apply.go b/pkg/kn/commands/service/apply.go
new file mode 100644
index 00000000..aaf0354c
--- /dev/null
+++ b/pkg/kn/commands/service/apply.go
@@ -0,0 +1,123 @@
+// 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.
+
+package service
+
+import (
+	"errors"
+	"fmt"
+
+	"github.com/spf13/cobra"
+	corev1 "k8s.io/api/core/v1"
+	apierrors "k8s.io/apimachinery/pkg/api/errors"
+	servingv1 "knative.dev/serving/pkg/apis/serving/v1"
+
+	"knative.dev/client/pkg/kn/commands"
+	clientservingv1 "knative.dev/client/pkg/serving/v1"
+)
+
+var applyExample = `
+# Create an initial service with using 'kn service apply', if the service has not
+# been already created
+kn service apply s0 --image knativesamples/helloworld
+
+# Apply the service again which is a no-operation if none of the options changed
+kn service apply s0 --image knativesamples/helloworld
+
+# Add an environment variable to your service. Note, that you have to always fully
+# specify all parameters (in contrast to 'kn service update')
+kn service apply s0 --image knativesamples/helloworld --env foo=bar
+
+# Read the service declaration from a file
+kn service apply s0 --filename my-svc.yml
+`
+
+func NewServiceApplyCommand(p *commands.KnParams) *cobra.Command {
+	var applyFlags ConfigurationEditFlags
+	var waitFlags commands.WaitFlags
+
+	serviceApplyCommand := &cobra.Command{
+		Use:     "apply NAME",
+		Short:   "Apply a service declaration",
+		Example: applyExample,
+		RunE: func(cmd *cobra.Command, args []string) (err error) {
+			if len(args) != 1 && applyFlags.Filename == "" {
+				return errors.New("'service apply' requires the service name given as single argument")
+			}
+			name := ""
+			if len(args) == 1 {
+				name = args[0]
+			}
+
+			namespace, err := p.GetNamespace(cmd)
+			if err != nil {
+				return err
+			}
+
+			var service *servingv1.Service
+			applyFlags.RevisionName = ""
+			if applyFlags.Filename == "" {
+				service, err = constructService(cmd, applyFlags, name, namespace)
+			} else {
+				service, err = constructServiceFromFile(cmd, applyFlags, name, namespace)
+			}
+			if err != nil {
+				return err
+			}
+
+			client, err := p.NewServingClient(namespace)
+			if err != nil {
+				return err
+			}
+
+			waitDoing, waitVerb, err := examineServiceForApply(cmd, client, service.Name)
+			if err != nil {
+				return err
+			}
+
+			hasChanged, err := client.ApplyService(service)
+			if err != nil {
+				return err
+			}
+			if !hasChanged {
+				fmt.Fprintf(cmd.OutOrStdout(), "No changes to apply to service '%s'.\n", service.Name)
+
+				return showUrl(client, service.Name, "unchanged", "", cmd.OutOrStdout())
+			}
+			return waitIfRequested(client, service.Name, waitFlags, waitDoing, waitVerb, cmd.OutOrStdout())
+		},
+	}
+	commands.AddNamespaceFlags(serviceApplyCommand.Flags(), false)
+	applyFlags.AddCreateFlags(serviceApplyCommand)
+	waitFlags.AddConditionWaitFlags(serviceApplyCommand, commands.WaitDefaultTimeout, "apply", "service", "ready")
+	return serviceApplyCommand
+}
+
+func examineServiceForApply(cmd *cobra.Command, client clientservingv1.KnServingClient, serviceName string) (string, string, error) {
+	currentService, err := client.GetService(serviceName)
+	if err != nil {
+		if apierrors.IsNotFound(err) {
+			return "Creating", "created", nil
+		}
+		return "", "", err
+	}
+
+	annotationMap := currentService.Annotations
+	if annotationMap != nil {
+		if _, ok := annotationMap[corev1.LastAppliedConfigAnnotation]; !ok {
+			fmt.Fprintf(cmd.OutOrStdout(), "Warning: 'kn service apply' should be used only for services created by 'kn service apply'\n")
+		}
+	}
+	return "Applying", "applied", nil
+}
diff --git a/pkg/kn/commands/service/create.go b/pkg/kn/commands/service/create.go
index 1e652459..74c66b9d 100644
--- a/pkg/kn/commands/service/create.go
+++ b/pkg/kn/commands/service/create.go
@@ -145,7 +145,7 @@ func createService(client clientservingv1.KnServingClient, service *servingv1.Se
 		return err
 	}
 
-	return waitIfRequested(client, service, waitFlags, "Creating", "created", out)
+	return waitIfRequested(client, service.Name, waitFlags, "Creating", "created", out)
 }
 
 func replaceService(client clientservingv1.KnServingClient, service *servingv1.Service, waitFlags commands.WaitFlags, out io.Writer) error {
@@ -153,23 +153,23 @@ func replaceService(client clientservingv1.KnServingClient, service *servingv1.S
 	if err != nil {
 		return err
 	}
-	return waitIfRequested(client, service, waitFlags, "Replacing", "replaced", out)
+	return waitIfRequested(client, service.Name, waitFlags, "Replacing", "replaced", out)
 }
 
-func waitIfRequested(client clientservingv1.KnServingClient, service *servingv1.Service, waitFlags commands.WaitFlags, verbDoing string, verbDone string, out io.Writer) error {
+func waitIfRequested(client clientservingv1.KnServingClient, serviceName string, waitFlags commands.WaitFlags, verbDoing string, verbDone string, out io.Writer) error {
 	//TODO: deprecated condition should be removed with --async flag
 	if waitFlags.Async {
 		fmt.Fprintf(out, "\nWARNING: flag --async is deprecated and going to be removed in future release, please use --no-wait instead.\n\n")
-		fmt.Fprintf(out, "Service '%s' %s in namespace '%s'.\n", service.Name, verbDone, client.Namespace())
+		fmt.Fprintf(out, "Service '%s' %s in namespace '%s'.\n", serviceName, verbDone, client.Namespace())
 		return nil
 	}
 	if !waitFlags.Wait {
-		fmt.Fprintf(out, "Service '%s' %s in namespace '%s'.\n", service.Name, verbDone, client.Namespace())
+		fmt.Fprintf(out, "Service '%s' %s in namespace '%s'.\n", serviceName, verbDone, client.Namespace())
 		return nil
 	}
 
-	fmt.Fprintf(out, "%s service '%s' in namespace '%s':\n", verbDoing, service.Name, client.Namespace())
-	return waitForServiceToGetReady(client, service.Name, waitFlags.TimeoutInSeconds, verbDone, out)
+	fmt.Fprintf(out, "%s service '%s' in namespace '%s':\n", verbDoing, serviceName, client.Namespace())
+	return waitForServiceToGetReady(client, serviceName, waitFlags.TimeoutInSeconds, verbDone, out)
 }
 
 func prepareAndUpdateService(client clientservingv1.KnServingClient, service *servingv1.Service) error {
@@ -239,6 +239,10 @@ func serviceExists(client clientservingv1.KnServingClient, name string) (bool, e
 func constructService(cmd *cobra.Command, editFlags ConfigurationEditFlags, name string, namespace string) (*servingv1.Service,
 	error) {
 
+	if name == "" || namespace == "" {
+		return nil, errors.New("internal: no name or namespace provided when constructing a service")
+	}
+
 	service := servingv1.Service{
 		ObjectMeta: metav1.ObjectMeta{
 			Name:      name,
diff --git a/pkg/kn/commands/service/service.go b/pkg/kn/commands/service/service.go
index 3a4a273b..72c73a04 100644
--- a/pkg/kn/commands/service/service.go
+++ b/pkg/kn/commands/service/service.go
@@ -42,6 +42,7 @@ func NewServiceCommand(p *commands.KnParams) *cobra.Command {
 	serviceCmd.AddCommand(NewServiceCreateCommand(p))
 	serviceCmd.AddCommand(NewServiceDeleteCommand(p))
 	serviceCmd.AddCommand(NewServiceUpdateCommand(p))
+	serviceCmd.AddCommand(NewServiceApplyCommand(p))
 	serviceCmd.AddCommand(NewServiceExportCommand(p))
 	return serviceCmd
 }
@@ -64,7 +65,7 @@ func showUrl(client clientservingv1.KnServingClient, serviceName string, origina
 	url := service.Status.URL.String()
 
 	newRevision := service.Status.LatestReadyRevisionName
-	if originalRevision != "" && originalRevision == newRevision {
+	if (originalRevision != "" && originalRevision == newRevision) || originalRevision == "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)
diff --git a/pkg/serving/v1/apply.go b/pkg/serving/v1/apply.go
new file mode 100644
index 00000000..616c4377
--- /dev/null
+++ b/pkg/serving/v1/apply.go
@@ -0,0 +1,268 @@
+package v1
+
+import (
+	"context"
+	"strings"
+	"time"
+
+	v1 "k8s.io/api/core/v1"
+	apierrors "k8s.io/apimachinery/pkg/api/errors"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
+	"k8s.io/apimachinery/pkg/runtime"
+	"k8s.io/apimachinery/pkg/runtime/serializer"
+	"k8s.io/apimachinery/pkg/types"
+	"k8s.io/apimachinery/pkg/util/jsonmergepatch"
+	servingv1 "knative.dev/serving/pkg/apis/serving/v1"
+
+	"knative.dev/client/pkg/util"
+)
+
+// 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.
+
+// Helper methods supporting Apply()
+
+// patch performs a 3-way merge and returns whether the original service has been changed
+// This method uses a simple JSON 3-way merge which has some severe limitations, like that arrays
+// can't be merged. Ideally a strategicpatch merge should be used, which allows a more fine grained
+// way for performing the merge (but this is not supported for custom resources)
+// See issue https://github.com/knative/client/issues/1073 for more details how this method should be
+// improved for a better merge strategy.
+func (cl *knServingClient) patch(modifiedService *servingv1.Service, currentService *servingv1.Service, uOriginalService []byte) (bool, error) {
+	uModifiedService, err := getModifiedConfiguration(modifiedService, true)
+	if err != nil {
+		return false, err
+	}
+	hasChanged, err := cl.patchSimple(currentService, uModifiedService, uOriginalService)
+	for i := 1; i <= 5 && apierrors.IsConflict(err); i++ {
+		if i > 1 {
+			time.Sleep(1 * time.Second)
+		}
+		currentService, err = cl.GetService(currentService.Name)
+		if err != nil {
+			return false, err
+		}
+		hasChanged, err = cl.patchSimple(currentService, uModifiedService, uOriginalService)
+	}
+	return hasChanged, err
+}
+
+func (cl *knServingClient) patchSimple(currentService *servingv1.Service, uModifiedService []byte, uOriginalService []byte) (bool, error) {
+	// Serialize the current configuration of the object from the server.
+	uCurrentService, err := encodeService(currentService)
+	if err != nil {
+		return false, err
+	}
+
+	patch, err := jsonmergepatch.CreateThreeWayJSONMergePatch(uOriginalService, uModifiedService, uCurrentService)
+	if err != nil {
+		return false, err
+	}
+
+	if string(patch) == "{}" {
+		return false, nil
+	}
+
+	// Check if the generation has been counted up, only then the backend detected a change
+	savedService, err := cl.patchService(currentService.Name, types.MergePatchType, patch)
+	if err != nil {
+		return false, err
+	}
+	return savedService.Generation != savedService.Status.ObservedGeneration, nil
+}
+
+// patchService patches the given service
+func (cl *knServingClient) patchService(name string, patchType types.PatchType, patch []byte) (*servingv1.Service, error) {
+	service, err := cl.client.Services(cl.namespace).Patch(context.TODO(), name, patchType, patch, metav1.PatchOptions{})
+	if err != nil {
+		return nil, err
+	}
+	err = updateServingGvk(service)
+
+	return service, err
+}
+
+func getOriginalConfiguration(service *servingv1.Service) []byte {
+	annots := service.Annotations
+	if annots == nil {
+		return nil
+	}
+	original, ok := annots[v1.LastAppliedConfigAnnotation]
+	if !ok {
+		return nil
+	}
+	return []byte(original)
+}
+
+func getModifiedConfiguration(service *servingv1.Service, annotate bool) ([]byte, error) {
+
+	// First serialize the object without the annotation to prevent recursion,
+	// then add that serialization to it as the annotation and serialize it again.
+	var uModifiedService []byte
+
+	// Otherwise, use the server side version of the object.
+	// Get the current annotations from the object.
+	annots := service.Annotations
+	if annots == nil {
+		annots = map[string]string{}
+	}
+
+	original := annots[v1.LastAppliedConfigAnnotation]
+	delete(annots, v1.LastAppliedConfigAnnotation)
+	service.Annotations = annots
+
+	uModifiedService, err := encodeService(service)
+	if err != nil {
+		return nil, err
+	}
+
+	if annotate {
+		annots[v1.LastAppliedConfigAnnotation] = strings.TrimRight(string(uModifiedService), "\n")
+
+		service.Annotations = annots
+		uModifiedService, err = encodeService(service)
+		if err != nil {
+			return nil, err
+		}
+	}
+
+	// Restore the object to its original condition.
+	annots[v1.LastAppliedConfigAnnotation] = original
+	service.Annotations = annots
+	return uModifiedService, nil
+}
+
+func updateLastAppliedAnnotation(service *servingv1.Service) error {
+	annots := service.Annotations
+	if annots == nil {
+		annots = map[string]string{}
+	}
+	lastApplied, err := encodeService(service)
+	if err != nil {
+		return err
+	}
+
+	// Cleanup any trailing newlines
+	annots[v1.LastAppliedConfigAnnotation] = strings.TrimRight(string(lastApplied), "\n")
+
+	service.Annotations = annots
+	return nil
+}
+
+func encodeService(service *servingv1.Service) ([]byte, error) {
+	scheme := runtime.NewScheme()
+	err := servingv1.AddToScheme(scheme)
+	if err != nil {
+		return nil, err
+	}
+	factory := serializer.NewCodecFactory(scheme)
+	encoder := factory.EncoderForVersion(unstructured.UnstructuredJSONScheme, servingv1.SchemeGroupVersion)
+	err = util.UpdateGroupVersionKindWithScheme(service, servingv1.SchemeGroupVersion, scheme)
+	if err != nil {
+		return nil, err
+	}
+
+	serviceUnstructured, err := util.ToUnstructured(service)
+	if err != nil {
+		return nil, err
+	}
+
+	// Remove/adapt service so that it can be used in the apply-annotation
+	cleanupServiceUnstructured(serviceUnstructured)
+
+	return runtime.Encode(encoder, serviceUnstructured)
+}
+
+func cleanupServiceUnstructured(uService *unstructured.Unstructured) {
+	clearCreationTimestamps(uService.Object)
+	removeStatus(uService.Object)
+	removeContainerNameAndResourcesIfNotSet(uService.Object)
+
+}
+
+func removeContainerNameAndResourcesIfNotSet(uService map[string]interface{}) {
+	uContainer := extractUserContainer(uService)
+	if uContainer == nil {
+		return
+	}
+	name, ok := uContainer["name"]
+	if ok && name != "" {
+		delete(uContainer, "name")
+	}
+
+	resources := uContainer["resources"]
+	if resources == nil {
+		return
+	}
+	resourcesMap := resources.(map[string]interface{})
+	if len(resourcesMap) == 0 {
+		delete(uContainer, "resources")
+	}
+}
+
+func extractUserContainer(uService map[string]interface{}) map[string]interface{} {
+	tSpec := extractTemplateSpec(uService)
+	if tSpec == nil {
+		return nil
+	}
+	containers := tSpec["containers"]
+	if len(containers.([]interface{})) == 0 {
+		return nil
+	}
+	return containers.([]interface{})[0].(map[string]interface{})
+}
+
+func removeStatus(uService map[string]interface{}) {
+	delete(uService, "status")
+}
+
+func clearCreationTimestamps(uService map[string]interface{}) {
+	meta := uService["metadata"]
+	if meta != nil {
+		delete(meta.(map[string]interface{}), "creationTimestamp")
+	}
+	template := extractTemplate(uService)
+	if template != nil {
+		meta = template["metadata"]
+		if meta != nil {
+			delete(meta.(map[string]interface{}), "creationTimestamp")
+		}
+	}
+}
+
+func extractTemplateSpec(uService map[string]interface{}) map[string]interface{} {
+	templ := extractTemplate(uService)
+	if templ == nil {
+		return nil
+	}
+	templSpec := templ["spec"]
+	if templSpec == nil {
+		return nil
+	}
+
+	return templSpec.(map[string]interface{})
+}
+
+func extractTemplate(uService map[string]interface{}) map[string]interface{} {
+	spec := uService["spec"]
+	if spec == nil {
+		return nil
+	}
+	templ := spec.(map[string]interface{})["template"]
+	if templ == nil {
+		return nil
+	}
+	return templ.(map[string]interface{})
+}
diff --git a/pkg/serving/v1/client.go b/pkg/serving/v1/client.go
index b464eebf..aa60f6bd 100644
--- a/pkg/serving/v1/client.go
+++ b/pkg/serving/v1/client.go
@@ -16,6 +16,7 @@ package v1
 
 import (
 	"context"
+	"errors"
 	"fmt"
 	"time"
 
@@ -68,6 +69,17 @@ type KnServingClient interface {
 	// place.
 	UpdateServiceWithRetry(name string, updateFunc ServiceUpdateFunc, nrRetries int) error
 
+	// Apply a service's definition to the cluster. The full service declaration needs to be provided,
+	// which is different to UpdateService which can also do a partial update. If the given
+	// service does not already exists (identified by name) then the service is create.
+	// If the service exists, then a three-way merge will be performed between the original
+	// configuration given (from the last "apply" operation), the new configuration as given ]
+	// here and the current configuration as found on the cluster.
+	// The returned bool indicates whether the service has been changed or whether this operation
+	// was a no-op
+	// An error can indicate a general error or a conflict that occurred during the three way merge.
+	ApplyService(service *servingv1.Service) (bool, error)
+
 	// Delete a service by name
 	DeleteService(name string, timeout time.Duration) error
 
@@ -267,6 +279,32 @@ func updateServiceWithRetry(cl KnServingClient, name string, updateFunc ServiceU
 	}
 }
 
+// ApplyService applies a service definition that contains the service's targer state
+func (cl *knServingClient) ApplyService(modifiedService *servingv1.Service) (bool, error) {
+	currentService, err := cl.GetService(modifiedService.Name)
+	if err != nil && !apierrors.IsNotFound(err) {
+		return false, err
+	}
+
+	containers := modifiedService.Spec.Template.Spec.Containers
+	if len(containers) == 0 || containers[0].Image == "" && currentService != nil {
+		return false, errors.New("'service apply' requires the image name to run provided with the --image option")
+	}
+
+	// No current service --> create a new service
+	if currentService == nil {
+		err := updateLastAppliedAnnotation(modifiedService)
+		if err != nil {
+			return false, err
+		}
+		return true, cl.CreateService(modifiedService)
+	}
+
+	// Merge with existing service
+	uOriginalService := getOriginalConfiguration(currentService)
+	return cl.patch(modifiedService, currentService, uOriginalService)
+}
+
 // Delete a service by name
 // Param `timeout` represents a duration to wait for a delete op to finish.
 // For `timeout == 0` delete is performed async without any wait.
diff --git a/pkg/serving/v1/client_mock.go b/pkg/serving/v1/client_mock.go
index 8c6d5742..2586b269 100644
--- a/pkg/serving/v1/client_mock.go
+++ b/pkg/serving/v1/client_mock.go
@@ -104,6 +104,16 @@ func (c *MockKnServingClient) UpdateServiceWithRetry(name string, updateFunc Ser
 	return updateServiceWithRetry(c, name, updateFunc, maxRetry)
 }
 
+// Update the given service
+func (sr *ServingRecorder) ApplyService(service interface{}, hasChanged bool, err error) {
+	sr.r.Add("ApplyService", []interface{}{service}, []interface{}{hasChanged, err})
+}
+
+func (c *MockKnServingClient) ApplyService(service *servingv1.Service) (bool, error) {
+	call := c.recorder.r.VerifyCall("ApplyService", service)
+	return call.Result[0].(bool), mock.ErrorOrNil(call.Result[1])
+}
+
 // Delete a service by name
 func (sr *ServingRecorder) DeleteService(name, timeout interface{}, err error) {
 	sr.r.Add("DeleteService", []interface{}{name, timeout}, []interface{}{err})
diff --git a/pkg/util/unstructured.go b/pkg/util/unstructured.go
index 77f2e7a3..ccaa6dc7 100644
--- a/pkg/util/unstructured.go
+++ b/pkg/util/unstructured.go
@@ -32,8 +32,8 @@ func ToUnstructuredList(obj runtime.Object) (*unstructured.UnstructuredList, err
 		if err != nil {
 			return nil, err
 		}
-		for _, item := range items {
-			ud, err := toUnstructured(item)
+		for _, obji := range items {
+			ud, err := ToUnstructured(obji)
 			if err != nil {
 				return nil, err
 			}
@@ -41,7 +41,7 @@ func ToUnstructuredList(obj runtime.Object) (*unstructured.UnstructuredList, err
 		}
 
 	} else {
-		ud, err := toUnstructured(obj)
+		ud, err := ToUnstructured(obj)
 		if err != nil {
 			return nil, err
 		}
@@ -51,7 +51,7 @@ func ToUnstructuredList(obj runtime.Object) (*unstructured.UnstructuredList, err
 
 }
 
-func toUnstructured(obj runtime.Object) (*unstructured.Unstructured, error) {
+func ToUnstructured(obj runtime.Object) (*unstructured.Unstructured, error) {
 	b, err := json.Marshal(obj)
 	if err != nil {
 		return nil, err
diff --git a/vendor/k8s.io/apimachinery/pkg/util/jsonmergepatch/patch.go b/vendor/k8s.io/apimachinery/pkg/util/jsonmergepatch/patch.go
new file mode 100644
index 00000000..e56e1773
--- /dev/null
+++ b/vendor/k8s.io/apimachinery/pkg/util/jsonmergepatch/patch.go
@@ -0,0 +1,160 @@
+/*
+Copyright 2017 The Kubernetes 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.
+*/
+
+package jsonmergepatch
+
+import (
+	"fmt"
+	"reflect"
+
+	"github.com/evanphx/json-patch"
+	"k8s.io/apimachinery/pkg/util/json"
+	"k8s.io/apimachinery/pkg/util/mergepatch"
+)
+
+// Create a 3-way merge patch based-on JSON merge patch.
+// Calculate addition-and-change patch between current and modified.
+// Calculate deletion patch between original and modified.
+func CreateThreeWayJSONMergePatch(original, modified, current []byte, fns ...mergepatch.PreconditionFunc) ([]byte, error) {
+	if len(original) == 0 {
+		original = []byte(`{}`)
+	}
+	if len(modified) == 0 {
+		modified = []byte(`{}`)
+	}
+	if len(current) == 0 {
+		current = []byte(`{}`)
+	}
+
+	addAndChangePatch, err := jsonpatch.CreateMergePatch(current, modified)
+	if err != nil {
+		return nil, err
+	}
+	// Only keep addition and changes
+	addAndChangePatch, addAndChangePatchObj, err := keepOrDeleteNullInJsonPatch(addAndChangePatch, false)
+	if err != nil {
+		return nil, err
+	}
+
+	deletePatch, err := jsonpatch.CreateMergePatch(original, modified)
+	if err != nil {
+		return nil, err
+	}
+	// Only keep deletion
+	deletePatch, deletePatchObj, err := keepOrDeleteNullInJsonPatch(deletePatch, true)
+	if err != nil {
+		return nil, err
+	}
+
+	hasConflicts, err := mergepatch.HasConflicts(addAndChangePatchObj, deletePatchObj)
+	if err != nil {
+		return nil, err
+	}
+	if hasConflicts {
+		return nil, mergepatch.NewErrConflict(mergepatch.ToYAMLOrError(addAndChangePatchObj), mergepatch.ToYAMLOrError(deletePatchObj))
+	}
+	patch, err := jsonpatch.MergePatch(deletePatch, addAndChangePatch)
+	if err != nil {
+		return nil, err
+	}
+
+	var patchMap map[string]interface{}
+	err = json.Unmarshal(patch, &patchMap)
+	if err != nil {
+		return nil, fmt.Errorf("Failed to unmarshal patch for precondition check: %s", patch)
+	}
+	meetPreconditions, err := meetPreconditions(patchMap, fns...)
+	if err != nil {
+		return nil, err
+	}
+	if !meetPreconditions {
+		return nil, mergepatch.NewErrPreconditionFailed(patchMap)
+	}
+
+	return patch, nil
+}
+
+// keepOrDeleteNullInJsonPatch takes a json-encoded byte array and a boolean.
+// It returns a filtered object and its corresponding json-encoded byte array.
+// It is a wrapper of func keepOrDeleteNullInObj
+func keepOrDeleteNullInJsonPatch(patch []byte, keepNull bool) ([]byte, map[string]interface{}, error) {
+	var patchMap map[string]interface{}
+	err := json.Unmarshal(patch, &patchMap)
+	if err != nil {
+		return nil, nil, err
+	}
+	filteredMap, err := keepOrDeleteNullInObj(patchMap, keepNull)
+	if err != nil {
+		return nil, nil, err
+	}
+	o, err := json.Marshal(filteredMap)
+	return o, filteredMap, err
+}
+
+// keepOrDeleteNullInObj will keep only the null value and delete all the others,
+// if keepNull is true. Otherwise, it will delete all the null value and keep the others.
+func keepOrDeleteNullInObj(m map[string]interface{}, keepNull bool) (map[string]interface{}, error) {
+	filteredMap := make(map[string]interface{})
+	var err error
+	for key, val := range m {
+		switch {
+		case keepNull && val == nil:
+			filteredMap[key] = nil
+		case val != nil:
+			switch typedVal := val.(type) {
+			case map[string]interface{}:
+				// Explicitly-set empty maps are treated as values instead of empty patches
+				if len(typedVal) == 0 {
+					if !keepNull {
+						filteredMap[key] = typedVal
+					}
+					continue
+				}
+
+				var filteredSubMap map[string]interface{}
+				filteredSubMap, err = keepOrDeleteNullInObj(typedVal, keepNull)
+				if err != nil {
+					return nil, err
+				}
+
+				// If the returned filtered submap was empty, this is an empty patch for the entire subdict, so the key
+				// should not be set
+				if len(filteredSubMap) != 0 {
+					filteredMap[key] = filteredSubMap
+				}
+
+			case []interface{}, string, float64, bool, int64, nil:
+				// Lists are always replaced in Json, no need to check each entry in the list.
+				if !keepNull {
+					filteredMap[key] = val
+				}
+			default:
+				return nil, fmt.Errorf("unknown type: %v", reflect.TypeOf(typedVal))
+			}
+		}
+	}
+	return filteredMap, nil
+}
+
+func meetPreconditions(patchObj map[string]interface{}, fns ...mergepatch.PreconditionFunc) (bool, error) {
+	// Apply the preconditions to the patch, and return an error if any of them fail.
+	for _, fn := range fns {
+		if !fn(patchObj) {
+			return false, fmt.Errorf("precondition failed for: %v", patchObj)
+		}
+	}
+	return true, nil
+}
diff --git a/vendor/modules.txt b/vendor/modules.txt
index 11beda38..ca2416af 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -109,6 +109,7 @@ github.com/golang/protobuf/ptypes/wrappers
 # github.com/google/btree v1.0.0
 github.com/google/btree
 # github.com/google/go-cmp v0.5.2
+## explicit
 github.com/google/go-cmp/cmp
 github.com/google/go-cmp/cmp/cmpopts
 github.com/google/go-cmp/cmp/internal/diff
@@ -549,6 +550,7 @@ k8s.io/apimachinery/pkg/util/errors
 k8s.io/apimachinery/pkg/util/framer
 k8s.io/apimachinery/pkg/util/intstr
 k8s.io/apimachinery/pkg/util/json
+k8s.io/apimachinery/pkg/util/jsonmergepatch
 k8s.io/apimachinery/pkg/util/mergepatch
 k8s.io/apimachinery/pkg/util/naming
 k8s.io/apimachinery/pkg/util/net
@@ -936,7 +938,6 @@ sigs.k8s.io/structured-merge-diff/v3/value
 ## explicit
 sigs.k8s.io/yaml
 # k8s.io/api => k8s.io/api v0.18.8
-# k8s.io/apiextensions-apiserver => k8s.io/apiextensions-apiserver v0.18.8
 # k8s.io/apimachinery => k8s.io/apimachinery v0.18.8
 # k8s.io/cli-runtime => k8s.io/cli-runtime v0.18.8
 # k8s.io/client-go => k8s.io/client-go v0.18.8


Test patch:
diff --git a/pkg/kn/commands/service/apply_mock_test.go b/pkg/kn/commands/service/apply_mock_test.go
new file mode 100644
index 00000000..1f1dcd10
--- /dev/null
+++ b/pkg/kn/commands/service/apply_mock_test.go
@@ -0,0 +1,187 @@
+// Copyright © 2019 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.
+
+package service
+
+import (
+	"fmt"
+	"testing"
+	"time"
+
+	"github.com/pkg/errors"
+	"gotest.tools/assert"
+	v1 "k8s.io/api/core/v1"
+	apierrors "k8s.io/apimachinery/pkg/api/errors"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	servingv1 "knative.dev/serving/pkg/apis/serving/v1"
+
+	knclient "knative.dev/client/pkg/serving/v1"
+	"knative.dev/client/pkg/util/mock"
+	"knative.dev/client/pkg/wait"
+
+	"knative.dev/client/pkg/util"
+)
+
+func TestServiceApplyCreateMock(t *testing.T) {
+	// New mock client
+	client := knclient.NewMockKnServiceClient(t)
+
+	r := setupServiceApplyRecorder(client, "foo", nil, apierrors.NewNotFound(servingv1.Resource("service"), "foo"), true)
+
+	// Testing:
+	output, err := executeServiceCommand(client, "apply", "foo", "--image", "gcr.io/foo/bar:baz")
+	assert.NilError(t, err)
+	assert.Assert(t, util.ContainsAll(output, "created", "foo", "http://foo.example.com", "Ready"))
+
+	// Validate that all recorded API methods have been called
+	r.Validate()
+}
+
+func TestServiceApplyCreateFromFileMock(t *testing.T) {
+	testWithServiceFiles(t, func(t *testing.T, file string) {
+		for _, testArgs := range [][]string{
+			{"apply", "foo", "--filename", file},
+			{"apply", "--filename", file},
+		} {
+			client := knclient.NewMockKnServiceClient(t)
+
+			r := setupServiceApplyRecorder(client, "foo", nil, apierrors.NewNotFound(servingv1.Resource("service"), "foo"), true)
+
+			// Testing:
+			output, err := executeServiceCommand(client, testArgs...)
+			assert.NilError(t, err)
+			assert.Assert(t, util.ContainsAll(output, "created", "foo", "http://foo.example.com", "Ready"))
+
+			// Validate that all recorded API methods have been called
+			r.Validate()
+		}
+	})
+}
+
+func TestServiceApplyCreateFromFileMockWithoutName(t *testing.T) {
+	testWithServiceFiles(t, func(t *testing.T, file string) {
+		client := knclient.NewMockKnServiceClient(t)
+
+		r := setupServiceApplyRecorder(client, "foo", nil, apierrors.NewNotFound(servingv1.Resource("service"), "foo"), true)
+
+		// Testing:
+		output, err := executeServiceCommand(client, "apply", "foo", "--filename", file)
+		assert.NilError(t, err)
+		assert.Assert(t, util.ContainsAll(output, "created", "foo", "http://foo.example.com", "Ready"))
+
+		// Validate that all recorded API methods have been called
+		r.Validate()
+	})
+}
+
+func TestServiceApplyUpdateMock(t *testing.T) {
+	// New mock client
+	client := knclient.NewMockKnServiceClient(t)
+
+	service := createServiceWithImage("foo", "gcr.io/foo/bar:baz")
+
+	r := setupServiceApplyRecorder(client, "foo", service, nil, true)
+
+	// Testing:
+	output, err := executeServiceCommand(client, "apply", "foo", "--image", "gcr.io/foo/bar:baz")
+	assert.NilError(t, err)
+	assert.Assert(t, util.ContainsAll(output, "applied", "foo", "http://foo.example.com", "Ready"))
+
+	// Validate that all recorded API methods have been called
+	r.Validate()
+}
+
+func TestServiceApplyUpdateUnchanged(t *testing.T) {
+	// New mock client
+	client := knclient.NewMockKnServiceClient(t)
+
+	service := createServiceWithImage("foo", "gcr.io/foo/bar:baz")
+
+	r := setupServiceApplyRecorder(client, "foo", service, nil, false)
+
+	// Testing:
+	output, err := executeServiceCommand(client, "apply", "foo", "--image", "gcr.io/foo/bar:baz")
+	assert.NilError(t, err)
+	assert.Assert(t, util.ContainsAll(output, "No changes", "apply", "foo", "http://foo.example.com"))
+
+	// Validate that all recorded API methods have been called
+	r.Validate()
+}
+
+func TestServiceApplyWithGetError(t *testing.T) {
+	// New mock client
+	client := knclient.NewMockKnServiceClient(t)
+
+	errThrown := errors.New("boom!")
+	r := setupServiceApplyRecorder(client, "foo", nil, errThrown, true)
+
+	_, err := executeServiceCommand(client, "apply", "foo", "--image", "gcr.io/foo/bar:baz")
+	assert.Equal(t, err, errThrown)
+
+	// Validate that all recorded API methods have been called
+	r.Validate()
+}
+
+func setupServiceApplyRecorder(client *knclient.MockKnServingClient, name string, service *servingv1.Service, err error, hasChanged bool) *knclient.ServingRecorder {
+	// Recording:
+	r := client.Recorder()
+	// Check for existing service --> no
+	r.GetService(name, service, err)
+	// Error test
+	if err != nil && !apierrors.IsNotFound(err) {
+		return r
+	}
+
+	// Create service (don't validate given service --> "Any()" arg is allowed)
+	r.ApplyService(func(t *testing.T, a interface{}) {
+		svc := a.(*servingv1.Service)
+		assert.Equal(t, svc.Name, name)
+		setUrl(svc, fmt.Sprintf("http://%s.example.com", name))
+	}, hasChanged, nil)
+
+	// Fetch service for URL
+	r.GetService(name, getServiceWithUrl(name, fmt.Sprintf("http://%s.example.com", name)), nil)
+
+	if !hasChanged {
+		return r
+	}
+	// Wait for service to become ready
+	r.WaitForService(name, mock.Any(), wait.NoopMessageCallback(), nil, time.Second)
+
+	return r
+}
+
+func createServiceWithImage(name string, image string) *servingv1.Service {
+	return &servingv1.Service{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      name,
+			Namespace: "default",
+		},
+		Spec: servingv1.ServiceSpec{
+			ConfigurationSpec: servingv1.ConfigurationSpec{
+				Template: servingv1.RevisionTemplateSpec{
+					Spec: servingv1.RevisionSpec{
+						PodSpec: v1.PodSpec{
+							Containers: []v1.Container{
+								{
+									Image: image,
+								},
+							},
+						},
+					},
+				},
+			},
+		},
+	}
+}
diff --git a/pkg/kn/commands/service/create_mock_test.go b/pkg/kn/commands/service/create_mock_test.go
index 277e563e..bbcbf12c 100644
--- a/pkg/kn/commands/service/create_mock_test.go
+++ b/pkg/kn/commands/service/create_mock_test.go
@@ -681,8 +681,12 @@ func TestServiceCreateWithAutoScaleServiceAnnotationsError(t *testing.T) {
 
 func getServiceWithUrl(name string, urlName string) *servingv1.Service {
 	service := servingv1.Service{}
-	url, _ := apis.ParseURL(urlName)
-	service.Status.URL = url
 	service.Name = name
+	setUrl(&service, urlName)
 	return &service
 }
+
+func setUrl(service *servingv1.Service, urlName string) {
+	url, _ := apis.ParseURL(urlName)
+	service.Status.URL = url
+}
diff --git a/pkg/kn/commands/service/create_test.go b/pkg/kn/commands/service/create_test.go
index 6ebd0a89..d155454c 100644
--- a/pkg/kn/commands/service/create_test.go
+++ b/pkg/kn/commands/service/create_test.go
@@ -822,93 +822,61 @@ var serviceJSON = `
   }
 }`
 
-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 TestServiceCreateFromFile(t *testing.T) {
+	testWithServiceFiles(t, func(t *testing.T, tempFile string) {
+		for _, testArgs := range [][]string{
+			{
+				"service", "create", "foo", "--filename", tempFile}, {
+				"service", "create", "--filename", tempFile},
+		} {
+			action, created, _, err := fakeServiceCreate(testArgs, 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) {
+func testWithServiceFiles(t *testing.T, testFunction func(t *testing.T, file string)) {
 	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")
+	for _, d := range []struct {
+		filename string
+		content  string
+	}{
+		{"service.yaml",
+			serviceYAML,
+		},
+		{"service.json",
+			serviceJSON,
+		},
+	} {
+		tempFile := filepath.Join(tempDir, d.filename)
+		err = ioutil.WriteFile(tempFile, []byte(d.content), os.FileMode(0666))
+		assert.NilError(t, err)
+		testFunction(t, tempFile)
+	}
 }
 
 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"))
+	testWithServiceFiles(t, func(t *testing.T, tempFile string) {
+		_, _, _, 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'"))
+
+		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) {
diff --git a/pkg/serving/v1/apply_test.go b/pkg/serving/v1/apply_test.go
new file mode 100644
index 00000000..d64093b9
--- /dev/null
+++ b/pkg/serving/v1/apply_test.go
@@ -0,0 +1,233 @@
+// 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.
+
+package v1
+
+import (
+	"reflect"
+	"testing"
+
+	"github.com/google/go-cmp/cmp"
+	"gotest.tools/assert"
+	corev1 "k8s.io/api/core/v1"
+	"k8s.io/apimachinery/pkg/api/errors"
+	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
+	"k8s.io/apimachinery/pkg/runtime"
+	clienttesting "k8s.io/client-go/testing"
+	servingv1 "knative.dev/serving/pkg/apis/serving/v1"
+	"sigs.k8s.io/yaml"
+
+	"knative.dev/client/pkg/util"
+)
+
+func TestApplyServiceWithNoImage(t *testing.T) {
+	_, client := setup()
+	serviceFaulty := newService("faulty-service")
+	_, err := client.ApplyService(serviceFaulty)
+	assert.Assert(t, err != nil)
+	assert.Assert(t, util.ContainsAll(err.Error(), "image name"))
+}
+
+func TestApplyServiceCreate(t *testing.T) {
+	serving, client := setup()
+
+	serviceNew := newServiceWithImage("new-service", "test/image")
+	serving.AddReactor("get", "services",
+		func(a clienttesting.Action) (bool, runtime.Object, error) {
+			name := a.(clienttesting.GetAction).GetName()
+			return true, nil, errors.NewNotFound(servingv1.Resource("service"), name)
+		})
+
+	serving.AddReactor("create", "services",
+		func(a clienttesting.Action) (bool, runtime.Object, error) {
+			assert.Equal(t, testNamespace, a.GetNamespace())
+			return true, serviceNew, nil
+		})
+
+	hasChanged, err := client.ApplyService(serviceNew)
+	assert.NilError(t, err)
+	assert.Assert(t, hasChanged, "service has changed")
+}
+
+func TestApplyServiceUpdate(t *testing.T) {
+	serving, client := setup()
+
+	serviceOld := newServiceWithImage("my-service", "test/image")
+	serviceNew := newServiceWithImage("my-service", "test/new-image")
+	serving.AddReactor("get", "services",
+		func(a clienttesting.Action) (bool, runtime.Object, error) {
+			name := a.(clienttesting.GetAction).GetName()
+			assert.Equal(t, name, "my-service")
+			return true, serviceOld, nil
+		})
+
+	serving.AddReactor("patch", "services",
+		func(a clienttesting.Action) (bool, runtime.Object, error) {
+			serviceNew.Generation = 2
+			serviceNew.Status.ObservedGeneration = 1
+			return true, serviceNew, nil
+		})
+
+	hasChanged, err := client.ApplyService(serviceNew)
+	assert.NilError(t, err)
+	assert.Assert(t, hasChanged, "service has changed")
+}
+
+func newServiceWithImage(name string, image string) *servingv1.Service {
+	svc := newService(name)
+	svc.Spec = servingv1.ServiceSpec{
+		ConfigurationSpec: servingv1.ConfigurationSpec{
+			Template: servingv1.RevisionTemplateSpec{
+				Spec: servingv1.RevisionSpec{
+					PodSpec: corev1.PodSpec{
+						Containers: []corev1.Container{
+							{
+								Image: image,
+							},
+						},
+					},
+				},
+			},
+		},
+	}
+	return svc
+}
+
+func TestExtractUserContainer(t *testing.T) {
+	tests := []struct {
+		name    string
+		service string
+		want    string
+	}{
+		{"Simple Service",
+			`
+spec:
+  template:
+    spec:
+      containers:
+        - image: gcr.io/foo/bar:baz
+`,
+			`
+image: gcr.io/foo/bar:baz
+`,
+		},
+		{
+			"No template",
+			`
+spec:
+`,
+			"",
+		}, {
+			"No template spec",
+			`
+spec:
+  template:
+`,
+			"",
+		},
+		{
+			"No template spec containers",
+			`
+spec:
+  template:
+    spec:
+`,
+			"",
+		},
+		{
+			"Empty template spec containers",
+			`
+spec:
+  template:
+    spec:
+      containers: []
+`,
+			"",
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			var serviceMap map[string]interface{}
+			yaml.Unmarshal([]byte(tt.service), &serviceMap)
+
+			got := extractUserContainer(serviceMap)
+
+			if tt.want == "" {
+				assert.Assert(t, got == nil)
+			} else {
+				var expectedMap map[string]interface{}
+				yaml.Unmarshal([]byte(tt.want), &expectedMap)
+				if !reflect.DeepEqual(got, expectedMap) {
+					t.Errorf("extractUserContainer() = %v, want %v", got, expectedMap)
+				}
+			}
+		})
+	}
+}
+
+func TestCleanupServiceUnstructured(t *testing.T) {
+	tests := []struct {
+		name    string
+		service string
+		want    string
+	}{
+		{"Simple Service with fields to remove",
+			`
+apiVersion: serving.knative.dev/v1
+kind: Service
+metadata:
+  name: foo
+  creationTimestamp: "2020-10-22T08:16:37Z"
+spec:
+  template:
+    metadata:
+      name: "bar"
+      creationTimestamp: null
+    spec:
+      containers:
+      - image: gcr.io/foo/bar:baz
+        name: "bla"
+        resources: {}
+status:
+  observedGeneration: 1
+`,
+			`
+apiVersion: serving.knative.dev/v1
+kind: Service
+metadata:
+  name: foo
+spec:
+  template:
+    metadata:
+      name: "bar"
+    spec:
+      containers:
+      - image: gcr.io/foo/bar:baz
+`,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			ud := &unstructured.Unstructured{}
+			assert.NilError(t, yaml.Unmarshal([]byte(tt.service), ud))
+			cleanupServiceUnstructured(ud)
+
+			expectedMap := &unstructured.Unstructured{}
+			yaml.Unmarshal([]byte(tt.want), &expectedMap)
+			if !reflect.DeepEqual(ud, expectedMap) {
+				t.Errorf("cleanupServiceUnstructured(): " + cmp.Diff(ud, expectedMap))
+			}
+		})
+	}
+}
diff --git a/pkg/serving/v1/client_mock_test.go b/pkg/serving/v1/client_mock_test.go
index 10f20b70..7ec9738a 100644
--- a/pkg/serving/v1/client_mock_test.go
+++ b/pkg/serving/v1/client_mock_test.go
@@ -37,6 +37,7 @@ func TestMockKnClient(t *testing.T) {
 	recorder.ListServices(mock.Any(), nil, nil)
 	recorder.CreateService(&servingv1.Service{}, nil)
 	recorder.UpdateService(&servingv1.Service{}, nil)
+	recorder.ApplyService(&servingv1.Service{}, true, nil)
 	recorder.DeleteService("hello", time.Duration(10)*time.Second, nil)
 	recorder.WaitForService("hello", time.Duration(10)*time.Second, wait.NoopMessageCallback(), nil, 10*time.Second)
 	recorder.GetRevision("hello", nil, nil)
@@ -52,6 +53,7 @@ func TestMockKnClient(t *testing.T) {
 	client.ListServices(WithLabel("foo", "bar"))
 	client.CreateService(&servingv1.Service{})
 	client.UpdateService(&servingv1.Service{})
+	client.ApplyService(&servingv1.Service{})
 	client.DeleteService("hello", time.Duration(10)*time.Second)
 	client.WaitForService("hello", time.Duration(10)*time.Second, wait.NoopMessageCallback())
 	client.GetRevision("hello")
diff --git a/test/e2e/service_apply_test.go b/test/e2e/service_apply_test.go
new file mode 100644
index 00000000..5c68ed17
--- /dev/null
+++ b/test/e2e/service_apply_test.go
@@ -0,0 +1,62 @@
+// Copyright 2019 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 (
+	"testing"
+
+	"gotest.tools/assert"
+
+	pkgtest "knative.dev/pkg/test"
+
+	"knative.dev/client/lib/test"
+	"knative.dev/client/pkg/util"
+)
+
+func TestServiceApply(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()
+
+	t.Log("apply hello service (initially)")
+	result := serviceApply(r, "hello-apply")
+	r.AssertNoError(result)
+	assert.Check(r.T(), util.ContainsAllIgnoreCase(result.Stdout, "creating", "service", "hello-apply", "ready", "http"))
+	t.Log("apply hello service (unchanged)")
+	result = serviceApply(r, "hello-apply")
+	r.AssertNoError(result)
+	assert.Check(r.T(), util.ContainsAllIgnoreCase(result.Stdout, "no changes", "service", "hello-apply", "http"))
+
+	t.Log("apply hello service (update env)")
+	result = serviceApply(r, "hello-apply", "--env", "tik=tok")
+	r.AssertNoError(result)
+	assert.Check(r.T(), util.ContainsAllIgnoreCase(result.Stdout, "applying", "service", "hello-apply", "ready", "http"))
+}
+
+// ServiceApply applies a test service and returns the output
+func serviceApply(r *test.KnRunResultCollector, serviceName string, args ...string) test.KnRunResult {
+	fullArgs := append([]string{}, "service", "apply", serviceName, "--image", pkgtest.ImagePath("helloworld"))
+	fullArgs = append(fullArgs, args...)
+	return r.KnTest().Kn().Run(fullArgs...)
+}