mirror of https://github.com/knative/client.git
Feature: "kn service apply" (#964)
* feat: "kn service apply" This commit introduces a client-side apply with a plain JsonPatchMerge. This is more limited than a StrategicPatchMerg as it does not allow to merge lists (they are just overwritten). Also is not a real 3-way merger that would lead to a conflict when both the, the server-side and the provide update overlapp in fields that updated, compared to the shared original configuration. This is a problem of JsonThreeWayMerger itself, as pointed out in https://github.com/kubernetes/kubernetes/pull/40666#pullrequestreview-502804243. This limitation is shared with kubectl, which suffers from the same issue if using `kubectl apply` with a custom resource (i.e. with everything that has schema that is not registered within kubectl). Tests are missing, too, but will come soon * chore: Add tests for 'kn apply' * refactor: Removed PatchService from pulic API interface * fix: Display of service URL at the end, when no changes apply * chore: Add initial E2E test * chore: Implemented review suggestions * More tests * Example for kn service apply * Remove commented-out code * lint fixes * fix formatting of kn service apply doc * fixing go.sum * chore: Update deps
This commit is contained in:
parent
d1552ee0a3
commit
8ca97c7920
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
2
go.mod
2
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
|
||||
|
|
|
|||
58
go.sum
58
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=
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
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"))
|
||||
|
||||
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")
|
||||
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) {
|
||||
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.json")
|
||||
err = ioutil.WriteFile(tempFile, []byte(serviceJSON), os.FileMode(0666))
|
||||
assert.NilError(t, err)
|
||||
|
||||
action, created, _, err := fakeServiceCreate([]string{
|
||||
"service", "create", "foo", "--filename", tempFile}, false)
|
||||
assert.NilError(t, err)
|
||||
assert.Assert(t, action.Matches("create", "services"))
|
||||
|
||||
assert.Equal(t, created.Name, "foo")
|
||||
assert.Equal(t, created.Spec.Template.Spec.GetContainer().Image, "gcr.io/foo/bar:baz")
|
||||
}
|
||||
|
||||
func TestServiceCreateFromFileWithName(t *testing.T) {
|
||||
tempDir, err := ioutil.TempDir("", "kn-file")
|
||||
defer os.RemoveAll(tempDir)
|
||||
assert.NilError(t, err)
|
||||
|
||||
tempFile := filepath.Join(tempDir, "service.yaml")
|
||||
err = ioutil.WriteFile(tempFile, []byte(serviceYAML), os.FileMode(0666))
|
||||
assert.NilError(t, err)
|
||||
|
||||
t.Log("no NAME param provided")
|
||||
action, created, _, err := fakeServiceCreate([]string{
|
||||
"service", "create", "--filename", tempFile}, false)
|
||||
assert.NilError(t, err)
|
||||
assert.Assert(t, action.Matches("create", "services"))
|
||||
|
||||
assert.Equal(t, created.Name, "foo")
|
||||
assert.Equal(t, created.Spec.Template.Spec.GetContainer().Image, "gcr.io/foo/bar:baz")
|
||||
|
||||
t.Log("no service.Name provided in file")
|
||||
err = ioutil.WriteFile(tempFile, []byte(strings.ReplaceAll(serviceYAML, "name: foo", "")), os.FileMode(0666))
|
||||
assert.NilError(t, err)
|
||||
action, created, _, err = fakeServiceCreate([]string{
|
||||
"service", "create", "cli-foo", "--filename", tempFile}, false)
|
||||
assert.NilError(t, err)
|
||||
assert.Assert(t, action.Matches("create", "services"))
|
||||
|
||||
assert.Equal(t, created.Name, "cli-foo")
|
||||
assert.Equal(t, created.Spec.Template.Spec.GetContainer().Image, "gcr.io/foo/bar:baz")
|
||||
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)
|
||||
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'"))
|
||||
|
||||
tempFile := filepath.Join(tempDir, "service.json")
|
||||
err = ioutil.WriteFile(tempFile, []byte(serviceJSON), os.FileMode(0666))
|
||||
assert.NilError(t, err)
|
||||
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"))
|
||||
|
||||
t.Log("NAME param nad service.Name differ")
|
||||
_, _, _, err = fakeServiceCreate([]string{
|
||||
"service", "create", "anotherFoo", "--filename", tempFile}, false)
|
||||
assert.Assert(t, err != nil)
|
||||
assert.Assert(t, util.ContainsAllIgnoreCase(err.Error(), "provided", "'anotherFoo'", "name", "match", "from", "file", "'foo'"))
|
||||
|
||||
t.Log("no NAME param & no service.Name provided in file")
|
||||
err = ioutil.WriteFile(tempFile, []byte(strings.ReplaceAll(serviceYAML, "name: foo", "")), os.FileMode(0666))
|
||||
assert.NilError(t, err)
|
||||
_, _, _, err = fakeServiceCreate([]string{
|
||||
"service", "create", "--filename", tempFile}, false)
|
||||
assert.Assert(t, err != nil)
|
||||
assert.Assert(t, util.ContainsAllIgnoreCase(err.Error(), "no", "service", "name", "provided", "parameter", "file"))
|
||||
})
|
||||
}
|
||||
|
||||
func TestServiceCreateFileError(t *testing.T) {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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{})
|
||||
}
|
||||
|
|
@ -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))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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})
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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...)
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue