diff --git a/glide.lock b/glide.lock index 9fbbb3021..1d3d3df7f 100644 --- a/glide.lock +++ b/glide.lock @@ -1,5 +1,5 @@ hash: f3e767e8346d32c0a86b9c441a9f5a580a4da1e4e9b201cffc7e2b313149bc5d -updated: 2017-10-04T22:45:37.982127629+05:30 +updated: 2017-11-14T15:40:02.556527845+05:30 imports: - name: github.com/davecgh/go-spew version: 5215b55f46b2b919f50a1df0eaa5886afe4e3b3d @@ -31,10 +31,6 @@ imports: version: 76626ae9c91c4f2a10f34cad8ce83ea42c93bb75 - name: github.com/novln/docker-parser version: 6030251119d652af8ead44ac7907444227b64d56 - subpackages: - - distribution/digest - - distribution/reference - - docker - name: github.com/pkg/errors version: 01fa4104b9c248c8945d14d9f128454d5b28d595 - name: github.com/Sirupsen/logrus @@ -50,5 +46,5 @@ imports: subpackages: - unix - name: gopkg.in/yaml.v2 - version: 53feefa2559fb8dfa8d81baad31be332c97d6c77 + version: eb3733d160e74a9c7e442f435eb3bea458e1d19f testImports: [] diff --git a/scripts/vendor-openshift.sh b/scripts/vendor-openshift.sh index 37cf60313..9c476c527 100755 --- a/scripts/vendor-openshift.sh +++ b/scripts/vendor-openshift.sh @@ -12,7 +12,7 @@ OPENSHIFT_REPO="https://github.com/openshift/origin" -OPENSHIFT_VERSION="v3.6.0" +OPENSHIFT_VERSION="v3.6.1" PROJECT_VENDOR="./vendor" diff --git a/vendor/github.com/coreos/etcd/client/client.go b/vendor/github.com/coreos/etcd/client/client.go index 498dfbcc8..19ce2ec01 100644 --- a/vendor/github.com/coreos/etcd/client/client.go +++ b/vendor/github.com/coreos/etcd/client/client.go @@ -372,12 +372,7 @@ func (c *httpClusterClient) Do(ctx context.Context, act httpAction) (*http.Respo if err == context.Canceled || err == context.DeadlineExceeded { return nil, nil, err } - if isOneShot { - return nil, nil, err - } - continue - } - if resp.StatusCode/100 == 5 { + } else if resp.StatusCode/100 == 5 { switch resp.StatusCode { case http.StatusInternalServerError, http.StatusServiceUnavailable: // TODO: make sure this is a no leader response @@ -385,10 +380,16 @@ func (c *httpClusterClient) Do(ctx context.Context, act httpAction) (*http.Respo default: cerr.Errors = append(cerr.Errors, fmt.Errorf("client: etcd member %s returns server error [%s]", eps[k].String(), http.StatusText(resp.StatusCode))) } - if isOneShot { - return nil, nil, cerr.Errors[0] + err = cerr.Errors[0] + } + if err != nil { + if !isOneShot { + continue } - continue + c.Lock() + c.pinned = (k + 1) % leps + c.Unlock() + return nil, nil, err } if k != pinned { c.Lock() diff --git a/vendor/github.com/google/cadvisor/manager/container.go b/vendor/github.com/google/cadvisor/manager/container.go index 53d8cad30..4924db5b5 100644 --- a/vendor/github.com/google/cadvisor/manager/container.go +++ b/vendor/github.com/google/cadvisor/manager/container.go @@ -113,16 +113,18 @@ func (c *containerData) allowErrorLogging() bool { return false } -func (c *containerData) GetInfo() (*containerInfo, error) { +func (c *containerData) GetInfo(shouldUpdateSubcontainers bool) (*containerInfo, error) { // Get spec and subcontainers. if time.Since(c.lastUpdatedTime) > 5*time.Second { err := c.updateSpec() if err != nil { return nil, err } - err = c.updateSubcontainers() - if err != nil { - return nil, err + if shouldUpdateSubcontainers { + err = c.updateSubcontainers() + if err != nil { + return nil, err + } } c.lastUpdatedTime = time.Now() } diff --git a/vendor/github.com/google/cadvisor/manager/manager.go b/vendor/github.com/google/cadvisor/manager/manager.go index dc5273ac8..30180b196 100644 --- a/vendor/github.com/google/cadvisor/manager/manager.go +++ b/vendor/github.com/google/cadvisor/manager/manager.go @@ -400,7 +400,7 @@ func (self *manager) GetContainerSpec(containerName string, options v2.RequestOp var errs partialFailure specs := make(map[string]v2.ContainerSpec) for name, cont := range conts { - cinfo, err := cont.GetInfo() + cinfo, err := cont.GetInfo(false) if err != nil { errs.append(name, "GetInfo", err) } @@ -449,7 +449,7 @@ func (self *manager) GetContainerInfoV2(containerName string, options v2.Request infos := make(map[string]v2.ContainerInfo, len(containers)) for name, container := range containers { result := v2.ContainerInfo{} - cinfo, err := container.GetInfo() + cinfo, err := container.GetInfo(false) if err != nil { errs.append(name, "GetInfo", err) infos[name] = result @@ -473,7 +473,7 @@ func (self *manager) GetContainerInfoV2(containerName string, options v2.Request func (self *manager) containerDataToContainerInfo(cont *containerData, query *info.ContainerInfoRequest) (*info.ContainerInfo, error) { // Get the info from the container. - cinfo, err := cont.GetInfo() + cinfo, err := cont.GetInfo(true) if err != nil { return nil, err } diff --git a/vendor/github.com/opencontainers/runc/libcontainer/cgroups/systemd/apply_systemd.go b/vendor/github.com/opencontainers/runc/libcontainer/cgroups/systemd/apply_systemd.go index fd428f90c..7ce066c06 100644 --- a/vendor/github.com/opencontainers/runc/libcontainer/cgroups/systemd/apply_systemd.go +++ b/vendor/github.com/opencontainers/runc/libcontainer/cgroups/systemd/apply_systemd.go @@ -269,6 +269,13 @@ func (m *Manager) Apply(pid int) error { newProp("CPUShares", uint64(c.Resources.CpuShares))) } + // cpu.cfs_quota_us and cpu.cfs_period_us are controlled by systemd. + if c.Resources.CpuQuota != 0 && c.Resources.CpuPeriod != 0 { + cpuQuotaPerSecUSec := c.Resources.CpuQuota * 1000000 / c.Resources.CpuPeriod + properties = append(properties, + newProp("CPUQuotaPerSecUSec", uint64(cpuQuotaPerSecUSec))) + } + if c.Resources.BlkioWeight != 0 { properties = append(properties, newProp("BlockIOWeight", uint64(c.Resources.BlkioWeight))) diff --git a/vendor/github.com/openshift/origin/Makefile b/vendor/github.com/openshift/origin/Makefile index 69c415d4f..16ce89fbd 100644 --- a/vendor/github.com/openshift/origin/Makefile +++ b/vendor/github.com/openshift/origin/Makefile @@ -45,17 +45,21 @@ all build: # # Example: # make build-tests -build-tests: build-extended-test build-integration-test +build-tests: build-extended-test build-integration-test build-router-e2e-test .PHONY: build-tests build-extended-test: hack/build-go.sh test/extended/extended.test .PHONY: build-extended-test -build-integration-test: +build-integration-test: build-router-e2e-test hack/build-go.sh test/integration/integration.test .PHONY: build-integration-test +build-router-e2e-test: + hack/build-go.sh test/end-to-end/end-to-end.test +.PHONY: build-router-e2e-test + # Run core verification and all self contained tests. # # Example: @@ -187,6 +191,7 @@ test-cmd: build # Example: # make test-end-to-end test-end-to-end: build + KUBE_COVER=" " KUBE_RACE=" " OS_TEST_PACKAGE=test/end-to-end hack/test-integration.sh hack/test-end-to-end.sh .PHONY: test-end-to-end diff --git a/vendor/github.com/openshift/origin/api/swagger-spec/openshift-openapi-spec.json b/vendor/github.com/openshift/origin/api/swagger-spec/openshift-openapi-spec.json index 99d71402e..c00e069a0 100644 --- a/vendor/github.com/openshift/origin/api/swagger-spec/openshift-openapi-spec.json +++ b/vendor/github.com/openshift/origin/api/swagger-spec/openshift-openapi-spec.json @@ -22627,64 +22627,6 @@ } ] }, - "/apis/authorization.openshift.io/v1/namespaces/{namespace}/resourceaccessreviews": { - "post": { - "description": "create a ResourceAccessReview", - "consumes": [ - "*/*" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "schemes": [ - "https" - ], - "tags": [ - "authorizationOpenshiftIo_v1" - ], - "operationId": "createAuthorizationOpenshiftIoV1NamespacedResourceAccessReview", - "parameters": [ - { - "name": "body", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/com.github.openshift.origin.pkg.authorization.apis.authorization.v1.ResourceAccessReview" - } - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/com.github.openshift.origin.pkg.authorization.apis.authorization.v1.ResourceAccessReview" - } - }, - "401": { - "description": "Unauthorized" - } - } - }, - "parameters": [ - { - "uniqueItems": true, - "type": "string", - "description": "object name and auth scope, such as for teams and projects", - "name": "namespace", - "in": "path", - "required": true - }, - { - "uniqueItems": true, - "type": "string", - "description": "If 'true', then the output is pretty printed.", - "name": "pretty", - "in": "query" - } - ] - }, "/apis/authorization.openshift.io/v1/namespaces/{namespace}/rolebindingrestrictions": { "get": { "description": "list or watch objects of kind RoleBindingRestriction", @@ -23791,64 +23733,6 @@ } ] }, - "/apis/authorization.openshift.io/v1/namespaces/{namespace}/subjectaccessreviews": { - "post": { - "description": "create a SubjectAccessReview", - "consumes": [ - "*/*" - ], - "produces": [ - "application/json", - "application/yaml", - "application/vnd.kubernetes.protobuf" - ], - "schemes": [ - "https" - ], - "tags": [ - "authorizationOpenshiftIo_v1" - ], - "operationId": "createAuthorizationOpenshiftIoV1NamespacedSubjectAccessReview", - "parameters": [ - { - "name": "body", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/com.github.openshift.origin.pkg.authorization.apis.authorization.v1.SubjectAccessReview" - } - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/com.github.openshift.origin.pkg.authorization.apis.authorization.v1.SubjectAccessReview" - } - }, - "401": { - "description": "Unauthorized" - } - } - }, - "parameters": [ - { - "uniqueItems": true, - "type": "string", - "description": "object name and auth scope, such as for teams and projects", - "name": "namespace", - "in": "path", - "required": true - }, - { - "uniqueItems": true, - "type": "string", - "description": "If 'true', then the output is pretty printed.", - "name": "pretty", - "in": "query" - } - ] - }, "/apis/authorization.openshift.io/v1/namespaces/{namespace}/subjectrulesreviews": { "post": { "description": "create a SubjectRulesReview", @@ -24160,7 +24044,7 @@ "tags": [ "authorizationOpenshiftIo_v1" ], - "operationId": "createAuthorizationOpenshiftIoV1ResourceAccessReviewForAllNamespaces", + "operationId": "createAuthorizationOpenshiftIoV1ResourceAccessReview", "parameters": [ { "name": "body", @@ -24614,7 +24498,7 @@ "tags": [ "authorizationOpenshiftIo_v1" ], - "operationId": "createAuthorizationOpenshiftIoV1SubjectAccessReviewForAllNamespaces", + "operationId": "createAuthorizationOpenshiftIoV1SubjectAccessReview", "parameters": [ { "name": "body", diff --git a/vendor/github.com/openshift/origin/contrib/completions/bash/oc b/vendor/github.com/openshift/origin/contrib/completions/bash/oc index dd7176b80..3944a13fd 100644 --- a/vendor/github.com/openshift/origin/contrib/completions/bash/oc +++ b/vendor/github.com/openshift/origin/contrib/completions/bash/oc @@ -7550,171 +7550,6 @@ _oc_create_clusterresourcequota() noun_aliases=() } -_oc_create_clusterrole() -{ - last_command="oc_create_clusterrole" - commands=() - - flags=() - two_word_flags=() - local_nonpersistent_flags=() - flags_with_completion=() - flags_completion=() - - flags+=("--allow-missing-template-keys") - local_nonpersistent_flags+=("--allow-missing-template-keys") - flags+=("--dry-run") - local_nonpersistent_flags+=("--dry-run") - flags+=("--no-headers") - local_nonpersistent_flags+=("--no-headers") - flags+=("--output=") - two_word_flags+=("-o") - local_nonpersistent_flags+=("--output=") - flags+=("--resource=") - local_nonpersistent_flags+=("--resource=") - flags+=("--resource-name=") - local_nonpersistent_flags+=("--resource-name=") - flags+=("--save-config") - local_nonpersistent_flags+=("--save-config") - flags+=("--schema-cache-dir=") - flags_with_completion+=("--schema-cache-dir") - flags_completion+=("_filedir") - local_nonpersistent_flags+=("--schema-cache-dir=") - flags+=("--show-all") - flags+=("-a") - local_nonpersistent_flags+=("--show-all") - flags+=("--show-labels") - local_nonpersistent_flags+=("--show-labels") - flags+=("--sort-by=") - local_nonpersistent_flags+=("--sort-by=") - flags+=("--template=") - flags_with_completion+=("--template") - flags_completion+=("_filedir") - local_nonpersistent_flags+=("--template=") - flags+=("--validate") - local_nonpersistent_flags+=("--validate") - flags+=("--verb=") - local_nonpersistent_flags+=("--verb=") - flags+=("--as=") - flags+=("--certificate-authority=") - flags_with_completion+=("--certificate-authority") - flags_completion+=("_filedir") - flags+=("--client-certificate=") - flags_with_completion+=("--client-certificate") - flags_completion+=("_filedir") - flags+=("--client-key=") - flags_with_completion+=("--client-key") - flags_completion+=("_filedir") - flags+=("--cluster=") - flags+=("--config=") - flags_with_completion+=("--config") - flags_completion+=("_filedir") - flags+=("--context=") - flags+=("--insecure-skip-tls-verify") - flags+=("--log-flush-frequency=") - flags+=("--loglevel=") - flags+=("--logspec=") - flags+=("--match-server-version") - flags+=("--namespace=") - flags_with_completion+=("--namespace") - flags_completion+=("__oc_get_namespaces") - two_word_flags+=("-n") - flags_with_completion+=("-n") - flags_completion+=("__oc_get_namespaces") - flags+=("--request-timeout=") - flags+=("--server=") - flags+=("--token=") - flags+=("--user=") - - must_have_one_flag=() - must_have_one_noun=() - noun_aliases=() -} - -_oc_create_clusterrolebinding() -{ - last_command="oc_create_clusterrolebinding" - commands=() - - flags=() - two_word_flags=() - local_nonpersistent_flags=() - flags_with_completion=() - flags_completion=() - - flags+=("--allow-missing-template-keys") - local_nonpersistent_flags+=("--allow-missing-template-keys") - flags+=("--clusterrole=") - local_nonpersistent_flags+=("--clusterrole=") - flags+=("--dry-run") - local_nonpersistent_flags+=("--dry-run") - flags+=("--generator=") - local_nonpersistent_flags+=("--generator=") - flags+=("--group=") - local_nonpersistent_flags+=("--group=") - flags+=("--no-headers") - local_nonpersistent_flags+=("--no-headers") - flags+=("--output=") - two_word_flags+=("-o") - local_nonpersistent_flags+=("--output=") - flags+=("--save-config") - local_nonpersistent_flags+=("--save-config") - flags+=("--schema-cache-dir=") - flags_with_completion+=("--schema-cache-dir") - flags_completion+=("_filedir") - local_nonpersistent_flags+=("--schema-cache-dir=") - flags+=("--serviceaccount=") - local_nonpersistent_flags+=("--serviceaccount=") - flags+=("--show-all") - flags+=("-a") - local_nonpersistent_flags+=("--show-all") - flags+=("--show-labels") - local_nonpersistent_flags+=("--show-labels") - flags+=("--sort-by=") - local_nonpersistent_flags+=("--sort-by=") - flags+=("--template=") - flags_with_completion+=("--template") - flags_completion+=("_filedir") - local_nonpersistent_flags+=("--template=") - flags+=("--user=") - local_nonpersistent_flags+=("--user=") - flags+=("--validate") - local_nonpersistent_flags+=("--validate") - flags+=("--as=") - flags+=("--certificate-authority=") - flags_with_completion+=("--certificate-authority") - flags_completion+=("_filedir") - flags+=("--client-certificate=") - flags_with_completion+=("--client-certificate") - flags_completion+=("_filedir") - flags+=("--client-key=") - flags_with_completion+=("--client-key") - flags_completion+=("_filedir") - flags+=("--cluster=") - flags+=("--config=") - flags_with_completion+=("--config") - flags_completion+=("_filedir") - flags+=("--context=") - flags+=("--insecure-skip-tls-verify") - flags+=("--log-flush-frequency=") - flags+=("--loglevel=") - flags+=("--logspec=") - flags+=("--match-server-version") - flags+=("--namespace=") - flags_with_completion+=("--namespace") - flags_completion+=("__oc_get_namespaces") - two_word_flags+=("-n") - flags_with_completion+=("-n") - flags_completion+=("__oc_get_namespaces") - flags+=("--request-timeout=") - flags+=("--server=") - flags+=("--token=") - - must_have_one_flag=() - must_have_one_noun=() - noun_aliases=() -} - _oc_create_configmap() { last_command="oc_create_configmap" @@ -8371,173 +8206,6 @@ _oc_create_quota() noun_aliases=() } -_oc_create_role() -{ - last_command="oc_create_role" - commands=() - - flags=() - two_word_flags=() - local_nonpersistent_flags=() - flags_with_completion=() - flags_completion=() - - flags+=("--allow-missing-template-keys") - local_nonpersistent_flags+=("--allow-missing-template-keys") - flags+=("--dry-run") - local_nonpersistent_flags+=("--dry-run") - flags+=("--no-headers") - local_nonpersistent_flags+=("--no-headers") - flags+=("--output=") - two_word_flags+=("-o") - local_nonpersistent_flags+=("--output=") - flags+=("--resource=") - local_nonpersistent_flags+=("--resource=") - flags+=("--resource-name=") - local_nonpersistent_flags+=("--resource-name=") - flags+=("--save-config") - local_nonpersistent_flags+=("--save-config") - flags+=("--schema-cache-dir=") - flags_with_completion+=("--schema-cache-dir") - flags_completion+=("_filedir") - local_nonpersistent_flags+=("--schema-cache-dir=") - flags+=("--show-all") - flags+=("-a") - local_nonpersistent_flags+=("--show-all") - flags+=("--show-labels") - local_nonpersistent_flags+=("--show-labels") - flags+=("--sort-by=") - local_nonpersistent_flags+=("--sort-by=") - flags+=("--template=") - flags_with_completion+=("--template") - flags_completion+=("_filedir") - local_nonpersistent_flags+=("--template=") - flags+=("--validate") - local_nonpersistent_flags+=("--validate") - flags+=("--verb=") - local_nonpersistent_flags+=("--verb=") - flags+=("--as=") - flags+=("--certificate-authority=") - flags_with_completion+=("--certificate-authority") - flags_completion+=("_filedir") - flags+=("--client-certificate=") - flags_with_completion+=("--client-certificate") - flags_completion+=("_filedir") - flags+=("--client-key=") - flags_with_completion+=("--client-key") - flags_completion+=("_filedir") - flags+=("--cluster=") - flags+=("--config=") - flags_with_completion+=("--config") - flags_completion+=("_filedir") - flags+=("--context=") - flags+=("--insecure-skip-tls-verify") - flags+=("--log-flush-frequency=") - flags+=("--loglevel=") - flags+=("--logspec=") - flags+=("--match-server-version") - flags+=("--namespace=") - flags_with_completion+=("--namespace") - flags_completion+=("__oc_get_namespaces") - two_word_flags+=("-n") - flags_with_completion+=("-n") - flags_completion+=("__oc_get_namespaces") - flags+=("--request-timeout=") - flags+=("--server=") - flags+=("--token=") - flags+=("--user=") - - must_have_one_flag=() - must_have_one_noun=() - noun_aliases=() -} - -_oc_create_rolebinding() -{ - last_command="oc_create_rolebinding" - commands=() - - flags=() - two_word_flags=() - local_nonpersistent_flags=() - flags_with_completion=() - flags_completion=() - - flags+=("--allow-missing-template-keys") - local_nonpersistent_flags+=("--allow-missing-template-keys") - flags+=("--clusterrole=") - local_nonpersistent_flags+=("--clusterrole=") - flags+=("--dry-run") - local_nonpersistent_flags+=("--dry-run") - flags+=("--generator=") - local_nonpersistent_flags+=("--generator=") - flags+=("--group=") - local_nonpersistent_flags+=("--group=") - flags+=("--no-headers") - local_nonpersistent_flags+=("--no-headers") - flags+=("--output=") - two_word_flags+=("-o") - local_nonpersistent_flags+=("--output=") - flags+=("--role=") - local_nonpersistent_flags+=("--role=") - flags+=("--save-config") - local_nonpersistent_flags+=("--save-config") - flags+=("--schema-cache-dir=") - flags_with_completion+=("--schema-cache-dir") - flags_completion+=("_filedir") - local_nonpersistent_flags+=("--schema-cache-dir=") - flags+=("--serviceaccount=") - local_nonpersistent_flags+=("--serviceaccount=") - flags+=("--show-all") - flags+=("-a") - local_nonpersistent_flags+=("--show-all") - flags+=("--show-labels") - local_nonpersistent_flags+=("--show-labels") - flags+=("--sort-by=") - local_nonpersistent_flags+=("--sort-by=") - flags+=("--template=") - flags_with_completion+=("--template") - flags_completion+=("_filedir") - local_nonpersistent_flags+=("--template=") - flags+=("--user=") - local_nonpersistent_flags+=("--user=") - flags+=("--validate") - local_nonpersistent_flags+=("--validate") - flags+=("--as=") - flags+=("--certificate-authority=") - flags_with_completion+=("--certificate-authority") - flags_completion+=("_filedir") - flags+=("--client-certificate=") - flags_with_completion+=("--client-certificate") - flags_completion+=("_filedir") - flags+=("--client-key=") - flags_with_completion+=("--client-key") - flags_completion+=("_filedir") - flags+=("--cluster=") - flags+=("--config=") - flags_with_completion+=("--config") - flags_completion+=("_filedir") - flags+=("--context=") - flags+=("--insecure-skip-tls-verify") - flags+=("--log-flush-frequency=") - flags+=("--loglevel=") - flags+=("--logspec=") - flags+=("--match-server-version") - flags+=("--namespace=") - flags_with_completion+=("--namespace") - flags_completion+=("__oc_get_namespaces") - two_word_flags+=("-n") - flags_with_completion+=("-n") - flags_completion+=("__oc_get_namespaces") - flags+=("--request-timeout=") - flags+=("--server=") - flags+=("--token=") - - must_have_one_flag=() - must_have_one_noun=() - noun_aliases=() -} - _oc_create_route_edge() { last_command="oc_create_route_edge" @@ -9766,8 +9434,6 @@ _oc_create() last_command="oc_create" commands=() commands+=("clusterresourcequota") - commands+=("clusterrole") - commands+=("clusterrolebinding") commands+=("configmap") commands+=("deployment") commands+=("deploymentconfig") @@ -9777,8 +9443,6 @@ _oc_create() commands+=("poddisruptionbudget") commands+=("policybinding") commands+=("quota") - commands+=("role") - commands+=("rolebinding") commands+=("route") commands+=("secret") commands+=("service") diff --git a/vendor/github.com/openshift/origin/contrib/completions/bash/openshift b/vendor/github.com/openshift/origin/contrib/completions/bash/openshift index b7f579348..818f852ff 100644 --- a/vendor/github.com/openshift/origin/contrib/completions/bash/openshift +++ b/vendor/github.com/openshift/origin/contrib/completions/bash/openshift @@ -12920,175 +12920,6 @@ _openshift_cli_create_clusterresourcequota() noun_aliases=() } -_openshift_cli_create_clusterrole() -{ - last_command="openshift_cli_create_clusterrole" - commands=() - - flags=() - two_word_flags=() - local_nonpersistent_flags=() - flags_with_completion=() - flags_completion=() - - flags+=("--allow-missing-template-keys") - local_nonpersistent_flags+=("--allow-missing-template-keys") - flags+=("--dry-run") - local_nonpersistent_flags+=("--dry-run") - flags+=("--no-headers") - local_nonpersistent_flags+=("--no-headers") - flags+=("--output=") - two_word_flags+=("-o") - local_nonpersistent_flags+=("--output=") - flags+=("--resource=") - local_nonpersistent_flags+=("--resource=") - flags+=("--resource-name=") - local_nonpersistent_flags+=("--resource-name=") - flags+=("--save-config") - local_nonpersistent_flags+=("--save-config") - flags+=("--schema-cache-dir=") - flags_with_completion+=("--schema-cache-dir") - flags_completion+=("_filedir") - local_nonpersistent_flags+=("--schema-cache-dir=") - flags+=("--show-all") - flags+=("-a") - local_nonpersistent_flags+=("--show-all") - flags+=("--show-labels") - local_nonpersistent_flags+=("--show-labels") - flags+=("--sort-by=") - local_nonpersistent_flags+=("--sort-by=") - flags+=("--template=") - flags_with_completion+=("--template") - flags_completion+=("_filedir") - local_nonpersistent_flags+=("--template=") - flags+=("--validate") - local_nonpersistent_flags+=("--validate") - flags+=("--verb=") - local_nonpersistent_flags+=("--verb=") - flags+=("--as=") - flags+=("--azure-container-registry-config=") - flags+=("--certificate-authority=") - flags_with_completion+=("--certificate-authority") - flags_completion+=("_filedir") - flags+=("--client-certificate=") - flags_with_completion+=("--client-certificate") - flags_completion+=("_filedir") - flags+=("--client-key=") - flags_with_completion+=("--client-key") - flags_completion+=("_filedir") - flags+=("--cluster=") - flags+=("--config=") - flags_with_completion+=("--config") - flags_completion+=("_filedir") - flags+=("--context=") - flags+=("--google-json-key=") - flags+=("--insecure-skip-tls-verify") - flags+=("--log-flush-frequency=") - flags+=("--loglevel=") - flags+=("--logspec=") - flags+=("--match-server-version") - flags+=("--namespace=") - flags_with_completion+=("--namespace") - flags_completion+=("__oc_get_namespaces") - two_word_flags+=("-n") - flags_with_completion+=("-n") - flags_completion+=("__oc_get_namespaces") - flags+=("--request-timeout=") - flags+=("--server=") - flags+=("--token=") - flags+=("--user=") - - must_have_one_flag=() - must_have_one_noun=() - noun_aliases=() -} - -_openshift_cli_create_clusterrolebinding() -{ - last_command="openshift_cli_create_clusterrolebinding" - commands=() - - flags=() - two_word_flags=() - local_nonpersistent_flags=() - flags_with_completion=() - flags_completion=() - - flags+=("--allow-missing-template-keys") - local_nonpersistent_flags+=("--allow-missing-template-keys") - flags+=("--clusterrole=") - local_nonpersistent_flags+=("--clusterrole=") - flags+=("--dry-run") - local_nonpersistent_flags+=("--dry-run") - flags+=("--generator=") - local_nonpersistent_flags+=("--generator=") - flags+=("--group=") - local_nonpersistent_flags+=("--group=") - flags+=("--no-headers") - local_nonpersistent_flags+=("--no-headers") - flags+=("--output=") - two_word_flags+=("-o") - local_nonpersistent_flags+=("--output=") - flags+=("--save-config") - local_nonpersistent_flags+=("--save-config") - flags+=("--schema-cache-dir=") - flags_with_completion+=("--schema-cache-dir") - flags_completion+=("_filedir") - local_nonpersistent_flags+=("--schema-cache-dir=") - flags+=("--serviceaccount=") - local_nonpersistent_flags+=("--serviceaccount=") - flags+=("--show-all") - flags+=("-a") - local_nonpersistent_flags+=("--show-all") - flags+=("--show-labels") - local_nonpersistent_flags+=("--show-labels") - flags+=("--sort-by=") - local_nonpersistent_flags+=("--sort-by=") - flags+=("--template=") - flags_with_completion+=("--template") - flags_completion+=("_filedir") - local_nonpersistent_flags+=("--template=") - flags+=("--user=") - local_nonpersistent_flags+=("--user=") - flags+=("--validate") - local_nonpersistent_flags+=("--validate") - flags+=("--as=") - flags+=("--azure-container-registry-config=") - flags+=("--certificate-authority=") - flags_with_completion+=("--certificate-authority") - flags_completion+=("_filedir") - flags+=("--client-certificate=") - flags_with_completion+=("--client-certificate") - flags_completion+=("_filedir") - flags+=("--client-key=") - flags_with_completion+=("--client-key") - flags_completion+=("_filedir") - flags+=("--cluster=") - flags+=("--config=") - flags_with_completion+=("--config") - flags_completion+=("_filedir") - flags+=("--context=") - flags+=("--google-json-key=") - flags+=("--insecure-skip-tls-verify") - flags+=("--log-flush-frequency=") - flags+=("--loglevel=") - flags+=("--logspec=") - flags+=("--match-server-version") - flags+=("--namespace=") - flags_with_completion+=("--namespace") - flags_completion+=("__oc_get_namespaces") - two_word_flags+=("-n") - flags_with_completion+=("-n") - flags_completion+=("__oc_get_namespaces") - flags+=("--request-timeout=") - flags+=("--server=") - flags+=("--token=") - - must_have_one_flag=() - must_have_one_noun=() - noun_aliases=() -} - _openshift_cli_create_configmap() { last_command="openshift_cli_create_configmap" @@ -13763,177 +13594,6 @@ _openshift_cli_create_quota() noun_aliases=() } -_openshift_cli_create_role() -{ - last_command="openshift_cli_create_role" - commands=() - - flags=() - two_word_flags=() - local_nonpersistent_flags=() - flags_with_completion=() - flags_completion=() - - flags+=("--allow-missing-template-keys") - local_nonpersistent_flags+=("--allow-missing-template-keys") - flags+=("--dry-run") - local_nonpersistent_flags+=("--dry-run") - flags+=("--no-headers") - local_nonpersistent_flags+=("--no-headers") - flags+=("--output=") - two_word_flags+=("-o") - local_nonpersistent_flags+=("--output=") - flags+=("--resource=") - local_nonpersistent_flags+=("--resource=") - flags+=("--resource-name=") - local_nonpersistent_flags+=("--resource-name=") - flags+=("--save-config") - local_nonpersistent_flags+=("--save-config") - flags+=("--schema-cache-dir=") - flags_with_completion+=("--schema-cache-dir") - flags_completion+=("_filedir") - local_nonpersistent_flags+=("--schema-cache-dir=") - flags+=("--show-all") - flags+=("-a") - local_nonpersistent_flags+=("--show-all") - flags+=("--show-labels") - local_nonpersistent_flags+=("--show-labels") - flags+=("--sort-by=") - local_nonpersistent_flags+=("--sort-by=") - flags+=("--template=") - flags_with_completion+=("--template") - flags_completion+=("_filedir") - local_nonpersistent_flags+=("--template=") - flags+=("--validate") - local_nonpersistent_flags+=("--validate") - flags+=("--verb=") - local_nonpersistent_flags+=("--verb=") - flags+=("--as=") - flags+=("--azure-container-registry-config=") - flags+=("--certificate-authority=") - flags_with_completion+=("--certificate-authority") - flags_completion+=("_filedir") - flags+=("--client-certificate=") - flags_with_completion+=("--client-certificate") - flags_completion+=("_filedir") - flags+=("--client-key=") - flags_with_completion+=("--client-key") - flags_completion+=("_filedir") - flags+=("--cluster=") - flags+=("--config=") - flags_with_completion+=("--config") - flags_completion+=("_filedir") - flags+=("--context=") - flags+=("--google-json-key=") - flags+=("--insecure-skip-tls-verify") - flags+=("--log-flush-frequency=") - flags+=("--loglevel=") - flags+=("--logspec=") - flags+=("--match-server-version") - flags+=("--namespace=") - flags_with_completion+=("--namespace") - flags_completion+=("__oc_get_namespaces") - two_word_flags+=("-n") - flags_with_completion+=("-n") - flags_completion+=("__oc_get_namespaces") - flags+=("--request-timeout=") - flags+=("--server=") - flags+=("--token=") - flags+=("--user=") - - must_have_one_flag=() - must_have_one_noun=() - noun_aliases=() -} - -_openshift_cli_create_rolebinding() -{ - last_command="openshift_cli_create_rolebinding" - commands=() - - flags=() - two_word_flags=() - local_nonpersistent_flags=() - flags_with_completion=() - flags_completion=() - - flags+=("--allow-missing-template-keys") - local_nonpersistent_flags+=("--allow-missing-template-keys") - flags+=("--clusterrole=") - local_nonpersistent_flags+=("--clusterrole=") - flags+=("--dry-run") - local_nonpersistent_flags+=("--dry-run") - flags+=("--generator=") - local_nonpersistent_flags+=("--generator=") - flags+=("--group=") - local_nonpersistent_flags+=("--group=") - flags+=("--no-headers") - local_nonpersistent_flags+=("--no-headers") - flags+=("--output=") - two_word_flags+=("-o") - local_nonpersistent_flags+=("--output=") - flags+=("--role=") - local_nonpersistent_flags+=("--role=") - flags+=("--save-config") - local_nonpersistent_flags+=("--save-config") - flags+=("--schema-cache-dir=") - flags_with_completion+=("--schema-cache-dir") - flags_completion+=("_filedir") - local_nonpersistent_flags+=("--schema-cache-dir=") - flags+=("--serviceaccount=") - local_nonpersistent_flags+=("--serviceaccount=") - flags+=("--show-all") - flags+=("-a") - local_nonpersistent_flags+=("--show-all") - flags+=("--show-labels") - local_nonpersistent_flags+=("--show-labels") - flags+=("--sort-by=") - local_nonpersistent_flags+=("--sort-by=") - flags+=("--template=") - flags_with_completion+=("--template") - flags_completion+=("_filedir") - local_nonpersistent_flags+=("--template=") - flags+=("--user=") - local_nonpersistent_flags+=("--user=") - flags+=("--validate") - local_nonpersistent_flags+=("--validate") - flags+=("--as=") - flags+=("--azure-container-registry-config=") - flags+=("--certificate-authority=") - flags_with_completion+=("--certificate-authority") - flags_completion+=("_filedir") - flags+=("--client-certificate=") - flags_with_completion+=("--client-certificate") - flags_completion+=("_filedir") - flags+=("--client-key=") - flags_with_completion+=("--client-key") - flags_completion+=("_filedir") - flags+=("--cluster=") - flags+=("--config=") - flags_with_completion+=("--config") - flags_completion+=("_filedir") - flags+=("--context=") - flags+=("--google-json-key=") - flags+=("--insecure-skip-tls-verify") - flags+=("--log-flush-frequency=") - flags+=("--loglevel=") - flags+=("--logspec=") - flags+=("--match-server-version") - flags+=("--namespace=") - flags_with_completion+=("--namespace") - flags_completion+=("__oc_get_namespaces") - two_word_flags+=("-n") - flags_with_completion+=("-n") - flags_completion+=("__oc_get_namespaces") - flags+=("--request-timeout=") - flags+=("--server=") - flags+=("--token=") - - must_have_one_flag=() - must_have_one_noun=() - noun_aliases=() -} - _openshift_cli_create_route_edge() { last_command="openshift_cli_create_route_edge" @@ -15194,8 +14854,6 @@ _openshift_cli_create() last_command="openshift_cli_create" commands=() commands+=("clusterresourcequota") - commands+=("clusterrole") - commands+=("clusterrolebinding") commands+=("configmap") commands+=("deployment") commands+=("deploymentconfig") @@ -15205,8 +14863,6 @@ _openshift_cli_create() commands+=("poddisruptionbudget") commands+=("policybinding") commands+=("quota") - commands+=("role") - commands+=("rolebinding") commands+=("route") commands+=("secret") commands+=("service") diff --git a/vendor/github.com/openshift/origin/contrib/completions/zsh/oc b/vendor/github.com/openshift/origin/contrib/completions/zsh/oc index f61190c48..3180a2d46 100644 --- a/vendor/github.com/openshift/origin/contrib/completions/zsh/oc +++ b/vendor/github.com/openshift/origin/contrib/completions/zsh/oc @@ -7699,171 +7699,6 @@ _oc_create_clusterresourcequota() noun_aliases=() } -_oc_create_clusterrole() -{ - last_command="oc_create_clusterrole" - commands=() - - flags=() - two_word_flags=() - local_nonpersistent_flags=() - flags_with_completion=() - flags_completion=() - - flags+=("--allow-missing-template-keys") - local_nonpersistent_flags+=("--allow-missing-template-keys") - flags+=("--dry-run") - local_nonpersistent_flags+=("--dry-run") - flags+=("--no-headers") - local_nonpersistent_flags+=("--no-headers") - flags+=("--output=") - two_word_flags+=("-o") - local_nonpersistent_flags+=("--output=") - flags+=("--resource=") - local_nonpersistent_flags+=("--resource=") - flags+=("--resource-name=") - local_nonpersistent_flags+=("--resource-name=") - flags+=("--save-config") - local_nonpersistent_flags+=("--save-config") - flags+=("--schema-cache-dir=") - flags_with_completion+=("--schema-cache-dir") - flags_completion+=("_filedir") - local_nonpersistent_flags+=("--schema-cache-dir=") - flags+=("--show-all") - flags+=("-a") - local_nonpersistent_flags+=("--show-all") - flags+=("--show-labels") - local_nonpersistent_flags+=("--show-labels") - flags+=("--sort-by=") - local_nonpersistent_flags+=("--sort-by=") - flags+=("--template=") - flags_with_completion+=("--template") - flags_completion+=("_filedir") - local_nonpersistent_flags+=("--template=") - flags+=("--validate") - local_nonpersistent_flags+=("--validate") - flags+=("--verb=") - local_nonpersistent_flags+=("--verb=") - flags+=("--as=") - flags+=("--certificate-authority=") - flags_with_completion+=("--certificate-authority") - flags_completion+=("_filedir") - flags+=("--client-certificate=") - flags_with_completion+=("--client-certificate") - flags_completion+=("_filedir") - flags+=("--client-key=") - flags_with_completion+=("--client-key") - flags_completion+=("_filedir") - flags+=("--cluster=") - flags+=("--config=") - flags_with_completion+=("--config") - flags_completion+=("_filedir") - flags+=("--context=") - flags+=("--insecure-skip-tls-verify") - flags+=("--log-flush-frequency=") - flags+=("--loglevel=") - flags+=("--logspec=") - flags+=("--match-server-version") - flags+=("--namespace=") - flags_with_completion+=("--namespace") - flags_completion+=("__oc_get_namespaces") - two_word_flags+=("-n") - flags_with_completion+=("-n") - flags_completion+=("__oc_get_namespaces") - flags+=("--request-timeout=") - flags+=("--server=") - flags+=("--token=") - flags+=("--user=") - - must_have_one_flag=() - must_have_one_noun=() - noun_aliases=() -} - -_oc_create_clusterrolebinding() -{ - last_command="oc_create_clusterrolebinding" - commands=() - - flags=() - two_word_flags=() - local_nonpersistent_flags=() - flags_with_completion=() - flags_completion=() - - flags+=("--allow-missing-template-keys") - local_nonpersistent_flags+=("--allow-missing-template-keys") - flags+=("--clusterrole=") - local_nonpersistent_flags+=("--clusterrole=") - flags+=("--dry-run") - local_nonpersistent_flags+=("--dry-run") - flags+=("--generator=") - local_nonpersistent_flags+=("--generator=") - flags+=("--group=") - local_nonpersistent_flags+=("--group=") - flags+=("--no-headers") - local_nonpersistent_flags+=("--no-headers") - flags+=("--output=") - two_word_flags+=("-o") - local_nonpersistent_flags+=("--output=") - flags+=("--save-config") - local_nonpersistent_flags+=("--save-config") - flags+=("--schema-cache-dir=") - flags_with_completion+=("--schema-cache-dir") - flags_completion+=("_filedir") - local_nonpersistent_flags+=("--schema-cache-dir=") - flags+=("--serviceaccount=") - local_nonpersistent_flags+=("--serviceaccount=") - flags+=("--show-all") - flags+=("-a") - local_nonpersistent_flags+=("--show-all") - flags+=("--show-labels") - local_nonpersistent_flags+=("--show-labels") - flags+=("--sort-by=") - local_nonpersistent_flags+=("--sort-by=") - flags+=("--template=") - flags_with_completion+=("--template") - flags_completion+=("_filedir") - local_nonpersistent_flags+=("--template=") - flags+=("--user=") - local_nonpersistent_flags+=("--user=") - flags+=("--validate") - local_nonpersistent_flags+=("--validate") - flags+=("--as=") - flags+=("--certificate-authority=") - flags_with_completion+=("--certificate-authority") - flags_completion+=("_filedir") - flags+=("--client-certificate=") - flags_with_completion+=("--client-certificate") - flags_completion+=("_filedir") - flags+=("--client-key=") - flags_with_completion+=("--client-key") - flags_completion+=("_filedir") - flags+=("--cluster=") - flags+=("--config=") - flags_with_completion+=("--config") - flags_completion+=("_filedir") - flags+=("--context=") - flags+=("--insecure-skip-tls-verify") - flags+=("--log-flush-frequency=") - flags+=("--loglevel=") - flags+=("--logspec=") - flags+=("--match-server-version") - flags+=("--namespace=") - flags_with_completion+=("--namespace") - flags_completion+=("__oc_get_namespaces") - two_word_flags+=("-n") - flags_with_completion+=("-n") - flags_completion+=("__oc_get_namespaces") - flags+=("--request-timeout=") - flags+=("--server=") - flags+=("--token=") - - must_have_one_flag=() - must_have_one_noun=() - noun_aliases=() -} - _oc_create_configmap() { last_command="oc_create_configmap" @@ -8520,173 +8355,6 @@ _oc_create_quota() noun_aliases=() } -_oc_create_role() -{ - last_command="oc_create_role" - commands=() - - flags=() - two_word_flags=() - local_nonpersistent_flags=() - flags_with_completion=() - flags_completion=() - - flags+=("--allow-missing-template-keys") - local_nonpersistent_flags+=("--allow-missing-template-keys") - flags+=("--dry-run") - local_nonpersistent_flags+=("--dry-run") - flags+=("--no-headers") - local_nonpersistent_flags+=("--no-headers") - flags+=("--output=") - two_word_flags+=("-o") - local_nonpersistent_flags+=("--output=") - flags+=("--resource=") - local_nonpersistent_flags+=("--resource=") - flags+=("--resource-name=") - local_nonpersistent_flags+=("--resource-name=") - flags+=("--save-config") - local_nonpersistent_flags+=("--save-config") - flags+=("--schema-cache-dir=") - flags_with_completion+=("--schema-cache-dir") - flags_completion+=("_filedir") - local_nonpersistent_flags+=("--schema-cache-dir=") - flags+=("--show-all") - flags+=("-a") - local_nonpersistent_flags+=("--show-all") - flags+=("--show-labels") - local_nonpersistent_flags+=("--show-labels") - flags+=("--sort-by=") - local_nonpersistent_flags+=("--sort-by=") - flags+=("--template=") - flags_with_completion+=("--template") - flags_completion+=("_filedir") - local_nonpersistent_flags+=("--template=") - flags+=("--validate") - local_nonpersistent_flags+=("--validate") - flags+=("--verb=") - local_nonpersistent_flags+=("--verb=") - flags+=("--as=") - flags+=("--certificate-authority=") - flags_with_completion+=("--certificate-authority") - flags_completion+=("_filedir") - flags+=("--client-certificate=") - flags_with_completion+=("--client-certificate") - flags_completion+=("_filedir") - flags+=("--client-key=") - flags_with_completion+=("--client-key") - flags_completion+=("_filedir") - flags+=("--cluster=") - flags+=("--config=") - flags_with_completion+=("--config") - flags_completion+=("_filedir") - flags+=("--context=") - flags+=("--insecure-skip-tls-verify") - flags+=("--log-flush-frequency=") - flags+=("--loglevel=") - flags+=("--logspec=") - flags+=("--match-server-version") - flags+=("--namespace=") - flags_with_completion+=("--namespace") - flags_completion+=("__oc_get_namespaces") - two_word_flags+=("-n") - flags_with_completion+=("-n") - flags_completion+=("__oc_get_namespaces") - flags+=("--request-timeout=") - flags+=("--server=") - flags+=("--token=") - flags+=("--user=") - - must_have_one_flag=() - must_have_one_noun=() - noun_aliases=() -} - -_oc_create_rolebinding() -{ - last_command="oc_create_rolebinding" - commands=() - - flags=() - two_word_flags=() - local_nonpersistent_flags=() - flags_with_completion=() - flags_completion=() - - flags+=("--allow-missing-template-keys") - local_nonpersistent_flags+=("--allow-missing-template-keys") - flags+=("--clusterrole=") - local_nonpersistent_flags+=("--clusterrole=") - flags+=("--dry-run") - local_nonpersistent_flags+=("--dry-run") - flags+=("--generator=") - local_nonpersistent_flags+=("--generator=") - flags+=("--group=") - local_nonpersistent_flags+=("--group=") - flags+=("--no-headers") - local_nonpersistent_flags+=("--no-headers") - flags+=("--output=") - two_word_flags+=("-o") - local_nonpersistent_flags+=("--output=") - flags+=("--role=") - local_nonpersistent_flags+=("--role=") - flags+=("--save-config") - local_nonpersistent_flags+=("--save-config") - flags+=("--schema-cache-dir=") - flags_with_completion+=("--schema-cache-dir") - flags_completion+=("_filedir") - local_nonpersistent_flags+=("--schema-cache-dir=") - flags+=("--serviceaccount=") - local_nonpersistent_flags+=("--serviceaccount=") - flags+=("--show-all") - flags+=("-a") - local_nonpersistent_flags+=("--show-all") - flags+=("--show-labels") - local_nonpersistent_flags+=("--show-labels") - flags+=("--sort-by=") - local_nonpersistent_flags+=("--sort-by=") - flags+=("--template=") - flags_with_completion+=("--template") - flags_completion+=("_filedir") - local_nonpersistent_flags+=("--template=") - flags+=("--user=") - local_nonpersistent_flags+=("--user=") - flags+=("--validate") - local_nonpersistent_flags+=("--validate") - flags+=("--as=") - flags+=("--certificate-authority=") - flags_with_completion+=("--certificate-authority") - flags_completion+=("_filedir") - flags+=("--client-certificate=") - flags_with_completion+=("--client-certificate") - flags_completion+=("_filedir") - flags+=("--client-key=") - flags_with_completion+=("--client-key") - flags_completion+=("_filedir") - flags+=("--cluster=") - flags+=("--config=") - flags_with_completion+=("--config") - flags_completion+=("_filedir") - flags+=("--context=") - flags+=("--insecure-skip-tls-verify") - flags+=("--log-flush-frequency=") - flags+=("--loglevel=") - flags+=("--logspec=") - flags+=("--match-server-version") - flags+=("--namespace=") - flags_with_completion+=("--namespace") - flags_completion+=("__oc_get_namespaces") - two_word_flags+=("-n") - flags_with_completion+=("-n") - flags_completion+=("__oc_get_namespaces") - flags+=("--request-timeout=") - flags+=("--server=") - flags+=("--token=") - - must_have_one_flag=() - must_have_one_noun=() - noun_aliases=() -} - _oc_create_route_edge() { last_command="oc_create_route_edge" @@ -9915,8 +9583,6 @@ _oc_create() last_command="oc_create" commands=() commands+=("clusterresourcequota") - commands+=("clusterrole") - commands+=("clusterrolebinding") commands+=("configmap") commands+=("deployment") commands+=("deploymentconfig") @@ -9926,8 +9592,6 @@ _oc_create() commands+=("poddisruptionbudget") commands+=("policybinding") commands+=("quota") - commands+=("role") - commands+=("rolebinding") commands+=("route") commands+=("secret") commands+=("service") diff --git a/vendor/github.com/openshift/origin/contrib/completions/zsh/openshift b/vendor/github.com/openshift/origin/contrib/completions/zsh/openshift index 117de05e0..e55366771 100644 --- a/vendor/github.com/openshift/origin/contrib/completions/zsh/openshift +++ b/vendor/github.com/openshift/origin/contrib/completions/zsh/openshift @@ -13069,175 +13069,6 @@ _openshift_cli_create_clusterresourcequota() noun_aliases=() } -_openshift_cli_create_clusterrole() -{ - last_command="openshift_cli_create_clusterrole" - commands=() - - flags=() - two_word_flags=() - local_nonpersistent_flags=() - flags_with_completion=() - flags_completion=() - - flags+=("--allow-missing-template-keys") - local_nonpersistent_flags+=("--allow-missing-template-keys") - flags+=("--dry-run") - local_nonpersistent_flags+=("--dry-run") - flags+=("--no-headers") - local_nonpersistent_flags+=("--no-headers") - flags+=("--output=") - two_word_flags+=("-o") - local_nonpersistent_flags+=("--output=") - flags+=("--resource=") - local_nonpersistent_flags+=("--resource=") - flags+=("--resource-name=") - local_nonpersistent_flags+=("--resource-name=") - flags+=("--save-config") - local_nonpersistent_flags+=("--save-config") - flags+=("--schema-cache-dir=") - flags_with_completion+=("--schema-cache-dir") - flags_completion+=("_filedir") - local_nonpersistent_flags+=("--schema-cache-dir=") - flags+=("--show-all") - flags+=("-a") - local_nonpersistent_flags+=("--show-all") - flags+=("--show-labels") - local_nonpersistent_flags+=("--show-labels") - flags+=("--sort-by=") - local_nonpersistent_flags+=("--sort-by=") - flags+=("--template=") - flags_with_completion+=("--template") - flags_completion+=("_filedir") - local_nonpersistent_flags+=("--template=") - flags+=("--validate") - local_nonpersistent_flags+=("--validate") - flags+=("--verb=") - local_nonpersistent_flags+=("--verb=") - flags+=("--as=") - flags+=("--azure-container-registry-config=") - flags+=("--certificate-authority=") - flags_with_completion+=("--certificate-authority") - flags_completion+=("_filedir") - flags+=("--client-certificate=") - flags_with_completion+=("--client-certificate") - flags_completion+=("_filedir") - flags+=("--client-key=") - flags_with_completion+=("--client-key") - flags_completion+=("_filedir") - flags+=("--cluster=") - flags+=("--config=") - flags_with_completion+=("--config") - flags_completion+=("_filedir") - flags+=("--context=") - flags+=("--google-json-key=") - flags+=("--insecure-skip-tls-verify") - flags+=("--log-flush-frequency=") - flags+=("--loglevel=") - flags+=("--logspec=") - flags+=("--match-server-version") - flags+=("--namespace=") - flags_with_completion+=("--namespace") - flags_completion+=("__oc_get_namespaces") - two_word_flags+=("-n") - flags_with_completion+=("-n") - flags_completion+=("__oc_get_namespaces") - flags+=("--request-timeout=") - flags+=("--server=") - flags+=("--token=") - flags+=("--user=") - - must_have_one_flag=() - must_have_one_noun=() - noun_aliases=() -} - -_openshift_cli_create_clusterrolebinding() -{ - last_command="openshift_cli_create_clusterrolebinding" - commands=() - - flags=() - two_word_flags=() - local_nonpersistent_flags=() - flags_with_completion=() - flags_completion=() - - flags+=("--allow-missing-template-keys") - local_nonpersistent_flags+=("--allow-missing-template-keys") - flags+=("--clusterrole=") - local_nonpersistent_flags+=("--clusterrole=") - flags+=("--dry-run") - local_nonpersistent_flags+=("--dry-run") - flags+=("--generator=") - local_nonpersistent_flags+=("--generator=") - flags+=("--group=") - local_nonpersistent_flags+=("--group=") - flags+=("--no-headers") - local_nonpersistent_flags+=("--no-headers") - flags+=("--output=") - two_word_flags+=("-o") - local_nonpersistent_flags+=("--output=") - flags+=("--save-config") - local_nonpersistent_flags+=("--save-config") - flags+=("--schema-cache-dir=") - flags_with_completion+=("--schema-cache-dir") - flags_completion+=("_filedir") - local_nonpersistent_flags+=("--schema-cache-dir=") - flags+=("--serviceaccount=") - local_nonpersistent_flags+=("--serviceaccount=") - flags+=("--show-all") - flags+=("-a") - local_nonpersistent_flags+=("--show-all") - flags+=("--show-labels") - local_nonpersistent_flags+=("--show-labels") - flags+=("--sort-by=") - local_nonpersistent_flags+=("--sort-by=") - flags+=("--template=") - flags_with_completion+=("--template") - flags_completion+=("_filedir") - local_nonpersistent_flags+=("--template=") - flags+=("--user=") - local_nonpersistent_flags+=("--user=") - flags+=("--validate") - local_nonpersistent_flags+=("--validate") - flags+=("--as=") - flags+=("--azure-container-registry-config=") - flags+=("--certificate-authority=") - flags_with_completion+=("--certificate-authority") - flags_completion+=("_filedir") - flags+=("--client-certificate=") - flags_with_completion+=("--client-certificate") - flags_completion+=("_filedir") - flags+=("--client-key=") - flags_with_completion+=("--client-key") - flags_completion+=("_filedir") - flags+=("--cluster=") - flags+=("--config=") - flags_with_completion+=("--config") - flags_completion+=("_filedir") - flags+=("--context=") - flags+=("--google-json-key=") - flags+=("--insecure-skip-tls-verify") - flags+=("--log-flush-frequency=") - flags+=("--loglevel=") - flags+=("--logspec=") - flags+=("--match-server-version") - flags+=("--namespace=") - flags_with_completion+=("--namespace") - flags_completion+=("__oc_get_namespaces") - two_word_flags+=("-n") - flags_with_completion+=("-n") - flags_completion+=("__oc_get_namespaces") - flags+=("--request-timeout=") - flags+=("--server=") - flags+=("--token=") - - must_have_one_flag=() - must_have_one_noun=() - noun_aliases=() -} - _openshift_cli_create_configmap() { last_command="openshift_cli_create_configmap" @@ -13912,177 +13743,6 @@ _openshift_cli_create_quota() noun_aliases=() } -_openshift_cli_create_role() -{ - last_command="openshift_cli_create_role" - commands=() - - flags=() - two_word_flags=() - local_nonpersistent_flags=() - flags_with_completion=() - flags_completion=() - - flags+=("--allow-missing-template-keys") - local_nonpersistent_flags+=("--allow-missing-template-keys") - flags+=("--dry-run") - local_nonpersistent_flags+=("--dry-run") - flags+=("--no-headers") - local_nonpersistent_flags+=("--no-headers") - flags+=("--output=") - two_word_flags+=("-o") - local_nonpersistent_flags+=("--output=") - flags+=("--resource=") - local_nonpersistent_flags+=("--resource=") - flags+=("--resource-name=") - local_nonpersistent_flags+=("--resource-name=") - flags+=("--save-config") - local_nonpersistent_flags+=("--save-config") - flags+=("--schema-cache-dir=") - flags_with_completion+=("--schema-cache-dir") - flags_completion+=("_filedir") - local_nonpersistent_flags+=("--schema-cache-dir=") - flags+=("--show-all") - flags+=("-a") - local_nonpersistent_flags+=("--show-all") - flags+=("--show-labels") - local_nonpersistent_flags+=("--show-labels") - flags+=("--sort-by=") - local_nonpersistent_flags+=("--sort-by=") - flags+=("--template=") - flags_with_completion+=("--template") - flags_completion+=("_filedir") - local_nonpersistent_flags+=("--template=") - flags+=("--validate") - local_nonpersistent_flags+=("--validate") - flags+=("--verb=") - local_nonpersistent_flags+=("--verb=") - flags+=("--as=") - flags+=("--azure-container-registry-config=") - flags+=("--certificate-authority=") - flags_with_completion+=("--certificate-authority") - flags_completion+=("_filedir") - flags+=("--client-certificate=") - flags_with_completion+=("--client-certificate") - flags_completion+=("_filedir") - flags+=("--client-key=") - flags_with_completion+=("--client-key") - flags_completion+=("_filedir") - flags+=("--cluster=") - flags+=("--config=") - flags_with_completion+=("--config") - flags_completion+=("_filedir") - flags+=("--context=") - flags+=("--google-json-key=") - flags+=("--insecure-skip-tls-verify") - flags+=("--log-flush-frequency=") - flags+=("--loglevel=") - flags+=("--logspec=") - flags+=("--match-server-version") - flags+=("--namespace=") - flags_with_completion+=("--namespace") - flags_completion+=("__oc_get_namespaces") - two_word_flags+=("-n") - flags_with_completion+=("-n") - flags_completion+=("__oc_get_namespaces") - flags+=("--request-timeout=") - flags+=("--server=") - flags+=("--token=") - flags+=("--user=") - - must_have_one_flag=() - must_have_one_noun=() - noun_aliases=() -} - -_openshift_cli_create_rolebinding() -{ - last_command="openshift_cli_create_rolebinding" - commands=() - - flags=() - two_word_flags=() - local_nonpersistent_flags=() - flags_with_completion=() - flags_completion=() - - flags+=("--allow-missing-template-keys") - local_nonpersistent_flags+=("--allow-missing-template-keys") - flags+=("--clusterrole=") - local_nonpersistent_flags+=("--clusterrole=") - flags+=("--dry-run") - local_nonpersistent_flags+=("--dry-run") - flags+=("--generator=") - local_nonpersistent_flags+=("--generator=") - flags+=("--group=") - local_nonpersistent_flags+=("--group=") - flags+=("--no-headers") - local_nonpersistent_flags+=("--no-headers") - flags+=("--output=") - two_word_flags+=("-o") - local_nonpersistent_flags+=("--output=") - flags+=("--role=") - local_nonpersistent_flags+=("--role=") - flags+=("--save-config") - local_nonpersistent_flags+=("--save-config") - flags+=("--schema-cache-dir=") - flags_with_completion+=("--schema-cache-dir") - flags_completion+=("_filedir") - local_nonpersistent_flags+=("--schema-cache-dir=") - flags+=("--serviceaccount=") - local_nonpersistent_flags+=("--serviceaccount=") - flags+=("--show-all") - flags+=("-a") - local_nonpersistent_flags+=("--show-all") - flags+=("--show-labels") - local_nonpersistent_flags+=("--show-labels") - flags+=("--sort-by=") - local_nonpersistent_flags+=("--sort-by=") - flags+=("--template=") - flags_with_completion+=("--template") - flags_completion+=("_filedir") - local_nonpersistent_flags+=("--template=") - flags+=("--user=") - local_nonpersistent_flags+=("--user=") - flags+=("--validate") - local_nonpersistent_flags+=("--validate") - flags+=("--as=") - flags+=("--azure-container-registry-config=") - flags+=("--certificate-authority=") - flags_with_completion+=("--certificate-authority") - flags_completion+=("_filedir") - flags+=("--client-certificate=") - flags_with_completion+=("--client-certificate") - flags_completion+=("_filedir") - flags+=("--client-key=") - flags_with_completion+=("--client-key") - flags_completion+=("_filedir") - flags+=("--cluster=") - flags+=("--config=") - flags_with_completion+=("--config") - flags_completion+=("_filedir") - flags+=("--context=") - flags+=("--google-json-key=") - flags+=("--insecure-skip-tls-verify") - flags+=("--log-flush-frequency=") - flags+=("--loglevel=") - flags+=("--logspec=") - flags+=("--match-server-version") - flags+=("--namespace=") - flags_with_completion+=("--namespace") - flags_completion+=("__oc_get_namespaces") - two_word_flags+=("-n") - flags_with_completion+=("-n") - flags_completion+=("__oc_get_namespaces") - flags+=("--request-timeout=") - flags+=("--server=") - flags+=("--token=") - - must_have_one_flag=() - must_have_one_noun=() - noun_aliases=() -} - _openshift_cli_create_route_edge() { last_command="openshift_cli_create_route_edge" @@ -15343,8 +15003,6 @@ _openshift_cli_create() last_command="openshift_cli_create" commands=() commands+=("clusterresourcequota") - commands+=("clusterrole") - commands+=("clusterrolebinding") commands+=("configmap") commands+=("deployment") commands+=("deploymentconfig") @@ -15354,8 +15012,6 @@ _openshift_cli_create() commands+=("poddisruptionbudget") commands+=("policybinding") commands+=("quota") - commands+=("role") - commands+=("rolebinding") commands+=("route") commands+=("secret") commands+=("service") diff --git a/vendor/github.com/openshift/origin/docs/man/man1/.files_generated_oc b/vendor/github.com/openshift/origin/docs/man/man1/.files_generated_oc index 1dad641c8..80387c8a6 100644 --- a/vendor/github.com/openshift/origin/docs/man/man1/.files_generated_oc +++ b/vendor/github.com/openshift/origin/docs/man/man1/.files_generated_oc @@ -128,8 +128,6 @@ oc-config.1 oc-convert.1 oc-cp.1 oc-create-clusterresourcequota.1 -oc-create-clusterrole.1 -oc-create-clusterrolebinding.1 oc-create-configmap.1 oc-create-deployment.1 oc-create-deploymentconfig.1 @@ -139,8 +137,6 @@ oc-create-namespace.1 oc-create-poddisruptionbudget.1 oc-create-policybinding.1 oc-create-quota.1 -oc-create-role.1 -oc-create-rolebinding.1 oc-create-route-edge.1 oc-create-route-passthrough.1 oc-create-route-reencrypt.1 diff --git a/vendor/github.com/openshift/origin/docs/man/man1/.files_generated_openshift b/vendor/github.com/openshift/origin/docs/man/man1/.files_generated_openshift index 735608ea3..2be1083b9 100644 --- a/vendor/github.com/openshift/origin/docs/man/man1/.files_generated_openshift +++ b/vendor/github.com/openshift/origin/docs/man/man1/.files_generated_openshift @@ -226,8 +226,6 @@ openshift-cli-config.1 openshift-cli-convert.1 openshift-cli-cp.1 openshift-cli-create-clusterresourcequota.1 -openshift-cli-create-clusterrole.1 -openshift-cli-create-clusterrolebinding.1 openshift-cli-create-configmap.1 openshift-cli-create-deployment.1 openshift-cli-create-deploymentconfig.1 @@ -237,8 +235,6 @@ openshift-cli-create-namespace.1 openshift-cli-create-poddisruptionbudget.1 openshift-cli-create-policybinding.1 openshift-cli-create-quota.1 -openshift-cli-create-role.1 -openshift-cli-create-rolebinding.1 openshift-cli-create-route-edge.1 openshift-cli-create-route-passthrough.1 openshift-cli-create-route-reencrypt.1 diff --git a/vendor/github.com/openshift/origin/docs/man/man1/oc-create-clusterrole.1 b/vendor/github.com/openshift/origin/docs/man/man1/oc-create-clusterrole.1 deleted file mode 100644 index b6fd7a0f9..000000000 --- a/vendor/github.com/openshift/origin/docs/man/man1/oc-create-clusterrole.1 +++ /dev/null @@ -1,3 +0,0 @@ -This file is autogenerated, but we've stopped checking such files into the -repository to reduce the need for rebases. Please run hack/generate-docs.sh to -populate this file. diff --git a/vendor/github.com/openshift/origin/docs/man/man1/oc-create-clusterrolebinding.1 b/vendor/github.com/openshift/origin/docs/man/man1/oc-create-clusterrolebinding.1 deleted file mode 100644 index b6fd7a0f9..000000000 --- a/vendor/github.com/openshift/origin/docs/man/man1/oc-create-clusterrolebinding.1 +++ /dev/null @@ -1,3 +0,0 @@ -This file is autogenerated, but we've stopped checking such files into the -repository to reduce the need for rebases. Please run hack/generate-docs.sh to -populate this file. diff --git a/vendor/github.com/openshift/origin/docs/man/man1/oc-create-role.1 b/vendor/github.com/openshift/origin/docs/man/man1/oc-create-role.1 deleted file mode 100644 index b6fd7a0f9..000000000 --- a/vendor/github.com/openshift/origin/docs/man/man1/oc-create-role.1 +++ /dev/null @@ -1,3 +0,0 @@ -This file is autogenerated, but we've stopped checking such files into the -repository to reduce the need for rebases. Please run hack/generate-docs.sh to -populate this file. diff --git a/vendor/github.com/openshift/origin/docs/man/man1/oc-create-rolebinding.1 b/vendor/github.com/openshift/origin/docs/man/man1/oc-create-rolebinding.1 deleted file mode 100644 index b6fd7a0f9..000000000 --- a/vendor/github.com/openshift/origin/docs/man/man1/oc-create-rolebinding.1 +++ /dev/null @@ -1,3 +0,0 @@ -This file is autogenerated, but we've stopped checking such files into the -repository to reduce the need for rebases. Please run hack/generate-docs.sh to -populate this file. diff --git a/vendor/github.com/openshift/origin/docs/man/man1/openshift-cli-create-clusterrole.1 b/vendor/github.com/openshift/origin/docs/man/man1/openshift-cli-create-clusterrole.1 deleted file mode 100644 index b6fd7a0f9..000000000 --- a/vendor/github.com/openshift/origin/docs/man/man1/openshift-cli-create-clusterrole.1 +++ /dev/null @@ -1,3 +0,0 @@ -This file is autogenerated, but we've stopped checking such files into the -repository to reduce the need for rebases. Please run hack/generate-docs.sh to -populate this file. diff --git a/vendor/github.com/openshift/origin/docs/man/man1/openshift-cli-create-clusterrolebinding.1 b/vendor/github.com/openshift/origin/docs/man/man1/openshift-cli-create-clusterrolebinding.1 deleted file mode 100644 index b6fd7a0f9..000000000 --- a/vendor/github.com/openshift/origin/docs/man/man1/openshift-cli-create-clusterrolebinding.1 +++ /dev/null @@ -1,3 +0,0 @@ -This file is autogenerated, but we've stopped checking such files into the -repository to reduce the need for rebases. Please run hack/generate-docs.sh to -populate this file. diff --git a/vendor/github.com/openshift/origin/docs/man/man1/openshift-cli-create-role.1 b/vendor/github.com/openshift/origin/docs/man/man1/openshift-cli-create-role.1 deleted file mode 100644 index b6fd7a0f9..000000000 --- a/vendor/github.com/openshift/origin/docs/man/man1/openshift-cli-create-role.1 +++ /dev/null @@ -1,3 +0,0 @@ -This file is autogenerated, but we've stopped checking such files into the -repository to reduce the need for rebases. Please run hack/generate-docs.sh to -populate this file. diff --git a/vendor/github.com/openshift/origin/docs/man/man1/openshift-cli-create-rolebinding.1 b/vendor/github.com/openshift/origin/docs/man/man1/openshift-cli-create-rolebinding.1 deleted file mode 100644 index b6fd7a0f9..000000000 --- a/vendor/github.com/openshift/origin/docs/man/man1/openshift-cli-create-rolebinding.1 +++ /dev/null @@ -1,3 +0,0 @@ -This file is autogenerated, but we've stopped checking such files into the -repository to reduce the need for rebases. Please run hack/generate-docs.sh to -populate this file. diff --git a/vendor/github.com/openshift/origin/images/node/Dockerfile b/vendor/github.com/openshift/origin/images/node/Dockerfile index 41852e353..434d1c6e6 100644 --- a/vendor/github.com/openshift/origin/images/node/Dockerfile +++ b/vendor/github.com/openshift/origin/images/node/Dockerfile @@ -14,11 +14,11 @@ COPY scripts/* /usr/local/bin/ COPY system-container/system-container-wrapper.sh /usr/local/bin/ COPY system-container/manifest.json system-container/config.json.template system-container/service.template system-container/tmpfiles.template /exports/ -RUN INSTALL_PKGS="libmnl libnetfilter_conntrack conntrack-tools openvswitch \ +RUN INSTALL_PKGS="origin-sdn-ovs libmnl libnetfilter_conntrack conntrack-tools openvswitch \ libnfnetlink iptables iproute bridge-utils procps-ng ethtool socat openssl \ binutils xz kmod-libs kmod sysvinit-tools device-mapper-libs dbus \ iscsi-initiator-utils bind-utils" && \ - yum install -y $INSTALL_PKGS && \ + yum --enablerepo=origin-local-release install -y $INSTALL_PKGS && \ rpm -V $INSTALL_PKGS && \ yum clean all && \ mkdir -p /usr/lib/systemd/system/origin-node.service.d /usr/lib/systemd/system/docker.service.d diff --git a/vendor/github.com/openshift/origin/images/node/Dockerfile.centos7 b/vendor/github.com/openshift/origin/images/node/Dockerfile.centos7 index 41852e353..434d1c6e6 100644 --- a/vendor/github.com/openshift/origin/images/node/Dockerfile.centos7 +++ b/vendor/github.com/openshift/origin/images/node/Dockerfile.centos7 @@ -14,11 +14,11 @@ COPY scripts/* /usr/local/bin/ COPY system-container/system-container-wrapper.sh /usr/local/bin/ COPY system-container/manifest.json system-container/config.json.template system-container/service.template system-container/tmpfiles.template /exports/ -RUN INSTALL_PKGS="libmnl libnetfilter_conntrack conntrack-tools openvswitch \ +RUN INSTALL_PKGS="origin-sdn-ovs libmnl libnetfilter_conntrack conntrack-tools openvswitch \ libnfnetlink iptables iproute bridge-utils procps-ng ethtool socat openssl \ binutils xz kmod-libs kmod sysvinit-tools device-mapper-libs dbus \ iscsi-initiator-utils bind-utils" && \ - yum install -y $INSTALL_PKGS && \ + yum --enablerepo=origin-local-release install -y $INSTALL_PKGS && \ rpm -V $INSTALL_PKGS && \ yum clean all && \ mkdir -p /usr/lib/systemd/system/origin-node.service.d /usr/lib/systemd/system/docker.service.d diff --git a/vendor/github.com/openshift/origin/pkg/authorization/apis/authorization/install/apigroup.go b/vendor/github.com/openshift/origin/pkg/authorization/apis/authorization/install/apigroup.go index 70c59c422..603245bde 100644 --- a/vendor/github.com/openshift/origin/pkg/authorization/apis/authorization/install/apigroup.go +++ b/vendor/github.com/openshift/origin/pkg/authorization/apis/authorization/install/apigroup.go @@ -23,7 +23,7 @@ func Install(groupFactoryRegistry announced.APIGroupFactoryRegistry, registry *r VersionPreferenceOrder: []string{authorizationapiv1.SchemeGroupVersion.Version}, ImportPrefix: importPrefix, AddInternalObjectsToScheme: authorizationapi.AddToScheme, - RootScopedKinds: sets.NewString("ClusterRole", "ClusterRoleBinding", "ClusterPolicy", "ClusterPolicyBinding"), + RootScopedKinds: sets.NewString("ClusterRole", "ClusterRoleBinding", "ClusterPolicy", "ClusterPolicyBinding", "SubjectAccessReview", "ResourceAccessReview"), }, announced.VersionToSchemeFunc{ authorizationapiv1.SchemeGroupVersion.Version: authorizationapiv1.AddToScheme, diff --git a/vendor/github.com/openshift/origin/pkg/authorization/apis/authorization/v1/conversion.go b/vendor/github.com/openshift/origin/pkg/authorization/apis/authorization/v1/conversion.go index b97da2a5b..9be16e404 100644 --- a/vendor/github.com/openshift/origin/pkg/authorization/apis/authorization/v1/conversion.go +++ b/vendor/github.com/openshift/origin/pkg/authorization/apis/authorization/v1/conversion.go @@ -399,36 +399,66 @@ func addConversionFuncs(scheme *runtime.Scheme) error { ); err != nil { return err } + if err := scheme.AddFieldLabelConversionFunc(SchemeGroupVersion.String(), "ClusterPolicy", + oapi.GetFieldLabelConversionFunc(newer.ClusterPolicyToSelectableFields(&newer.ClusterPolicy{}), nil), + ); err != nil { + return err + } if err := scheme.AddFieldLabelConversionFunc("v1", "ClusterPolicyBinding", oapi.GetFieldLabelConversionFunc(newer.ClusterPolicyBindingToSelectableFields(&newer.ClusterPolicyBinding{}), nil), ); err != nil { return err } + if err := scheme.AddFieldLabelConversionFunc(SchemeGroupVersion.String(), "ClusterPolicyBinding", + oapi.GetFieldLabelConversionFunc(newer.ClusterPolicyBindingToSelectableFields(&newer.ClusterPolicyBinding{}), nil), + ); err != nil { + return err + } if err := scheme.AddFieldLabelConversionFunc("v1", "Policy", oapi.GetFieldLabelConversionFunc(newer.PolicyToSelectableFields(&newer.Policy{}), nil), ); err != nil { return err } + if err := scheme.AddFieldLabelConversionFunc(SchemeGroupVersion.String(), "Policy", + oapi.GetFieldLabelConversionFunc(newer.PolicyToSelectableFields(&newer.Policy{}), nil), + ); err != nil { + return err + } if err := scheme.AddFieldLabelConversionFunc("v1", "PolicyBinding", oapi.GetFieldLabelConversionFunc(newer.PolicyBindingToSelectableFields(&newer.PolicyBinding{}), nil), ); err != nil { return err } + if err := scheme.AddFieldLabelConversionFunc(SchemeGroupVersion.String(), "PolicyBinding", + oapi.GetFieldLabelConversionFunc(newer.PolicyBindingToSelectableFields(&newer.PolicyBinding{}), nil), + ); err != nil { + return err + } if err := scheme.AddFieldLabelConversionFunc("v1", "Role", oapi.GetFieldLabelConversionFunc(newer.RoleToSelectableFields(&newer.Role{}), nil), ); err != nil { return err } + if err := scheme.AddFieldLabelConversionFunc(SchemeGroupVersion.String(), "Role", + oapi.GetFieldLabelConversionFunc(newer.RoleToSelectableFields(&newer.Role{}), nil), + ); err != nil { + return err + } if err := scheme.AddFieldLabelConversionFunc("v1", "RoleBinding", oapi.GetFieldLabelConversionFunc(newer.RoleBindingToSelectableFields(&newer.RoleBinding{}), nil), ); err != nil { return err } + if err := scheme.AddFieldLabelConversionFunc(SchemeGroupVersion.String(), "RoleBinding", + oapi.GetFieldLabelConversionFunc(newer.RoleBindingToSelectableFields(&newer.RoleBinding{}), nil), + ); err != nil { + return err + } return nil } diff --git a/vendor/github.com/openshift/origin/pkg/authorization/registry/clusterpolicy/registry.go b/vendor/github.com/openshift/origin/pkg/authorization/registry/clusterpolicy/registry.go index 5e58859dc..ac616540f 100644 --- a/vendor/github.com/openshift/origin/pkg/authorization/registry/clusterpolicy/registry.go +++ b/vendor/github.com/openshift/origin/pkg/authorization/registry/clusterpolicy/registry.go @@ -100,6 +100,9 @@ func NewSimulatedRegistry(clusterRegistry Registry) policy.Registry { func (s *simulatedStorage) ListPolicies(ctx apirequest.Context, options *metainternal.ListOptions) (*authorizationapi.PolicyList, error) { ret, err := s.clusterRegistry.ListClusterPolicies(ctx, options) + if err != nil { + return nil, err + } return authorizationapi.ToPolicyList(ret), err } @@ -113,6 +116,9 @@ func (s *simulatedStorage) UpdatePolicy(ctx apirequest.Context, policy *authoriz func (s *simulatedStorage) GetPolicy(ctx apirequest.Context, name string, options *metav1.GetOptions) (*authorizationapi.Policy, error) { ret, err := s.clusterRegistry.GetClusterPolicy(ctx, name, options) + if err != nil { + return nil, err + } return authorizationapi.ToPolicy(ret), err } diff --git a/vendor/github.com/openshift/origin/pkg/authorization/registry/clusterpolicybinding/registry.go b/vendor/github.com/openshift/origin/pkg/authorization/registry/clusterpolicybinding/registry.go index bce2c8182..13efe1572 100644 --- a/vendor/github.com/openshift/origin/pkg/authorization/registry/clusterpolicybinding/registry.go +++ b/vendor/github.com/openshift/origin/pkg/authorization/registry/clusterpolicybinding/registry.go @@ -100,6 +100,9 @@ func NewSimulatedRegistry(clusterRegistry Registry) policybinding.Registry { func (s *simulatedStorage) ListPolicyBindings(ctx apirequest.Context, options *metainternal.ListOptions) (*authorizationapi.PolicyBindingList, error) { ret, err := s.clusterRegistry.ListClusterPolicyBindings(ctx, options) + if err != nil { + return nil, err + } return authorizationapi.ToPolicyBindingList(ret), err } @@ -113,6 +116,9 @@ func (s *simulatedStorage) UpdatePolicyBinding(ctx apirequest.Context, policyBin func (s *simulatedStorage) GetPolicyBinding(ctx apirequest.Context, name string, options *metav1.GetOptions) (*authorizationapi.PolicyBinding, error) { ret, err := s.clusterRegistry.GetClusterPolicyBinding(ctx, name, options) + if err != nil { + return nil, err + } return authorizationapi.ToPolicyBinding(ret), err } diff --git a/vendor/github.com/openshift/origin/pkg/bootstrap/bindata.go b/vendor/github.com/openshift/origin/pkg/bootstrap/bindata.go index 97d332555..ff5b7bd40 100644 --- a/vendor/github.com/openshift/origin/pkg/bootstrap/bindata.go +++ b/vendor/github.com/openshift/origin/pkg/bootstrap/bindata.go @@ -13596,7 +13596,6 @@ executionRules: - key: images.openshift.io/deny-execution value: "true" skipOnResolutionFailure: true - `) func pkgImageAdmissionImagepolicyApiV1DefaultPolicyYamlBytes() ([]byte, error) { diff --git a/vendor/github.com/openshift/origin/pkg/build/apis/build/v1/conversion.go b/vendor/github.com/openshift/origin/pkg/build/apis/build/v1/conversion.go index 815fd55f3..20f26851a 100644 --- a/vendor/github.com/openshift/origin/pkg/build/apis/build/v1/conversion.go +++ b/vendor/github.com/openshift/origin/pkg/build/apis/build/v1/conversion.go @@ -179,11 +179,21 @@ func addConversionFuncs(scheme *runtime.Scheme) error { ); err != nil { return err } + if err := scheme.AddFieldLabelConversionFunc(SchemeGroupVersion.String(), "Build", + oapi.GetFieldLabelConversionFunc(newer.BuildToSelectableFields(&newer.Build{}), nil), + ); err != nil { + return err + } if err := scheme.AddFieldLabelConversionFunc("v1", "BuildConfig", oapi.GetFieldLabelConversionFunc(newer.BuildConfigToSelectableFields(&newer.BuildConfig{}), map[string]string{"name": "metadata.name"}), ); err != nil { return err } + if err := scheme.AddFieldLabelConversionFunc(SchemeGroupVersion.String(), "BuildConfig", + oapi.GetFieldLabelConversionFunc(newer.BuildConfigToSelectableFields(&newer.BuildConfig{}), nil), + ); err != nil { + return err + } return nil } diff --git a/vendor/github.com/openshift/origin/pkg/cmd/admin/node/listpods.go b/vendor/github.com/openshift/origin/pkg/cmd/admin/node/listpods.go index 97c9b5577..afd822cfc 100644 --- a/vendor/github.com/openshift/origin/pkg/cmd/admin/node/listpods.go +++ b/vendor/github.com/openshift/origin/pkg/cmd/admin/node/listpods.go @@ -11,6 +11,7 @@ import ( "k8s.io/apimachinery/pkg/runtime/schema" kerrors "k8s.io/apimachinery/pkg/util/errors" kapi "k8s.io/kubernetes/pkg/api" + kapiv1 "k8s.io/kubernetes/pkg/api/v1" kcmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" kprinters "k8s.io/kubernetes/pkg/printers" ) @@ -90,7 +91,12 @@ func (l *ListPodsOptions) runListPods(node *kapi.Node, printer kprinters.Resourc // objects for every node, into a single list. This allows output containing multiple nodes to be // printed to a single writer, and be easily parsed as a single data format. func (l *ListPodsOptions) handleRESTOutput(nodes []*kapi.Node, printer kprinters.ResourcePrinter) []error { - unifiedPodList := &kapi.PodList{} + unifiedPodList := &kapiv1.PodList{ + TypeMeta: metav1.TypeMeta{ + Kind: "List", + APIVersion: "v1", + }, + } errList := []error{} for _, node := range nodes { @@ -101,7 +107,7 @@ func (l *ListPodsOptions) handleRESTOutput(nodes []*kapi.Node, printer kprinters } fieldSelector := fields.Set{GetPodHostFieldLabel(node.TypeMeta.APIVersion): node.ObjectMeta.Name}.AsSelector() - pods, err := l.Options.KubeClient.Core().Pods(metav1.NamespaceAll).List(metav1.ListOptions{LabelSelector: labelSelector.String(), FieldSelector: fieldSelector.String()}) + pods, err := l.Options.ExternalKubeClient.CoreV1().Pods(metav1.NamespaceAll).List(metav1.ListOptions{LabelSelector: labelSelector.String(), FieldSelector: fieldSelector.String()}) if err != nil { errList = append(errList, err) continue diff --git a/vendor/github.com/openshift/origin/pkg/cmd/admin/node/node_options.go b/vendor/github.com/openshift/origin/pkg/cmd/admin/node/node_options.go index 43259fbfa..b46ac8418 100644 --- a/vendor/github.com/openshift/origin/pkg/cmd/admin/node/node_options.go +++ b/vendor/github.com/openshift/origin/pkg/cmd/admin/node/node_options.go @@ -16,6 +16,7 @@ import ( kerrors "k8s.io/apimachinery/pkg/util/errors" "k8s.io/apimachinery/pkg/util/sets" kapi "k8s.io/kubernetes/pkg/api" + externalclientset "k8s.io/kubernetes/pkg/client/clientset_generated/clientset" kclientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" "k8s.io/kubernetes/pkg/kubectl/resource" kprinters "k8s.io/kubernetes/pkg/printers" @@ -24,10 +25,11 @@ import ( ) type NodeOptions struct { - DefaultNamespace string - KubeClient kclientset.Interface - Writer io.Writer - ErrWriter io.Writer + DefaultNamespace string + KubeClient kclientset.Interface + ExternalKubeClient externalclientset.Interface + Writer io.Writer + ErrWriter io.Writer Mapper meta.RESTMapper Typer runtime.ObjectTyper @@ -49,10 +51,21 @@ func (n *NodeOptions) Complete(f *clientcmd.Factory, c *cobra.Command, args []st if err != nil { return err } + _, kc, err := f.Clients() if err != nil { return err } + + config, err := f.ClientConfig() + if err != nil { + return err + } + externalkc, err := externalclientset.NewForConfig(config) + if err != nil { + return err + } + cmdPrinter, output, err := f.PrinterForCommand(c) if err != nil { return err @@ -61,6 +74,7 @@ func (n *NodeOptions) Complete(f *clientcmd.Factory, c *cobra.Command, args []st n.DefaultNamespace = defaultNamespace n.KubeClient = kc + n.ExternalKubeClient = externalkc n.Writer = out n.ErrWriter = errout n.Mapper = mapper diff --git a/vendor/github.com/openshift/origin/pkg/cmd/admin/prune/images.go b/vendor/github.com/openshift/origin/pkg/cmd/admin/prune/images.go index 11950852f..f652fc787 100644 --- a/vendor/github.com/openshift/origin/pkg/cmd/admin/prune/images.go +++ b/vendor/github.com/openshift/origin/pkg/cmd/admin/prune/images.go @@ -34,6 +34,8 @@ import ( // PruneImagesRecommendedName is the recommended command name const PruneImagesRecommendedName = "images" +var errNoToken = errors.New("you must use a client config with a token") + var ( imagesLongDesc = templates.LongDesc(` Remove image stream tags, images, and image layers by age or usage @@ -53,11 +55,14 @@ var ( authority other than the one present in current user's config, you may need to specify it using --certificate-authority flag. - Insecure connection is allowed in following cases unless certificate-authority is specified: - 1. --force-insecure is given - 2. user's config allows for insecure connection (the user logged in to the cluster with - --insecure-skip-tls-verify or allowed for insecure connection) - 3. registry url is not given or it's a private/link-local address`) + Insecure connection is allowed if certificate-authority is not specified and one of the following + conditions holds true: + + 1. --force-insecure is given + 2. provided registry-url is prefixed with http:// + 3. registry url is a private or link-local address + 4. user's config allows for insecure connection (the user logged in to the cluster with + --insecure-skip-tls-verify or allowed for insecure connection)`) imagesExample = templates.Examples(` # See, what the prune command would delete if only images more than an hour old and obsoleted @@ -72,7 +77,13 @@ var ( %[1]s %[2]s --prune-over-size-limit # To actually perform the prune operation, the confirm flag must be appended - %[1]s %[2]s --prune-over-size-limit --confirm`) + %[1]s %[2]s --prune-over-size-limit --confirm + + # Force the insecure http protocol with the particular registry host name + %[1]s %[2]s --registry-url=http://registry.example.org --confirm + + # Force a secure connection with a custom certificate authority to the particular registry host name + %[1]s %[2]s --registry-url=registry.example.org --certificate-authority=/path/to/custom/ca.crt --confirm`) ) var ( @@ -93,11 +104,10 @@ type PruneImagesOptions struct { Namespace string ForceInsecure bool - OSClient client.Interface - KClient kclientset.Interface - RegistryClient *http.Client - Out io.Writer - Insecure bool + ClientConfig *restclient.Config + OSClient client.Interface + KubeClient kclientset.Interface + Out io.Writer } // NewCmdPruneImages implements the OpenShift cli prune images command. @@ -131,7 +141,7 @@ func NewCmdPruneImages(f *clientcmd.Factory, parentName, name string, out io.Wri cmd.Flags().IntVar(opts.KeepTagRevisions, "keep-tag-revisions", *opts.KeepTagRevisions, "Specify the number of image revisions for a tag in an image stream that will be preserved.") cmd.Flags().BoolVar(opts.PruneOverSizeLimit, "prune-over-size-limit", *opts.PruneOverSizeLimit, "Specify if images which are exceeding LimitRanges (see 'openshift.io/Image'), specified in the same namespace, should be considered for pruning. This flag cannot be combined with --keep-younger-than nor --keep-tag-revisions.") cmd.Flags().StringVar(&opts.CABundle, "certificate-authority", opts.CABundle, "The path to a certificate authority bundle to use when communicating with the managed Docker registries. Defaults to the certificate authority data from the current user's config file. It cannot be used together with --force-insecure.") - cmd.Flags().StringVar(&opts.RegistryUrlOverride, "registry-url", opts.RegistryUrlOverride, "The address to use when contacting the registry, instead of using the default value. This is useful if you can't resolve or reach the registry (e.g.; the default is a cluster-internal URL) but you do have an alternative route that works.") + cmd.Flags().StringVar(&opts.RegistryUrlOverride, "registry-url", opts.RegistryUrlOverride, "The address to use when contacting the registry, instead of using the default value. This is useful if you can't resolve or reach the registry (e.g.; the default is a cluster-internal URL) but you do have an alternative route that works. Particular transport protocol can be enforced using '://' prefix.") cmd.Flags().BoolVar(&opts.ForceInsecure, "force-insecure", opts.ForceInsecure, "If true, allow an insecure connection to the docker registry that is hosted via HTTP or has an invalid HTTPS certificate. Whenever possible, use --certificate-authority instead of this dangerous option.") return cmd @@ -169,18 +179,14 @@ func (o *PruneImagesOptions) Complete(f *clientcmd.Factory, cmd *cobra.Command, if err != nil { return err } + o.ClientConfig = clientConfig - o.Insecure = o.ForceInsecure - if !o.Insecure && len(o.CABundle) == 0 { - o.Insecure = clientConfig.TLSClientConfig.Insecure || len(o.RegistryUrlOverride) == 0 || netutils.IsPrivateAddress(o.RegistryUrlOverride) - } - osClient, kClient, registryClient, err := getClients(f, o.CABundle, o.Insecure) + osClient, kubeClient, err := getClients(f) if err != nil { return err } o.OSClient = osClient - o.KClient = kClient - o.RegistryClient = registryClient + o.KubeClient = kubeClient return nil } @@ -196,12 +202,15 @@ func (o PruneImagesOptions) Validate() error { if o.KeepTagRevisions != nil && *o.KeepTagRevisions < 0 { return fmt.Errorf("--keep-tag-revisions must be greater than or equal to 0") } - if _, err := url.Parse(o.RegistryUrlOverride); err != nil { + if err := imageapi.ValidateRegistryURL(o.RegistryUrlOverride); len(o.RegistryUrlOverride) > 0 && err != nil { return fmt.Errorf("invalid --registry-url flag: %v", err) } if o.ForceInsecure && len(o.CABundle) > 0 { return fmt.Errorf("--certificate-authority cannot be specified with --force-insecure") } + if len(o.CABundle) > 0 && strings.HasPrefix(o.RegistryUrlOverride, "http://") { + return fmt.Errorf("--cerificate-authority cannot be specified for insecure http protocol") + } return nil } @@ -217,12 +226,12 @@ func (o PruneImagesOptions) Run() error { return err } - allPods, err := o.KClient.Core().Pods(o.Namespace).List(metav1.ListOptions{}) + allPods, err := o.KubeClient.Core().Pods(o.Namespace).List(metav1.ListOptions{}) if err != nil { return err } - allRCs, err := o.KClient.Core().ReplicationControllers(o.Namespace).List(metav1.ListOptions{}) + allRCs, err := o.KubeClient.Core().ReplicationControllers(o.Namespace).List(metav1.ListOptions{}) if err != nil { return err } @@ -246,7 +255,7 @@ func (o PruneImagesOptions) Run() error { return err } - limitRangesList, err := o.KClient.Core().LimitRanges(o.Namespace).List(metav1.ListOptions{}) + limitRangesList, err := o.KubeClient.Core().LimitRanges(o.Namespace).List(metav1.ListOptions{}) if err != nil { return err } @@ -261,6 +270,43 @@ func (o PruneImagesOptions) Run() error { limitRangesMap[limit.Namespace] = limits } + var ( + registryHost = o.RegistryUrlOverride + registryClient *http.Client + registryPinger prune.RegistryPinger + ) + + if o.Confirm { + if len(registryHost) == 0 { + registryHost, err = prune.DetermineRegistryHost(allImages, allStreams) + if err != nil { + return fmt.Errorf("unable to determine registry: %v", err) + } + } + + insecure := o.ForceInsecure + if !insecure && len(o.CABundle) == 0 { + insecure = o.ClientConfig.TLSClientConfig.Insecure || netutils.IsPrivateAddress(registryHost) || + strings.HasPrefix(registryHost, "http://") + } + + registryClient, err = getRegistryClient(o.ClientConfig, o.CABundle, insecure) + if err != nil { + return err + } + registryPinger = &prune.DefaultRegistryPinger{ + Client: registryClient, + Insecure: insecure, + } + } else { + registryPinger = &prune.DryRunRegistryPinger{} + } + + registryURL, err := registryPinger.Ping(registryHost) + if err != nil { + return fmt.Errorf("error communicating with registry %s: %v", registryHost, err) + } + options := prune.PrunerOptions{ KeepYoungerThan: o.KeepYoungerThan, KeepTagRevisions: o.KeepTagRevisions, @@ -275,9 +321,8 @@ func (o PruneImagesOptions) Run() error { DCs: allDCs, LimitRanges: limitRangesMap, DryRun: o.Confirm == false, - RegistryClient: o.RegistryClient, - RegistryURL: o.RegistryUrlOverride, - Insecure: o.Insecure, + RegistryClient: registryClient, + RegistryURL: registryURL, } if o.Namespace != metav1.NamespaceAll { options.Namespace = o.Namespace @@ -316,7 +361,11 @@ type describingImageStreamDeleter struct { var _ prune.ImageStreamDeleter = &describingImageStreamDeleter{} -func (p *describingImageStreamDeleter) DeleteImageStream(stream *imageapi.ImageStream, image *imageapi.Image, updatedTags []string) (*imageapi.ImageStream, error) { +func (p *describingImageStreamDeleter) GetImageStream(stream *imageapi.ImageStream) (*imageapi.ImageStream, error) { + return stream, nil +} + +func (p *describingImageStreamDeleter) UpdateImageStream(stream *imageapi.ImageStream, image *imageapi.Image, updatedTags []string) (*imageapi.ImageStream, error) { if !p.headerPrinted { p.headerPrinted = true fmt.Fprintln(p.w, "Deleting references from image streams to images ...") @@ -329,7 +378,7 @@ func (p *describingImageStreamDeleter) DeleteImageStream(stream *imageapi.ImageS return stream, nil } - updatedStream, err := p.delegate.DeleteImageStream(stream, image, updatedTags) + updatedStream, err := p.delegate.UpdateImageStream(stream, image, updatedTags) if err != nil { fmt.Fprintf(os.Stderr, "error updating image stream %s/%s to remove references to image %s: %v\n", stream.Namespace, stream.Name, image.Name, err) } @@ -378,7 +427,7 @@ type describingLayerLinkDeleter struct { var _ prune.LayerLinkDeleter = &describingLayerLinkDeleter{} -func (p *describingLayerLinkDeleter) DeleteLayerLink(registryClient *http.Client, registryURL, repo, name string) error { +func (p *describingLayerLinkDeleter) DeleteLayerLink(registryClient *http.Client, registryURL *url.URL, repo, name string) error { if !p.headerPrinted { p.headerPrinted = true fmt.Fprintln(p.w, "\nDeleting registry repository layer links ...") @@ -409,7 +458,7 @@ type describingBlobDeleter struct { var _ prune.BlobDeleter = &describingBlobDeleter{} -func (p *describingBlobDeleter) DeleteBlob(registryClient *http.Client, registryURL, layer string) error { +func (p *describingBlobDeleter) DeleteBlob(registryClient *http.Client, registryURL *url.URL, layer string) error { if !p.headerPrinted { p.headerPrinted = true fmt.Fprintln(p.w, "\nDeleting registry layer blobs ...") @@ -441,7 +490,7 @@ type describingManifestDeleter struct { var _ prune.ManifestDeleter = &describingManifestDeleter{} -func (p *describingManifestDeleter) DeleteManifest(registryClient *http.Client, registryURL, repo, manifest string) error { +func (p *describingManifestDeleter) DeleteManifest(registryClient *http.Client, registryURL *url.URL, repo, manifest string) error { if !p.headerPrinted { p.headerPrinted = true fmt.Fprintln(p.w, "\nDeleting registry repository manifest data ...") @@ -456,46 +505,48 @@ func (p *describingManifestDeleter) DeleteManifest(registryClient *http.Client, err := p.delegate.DeleteManifest(registryClient, registryURL, repo, manifest) if err != nil { - fmt.Fprintf(os.Stderr, "error deleting data for repository %s image manifest %s from the registry: %v\n", repo, manifest, err) + fmt.Fprintf(os.Stderr, "error deleting manifest %s from repository %s: %v\n", manifest, repo, err) } return err } -// getClients returns a Kube client, OpenShift client, and registry client. Note that -// registryCABundle and registryInsecure=true are mutually exclusive. If registryInsecure=true is -// specified, the ca bundle is ignored. -func getClients(f *clientcmd.Factory, registryCABundle string, registryInsecure bool) (*client.Client, kclientset.Interface, *http.Client, error) { +// getClients returns a OpenShift client and Kube client. +func getClients(f *clientcmd.Factory) (*client.Client, kclientset.Interface, error) { clientConfig, err := f.ClientConfig() if err != nil { - return nil, nil, nil, err + return nil, nil, err + } + + if len(clientConfig.BearerToken) == 0 { + return nil, nil, errNoToken } + osClient, kubeClient, err := f.Clients() + if err != nil { + return nil, nil, err + } + return osClient, kubeClient, err +} + +// getRegistryClient returns a registry client. Note that registryCABundle and registryInsecure=true are +// mutually exclusive. If registryInsecure=true is specified, the ca bundle is ignored. +func getRegistryClient(clientConfig *restclient.Config, registryCABundle string, registryInsecure bool) (*http.Client, error) { var ( - token string - osClient *client.Client - kClient kclientset.Interface - registryClient *http.Client + err error + cadata []byte + registryCABundleIncluded = false + token = clientConfig.BearerToken ) - switch { - case len(clientConfig.BearerToken) > 0: - osClient, kClient, err = f.Clients() - if err != nil { - return nil, nil, nil, err - } - token = clientConfig.BearerToken - default: - err = errors.New("you must use a client config with a token") - return nil, nil, nil, err + if len(token) == 0 { + return nil, errNoToken } - cadata := []byte{} - registryCABundleIncluded := false if len(registryCABundle) > 0 { cadata, err = ioutil.ReadFile(registryCABundle) if err != nil { - return nil, nil, nil, fmt.Errorf("failed to read registry ca bundle: %v", err) + return nil, fmt.Errorf("failed to read registry ca bundle: %v", err) } } @@ -531,7 +582,7 @@ func getClients(f *clientcmd.Factory, registryCABundle string, registryInsecure tlsConfig, err := restclient.TLSConfigFor(®istryClientConfig) if err != nil { - return nil, nil, nil, err + return nil, err } // Add the CA bundle to the client config's CA roots if provided and we haven't done that already. @@ -549,12 +600,10 @@ func getClients(f *clientcmd.Factory, registryCABundle string, registryInsecure wrappedTransport, err := restclient.HTTPWrappersForConfig(®istryClientConfig, transport) if err != nil { - return nil, nil, nil, err + return nil, err } - registryClient = &http.Client{ + return &http.Client{ Transport: wrappedTransport, - } - - return osClient, kClient, registryClient, nil + }, nil } diff --git a/vendor/github.com/openshift/origin/pkg/cmd/admin/prune/images_test.go b/vendor/github.com/openshift/origin/pkg/cmd/admin/prune/images_test.go index caf0f4129..2b4266c3b 100644 --- a/vendor/github.com/openshift/origin/pkg/cmd/admin/prune/images_test.go +++ b/vendor/github.com/openshift/origin/pkg/cmd/admin/prune/images_test.go @@ -15,9 +15,9 @@ func TestImagePruneNamespaced(t *testing.T) { opts := &PruneImagesOptions{ Namespace: "foo", - OSClient: osFake, - KClient: kFake, - Out: ioutil.Discard, + OSClient: osFake, + KubeClient: kFake, + Out: ioutil.Discard, } if err := opts.Run(); err != nil { diff --git a/vendor/github.com/openshift/origin/pkg/cmd/cli/cmd/wrappers.go b/vendor/github.com/openshift/origin/pkg/cmd/cli/cmd/wrappers.go index a144fba72..91d0d12c7 100644 --- a/vendor/github.com/openshift/origin/pkg/cmd/cli/cmd/wrappers.go +++ b/vendor/github.com/openshift/origin/pkg/cmd/cli/cmd/wrappers.go @@ -7,6 +7,7 @@ import ( "strings" "github.com/spf13/cobra" + "k8s.io/apimachinery/pkg/util/sets" kvalidation "k8s.io/apimachinery/pkg/util/validation" kclientcmd "k8s.io/client-go/tools/clientcmd" kcmd "k8s.io/kubernetes/pkg/kubectl/cmd" @@ -179,6 +180,13 @@ func NewCmdCreate(parentName string, f *clientcmd.Factory, out, errOut io.Writer cmd.Long = createLong cmd.Example = fmt.Sprintf(createExample, parentName) + exclude := sets.NewString("clusterrole", "clusterrolebinding", "role", "rolebinding") + for _, c := range cmd.Commands() { + if exclude.Has(c.Name()) { + cmd.RemoveCommand(c) + } + } + // normalize long descs and examples // TODO remove when normalization is moved upstream templates.NormalizeAll(cmd) diff --git a/vendor/github.com/openshift/origin/pkg/cmd/dockerregistry/dockerregistry.go b/vendor/github.com/openshift/origin/pkg/cmd/dockerregistry/dockerregistry.go index 5126d0997..885f708b9 100644 --- a/vendor/github.com/openshift/origin/pkg/cmd/dockerregistry/dockerregistry.go +++ b/vendor/github.com/openshift/origin/pkg/cmd/dockerregistry/dockerregistry.go @@ -3,14 +3,17 @@ package dockerregistry import ( "crypto/tls" "crypto/x509" + "flag" "fmt" "io" "io/ioutil" "net/http" "os" + "strings" "time" log "github.com/Sirupsen/logrus" + "github.com/docker/go-units" gorillahandlers "github.com/gorilla/handlers" "github.com/Sirupsen/logrus/formatters/logstash" @@ -19,8 +22,10 @@ import ( "github.com/docker/distribution/health" "github.com/docker/distribution/registry/auth" "github.com/docker/distribution/registry/handlers" + "github.com/docker/distribution/registry/storage" + "github.com/docker/distribution/registry/storage/driver/factory" "github.com/docker/distribution/uuid" - "github.com/docker/distribution/version" + distversion "github.com/docker/distribution/version" _ "github.com/docker/distribution/registry/auth/htpasswd" _ "github.com/docker/distribution/registry/auth/token" @@ -35,7 +40,7 @@ import ( _ "github.com/docker/distribution/registry/storage/driver/s3-aws" _ "github.com/docker/distribution/registry/storage/driver/swift" - "strings" + kubeversion "k8s.io/kubernetes/pkg/version" "github.com/openshift/origin/pkg/cmd/server/crypto" "github.com/openshift/origin/pkg/cmd/util/clientcmd" @@ -43,10 +48,96 @@ import ( "github.com/openshift/origin/pkg/dockerregistry/server/api" "github.com/openshift/origin/pkg/dockerregistry/server/audit" registryconfig "github.com/openshift/origin/pkg/dockerregistry/server/configuration" + "github.com/openshift/origin/pkg/dockerregistry/server/prune" + "github.com/openshift/origin/pkg/version" ) +var pruneMode = flag.String("prune", "", "prune blobs from the storage and exit (check, delete)") + +func versionFields() log.Fields { + return log.Fields{ + "distribution_version": distversion.Version, + "kubernetes_version": kubeversion.Get(), + "openshift_version": version.Get(), + } +} + +// ExecutePruner runs the pruner. +func ExecutePruner(configFile io.Reader, dryRun bool) { + config, _, err := registryconfig.Parse(configFile) + if err != nil { + log.Fatalf("error parsing configuration file: %s", err) + } + + // A lot of installations have the 'debug' log level in their config files, + // but it's too verbose for pruning. Therefore we ignore it, but we still + // respect overrides using environment variables. + config.Loglevel = "" + config.Log.Level = configuration.Loglevel(os.Getenv("REGISTRY_LOG_LEVEL")) + if len(config.Log.Level) == 0 { + config.Log.Level = "warning" + } + + ctx := context.Background() + ctx, err = configureLogging(ctx, config) + if err != nil { + log.Fatalf("error configuring logging: %s", err) + } + + startPrune := "start prune" + var registryOptions []storage.RegistryOption + if dryRun { + startPrune += " (dry-run mode)" + } else { + registryOptions = append(registryOptions, storage.EnableDelete) + } + log.WithFields(versionFields()).Info(startPrune) + + registryClient := server.NewRegistryClient(clientcmd.NewConfig().BindToFile()) + + storageDriver, err := factory.Create(config.Storage.Type(), config.Storage.Parameters()) + if err != nil { + log.Fatalf("error creating storage driver: %s", err) + } + + registry, err := storage.NewRegistry(ctx, storageDriver, registryOptions...) + if err != nil { + log.Fatalf("error creating registry: %s", err) + } + + stats, err := prune.Prune(ctx, storageDriver, registry, registryClient, dryRun) + if err != nil { + log.Error(err) + } + if dryRun { + fmt.Printf("Would delete %d blobs\n", stats.Blobs) + fmt.Printf("Would free up %s of disk space\n", units.BytesSize(float64(stats.DiskSpace))) + fmt.Println("Use -prune=delete to actually delete the data") + } else { + fmt.Printf("Deleted %d blobs\n", stats.Blobs) + fmt.Printf("Freed up %s of disk space\n", units.BytesSize(float64(stats.DiskSpace))) + } + if err != nil { + os.Exit(1) + } +} + // Execute runs the Docker registry. func Execute(configFile io.Reader) { + if len(*pruneMode) != 0 { + var dryRun bool + switch *pruneMode { + case "delete": + dryRun = false + case "check": + dryRun = true + default: + log.Fatal("invalid value for the -prune option") + } + ExecutePruner(configFile, dryRun) + return + } + dockerConfig, extraConfig, err := registryconfig.Parse(configFile) if err != nil { log.Fatalf("error parsing configuration file: %s", err) @@ -64,7 +155,7 @@ func Execute(configFile io.Reader) { registryClient := server.NewRegistryClient(clientcmd.NewConfig().BindToFile()) ctx = server.WithRegistryClient(ctx, registryClient) - log.Infof("version=%s", version.Version) + log.WithFields(versionFields()).Info("start registry") // inject a logger into the uuid library. warns us if there is a problem // with uuid generation under low entropy. uuid.Loggerf = context.GetLogger(ctx).Warnf diff --git a/vendor/github.com/openshift/origin/pkg/cmd/server/kubernetes/master/master_config_test.go b/vendor/github.com/openshift/origin/pkg/cmd/server/kubernetes/master/master_config_test.go index f649538b5..764dbd920 100644 --- a/vendor/github.com/openshift/origin/pkg/cmd/server/kubernetes/master/master_config_test.go +++ b/vendor/github.com/openshift/origin/pkg/cmd/server/kubernetes/master/master_config_test.go @@ -126,6 +126,9 @@ func TestAPIServerDefaults(t *testing.T) { ServiceAccounts: &kubeoptions.ServiceAccountAuthenticationOptions{}, TokenFile: &kubeoptions.TokenFileAuthenticationOptions{}, WebHook: &kubeoptions.WebHookAuthenticationOptions{CacheTTL: 2 * time.Minute}, + + TokenSuccessCacheTTL: 10 * time.Second, + TokenFailureCacheTTL: 0, }, Authorization: &kubeoptions.BuiltInAuthorizationOptions{ Mode: "AlwaysAllow", diff --git a/vendor/github.com/openshift/origin/pkg/cmd/server/origin/master_config.go b/vendor/github.com/openshift/origin/pkg/cmd/server/origin/master_config.go index 580aad596..43c4a472c 100644 --- a/vendor/github.com/openshift/origin/pkg/cmd/server/origin/master_config.go +++ b/vendor/github.com/openshift/origin/pkg/cmd/server/origin/master_config.go @@ -29,6 +29,8 @@ import ( "k8s.io/apiserver/pkg/authentication/request/union" "k8s.io/apiserver/pkg/authentication/request/websocket" x509request "k8s.io/apiserver/pkg/authentication/request/x509" + tokencache "k8s.io/apiserver/pkg/authentication/token/cache" + tokenunion "k8s.io/apiserver/pkg/authentication/token/union" "k8s.io/apiserver/pkg/authentication/user" kauthorizer "k8s.io/apiserver/pkg/authorization/authorizer" "k8s.io/apiserver/pkg/authorization/authorizerfactory" @@ -924,7 +926,7 @@ func newServiceAccountTokenGetter(options configapi.MasterConfig) (serviceaccoun func newAuthenticator(config configapi.MasterConfig, restOptionsGetter restoptions.Getter, tokenGetter serviceaccount.ServiceAccountTokenGetter, apiClientCAs *x509.CertPool, groupMapper identitymapper.UserToGroupMapper) (authenticator.Request, error) { authenticators := []authenticator.Request{} - tokenAuthenticators := []authenticator.Request{} + tokenAuthenticators := []authenticator.Token{} // ServiceAccount token if len(config.ServiceAccountConfig.PublicKeyFiles) > 0 { @@ -937,12 +939,7 @@ func newAuthenticator(config configapi.MasterConfig, restOptionsGetter restoptio publicKeys = append(publicKeys, readPublicKeys...) } serviceAccountTokenAuthenticator := serviceaccount.JWTTokenAuthenticator(publicKeys, true, tokenGetter) - tokenAuthenticators = append( - tokenAuthenticators, - bearertoken.New(serviceAccountTokenAuthenticator), - websocket.NewProtocolAuthenticator(serviceAccountTokenAuthenticator), - paramtoken.New("access_token", serviceAccountTokenAuthenticator, true), - ) + tokenAuthenticators = append(tokenAuthenticators, serviceAccountTokenAuthenticator) } // OAuth token @@ -951,20 +948,26 @@ func newAuthenticator(config configapi.MasterConfig, restOptionsGetter restoptio if err != nil { return nil, fmt.Errorf("Error building OAuth token authenticator: %v", err) } - oauthTokenRequestAuthenticators := []authenticator.Request{ - bearertoken.New(oauthTokenAuthenticator), - websocket.NewProtocolAuthenticator(oauthTokenAuthenticator), - paramtoken.New("access_token", oauthTokenAuthenticator, true), - } - tokenAuthenticators = append(tokenAuthenticators, // if you have a bearer token, you're a human (usually) // if you change this, have a look at the impersonationFilter where we attach groups to the impersonated user - group.NewGroupAdder(union.New(oauthTokenRequestAuthenticators...), []string{bootstrappolicy.AuthenticatedOAuthGroup})) + group.NewTokenGroupAdder(oauthTokenAuthenticator, []string{bootstrappolicy.AuthenticatedOAuthGroup})) } if len(tokenAuthenticators) > 0 { - authenticators = append(authenticators, union.New(tokenAuthenticators...)) + // Combine all token authenticators + tokenAuth := tokenunion.New(tokenAuthenticators...) + + // wrap with short cache on success. + // this means a revoked service account token or access token will be valid for up to 10 seconds. + // it also means group membership changes on users may take up to 10 seconds to become effective. + tokenAuth = tokencache.New(tokenAuth, 10*time.Second, 0) + + authenticators = append(authenticators, + bearertoken.New(tokenAuth), + websocket.NewProtocolAuthenticator(tokenAuth), + paramtoken.New("access_token", tokenAuth, true), + ) } if configapi.UseTLS(config.ServingInfo.ServingInfo) { diff --git a/vendor/github.com/openshift/origin/pkg/deploy/apis/apps/v1/conversion.go b/vendor/github.com/openshift/origin/pkg/deploy/apis/apps/v1/conversion.go index 1eacf38cb..4733b5acd 100644 --- a/vendor/github.com/openshift/origin/pkg/deploy/apis/apps/v1/conversion.go +++ b/vendor/github.com/openshift/origin/pkg/deploy/apis/apps/v1/conversion.go @@ -130,5 +130,10 @@ func addConversionFuncs(scheme *runtime.Scheme) error { ); err != nil { return err } + if err := scheme.AddFieldLabelConversionFunc(SchemeGroupVersion.String(), "DeploymentConfig", + oapi.GetFieldLabelConversionFunc(newer.DeploymentConfigToSelectableFields(&newer.DeploymentConfig{}), nil), + ); err != nil { + return err + } return nil } diff --git a/vendor/github.com/openshift/origin/pkg/dockerregistry/server/errorblobstore.go b/vendor/github.com/openshift/origin/pkg/dockerregistry/server/errorblobstore.go index 3db686f65..c56eb240a 100644 --- a/vendor/github.com/openshift/origin/pkg/dockerregistry/server/errorblobstore.go +++ b/vendor/github.com/openshift/origin/pkg/dockerregistry/server/errorblobstore.go @@ -151,7 +151,8 @@ func (f statCrossMountCreateOptions) Apply(v interface{}) error { if err != nil { context.GetLogger(f.ctx).Infof("cannot mount blob %s from repository %s: %v - disabling cross-repo mount", opts.Mount.From.Digest().String(), - opts.Mount.From.Name()) + opts.Mount.From.Name(), + err) opts.Mount.ShouldMount = false return nil } diff --git a/vendor/github.com/openshift/origin/pkg/dockerregistry/server/prune/prune.go b/vendor/github.com/openshift/origin/pkg/dockerregistry/server/prune/prune.go new file mode 100644 index 000000000..f9dec5ede --- /dev/null +++ b/vendor/github.com/openshift/origin/pkg/dockerregistry/server/prune/prune.go @@ -0,0 +1,200 @@ +package prune + +import ( + "fmt" + + "github.com/docker/distribution" + "github.com/docker/distribution/context" + "github.com/docker/distribution/digest" + "github.com/docker/distribution/manifest/schema2" + "github.com/docker/distribution/reference" + "github.com/docker/distribution/registry/storage" + "github.com/docker/distribution/registry/storage/driver" + + kerrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/openshift/origin/pkg/dockerregistry/server" + imageapi "github.com/openshift/origin/pkg/image/apis/image" +) + +func imageStreamHasManifestDigest(is *imageapi.ImageStream, dgst digest.Digest) bool { + for _, tagEventList := range is.Status.Tags { + for _, tagEvent := range tagEventList.Items { + if tagEvent.Image == string(dgst) { + return true + } + } + } + return false +} + +// Summary is cumulative information about what was pruned. +type Summary struct { + Blobs int + DiskSpace int64 +} + +// Prune removes blobs which are not used by Images in OpenShift. +// +// On error, the Summary will contain what was deleted so far. +// +// TODO(dmage): remove layer links to a blob if the blob is removed or it doesn't belong to the ImageStream. +// TODO(dmage): keep young blobs (docker/distribution#2297). +func Prune(ctx context.Context, storageDriver driver.StorageDriver, registry distribution.Namespace, registryClient server.RegistryClient, dryRun bool) (Summary, error) { + logger := context.GetLogger(ctx) + + repositoryEnumerator, ok := registry.(distribution.RepositoryEnumerator) + if !ok { + return Summary{}, fmt.Errorf("unable to convert Namespace to RepositoryEnumerator") + } + + oc, _, err := registryClient.Clients() + if err != nil { + return Summary{}, fmt.Errorf("error getting clients: %v", err) + } + + imageList, err := oc.Images().List(metav1.ListOptions{}) + if err != nil { + return Summary{}, fmt.Errorf("error listing images: %v", err) + } + + inuse := make(map[string]string) + for _, image := range imageList.Items { + // Keep the manifest. + inuse[image.Name] = image.DockerImageReference + + // Keep the config for a schema 2 manifest. + if image.DockerImageManifestMediaType == schema2.MediaTypeManifest { + inuse[image.DockerImageMetadata.ID] = image.DockerImageReference + } + + // Keep image layers. + for _, layer := range image.DockerImageLayers { + inuse[layer.Name] = image.DockerImageReference + } + } + + var stats Summary + + var reposToDelete []string + err = repositoryEnumerator.Enumerate(ctx, func(repoName string) error { + logger.Debugln("Processing repository", repoName) + + named, err := reference.WithName(repoName) + if err != nil { + return fmt.Errorf("failed to parse the repo name %s: %v", repoName, err) + } + + ref, err := imageapi.ParseDockerImageReference(repoName) + if err != nil { + return fmt.Errorf("failed to parse the image reference %s: %v", repoName, err) + } + + is, err := oc.ImageStreams(ref.Namespace).Get(ref.Name, metav1.GetOptions{}) + if kerrors.IsNotFound(err) { + logger.Printf("The image stream %s/%s is not found, will remove the whole repository", ref.Namespace, ref.Name) + + // We cannot delete the repository at this point, because it would break Enumerate. + reposToDelete = append(reposToDelete, repoName) + + return nil + } else if err != nil { + return fmt.Errorf("failed to get the image stream %s: %v", repoName, err) + } + + repository, err := registry.Repository(ctx, named) + if err != nil { + return err + } + + manifestService, err := repository.Manifests(ctx) + if err != nil { + return err + } + + manifestEnumerator, ok := manifestService.(distribution.ManifestEnumerator) + if !ok { + return fmt.Errorf("unable to convert ManifestService into ManifestEnumerator") + } + + err = manifestEnumerator.Enumerate(ctx, func(dgst digest.Digest) error { + if _, ok := inuse[string(dgst)]; ok && imageStreamHasManifestDigest(is, dgst) { + logger.Debugf("Keeping the manifest link %s@%s", repoName, dgst) + return nil + } + + if dryRun { + logger.Printf("Would delete manifest link: %s@%s", repoName, dgst) + return nil + } + + logger.Printf("Deleting manifest link: %s@%s", repoName, dgst) + if err := manifestService.Delete(ctx, dgst); err != nil { + return fmt.Errorf("failed to delete the manifest link %s@%s: %v", repoName, dgst, err) + } + + return nil + }) + if e, ok := err.(driver.PathNotFoundError); ok { + logger.Printf("Skipped manifest link pruning for the repository %s: %v", repoName, e) + } else if err != nil { + return fmt.Errorf("failed to prune manifest links in the repository %s: %v", repoName, err) + } + + return nil + }) + if e, ok := err.(driver.PathNotFoundError); ok { + logger.Warnf("No repositories found: %v", e) + return stats, nil + } else if err != nil { + return stats, err + } + + vacuum := storage.NewVacuum(ctx, storageDriver) + + logger.Debugln("Removing repositories") + for _, repoName := range reposToDelete { + if dryRun { + logger.Printf("Would delete repository: %s", repoName) + continue + } + + if err = vacuum.RemoveRepository(repoName); err != nil { + return stats, fmt.Errorf("unable to remove the repository %s: %v", repoName, err) + } + } + + logger.Debugln("Processing blobs") + blobStatter := registry.BlobStatter() + err = registry.Blobs().Enumerate(ctx, func(dgst digest.Digest) error { + if imageReference, ok := inuse[string(dgst)]; ok { + logger.Debugf("Keeping the blob %s (it belongs to the image %s)", dgst, imageReference) + return nil + } + + desc, err := blobStatter.Stat(ctx, dgst) + if err != nil { + return err + } + + stats.Blobs++ + stats.DiskSpace += desc.Size + + if dryRun { + logger.Printf("Would delete blob: %s", dgst) + return nil + } + + if err := vacuum.RemoveBlob(string(dgst)); err != nil { + return fmt.Errorf("failed to delete the blob %s: %v", dgst, err) + } + + return nil + }) + if e, ok := err.(driver.PathNotFoundError); ok { + logger.Warnf("No repositories found: %v", e) + return stats, nil + } + return stats, err +} diff --git a/vendor/github.com/openshift/origin/pkg/image/admission/imagepolicy/api/types.go b/vendor/github.com/openshift/origin/pkg/image/admission/imagepolicy/api/types.go index 110bbf0ab..c6b08f31c 100644 --- a/vendor/github.com/openshift/origin/pkg/image/admission/imagepolicy/api/types.go +++ b/vendor/github.com/openshift/origin/pkg/image/admission/imagepolicy/api/types.go @@ -27,7 +27,9 @@ type ImagePolicyConfig struct { // ResolutionRules allows more specific image resolution rules to be applied per resource. If // empty, it defaults to allowing local image stream lookups - "mysql" will map to the image stream - // tag "mysql:latest" in the current namespace if the stream supports it. + // tag "mysql:latest" in the current namespace if the stream supports it. The default for this + // field is all known types that support image resolution, and the policy for those rules will be + // set to the overall resolution policy if an execution rule references the same resource. ResolutionRules []ImageResolutionPolicyRule // ExecutionRules determine whether the use of an image is allowed in an object with a pod spec. @@ -53,6 +55,9 @@ var ( // ImageResolutionPolicyRule describes resolution rules based on resource. type ImageResolutionPolicyRule struct { + // Policy controls whether resolution will happen if the rule doesn't match local names. This value + // overrides the global image policy for all target resources. + Policy ImageResolutionType // TargetResource is the identified group and resource. If Resource is *, this rule will apply // to all resources in that group. TargetResource metav1.GroupResource diff --git a/vendor/github.com/openshift/origin/pkg/image/admission/imagepolicy/api/v1/default-policy.yaml b/vendor/github.com/openshift/origin/pkg/image/admission/imagepolicy/api/v1/default-policy.yaml index 4736ead82..f7272a0ac 100644 --- a/vendor/github.com/openshift/origin/pkg/image/admission/imagepolicy/api/v1/default-policy.yaml +++ b/vendor/github.com/openshift/origin/pkg/image/admission/imagepolicy/api/v1/default-policy.yaml @@ -17,4 +17,3 @@ executionRules: - key: images.openshift.io/deny-execution value: "true" skipOnResolutionFailure: true - diff --git a/vendor/github.com/openshift/origin/pkg/image/admission/imagepolicy/api/v1/defaults.go b/vendor/github.com/openshift/origin/pkg/image/admission/imagepolicy/api/v1/defaults.go index 93313355d..93c6b2eaa 100644 --- a/vendor/github.com/openshift/origin/pkg/image/admission/imagepolicy/api/v1/defaults.go +++ b/vendor/github.com/openshift/origin/pkg/image/admission/imagepolicy/api/v1/defaults.go @@ -14,26 +14,38 @@ func SetDefaults_ImagePolicyConfig(obj *ImagePolicyConfig) { obj.ResolveImages = Attempt } + for i := range obj.ExecutionRules { + if len(obj.ExecutionRules[i].OnResources) == 0 { + obj.ExecutionRules[i].OnResources = []GroupResource{{Resource: "pods", Group: kapi.GroupName}} + } + } + if obj.ResolutionRules == nil { obj.ResolutionRules = []ImageResolutionPolicyRule{ {TargetResource: GroupResource{Resource: "pods"}, LocalNames: true}, {TargetResource: GroupResource{Group: "build.openshift.io", Resource: "builds"}, LocalNames: true}, - {TargetResource: GroupResource{Resource: "replicationcontrollers"}, LocalNames: true}, - {TargetResource: GroupResource{Group: "extensions", Resource: "replicasets"}, LocalNames: true}, {TargetResource: GroupResource{Group: "batch", Resource: "jobs"}, LocalNames: true}, - - // TODO: consider adding these - // {TargetResource: GroupResource{Group: "extensions", Resource: "deployments"}, LocalNames: true}, - // {TargetResource: GroupResource{Group: "apps", Resource: "statefulsets"}, LocalNames: true}, + {TargetResource: GroupResource{Group: "extensions", Resource: "replicasets"}, LocalNames: true}, + {TargetResource: GroupResource{Resource: "replicationcontrollers"}, LocalNames: true}, + {TargetResource: GroupResource{Group: "apps", Resource: "deployments"}, LocalNames: true}, + {TargetResource: GroupResource{Group: "extensions", Resource: "deployments"}, LocalNames: true}, + {TargetResource: GroupResource{Group: "apps", Resource: "statefulsets"}, LocalNames: true}, + {TargetResource: GroupResource{Group: "extensions", Resource: "daemonsets"}, LocalNames: true}, } - } - - for i := range obj.ExecutionRules { - if len(obj.ExecutionRules[i].OnResources) == 0 { - obj.ExecutionRules[i].OnResources = []GroupResource{{Resource: "pods", Group: kapi.GroupName}} + // default the resolution policy to the global default + for i := range obj.ResolutionRules { + if len(obj.ResolutionRules[i].Policy) != 0 { + continue + } + obj.ResolutionRules[i].Policy = DoNotAttempt + for _, rule := range obj.ExecutionRules { + if executionRuleCoversResource(rule, obj.ResolutionRules[i].TargetResource) { + obj.ResolutionRules[i].Policy = obj.ResolveImages + break + } + } } } - } func addDefaultingFuncs(scheme *runtime.Scheme) error { @@ -41,3 +53,13 @@ func addDefaultingFuncs(scheme *runtime.Scheme) error { SetDefaults_ImagePolicyConfig, ) } + +// executionRuleCoversResource returns true if gr is covered by rule. +func executionRuleCoversResource(rule ImageExecutionPolicyRule, gr GroupResource) bool { + for _, target := range rule.OnResources { + if target.Group == gr.Group && (target.Resource == gr.Resource || target.Resource == "*") { + return true + } + } + return false +} diff --git a/vendor/github.com/openshift/origin/pkg/image/admission/imagepolicy/api/v1/swagger_doc.go b/vendor/github.com/openshift/origin/pkg/image/admission/imagepolicy/api/v1/swagger_doc.go index a5616dad3..9f728ec2a 100644 --- a/vendor/github.com/openshift/origin/pkg/image/admission/imagepolicy/api/v1/swagger_doc.go +++ b/vendor/github.com/openshift/origin/pkg/image/admission/imagepolicy/api/v1/swagger_doc.go @@ -45,7 +45,7 @@ func (ImageExecutionPolicyRule) SwaggerDoc() map[string]string { var map_ImagePolicyConfig = map[string]string{ "": "ImagePolicyConfig is the configuration for control of images running on the platform.", "resolveImages": "ResolveImages indicates the default image resolution behavior. If a rewriting policy is chosen, then the image pull specs will be updated.", - "resolutionRules": "ResolutionRules allows more specific image resolution rules to be applied per resource. If empty, it defaults to allowing local image stream lookups - \"mysql\" will map to the image stream tag \"mysql:latest\" in the current namespace if the stream supports it.", + "resolutionRules": "ResolutionRules allows more specific image resolution rules to be applied per resource. If empty, it defaults to allowing local image stream lookups - \"mysql\" will map to the image stream tag \"mysql:latest\" in the current namespace if the stream supports it. The default for this field is all known types that support image resolution, and the policy for those rules will be set to the overall resolution policy if an execution rule references the same resource.", "executionRules": "ExecutionRules determine whether the use of an image is allowed in an object with a pod spec. By default, these rules only apply to pods, but may be extended to other resource types. If all execution rules are negations, the default behavior is allow all. If any execution rule is an allow, the default behavior is to reject all.", } @@ -55,6 +55,7 @@ func (ImagePolicyConfig) SwaggerDoc() map[string]string { var map_ImageResolutionPolicyRule = map[string]string{ "": "ImageResolutionPolicyRule describes resolution rules based on resource.", + "policy": "Policy controls whether resolution will happen if the rule doesn't match local names. This value overrides the global image policy for all target resources.", "targetResource": "TargetResource is the identified group and resource. If Resource is *, this rule will apply to all resources in that group.", "localNames": "LocalNames will allow single segment names to be interpreted as namespace local image stream tags, but only if the target image stream tag has the \"resolveLocalNames\" field set.", } diff --git a/vendor/github.com/openshift/origin/pkg/image/admission/imagepolicy/api/v1/types.go b/vendor/github.com/openshift/origin/pkg/image/admission/imagepolicy/api/v1/types.go index 40ba7d27b..89e4f1b4d 100644 --- a/vendor/github.com/openshift/origin/pkg/image/admission/imagepolicy/api/v1/types.go +++ b/vendor/github.com/openshift/origin/pkg/image/admission/imagepolicy/api/v1/types.go @@ -14,7 +14,9 @@ type ImagePolicyConfig struct { // ResolutionRules allows more specific image resolution rules to be applied per resource. If // empty, it defaults to allowing local image stream lookups - "mysql" will map to the image stream - // tag "mysql:latest" in the current namespace if the stream supports it. + // tag "mysql:latest" in the current namespace if the stream supports it. The default for this + // field is all known types that support image resolution, and the policy for those rules will be + // set to the overall resolution policy if an execution rule references the same resource. ResolutionRules []ImageResolutionPolicyRule `json:"resolutionRules"` // ExecutionRules determine whether the use of an image is allowed in an object with a pod spec. @@ -42,6 +44,9 @@ var ( // ImageResolutionPolicyRule describes resolution rules based on resource. type ImageResolutionPolicyRule struct { + // Policy controls whether resolution will happen if the rule doesn't match local names. This value + // overrides the global image policy for all target resources. + Policy ImageResolutionType `json:"policy"` // TargetResource is the identified group and resource. If Resource is *, this rule will apply // to all resources in that group. TargetResource GroupResource `json:"targetResource"` diff --git a/vendor/github.com/openshift/origin/pkg/image/admission/imagepolicy/api/validation/validation.go b/vendor/github.com/openshift/origin/pkg/image/admission/imagepolicy/api/validation/validation.go index c8c92e8fc..d3dda34d6 100644 --- a/vendor/github.com/openshift/origin/pkg/image/admission/imagepolicy/api/validation/validation.go +++ b/vendor/github.com/openshift/origin/pkg/image/admission/imagepolicy/api/validation/validation.go @@ -28,6 +28,9 @@ func Validate(config *api.ImagePolicyConfig) field.ErrorList { } for i, rule := range config.ResolutionRules { + if len(rule.Policy) == 0 { + allErrs = append(allErrs, field.Required(field.NewPath(api.PluginName, "resolutionRules").Index(i).Child("policy"), "a policy must be specified for this resource")) + } if len(rule.TargetResource.Resource) == 0 { allErrs = append(allErrs, field.Required(field.NewPath(api.PluginName, "resolutionRules").Index(i).Child("targetResource", "resource"), "a target resource name or '*' must be provided")) } diff --git a/vendor/github.com/openshift/origin/pkg/image/admission/imagepolicy/api/validation/validation_test.go b/vendor/github.com/openshift/origin/pkg/image/admission/imagepolicy/api/validation/validation_test.go index a8bd7219d..9669fe0fb 100644 --- a/vendor/github.com/openshift/origin/pkg/image/admission/imagepolicy/api/validation/validation_test.go +++ b/vendor/github.com/openshift/origin/pkg/image/admission/imagepolicy/api/validation/validation_test.go @@ -47,6 +47,33 @@ func TestValidation(t *testing.T) { t.Fatal(errs) } + if errs := Validate(&api.ImagePolicyConfig{ + ResolveImages: api.DoNotAttempt, + ResolutionRules: []api.ImageResolutionPolicyRule{ + {TargetResource: metav1.GroupResource{Resource: "test"}, Policy: api.Attempt}, + }, + }); len(errs) != 0 { + t.Fatal(errs) + } + + if errs := Validate(&api.ImagePolicyConfig{ + ResolveImages: api.DoNotAttempt, + ResolutionRules: []api.ImageResolutionPolicyRule{ + {TargetResource: metav1.GroupResource{Resource: "test"}}, + }, + }); len(errs) == 0 { + t.Fatal(errs) + } + + if errs := Validate(&api.ImagePolicyConfig{ + ResolveImages: api.DoNotAttempt, + ResolutionRules: []api.ImageResolutionPolicyRule{ + {Policy: api.Attempt}, + }, + }); len(errs) == 0 { + t.Fatal(errs) + } + if errs := Validate(&api.ImagePolicyConfig{ ResolveImages: api.DoNotAttempt, ExecutionRules: []api.ImageExecutionPolicyRule{ diff --git a/vendor/github.com/openshift/origin/pkg/image/admission/imagepolicy/imagepolicy.go b/vendor/github.com/openshift/origin/pkg/image/admission/imagepolicy/imagepolicy.go index 009f12f5e..1d16481e2 100644 --- a/vendor/github.com/openshift/origin/pkg/image/admission/imagepolicy/imagepolicy.go +++ b/vendor/github.com/openshift/origin/pkg/image/admission/imagepolicy/imagepolicy.go @@ -451,26 +451,32 @@ var skipImageRewriteOnUpdate = map[schema.GroupResource]struct{}{ } // RewriteImagePullSpec applies to implicit rewrite attributes and local resources as well as if the policy requires it. +// If a local name check is requested and a rule matches true is returned. The policy default resolution is only respected +// if a resource isn't covered by a rule - if pods have a rule with DoNotAttempt and the global policy is RequiredRewrite, +// no pods will be rewritten. func (config resolutionConfig) RewriteImagePullSpec(attr *rules.ImagePolicyAttributes, isUpdate bool, gr schema.GroupResource) bool { if isUpdate { if _, ok := skipImageRewriteOnUpdate[gr]; ok { return false } } - if api.RequestsResolution(config.config.ResolveImages) { - return true - } - if attr.LocalRewrite { - for _, rule := range config.config.ResolutionRules { - if !rule.LocalNames { - continue - } - if resolutionRuleCoversResource(rule.TargetResource, gr) { - return true - } + hasMatchingRule := false + for _, rule := range config.config.ResolutionRules { + if !resolutionRuleCoversResource(rule.TargetResource, gr) { + continue } + if rule.LocalNames && attr.LocalRewrite { + return true + } + if api.RewriteImagePullSpec(rule.Policy) { + return true + } + hasMatchingRule = true } - return false + if hasMatchingRule { + return false + } + return api.RewriteImagePullSpec(config.config.ResolveImages) } // resolutionRuleCoversResource implements wildcard checking on Resource names diff --git a/vendor/github.com/openshift/origin/pkg/image/admission/imagepolicy/imagepolicy_test.go b/vendor/github.com/openshift/origin/pkg/image/admission/imagepolicy/imagepolicy_test.go index 7400fdc4e..0c61ce682 100644 --- a/vendor/github.com/openshift/origin/pkg/image/admission/imagepolicy/imagepolicy_test.go +++ b/vendor/github.com/openshift/origin/pkg/image/admission/imagepolicy/imagepolicy_test.go @@ -1,6 +1,7 @@ package imagepolicy import ( + "bytes" "os" "reflect" "strings" @@ -436,9 +437,16 @@ func TestAdmissionResolveImages(t *testing.T) { DockerImageReference: "integrated.registry/image1/image1:latest", } + obj, err := configlatest.ReadYAML(bytes.NewBufferString(`{"kind":"ImagePolicyConfig","apiVersion":"v1"}`)) + if err != nil || obj == nil { + t.Fatal(err) + } + defaultPolicyConfig := obj.(*api.ImagePolicyConfig) + testCases := []struct { client *testclient.Fake policy api.ImageResolutionType + config *api.ImagePolicyConfig attrs admission.Attributes admit bool expect runtime.Object @@ -585,6 +593,168 @@ func TestAdmissionResolveImages(t *testing.T) { }, }, }, + + // resolves images in the integrated registry on builds without altering their ref (avoids looking up the tag) + { + policy: api.RequiredRewrite, + client: testclient.NewSimpleFake( + image1, + ), + attrs: admission.NewAttributesRecord( + &buildapi.Build{ + Spec: buildapi.BuildSpec{ + CommonSpec: buildapi.CommonSpec{ + Strategy: buildapi.BuildStrategy{ + SourceStrategy: &buildapi.SourceBuildStrategy{ + From: kapi.ObjectReference{Kind: "DockerImage", Name: "integrated.registry/test/mysql@sha256:0000000000000000000000000000000000000000000000000000000000000001"}, + }, + }, + }, + }, + }, nil, schema.GroupVersionKind{Version: "v1", Kind: "Build"}, + "default", "build1", schema.GroupVersionResource{Version: "v1", Resource: "builds"}, + "", admission.Create, nil, + ), + admit: true, + expect: &buildapi.Build{ + Spec: buildapi.BuildSpec{ + CommonSpec: buildapi.CommonSpec{ + Strategy: buildapi.BuildStrategy{ + SourceStrategy: &buildapi.SourceBuildStrategy{ + From: kapi.ObjectReference{Kind: "DockerImage", Name: "integrated.registry/test/mysql@sha256:0000000000000000000000000000000000000000000000000000000000000001"}, + }, + }, + }, + }, + }, + }, + // does not rewrite the config because build has DoNotAttempt by default, which overrides global policy + { + config: &api.ImagePolicyConfig{ + ResolveImages: api.RequiredRewrite, + ResolutionRules: []api.ImageResolutionPolicyRule{ + {TargetResource: metav1.GroupResource{Group: "", Resource: "builds"}}, + }, + }, + client: testclient.NewSimpleFake( + &imageapi.ImageStreamTag{ + ObjectMeta: metav1.ObjectMeta{Name: "test:other", Namespace: "default"}, + Image: *image1, + }, + ), + attrs: admission.NewAttributesRecord( + &buildapi.Build{ + Spec: buildapi.BuildSpec{ + CommonSpec: buildapi.CommonSpec{ + Strategy: buildapi.BuildStrategy{ + CustomStrategy: &buildapi.CustomBuildStrategy{ + From: kapi.ObjectReference{Kind: "ImageStreamTag", Name: "test:other"}, + }, + }, + }, + }, + }, nil, schema.GroupVersionKind{Version: "v1", Kind: "Build"}, + "default", "build1", schema.GroupVersionResource{Version: "v1", Resource: "builds"}, + "", admission.Create, nil, + ), + admit: true, + expect: &buildapi.Build{ + Spec: buildapi.BuildSpec{ + CommonSpec: buildapi.CommonSpec{ + Strategy: buildapi.BuildStrategy{ + CustomStrategy: &buildapi.CustomBuildStrategy{ + From: kapi.ObjectReference{Kind: "ImageStreamTag", Name: "test:other"}, + }, + }, + }, + }, + }, + }, + // does not rewrite the config because build has Attempt by default, which overrides global policy + { + config: &api.ImagePolicyConfig{ + ResolveImages: api.RequiredRewrite, + ResolutionRules: []api.ImageResolutionPolicyRule{ + {TargetResource: metav1.GroupResource{Group: "", Resource: "builds"}, Policy: api.Attempt}, + }, + }, + client: testclient.NewSimpleFake( + &imageapi.ImageStreamTag{ + ObjectMeta: metav1.ObjectMeta{Name: "test:other", Namespace: "default"}, + Image: *image1, + }, + ), + attrs: admission.NewAttributesRecord( + &buildapi.Build{ + Spec: buildapi.BuildSpec{ + CommonSpec: buildapi.CommonSpec{ + Strategy: buildapi.BuildStrategy{ + CustomStrategy: &buildapi.CustomBuildStrategy{ + From: kapi.ObjectReference{Kind: "ImageStreamTag", Name: "test:other"}, + }, + }, + }, + }, + }, nil, schema.GroupVersionKind{Version: "v1", Kind: "Build"}, + "default", "build1", schema.GroupVersionResource{Version: "v1", Resource: "builds"}, + "", admission.Create, nil, + ), + admit: true, + expect: &buildapi.Build{ + Spec: buildapi.BuildSpec{ + CommonSpec: buildapi.CommonSpec{ + Strategy: buildapi.BuildStrategy{ + CustomStrategy: &buildapi.CustomBuildStrategy{ + From: kapi.ObjectReference{Kind: "ImageStreamTag", Name: "test:other"}, + }, + }, + }, + }, + }, + }, + // rewrites the config because build has AttemptRewrite which overrides the global policy + { + config: &api.ImagePolicyConfig{ + ResolveImages: api.DoNotAttempt, + ResolutionRules: []api.ImageResolutionPolicyRule{ + {TargetResource: metav1.GroupResource{Group: "", Resource: "builds"}, Policy: api.AttemptRewrite}, + }, + }, + client: testclient.NewSimpleFake( + &imageapi.ImageStreamTag{ + ObjectMeta: metav1.ObjectMeta{Name: "test:other", Namespace: "default"}, + Image: *image1, + }, + ), + attrs: admission.NewAttributesRecord( + &buildapi.Build{ + Spec: buildapi.BuildSpec{ + CommonSpec: buildapi.CommonSpec{ + Strategy: buildapi.BuildStrategy{ + CustomStrategy: &buildapi.CustomBuildStrategy{ + From: kapi.ObjectReference{Kind: "ImageStreamTag", Name: "test:other"}, + }, + }, + }, + }, + }, nil, schema.GroupVersionKind{Version: "v1", Kind: "Build"}, + "default", "build1", schema.GroupVersionResource{Version: "v1", Resource: "builds"}, + "", admission.Create, nil, + ), + admit: true, + expect: &buildapi.Build{ + Spec: buildapi.BuildSpec{ + CommonSpec: buildapi.CommonSpec{ + Strategy: buildapi.BuildStrategy{ + CustomStrategy: &buildapi.CustomBuildStrategy{ + From: kapi.ObjectReference{Kind: "DockerImage", Name: "integrated.registry/image1/image1@sha256:0000000000000000000000000000000000000000000000000000000000000001"}, + }, + }, + }, + }, + }, + }, + // resolves builds.build.openshift.io with image stream tags, uses the image DockerImageReference with SHA set. { policy: api.RequiredRewrite, @@ -735,6 +905,43 @@ func TestAdmissionResolveImages(t *testing.T) { }, }, }, + // does not resolve replica sets by default + { + config: defaultPolicyConfig, + client: testclient.NewSimpleFake( + &imageapi.ImageStreamTag{ + ObjectMeta: metav1.ObjectMeta{Name: "test:other", Namespace: "default"}, + Image: *image1, + }, + ), + attrs: admission.NewAttributesRecord( + &kapiextensions.ReplicaSet{ + Spec: kapiextensions.ReplicaSetSpec{ + Template: kapi.PodTemplateSpec{ + Spec: kapi.PodSpec{ + Containers: []kapi.Container{ + {Image: "integrated.registry/default/test:other"}, + }, + }, + }, + }, + }, nil, schema.GroupVersionKind{Version: "v1", Kind: "ReplicaSet", Group: "extensions"}, + "default", "rs1", schema.GroupVersionResource{Version: "v1", Resource: "replicasets", Group: "extensions"}, + "", admission.Create, nil, + ), + admit: true, + expect: &kapiextensions.ReplicaSet{ + Spec: kapiextensions.ReplicaSetSpec{ + Template: kapi.PodTemplateSpec{ + Spec: kapi.PodSpec{ + Containers: []kapi.Container{ + {Image: "integrated.registry/default/test:other"}, + }, + }, + }, + }, + }, + }, // resolves replica sets that specifically request lookup { policy: api.RequiredRewrite, @@ -925,16 +1132,21 @@ func TestAdmissionResolveImages(t *testing.T) { } for i, test := range testCases { onResources := []schema.GroupResource{{Resource: "builds"}, {Resource: "pods"}} - p, err := newImagePolicyPlugin(&api.ImagePolicyConfig{ - ResolveImages: test.policy, - ResolutionRules: []api.ImageResolutionPolicyRule{ - {LocalNames: true, TargetResource: metav1.GroupResource{Resource: "*"}}, - {LocalNames: true, TargetResource: metav1.GroupResource{Group: "extensions", Resource: "*"}}, - }, - ExecutionRules: []api.ImageExecutionPolicyRule{ - {ImageCondition: api.ImageCondition{OnResources: onResources}}, - }, - }) + config := test.config + if config == nil { + // old style config + config = &api.ImagePolicyConfig{ + ResolveImages: test.policy, + ResolutionRules: []api.ImageResolutionPolicyRule{ + {LocalNames: true, TargetResource: metav1.GroupResource{Resource: "*"}, Policy: test.policy}, + {LocalNames: true, TargetResource: metav1.GroupResource{Group: "extensions", Resource: "*"}, Policy: test.policy}, + }, + ExecutionRules: []api.ImageExecutionPolicyRule{ + {ImageCondition: api.ImageCondition{OnResources: onResources}}, + }, + } + } + p, err := newImagePolicyPlugin(config) if err != nil { t.Fatal(err) } diff --git a/vendor/github.com/openshift/origin/pkg/image/apis/image/helper.go b/vendor/github.com/openshift/origin/pkg/image/apis/image/helper.go index 2100045d5..b334a2b10 100644 --- a/vendor/github.com/openshift/origin/pkg/image/apis/image/helper.go +++ b/vendor/github.com/openshift/origin/pkg/image/apis/image/helper.go @@ -41,6 +41,10 @@ const ( ImportRegistryNotAllowed = "registry is not allowed for import" ) +var errNoRegistryURLPathAllowed = fmt.Errorf("no path after [:] is allowed") +var errNoRegistryURLQueryAllowed = fmt.Errorf("no query arguments are allowed after [:]") +var errRegistryURLHostEmpty = fmt.Errorf("no host name specified") + // DefaultRegistry returns the default Docker registry (host or host:port), or false if it is not available. type DefaultRegistry interface { DefaultRegistry() (string, bool) @@ -1161,3 +1165,41 @@ func (tagref TagReference) HasAnnotationTag(searchTag string) bool { } return false } + +// ValidateRegistryURL returns error if the given input is not a valid registry URL. The url may be prefixed +// with http:// or https:// schema. It may not contain any path or query after the host:[port]. +func ValidateRegistryURL(registryURL string) error { + var ( + u *url.URL + err error + parts = strings.SplitN(registryURL, "://", 2) + ) + + switch len(parts) { + case 2: + u, err = url.Parse(registryURL) + if err != nil { + return err + } + switch u.Scheme { + case "http", "https": + default: + return fmt.Errorf("unsupported scheme: %s", u.Scheme) + } + case 1: + u, err = url.Parse("https://" + registryURL) + if err != nil { + return err + } + } + if len(u.Path) > 0 && u.Path != "/" { + return errNoRegistryURLPathAllowed + } + if len(u.RawQuery) > 0 { + return errNoRegistryURLQueryAllowed + } + if len(u.Host) == 0 { + return errRegistryURLHostEmpty + } + return nil +} diff --git a/vendor/github.com/openshift/origin/pkg/image/apis/image/helper_test.go b/vendor/github.com/openshift/origin/pkg/image/apis/image/helper_test.go index ea25964f8..62e680c99 100644 --- a/vendor/github.com/openshift/origin/pkg/image/apis/image/helper_test.go +++ b/vendor/github.com/openshift/origin/pkg/image/apis/image/helper_test.go @@ -1833,3 +1833,85 @@ func TestDockerImageReferenceForImage(t *testing.T) { t.Errorf("expected failure for unknown image") } } + +func TestValidateRegistryURL(t *testing.T) { + for _, tc := range []struct { + input string + expectedError bool + expectedErrorString string + }{ + {input: "172.30.30.30:5000"}, + {input: ":5000"}, + {input: "[fd12:3456:789a:1::1]:80/"}, + {input: "[fd12:3456:789a:1::1]:80"}, + {input: "http://172.30.30.30:5000"}, + {input: "http://[fd12:3456:789a:1::1]:5000/"}, + {input: "http://[fd12:3456:789a:1::1]:5000"}, + {input: "http://registry.org:5000"}, + {input: "https://172.30.30.30:5000"}, + {input: "https://:80/"}, + {input: "https://[fd12:3456:789a:1::1]/"}, + {input: "https://[fd12:3456:789a:1::1]"}, + {input: "https://[fd12:3456:789a:1::1]:5000/"}, + {input: "https://[fd12:3456:789a:1::1]:5000"}, + {input: "https://registry.org/"}, + {input: "https://registry.org"}, + {input: "localhost/"}, + {input: "localhost"}, + {input: "localhost:80"}, + {input: "registry.org/"}, + {input: "registry.org"}, + {input: "registry.org:5000"}, + + { + input: "httpss://registry.org", + expectedErrorString: "unsupported scheme: httpss", + }, + { + input: "ftp://registry.org", + expectedErrorString: "unsupported scheme: ftp", + }, + { + input: "http://registry.org://", + expectedErrorString: errNoRegistryURLPathAllowed.Error(), + }, + { + input: "http://registry.org/path", + expectedErrorString: errNoRegistryURLPathAllowed.Error(), + }, + { + input: "[fd12:3456:789a:1::1", + expectedError: true, + }, + { + input: "bad url", + expectedError: true, + }, + { + input: "/registry.org", + expectedErrorString: errNoRegistryURLPathAllowed.Error(), + }, + { + input: "https:///", + expectedErrorString: errRegistryURLHostEmpty.Error(), + }, + { + input: "http://registry.org?parm=arg", + expectedErrorString: errNoRegistryURLQueryAllowed.Error(), + }, + } { + + err := ValidateRegistryURL(tc.input) + if err != nil { + if len(tc.expectedErrorString) > 0 && err.Error() != tc.expectedErrorString { + t.Errorf("[%s] unexpected error string: %q != %q", tc.input, err.Error(), tc.expectedErrorString) + } else if len(tc.expectedErrorString) == 0 && !tc.expectedError { + t.Errorf("[%s] unexpected error: %q", tc.input, err.Error()) + } + } else if len(tc.expectedErrorString) > 0 { + t.Errorf("[%s] got non-error while expecting %q", tc.input, tc.expectedErrorString) + } else if tc.expectedError { + t.Errorf("[%s] got unexpected non-error", tc.input) + } + } +} diff --git a/vendor/github.com/openshift/origin/pkg/image/apis/image/v1/conversion.go b/vendor/github.com/openshift/origin/pkg/image/apis/image/v1/conversion.go index a4744f4ee..063d63764 100644 --- a/vendor/github.com/openshift/origin/pkg/image/apis/image/v1/conversion.go +++ b/vendor/github.com/openshift/origin/pkg/image/apis/image/v1/conversion.go @@ -283,11 +283,21 @@ func addConversionFuncs(scheme *runtime.Scheme) error { ); err != nil { return err } + if err := scheme.AddFieldLabelConversionFunc(SchemeGroupVersion.String(), "Image", + oapi.GetFieldLabelConversionFunc(newer.ImageToSelectableFields(&newer.Image{}), nil), + ); err != nil { + return err + } if err := scheme.AddFieldLabelConversionFunc("v1", "ImageStream", oapi.GetFieldLabelConversionFunc(newer.ImageStreamToSelectableFields(&newer.ImageStream{}), map[string]string{"name": "metadata.name"}), ); err != nil { return err } + if err := scheme.AddFieldLabelConversionFunc(SchemeGroupVersion.String(), "ImageStream", + oapi.GetFieldLabelConversionFunc(newer.ImageStreamToSelectableFields(&newer.ImageStream{}), nil), + ); err != nil { + return err + } return nil } diff --git a/vendor/github.com/openshift/origin/pkg/image/prune/doc.go b/vendor/github.com/openshift/origin/pkg/image/prune/doc.go new file mode 100644 index 000000000..f910671d2 --- /dev/null +++ b/vendor/github.com/openshift/origin/pkg/image/prune/doc.go @@ -0,0 +1,2 @@ +// Package prune contains logic for pruning images and interoperating with the integrated Docker registry. +package prune diff --git a/vendor/github.com/openshift/origin/pkg/image/prune/helper.go b/vendor/github.com/openshift/origin/pkg/image/prune/helper.go new file mode 100644 index 000000000..57380919b --- /dev/null +++ b/vendor/github.com/openshift/origin/pkg/image/prune/helper.go @@ -0,0 +1,208 @@ +package prune + +import ( + "fmt" + "net/http" + "net/url" + "sort" + "strings" + + "github.com/docker/distribution/registry/api/errcode" + "github.com/golang/glog" + + kerrors "k8s.io/apimachinery/pkg/util/errors" + + imageapi "github.com/openshift/origin/pkg/image/apis/image" + "github.com/openshift/origin/pkg/util/netutils" +) + +// order younger images before older +type imgByAge []*imageapi.Image + +func (ba imgByAge) Len() int { return len(ba) } +func (ba imgByAge) Swap(i, j int) { ba[i], ba[j] = ba[j], ba[i] } +func (ba imgByAge) Less(i, j int) bool { + return ba[i].CreationTimestamp.After(ba[j].CreationTimestamp.Time) +} + +// order younger image stream before older +type isByAge []imageapi.ImageStream + +func (ba isByAge) Len() int { return len(ba) } +func (ba isByAge) Swap(i, j int) { ba[i], ba[j] = ba[j], ba[i] } +func (ba isByAge) Less(i, j int) bool { + return ba[i].CreationTimestamp.After(ba[j].CreationTimestamp.Time) +} + +// DetermineRegistryHost returns registry host embedded in a pull-spec of the latest unmanaged image or the +// latest imagestream from the provided lists. If no such pull-spec is found, error is returned. +func DetermineRegistryHost(images *imageapi.ImageList, imageStreams *imageapi.ImageStreamList) (string, error) { + var pullSpec string + var managedImages []*imageapi.Image + + // 1st try to determine registry url from a pull spec of the youngest managed image + for i := range images.Items { + image := &images.Items[i] + if image.Annotations[imageapi.ManagedByOpenShiftAnnotation] != "true" { + continue + } + managedImages = append(managedImages, image) + } + // be sure to pick up the newest managed image which should have an up to date information + sort.Sort(imgByAge(managedImages)) + + if len(managedImages) > 0 { + pullSpec = managedImages[0].DockerImageReference + } else { + // 2nd try to get the pull spec from any image stream + // Sorting by creation timestamp may not get us up to date info. Modification time would be much + // better if there were such an attribute. + sort.Sort(isByAge(imageStreams.Items)) + for _, is := range imageStreams.Items { + if len(is.Status.DockerImageRepository) == 0 { + continue + } + pullSpec = is.Status.DockerImageRepository + } + } + + if len(pullSpec) == 0 { + return "", fmt.Errorf("no managed image found") + } + + ref, err := imageapi.ParseDockerImageReference(pullSpec) + if err != nil { + return "", fmt.Errorf("unable to parse %q: %v", pullSpec, err) + } + + if len(ref.Registry) == 0 { + return "", fmt.Errorf("%s does not include a registry", pullSpec) + } + + return ref.Registry, nil +} + +// RegistryPinger performs a health check against a registry. +type RegistryPinger interface { + // Ping performs a health check against registry. It returns registry url qualified with schema unless an + // error occurs. + Ping(registry string) (*url.URL, error) +} + +// DefaultRegistryPinger implements RegistryPinger. +type DefaultRegistryPinger struct { + Client *http.Client + Insecure bool +} + +// Ping verifies that the integrated registry is ready, determines its transport protocol and returns its url +// or error. +func (drp *DefaultRegistryPinger) Ping(registry string) (*url.URL, error) { + var ( + registryURL *url.URL + err error + ) + +pathLoop: + // first try the new default / path, then fall-back to the obsolete /healthz endpoint + for _, path := range []string{"/", "/healthz"} { + registryURL, err = TryProtocolsWithRegistryURL(registry, drp.Insecure, func(u url.URL) error { + u.Path = path + healthResponse, err := drp.Client.Get(u.String()) + if err != nil { + return err + } + defer healthResponse.Body.Close() + + if healthResponse.StatusCode != http.StatusOK { + return &retryPath{err: fmt.Errorf("unexpected status: %s", healthResponse.Status)} + } + + return nil + }) + + // determine whether to retry with another endpoint + switch t := err.(type) { + case *retryPath: + // return the nested error if this is the last ping attempt + err = t.err + continue pathLoop + case kerrors.Aggregate: + // if any aggregated error indicates a possible retry, do it + for _, err := range t.Errors() { + if _, ok := err.(*retryPath); ok { + continue pathLoop + } + } + } + + break + } + + return registryURL, err +} + +// DryRunRegistryPinger implements RegistryPinger. +type DryRunRegistryPinger struct { +} + +// Ping implements Ping method. +func (*DryRunRegistryPinger) Ping(registry string) (*url.URL, error) { + return url.Parse("https://" + registry) +} + +// TryProtocolsWithRegistryURL runs given action with different protocols until no error is returned. The +// https protocol is the first attempt. If it fails and allowInsecure is true, http will be the next. Obtained +// errors will be concatenated and returned. +func TryProtocolsWithRegistryURL(registry string, allowInsecure bool, action func(registryURL url.URL) error) (*url.URL, error) { + var errs []error + + if !strings.Contains(registry, "://") { + registry = "unset://" + registry + } + url, err := url.Parse(registry) + if err != nil { + return nil, err + } + var protos []string + switch { + case len(url.Scheme) > 0 && url.Scheme != "unset": + protos = []string{url.Scheme} + case allowInsecure || netutils.IsPrivateAddress(registry): + protos = []string{"https", "http"} + default: + protos = []string{"https"} + } + registry = url.Host + + for _, proto := range protos { + glog.V(4).Infof("Trying protocol %s for the registry URL %s", proto, registry) + url.Scheme = proto + err := action(*url) + if err == nil { + return url, nil + } + + if err != nil { + glog.V(4).Infof("Error with %s for %s: %v", proto, registry, err) + } + + if _, ok := err.(*errcode.Errors); ok { + // we got a response back from the registry, so return it + return url, err + } + errs = append(errs, err) + if proto == "https" && strings.Contains(err.Error(), "server gave HTTP response to HTTPS client") && !allowInsecure { + errs = append(errs, fmt.Errorf("\n* Append --force-insecure if you really want to prune the registry using insecure connection.")) + } else if proto == "http" && strings.Contains(err.Error(), "malformed HTTP response") { + errs = append(errs, fmt.Errorf("\n* Are you trying to connect to a TLS-enabled registry without TLS?")) + } + } + + return nil, kerrors.NewAggregate(errs) +} + +// retryPath is an error indicating that another connection attempt may be retried with a different path +type retryPath struct{ err error } + +func (rp *retryPath) Error() string { return rp.err.Error() } diff --git a/vendor/github.com/openshift/origin/pkg/image/prune/helper_test.go b/vendor/github.com/openshift/origin/pkg/image/prune/helper_test.go new file mode 100644 index 000000000..79bb91daf --- /dev/null +++ b/vendor/github.com/openshift/origin/pkg/image/prune/helper_test.go @@ -0,0 +1,217 @@ +package prune + +import ( + "crypto/tls" + "net/http" + "net/http/httptest" + "reflect" + "strings" + "sync" + "testing" + + knet "k8s.io/apimachinery/pkg/util/net" + "k8s.io/kubernetes/staging/src/k8s.io/apimachinery/pkg/util/diff" +) + +type requestStats struct { + lock sync.Mutex + requests []string +} + +func (rs *requestStats) addRequest(r *http.Request) { + rs.lock.Lock() + defer rs.lock.Unlock() + rs.requests = append(rs.requests, r.URL.String()) +} +func (rs *requestStats) clear() { + rs.lock.Lock() + defer rs.lock.Unlock() + rs.requests = rs.requests[:0] +} +func (rs *requestStats) getRequests() []string { + rs.lock.Lock() + defer rs.lock.Unlock() + res := make([]string, 0, len(rs.requests)) + for _, r := range rs.requests { + res = append(res, r) + } + return res +} + +func TestDefaultImagePinger(t *testing.T) { + rs := requestStats{requests: []string{}} + + type statusForPath map[string]int + + rt := knet.SetTransportDefaults(&http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + }) + insecureClient := http.Client{Transport: rt} + secureClient := http.Client{} + + for _, tc := range []struct { + name string + schemePrefix string + securedRegistry bool + insecure bool + statusForPath statusForPath + expectedErrorSubstring string + expectedRequests []string + }{ + { + name: "tls secured registry with insecure fallback", + securedRegistry: true, + insecure: true, + statusForPath: statusForPath{"/": http.StatusOK}, + expectedRequests: []string{"/"}, + }, + + { + name: "tls secured registry prefixed by scheme with insecure fallback", + schemePrefix: "https://", + securedRegistry: true, + insecure: true, + statusForPath: statusForPath{"/": http.StatusOK}, + expectedRequests: []string{"/"}, + }, + + { + name: "tls secured registry prefixed by http scheme with insecure fallback", + schemePrefix: "http://", + securedRegistry: true, + insecure: true, + statusForPath: statusForPath{"/": http.StatusOK}, + expectedErrorSubstring: "malformed HTTP response", + }, + + { + name: "tls secured registry with no fallback", + securedRegistry: true, + insecure: false, + statusForPath: statusForPath{"/": http.StatusOK, "/healthz": http.StatusOK}, + expectedErrorSubstring: "x509: certificate signed by unknown authority", + }, + + { + name: "tls secured registry with old healthz endpoint", + securedRegistry: true, + insecure: true, + statusForPath: statusForPath{"/healthz": http.StatusOK}, + expectedRequests: []string{"/", "/healthz"}, + }, + + { + name: "insecure registry with insecure fallback", + securedRegistry: false, + insecure: true, + statusForPath: statusForPath{"/": http.StatusOK}, + expectedRequests: []string{"/"}, + }, + + { + name: "insecure registry prefixed by scheme with insecure fallback", + schemePrefix: "http://", + securedRegistry: false, + insecure: true, + statusForPath: statusForPath{"/": http.StatusOK}, + expectedRequests: []string{"/"}, + }, + + { + name: "insecure registry prefixed by https scheme with insecure fallback", + schemePrefix: "https://", + securedRegistry: false, + insecure: true, + statusForPath: statusForPath{"/": http.StatusOK}, + expectedErrorSubstring: "server gave HTTP response to HTTPS client", + }, + + { + name: "insecure registry with no fallback", + securedRegistry: false, + statusForPath: statusForPath{"/": http.StatusOK, "/healthz": http.StatusOK}, + expectedErrorSubstring: "server gave HTTP response to HTTPS client", + }, + + { + name: "insecure registry with old healthz endpoint", + securedRegistry: false, + insecure: true, + statusForPath: statusForPath{"/healthz": http.StatusOK}, + expectedRequests: []string{"/", "/healthz"}, + }, + + { + name: "initializing insecure registry", + securedRegistry: false, + insecure: true, + statusForPath: statusForPath{}, + expectedErrorSubstring: "server gave HTTP response to HTTPS client, unexpected status: 404 Not Found", + expectedRequests: []string{"/", "/healthz"}, + }, + } { + func() { + defer rs.clear() + + handler := func(w http.ResponseWriter, r *http.Request) { + rs.addRequest(r) + if s, ok := tc.statusForPath[r.URL.Path]; ok { + w.WriteHeader(s) + } else { + w.WriteHeader(http.StatusNotFound) + } + } + + var server *httptest.Server + if tc.securedRegistry { + server = httptest.NewTLSServer(http.HandlerFunc(handler)) + } else { + server = httptest.NewServer(http.HandlerFunc(handler)) + } + defer server.Close() + serverHost := strings.TrimLeft(strings.TrimLeft(server.URL, "http://"), "https://") + + client := &secureClient + if tc.insecure { + client = &insecureClient + } + + pinger := DefaultRegistryPinger{ + Client: client, + Insecure: tc.insecure, + } + + registryURL, err := pinger.Ping(tc.schemePrefix + serverHost) + if err != nil { + if len(tc.expectedErrorSubstring) == 0 { + t.Errorf("[%s] got unexpected ping error of type %T: %v", tc.name, err, err) + } else if !strings.Contains(err.Error(), tc.expectedErrorSubstring) { + t.Errorf("[%s] expected substring %q not found in error message: %s", tc.name, tc.expectedErrorSubstring, err.Error()) + } + } else if len(tc.expectedErrorSubstring) > 0 { + t.Errorf("[%s] unexpected non-error", tc.name) + } + + e := server.URL + if len(tc.expectedErrorSubstring) > 0 { + // the pinger should return unchanged input in case of error + e = "" + } + a := "" + if registryURL != nil { + a = registryURL.String() + } + if a != e { + t.Errorf("[%s] unexpected registry url: %q != %q", tc.name, a, e) + } + + ers := tc.expectedRequests + if ers == nil { + ers = []string{} + } + if a := rs.getRequests(); !reflect.DeepEqual(a, ers) { + t.Errorf("[%s] got unexpected requests: %s", tc.name, diff.ObjectDiff(a, ers)) + } + }() + } +} diff --git a/vendor/github.com/openshift/origin/pkg/image/prune/prune.go b/vendor/github.com/openshift/origin/pkg/image/prune/prune.go index f24b3ba0e..129167314 100644 --- a/vendor/github.com/openshift/origin/pkg/image/prune/prune.go +++ b/vendor/github.com/openshift/origin/pkg/image/prune/prune.go @@ -4,9 +4,8 @@ import ( "encoding/json" "fmt" "net/http" + "net/url" "reflect" - "sort" - "strings" "time" "github.com/docker/distribution/manifest/schema2" @@ -21,6 +20,7 @@ import ( kerrors "k8s.io/apimachinery/pkg/util/errors" "k8s.io/apimachinery/pkg/util/sets" kapi "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/client/retry" "github.com/openshift/origin/pkg/api/graph" kubegraph "github.com/openshift/origin/pkg/api/kubegraph/nodes" @@ -32,7 +32,6 @@ import ( deploygraph "github.com/openshift/origin/pkg/deploy/graph/nodes" imageapi "github.com/openshift/origin/pkg/image/apis/image" imagegraph "github.com/openshift/origin/pkg/image/graph/nodes" - "github.com/openshift/origin/pkg/util/netutils" ) // TODO these edges should probably have an `Add***Edges` method in images/graph and be moved there @@ -73,23 +72,25 @@ type ImageDeleter interface { // ImageStreamDeleter knows how to remove an image reference from an image stream. type ImageStreamDeleter interface { - // DeleteImageStream removes all references to the image from the image + // GetImageStream returns a fresh copy of an image stream. + GetImageStream(stream *imageapi.ImageStream) (*imageapi.ImageStream, error) + // UpdateImageStream removes all references to the image from the image // stream's status.tags. The updated image stream is returned. - DeleteImageStream(stream *imageapi.ImageStream, image *imageapi.Image, updatedTags []string) (*imageapi.ImageStream, error) + UpdateImageStream(stream *imageapi.ImageStream, image *imageapi.Image, updatedTags []string) (*imageapi.ImageStream, error) } // BlobDeleter knows how to delete a blob from the Docker registry. type BlobDeleter interface { // DeleteBlob uses registryClient to ask the registry at registryURL // to remove the blob. - DeleteBlob(registryClient *http.Client, registryURL, blob string) error + DeleteBlob(registryClient *http.Client, registryURL *url.URL, blob string) error } // LayerLinkDeleter knows how to delete a repository layer link from the Docker registry. type LayerLinkDeleter interface { // DeleteLayerLink uses registryClient to ask the registry at registryURL to // delete the repository layer link. - DeleteLayerLink(registryClient *http.Client, registryURL, repo, linkName string) error + DeleteLayerLink(registryClient *http.Client, registryURL *url.URL, repo, linkName string) error } // ManifestDeleter knows how to delete image manifest data for a repository from @@ -97,7 +98,7 @@ type LayerLinkDeleter interface { type ManifestDeleter interface { // DeleteManifest uses registryClient to ask the registry at registryURL to // delete the repository's image manifest data. - DeleteManifest(registryClient *http.Client, registryURL, repo, manifest string) error + DeleteManifest(registryClient *http.Client, registryURL *url.URL, repo, manifest string) error } // PrunerOptions contains the fields used to initialize a new Pruner. @@ -112,7 +113,6 @@ type PrunerOptions struct { // will be considered as candidates for pruning. PruneOverSizeLimit *bool // AllImages considers all images for pruning, not just those pushed directly to the registry. - // Requires RegistryURL be set. AllImages *bool // Namespace to be pruned, if specified it should never remove Images. Namespace string @@ -142,10 +142,8 @@ type PrunerOptions struct { DryRun bool // RegistryClient is the http.Client to use when contacting the registry. RegistryClient *http.Client - // RegistryURL is the URL for the registry. - RegistryURL string - // Allow a fallback to insecure transport when contacting the registry. - Insecure bool + // RegistryURL is the URL of the integrated Docker registry. + RegistryURL *url.URL } // Pruner knows how to prune istags, images, layers and image configs. @@ -161,68 +159,12 @@ type Pruner interface { type pruner struct { g graph.Graph algorithm pruneAlgorithm - registryPinger registryPinger registryClient *http.Client - registryURL string + registryURL *url.URL } var _ Pruner = &pruner{} -// registryPinger performs a health check against a registry. -type registryPinger interface { - // ping performs a health check against registry. - ping(registry string) error -} - -// defaultRegistryPinger implements registryPinger. -type defaultRegistryPinger struct { - client *http.Client - insecure bool -} - -func (drp *defaultRegistryPinger) ping(registry string) error { - healthCheck := func(proto, registry string) error { - // TODO: `/healthz` route is deprecated by `/`; remove it in future versions - healthResponse, err := drp.client.Get(fmt.Sprintf("%s://%s/healthz", proto, registry)) - if err != nil { - return err - } - defer healthResponse.Body.Close() - - if healthResponse.StatusCode != http.StatusOK { - return fmt.Errorf("unexpected status: %s", healthResponse.Status) - } - - return nil - } - - var errs []error - protos := make([]string, 0, 2) - protos = append(protos, "https") - if drp.insecure || netutils.IsPrivateAddress(registry) { - protos = append(protos, "http") - } - for _, proto := range protos { - glog.V(4).Infof("Trying %s for %s", proto, registry) - err := healthCheck(proto, registry) - if err == nil { - return nil - } - errs = append(errs, err) - glog.V(4).Infof("Error with %s for %s: %v", proto, registry, err) - } - - return kerrors.NewAggregate(errs) -} - -// dryRunRegistryPinger implements registryPinger. -type dryRunRegistryPinger struct { -} - -func (*dryRunRegistryPinger) ping(registry string) error { - return nil -} - // NewPruner creates a Pruner. // // Images younger than keepYoungerThan and images referenced by image streams @@ -296,20 +238,9 @@ func NewPruner(options PrunerOptions) Pruner { addBuildsToGraph(g, options.Builds) addDeploymentConfigsToGraph(g, options.DCs) - var rp registryPinger - if options.DryRun { - rp = &dryRunRegistryPinger{} - } else { - rp = &defaultRegistryPinger{ - client: options.RegistryClient, - insecure: options.Insecure, - } - } - return &pruner{ g: g, algorithm: algorithm, - registryPinger: rp, registryClient: options.RegistryClient, registryURL: options.RegistryURL, } @@ -743,7 +674,7 @@ func calculatePrunableImageComponents(g graph.Graph) []*imagegraph.ImageComponen } // pruneStreams removes references from all image streams' status.tags entries -// to prunable images, invoking streamPruner.DeleteImageStream for each updated +// to prunable images, invoking streamPruner.UpdateImageStream for each updated // stream. func pruneStreams(g graph.Graph, imageNodes []*imagegraph.ImageNode, streamPruner ImageStreamDeleter) []error { errs := []error{} @@ -756,43 +687,49 @@ func pruneStreams(g graph.Graph, imageNodes []*imagegraph.ImageNode, streamPrune continue } - stream := streamNode.ImageStream - updatedTags := sets.NewString() + if err := retry.RetryOnConflict(retry.DefaultRetry, func() error { + stream, err := streamPruner.GetImageStream(streamNode.ImageStream) + if err != nil { + return err + } + updatedTags := sets.NewString() - glog.V(4).Infof("Checking if ImageStream %s has references to image %s in status.tags", getName(stream), imageNode.Image.Name) + glog.V(4).Infof("Checking if ImageStream %s has references to image %s in status.tags", getName(stream), imageNode.Image.Name) - for tag, history := range stream.Status.Tags { - glog.V(4).Infof("Checking tag %q", tag) + for tag, history := range stream.Status.Tags { + glog.V(4).Infof("Checking tag %q", tag) - newHistory := imageapi.TagEventList{} + newHistory := imageapi.TagEventList{} - for i, tagEvent := range history.Items { - glog.V(4).Infof("Checking tag event %d with image %q", i, tagEvent.Image) + for i, tagEvent := range history.Items { + glog.V(4).Infof("Checking tag event %d with image %q", i, tagEvent.Image) - if tagEvent.Image != imageNode.Image.Name { - glog.V(4).Infof("Tag event doesn't match deleted image - keeping") - newHistory.Items = append(newHistory.Items, tagEvent) + if tagEvent.Image != imageNode.Image.Name { + glog.V(4).Infof("Tag event doesn't match deleted image - keeping") + newHistory.Items = append(newHistory.Items, tagEvent) + } else { + glog.V(4).Infof("Tag event matches deleted image - removing reference") + updatedTags.Insert(tag) + } + } + if len(newHistory.Items) == 0 { + glog.V(4).Infof("Removing tag %q from status.tags of ImageStream %s", tag, getName(stream)) + delete(stream.Status.Tags, tag) } else { - glog.V(4).Infof("Tag event matches deleted image - removing reference") - updatedTags.Insert(tag) + stream.Status.Tags[tag] = newHistory } } - if len(newHistory.Items) == 0 { - glog.V(4).Infof("Removing tag %q from status.tags of ImageStream %s", tag, getName(stream)) - delete(stream.Status.Tags, tag) - } else { - stream.Status.Tags[tag] = newHistory - } - } - updatedStream, err := streamPruner.DeleteImageStream(stream, imageNode.Image, updatedTags.List()) - if err != nil { + updatedStream, err := streamPruner.UpdateImageStream(stream, imageNode.Image, updatedTags.List()) + if err == nil { + streamNode.ImageStream = updatedStream + } + return err + }); err != nil { errs = append(errs, fmt.Errorf("error removing image %s from stream %s: %v", - imageNode.Image.Name, getName(stream), err)) + imageNode.Image.Name, getName(streamNode.ImageStream), err)) continue } - - streamNode.ImageStream = updatedStream } } glog.V(4).Infof("Done removing pruned image references from streams") @@ -812,73 +749,6 @@ func pruneImages(g graph.Graph, imageNodes []*imagegraph.ImageNode, imagePruner return errs } -// order younger images before older -type imgByAge []*imageapi.Image - -func (ba imgByAge) Len() int { return len(ba) } -func (ba imgByAge) Swap(i, j int) { ba[i], ba[j] = ba[j], ba[i] } -func (ba imgByAge) Less(i, j int) bool { - return ba[i].CreationTimestamp.After(ba[j].CreationTimestamp.Time) -} - -// order younger image stream before older -type isByAge []*imagegraph.ImageStreamNode - -func (ba isByAge) Len() int { return len(ba) } -func (ba isByAge) Swap(i, j int) { ba[i], ba[j] = ba[j], ba[i] } -func (ba isByAge) Less(i, j int) bool { - return ba[i].ImageStream.CreationTimestamp.After(ba[j].ImageStream.CreationTimestamp.Time) -} - -func (p *pruner) determineRegistry(imageNodes []*imagegraph.ImageNode, isNodes []*imagegraph.ImageStreamNode) (string, error) { - if len(p.registryURL) > 0 { - return p.registryURL, nil - } - - var pullSpec string - var managedImages []*imageapi.Image - - // 1st try to determine registry url from a pull spec of the youngest managed image - for _, node := range imageNodes { - if node.Image.Annotations[imageapi.ManagedByOpenShiftAnnotation] != "true" { - continue - } - managedImages = append(managedImages, node.Image) - } - // be sure to pick up the newest managed image which should have an up to date information - sort.Sort(imgByAge(managedImages)) - - if len(managedImages) > 0 { - pullSpec = managedImages[0].DockerImageReference - } else { - // 2nd try to get the pull spec from any image stream - // Sorting by creation timestamp may not get us up to date info. Modification time would be much - // better if there were such an attribute. - sort.Sort(isByAge(isNodes)) - for _, node := range isNodes { - if len(node.ImageStream.Status.DockerImageRepository) == 0 { - continue - } - pullSpec = node.ImageStream.Status.DockerImageRepository - } - } - - if len(pullSpec) == 0 { - return "", fmt.Errorf("no managed image found") - } - - ref, err := imageapi.ParseDockerImageReference(pullSpec) - if err != nil { - return "", fmt.Errorf("unable to parse %q: %v", pullSpec, err) - } - - if len(ref.Registry) == 0 { - return "", fmt.Errorf("%s does not include a registry", pullSpec) - } - - return ref.Registry, nil -} - // Run identifies images eligible for pruning, invoking imagePruner for each image, and then it identifies // image configs and layers eligible for pruning, invoking layerLinkPruner for each registry URL that has // layers or configs that can be pruned. @@ -896,16 +766,6 @@ func (p *pruner) Prune( return nil } - registryURL, err := p.determineRegistry(imageNodes, getImageStreamNodes(allNodes)) - if err != nil { - return fmt.Errorf("unable to determine registry: %v", err) - } - glog.V(1).Infof("Using registry: %s", registryURL) - - if err := p.registryPinger.ping(registryURL); err != nil { - return fmt.Errorf("error communicating with registry %s: %v", registryURL, err) - } - prunableImageNodes, prunableImageIDs := calculatePrunableImages(p.g, imageNodes) errs := []error{} @@ -917,9 +777,9 @@ func (p *pruner) Prune( graphWithoutPrunableImages := subgraphWithoutPrunableImages(p.g, prunableImageIDs) prunableComponents := calculatePrunableImageComponents(graphWithoutPrunableImages) - errs = append(errs, pruneImageComponents(p.g, p.registryClient, registryURL, prunableComponents, layerLinkPruner)...) - errs = append(errs, pruneBlobs(p.g, p.registryClient, registryURL, prunableComponents, blobPruner)...) - errs = append(errs, pruneManifests(p.g, p.registryClient, registryURL, prunableImageNodes, manifestPruner)...) + errs = append(errs, pruneImageComponents(p.g, p.registryClient, p.registryURL, prunableComponents, layerLinkPruner)...) + errs = append(errs, pruneBlobs(p.g, p.registryClient, p.registryURL, prunableComponents, blobPruner)...) + errs = append(errs, pruneManifests(p.g, p.registryClient, p.registryURL, prunableImageNodes, manifestPruner)...) if len(errs) > 0 { // If we had any errors removing image references from image streams or deleting @@ -964,7 +824,7 @@ func streamsReferencingImageComponent(g graph.Graph, cn *imagegraph.ImageCompone func pruneImageComponents( g graph.Graph, registryClient *http.Client, - registryURL string, + registryURL *url.URL, imageComponents []*imagegraph.ImageComponentNode, layerLinkDeleter LayerLinkDeleter, ) []error { @@ -975,12 +835,10 @@ func pruneImageComponents( streamNodes := streamsReferencingImageComponent(g, cn) for _, streamNode := range streamNodes { - stream := streamNode.ImageStream - streamName := fmt.Sprintf("%s/%s", stream.Namespace, stream.Name) - - glog.V(4).Infof("Pruning registry=%q, repo=%q, %s", registryURL, streamName, cn.Describe()) + streamName := getName(streamNode.ImageStream) + glog.V(4).Infof("Pruning repository %s/%s: %s", registryURL.Host, streamName, cn.Describe()) if err := layerLinkDeleter.DeleteLayerLink(registryClient, registryURL, streamName, cn.Component); err != nil { - errs = append(errs, fmt.Errorf("error pruning layer link %s in repo %q: %v", cn.Component, streamName, err)) + errs = append(errs, fmt.Errorf("error pruning layer link %s in the repository %s: %v", cn.Component, streamName, err)) } } } @@ -993,7 +851,7 @@ func pruneImageComponents( func pruneBlobs( g graph.Graph, registryClient *http.Client, - registryURL string, + registryURL *url.URL, componentNodes []*imagegraph.ImageComponentNode, blobPruner BlobDeleter, ) []error { @@ -1001,8 +859,8 @@ func pruneBlobs( for _, cn := range componentNodes { if err := blobPruner.DeleteBlob(registryClient, registryURL, cn.Component); err != nil { - errs = append(errs, fmt.Errorf("error removing blob from registry %s: blob %q: %v", - registryURL, cn.Component, err)) + errs = append(errs, fmt.Errorf("error removing blob %s from the registry %s: %v", + cn.Component, registryURL.Host, err)) } } @@ -1011,7 +869,13 @@ func pruneBlobs( // pruneManifests invokes manifestPruner.DeleteManifest for each repository // manifest to be deleted from the registry. -func pruneManifests(g graph.Graph, registryClient *http.Client, registryURL string, imageNodes []*imagegraph.ImageNode, manifestPruner ManifestDeleter) []error { +func pruneManifests( + g graph.Graph, + registryClient *http.Client, + registryURL *url.URL, + imageNodes []*imagegraph.ImageNode, + manifestPruner ManifestDeleter, +) []error { errs := []error{} for _, imageNode := range imageNodes { @@ -1021,12 +885,12 @@ func pruneManifests(g graph.Graph, registryClient *http.Client, registryURL stri continue } - stream := streamNode.ImageStream - repoName := fmt.Sprintf("%s/%s", stream.Namespace, stream.Name) + repoName := getName(streamNode.ImageStream) - glog.V(4).Infof("Pruning manifest for registry %q, repo %q, image %q", registryURL, repoName, imageNode.Image.Name) + glog.V(4).Infof("Pruning manifest %s in the repository %s/%s", imageNode.Image.Name, registryURL.Host, repoName) if err := manifestPruner.DeleteManifest(registryClient, registryURL, repoName, imageNode.Image.Name); err != nil { - errs = append(errs, fmt.Errorf("error pruning manifest for registry %q, repo %q, image %q: %v", registryURL, repoName, imageNode.Image.Name, err)) + errs = append(errs, fmt.Errorf("error pruning manifest %s in the repository %s/%s: %v", + imageNode.Image.Name, registryURL.Host, repoName, err)) } } } @@ -1067,73 +931,59 @@ func NewImageStreamDeleter(streams client.ImageStreamsNamespacer) ImageStreamDel } } -func (p *imageStreamDeleter) DeleteImageStream(stream *imageapi.ImageStream, image *imageapi.Image, updatedTags []string) (*imageapi.ImageStream, error) { +func (p *imageStreamDeleter) GetImageStream(stream *imageapi.ImageStream) (*imageapi.ImageStream, error) { + return p.streams.ImageStreams(stream.Namespace).Get(stream.Name, metav1.GetOptions{}) +} + +func (p *imageStreamDeleter) UpdateImageStream(stream *imageapi.ImageStream, image *imageapi.Image, updatedTags []string) (*imageapi.ImageStream, error) { glog.V(4).Infof("Updating ImageStream %s", getName(stream)) - glog.V(5).Infof("Updated stream: %#v", stream) - return p.streams.ImageStreams(stream.Namespace).UpdateStatus(stream) + is, err := p.streams.ImageStreams(stream.Namespace).UpdateStatus(stream) + if err == nil { + glog.V(5).Infof("Updated ImageStream: %#v", is) + } + return is, err } // deleteFromRegistry uses registryClient to send a DELETE request to the // provided url. It attempts an https request first; if that fails, it fails // back to http. func deleteFromRegistry(registryClient *http.Client, url string) error { - deleteFunc := func(proto, url string) error { - req, err := http.NewRequest("DELETE", url, nil) - if err != nil { - return err - } - - glog.V(4).Infof("Sending request to registry") - resp, err := registryClient.Do(req) - if err != nil { - if proto != "https" && strings.Contains(err.Error(), "malformed HTTP response") { - return fmt.Errorf("%v.\n* Are you trying to connect to a TLS-enabled registry without TLS?", err) - } - return err - } - defer resp.Body.Close() + req, err := http.NewRequest("DELETE", url, nil) + if err != nil { + return err + } - // TODO: investigate why we're getting non-existent layers, for now we're logging - // them out and continue working - if resp.StatusCode == http.StatusNotFound { - glog.Warningf("Unable to prune layer %s, returned %v", url, resp.Status) - return nil - } - // non-2xx/3xx response doesn't cause an error, so we need to check for it - // manually and return it to caller - if resp.StatusCode < http.StatusOK || resp.StatusCode >= http.StatusBadRequest { - return fmt.Errorf(resp.Status) - } - if resp.StatusCode != http.StatusNoContent && resp.StatusCode != http.StatusAccepted { - glog.V(1).Infof("Unexpected status code in response: %d", resp.StatusCode) - var response errcode.Errors - decoder := json.NewDecoder(resp.Body) - if err := decoder.Decode(&response); err != nil { - return err - } - glog.V(1).Infof("Response: %#v", response) - return &response - } + glog.V(5).Infof(`Sending request "%s %s" to the registry`, req.Method, req.URL.String()) + resp, err := registryClient.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + // TODO: investigate why we're getting non-existent layers, for now we're logging + // them out and continue working + if resp.StatusCode == http.StatusNotFound { + glog.Warningf("Unable to prune layer %s, returned %v", url, resp.Status) return nil } - var err error - for _, proto := range []string{"https", "http"} { - glog.V(4).Infof("Trying %s for %s", proto, url) - err = deleteFunc(proto, fmt.Sprintf("%s://%s", proto, url)) - if err == nil { - return nil - } + // non-2xx/3xx response doesn't cause an error, so we need to check for it + // manually and return it to caller + if resp.StatusCode < http.StatusOK || resp.StatusCode >= http.StatusBadRequest { + return fmt.Errorf(resp.Status) + } - if _, ok := err.(*errcode.Errors); ok { - // we got a response back from the registry, so return it + if resp.StatusCode != http.StatusNoContent && resp.StatusCode != http.StatusAccepted { + glog.V(1).Infof("Unexpected status code in response: %d", resp.StatusCode) + var response errcode.Errors + decoder := json.NewDecoder(resp.Body) + if err := decoder.Decode(&response); err != nil { return err } - - // we didn't get a success or a errcode.Errors response back from the registry - glog.V(4).Infof("Error with %s for %s: %v", proto, url, err) + glog.V(1).Infof("Response: %#v", response) + return &response } + return err } @@ -1147,9 +997,9 @@ func NewLayerLinkDeleter() LayerLinkDeleter { return &layerLinkDeleter{} } -func (p *layerLinkDeleter) DeleteLayerLink(registryClient *http.Client, registryURL, repoName, linkName string) error { - glog.V(4).Infof("Deleting layer link from registry %q: repo %q, layer link %q", registryURL, repoName, linkName) - return deleteFromRegistry(registryClient, fmt.Sprintf("%s/v2/%s/blobs/%s", registryURL, repoName, linkName)) +func (p *layerLinkDeleter) DeleteLayerLink(registryClient *http.Client, registryURL *url.URL, repoName, linkName string) error { + glog.V(4).Infof("Deleting layer link %s from repository %s/%s", linkName, registryURL.Host, repoName) + return deleteFromRegistry(registryClient, fmt.Sprintf("%s/v2/%s/blobs/%s", registryURL.String(), repoName, linkName)) } // blobDeleter removes a blob from the registry. @@ -1162,9 +1012,9 @@ func NewBlobDeleter() BlobDeleter { return &blobDeleter{} } -func (p *blobDeleter) DeleteBlob(registryClient *http.Client, registryURL, blob string) error { - glog.V(4).Infof("Deleting blob from registry %q: blob %q", registryURL, blob) - return deleteFromRegistry(registryClient, fmt.Sprintf("%s/admin/blobs/%s", registryURL, blob)) +func (p *blobDeleter) DeleteBlob(registryClient *http.Client, registryURL *url.URL, blob string) error { + glog.V(4).Infof("Deleting blob %s from registry %s", blob, registryURL.Host) + return deleteFromRegistry(registryClient, fmt.Sprintf("%s/admin/blobs/%s", registryURL.String(), blob)) } // manifestDeleter deletes repository manifest data from the registry. @@ -1177,9 +1027,9 @@ func NewManifestDeleter() ManifestDeleter { return &manifestDeleter{} } -func (p *manifestDeleter) DeleteManifest(registryClient *http.Client, registryURL, repoName, manifest string) error { - glog.V(4).Infof("Deleting manifest from registry %q: repo %q, manifest %q", registryURL, repoName, manifest) - return deleteFromRegistry(registryClient, fmt.Sprintf("%s/v2/%s/manifests/%s", registryURL, repoName, manifest)) +func (p *manifestDeleter) DeleteManifest(registryClient *http.Client, registryURL *url.URL, repoName, manifest string) error { + glog.V(4).Infof("Deleting manifest %s from repository %s/%s", manifest, registryURL.Host, repoName) + return deleteFromRegistry(registryClient, fmt.Sprintf("%s/v2/%s/manifests/%s", registryURL.String(), repoName, manifest)) } func getName(obj runtime.Object) string { diff --git a/vendor/github.com/openshift/origin/pkg/image/prune/prune_test.go b/vendor/github.com/openshift/origin/pkg/image/prune/prune_test.go index 0d5c8c4ce..a2e3a1152 100644 --- a/vendor/github.com/openshift/origin/pkg/image/prune/prune_test.go +++ b/vendor/github.com/openshift/origin/pkg/image/prune/prune_test.go @@ -2,11 +2,11 @@ package prune import ( "bytes" - "errors" "flag" "fmt" "io/ioutil" "net/http" + "net/url" "reflect" "testing" "time" @@ -21,6 +21,7 @@ import ( "k8s.io/client-go/rest/fake" clientgotesting "k8s.io/client-go/testing" kapi "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/staging/src/k8s.io/apimachinery/pkg/util/diff" "github.com/openshift/origin/pkg/api/graph" buildapi "github.com/openshift/origin/pkg/build/apis/build" @@ -30,16 +31,6 @@ import ( imagegraph "github.com/openshift/origin/pkg/image/graph/nodes" ) -type fakeRegistryPinger struct { - err error - requests []string -} - -func (f *fakeRegistryPinger) ping(registry string) error { - f.requests = append(f.requests, registry) - return f.err -} - func imageList(images ...imageapi.Image) imageapi.ImageList { return imageapi.ImageList{ Items: images, @@ -374,7 +365,11 @@ type fakeImageStreamDeleter struct { var _ ImageStreamDeleter = &fakeImageStreamDeleter{} -func (p *fakeImageStreamDeleter) DeleteImageStream(stream *imageapi.ImageStream, image *imageapi.Image, updatedTags []string) (*imageapi.ImageStream, error) { +func (p *fakeImageStreamDeleter) GetImageStream(stream *imageapi.ImageStream) (*imageapi.ImageStream, error) { + return stream, p.err +} + +func (p *fakeImageStreamDeleter) UpdateImageStream(stream *imageapi.ImageStream, image *imageapi.Image, updatedTags []string) (*imageapi.ImageStream, error) { p.invocations.Insert(fmt.Sprintf("%s/%s|%s", stream.Namespace, stream.Name, image.Name)) return stream, p.err } @@ -386,8 +381,8 @@ type fakeBlobDeleter struct { var _ BlobDeleter = &fakeBlobDeleter{} -func (p *fakeBlobDeleter) DeleteBlob(registryClient *http.Client, registryURL, blob string) error { - p.invocations.Insert(fmt.Sprintf("%s|%s", registryURL, blob)) +func (p *fakeBlobDeleter) DeleteBlob(registryClient *http.Client, registryURL *url.URL, blob string) error { + p.invocations.Insert(fmt.Sprintf("%s|%s", registryURL.String(), blob)) return p.err } @@ -398,8 +393,8 @@ type fakeLayerLinkDeleter struct { var _ LayerLinkDeleter = &fakeLayerLinkDeleter{} -func (p *fakeLayerLinkDeleter) DeleteLayerLink(registryClient *http.Client, registryURL, repo, layer string) error { - p.invocations.Insert(fmt.Sprintf("%s|%s|%s", registryURL, repo, layer)) +func (p *fakeLayerLinkDeleter) DeleteLayerLink(registryClient *http.Client, registryURL *url.URL, repo, layer string) error { + p.invocations.Insert(fmt.Sprintf("%s|%s|%s", registryURL.String(), repo, layer)) return p.err } @@ -410,8 +405,8 @@ type fakeManifestDeleter struct { var _ ManifestDeleter = &fakeManifestDeleter{} -func (p *fakeManifestDeleter) DeleteManifest(registryClient *http.Client, registryURL, repo, manifest string) error { - p.invocations.Insert(fmt.Sprintf("%s|%s|%s", registryURL, repo, manifest)) +func (p *fakeManifestDeleter) DeleteManifest(registryClient *http.Client, registryURL *url.URL, repo, manifest string) error { + p.invocations.Insert(fmt.Sprintf("%s|%s|%s", registryURL.String(), repo, manifest)) return p.err } @@ -420,11 +415,12 @@ var testCase = flag.String("testcase", "", "") func TestImagePruning(t *testing.T) { flag.Lookup("v").Value.Set(fmt.Sprint(*logLevel)) - registryURL := "registry.io" + registryHost := "registry.io" + registryURL := "https://" + registryHost tests := map[string]struct { pruneOverSizeLimit *bool - registryURLs []string + allImages *bool namespace string images imageapi.ImageList pods kapi.PodList @@ -440,36 +436,40 @@ func TestImagePruning(t *testing.T) { expectedBlobDeletions []string }{ "1 pod - phase pending - don't prune": { - images: imageList(image("sha256:0000000000000000000000000000000000000000000000000000000000000000", registryURL+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), - pods: podList(pod("foo", "pod1", kapi.PodPending, registryURL+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), + images: imageList(image("sha256:0000000000000000000000000000000000000000000000000000000000000000", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), + pods: podList(pod("foo", "pod1", kapi.PodPending, registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), expectedImageDeletions: []string{}, }, + "3 pods - last phase pending - don't prune": { - images: imageList(image("sha256:0000000000000000000000000000000000000000000000000000000000000000", registryURL+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), + images: imageList(image("sha256:0000000000000000000000000000000000000000000000000000000000000000", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), pods: podList( - pod("foo", "pod1", kapi.PodSucceeded, registryURL+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000"), - pod("foo", "pod2", kapi.PodSucceeded, registryURL+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000"), - pod("foo", "pod3", kapi.PodPending, registryURL+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000"), + pod("foo", "pod1", kapi.PodSucceeded, registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000"), + pod("foo", "pod2", kapi.PodSucceeded, registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000"), + pod("foo", "pod3", kapi.PodPending, registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000"), ), expectedImageDeletions: []string{}, }, + "1 pod - phase running - don't prune": { - images: imageList(image("sha256:0000000000000000000000000000000000000000000000000000000000000000", registryURL+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), - pods: podList(pod("foo", "pod1", kapi.PodRunning, registryURL+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), + images: imageList(image("sha256:0000000000000000000000000000000000000000000000000000000000000000", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), + pods: podList(pod("foo", "pod1", kapi.PodRunning, registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), expectedImageDeletions: []string{}, }, + "3 pods - last phase running - don't prune": { - images: imageList(image("sha256:0000000000000000000000000000000000000000000000000000000000000000", registryURL+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), + images: imageList(image("sha256:0000000000000000000000000000000000000000000000000000000000000000", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), pods: podList( - pod("foo", "pod1", kapi.PodSucceeded, registryURL+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000"), - pod("foo", "pod2", kapi.PodSucceeded, registryURL+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000"), - pod("foo", "pod3", kapi.PodRunning, registryURL+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000"), + pod("foo", "pod1", kapi.PodSucceeded, registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000"), + pod("foo", "pod2", kapi.PodSucceeded, registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000"), + pod("foo", "pod3", kapi.PodRunning, registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000"), ), expectedImageDeletions: []string{}, }, + "pod phase succeeded - prune": { - images: imageList(image("sha256:0000000000000000000000000000000000000000000000000000000000000000", registryURL+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), - pods: podList(pod("foo", "pod1", kapi.PodSucceeded, registryURL+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), + images: imageList(image("sha256:0000000000000000000000000000000000000000000000000000000000000000", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), + pods: podList(pod("foo", "pod1", kapi.PodSucceeded, registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), expectedImageDeletions: []string{"sha256:0000000000000000000000000000000000000000000000000000000000000000"}, expectedBlobDeletions: []string{ registryURL + "|" + layer1, @@ -479,22 +479,25 @@ func TestImagePruning(t *testing.T) { registryURL + "|" + layer5, }, }, + "pod phase succeeded, pod less than min pruning age - don't prune": { - images: imageList(image("sha256:0000000000000000000000000000000000000000000000000000000000000000", registryURL+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), - pods: podList(agedPod("foo", "pod1", kapi.PodSucceeded, 5, registryURL+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), + images: imageList(image("sha256:0000000000000000000000000000000000000000000000000000000000000000", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), + pods: podList(agedPod("foo", "pod1", kapi.PodSucceeded, 5, registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), expectedImageDeletions: []string{}, }, + "pod phase succeeded, image less than min pruning age - don't prune": { - images: imageList(agedImage("sha256:0000000000000000000000000000000000000000000000000000000000000000", registryURL+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000", 5)), - pods: podList(pod("foo", "pod1", kapi.PodSucceeded, registryURL+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), + images: imageList(agedImage("sha256:0000000000000000000000000000000000000000000000000000000000000000", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000", 5)), + pods: podList(pod("foo", "pod1", kapi.PodSucceeded, registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), expectedImageDeletions: []string{}, }, + "pod phase failed - prune": { - images: imageList(image("sha256:0000000000000000000000000000000000000000000000000000000000000000", registryURL+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), + images: imageList(image("sha256:0000000000000000000000000000000000000000000000000000000000000000", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), pods: podList( - pod("foo", "pod1", kapi.PodFailed, registryURL+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000"), - pod("foo", "pod2", kapi.PodFailed, registryURL+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000"), - pod("foo", "pod3", kapi.PodFailed, registryURL+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000"), + pod("foo", "pod1", kapi.PodFailed, registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000"), + pod("foo", "pod2", kapi.PodFailed, registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000"), + pod("foo", "pod3", kapi.PodFailed, registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000"), ), expectedImageDeletions: []string{"sha256:0000000000000000000000000000000000000000000000000000000000000000"}, expectedBlobDeletions: []string{ @@ -505,12 +508,13 @@ func TestImagePruning(t *testing.T) { registryURL + "|" + layer5, }, }, + "pod phase unknown - prune": { - images: imageList(image("sha256:0000000000000000000000000000000000000000000000000000000000000000", registryURL+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), + images: imageList(image("sha256:0000000000000000000000000000000000000000000000000000000000000000", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), pods: podList( - pod("foo", "pod1", kapi.PodUnknown, registryURL+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000"), - pod("foo", "pod2", kapi.PodUnknown, registryURL+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000"), - pod("foo", "pod3", kapi.PodUnknown, registryURL+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000"), + pod("foo", "pod1", kapi.PodUnknown, registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000"), + pod("foo", "pod2", kapi.PodUnknown, registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000"), + pod("foo", "pod3", kapi.PodUnknown, registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000"), ), expectedImageDeletions: []string{"sha256:0000000000000000000000000000000000000000000000000000000000000000"}, expectedBlobDeletions: []string{ @@ -521,8 +525,9 @@ func TestImagePruning(t *testing.T) { registryURL + "|" + layer5, }, }, + "pod container image not parsable": { - images: imageList(image("sha256:0000000000000000000000000000000000000000000000000000000000000000", registryURL+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), + images: imageList(image("sha256:0000000000000000000000000000000000000000000000000000000000000000", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), pods: podList( pod("foo", "pod1", kapi.PodRunning, "a/b/c/d/e"), ), @@ -535,8 +540,9 @@ func TestImagePruning(t *testing.T) { registryURL + "|" + layer5, }, }, + "pod container image doesn't have an id": { - images: imageList(image("sha256:0000000000000000000000000000000000000000000000000000000000000000", registryURL+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), + images: imageList(image("sha256:0000000000000000000000000000000000000000000000000000000000000000", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), pods: podList( pod("foo", "pod1", kapi.PodRunning, "foo/bar:latest"), ), @@ -549,10 +555,11 @@ func TestImagePruning(t *testing.T) { registryURL + "|" + layer5, }, }, + "pod refers to image not in graph": { - images: imageList(image("sha256:0000000000000000000000000000000000000000000000000000000000000000", registryURL+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), + images: imageList(image("sha256:0000000000000000000000000000000000000000000000000000000000000000", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), pods: podList( - pod("foo", "pod1", kapi.PodRunning, registryURL+"/foo/bar@otherid"), + pod("foo", "pod1", kapi.PodRunning, registryHost+"/foo/bar@otherid"), ), expectedImageDeletions: []string{"sha256:0000000000000000000000000000000000000000000000000000000000000000"}, expectedBlobDeletions: []string{ @@ -563,193 +570,293 @@ func TestImagePruning(t *testing.T) { registryURL + "|" + layer5, }, }, + "referenced by rc - don't prune": { - images: imageList(image("sha256:0000000000000000000000000000000000000000000000000000000000000000", registryURL+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), - rcs: rcList(rc("foo", "rc1", registryURL+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), + images: imageList(image("sha256:0000000000000000000000000000000000000000000000000000000000000000", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), + rcs: rcList(rc("foo", "rc1", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), expectedImageDeletions: []string{}, }, + "referenced by dc - don't prune": { - images: imageList(image("sha256:0000000000000000000000000000000000000000000000000000000000000000", registryURL+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), - dcs: dcList(dc("foo", "rc1", registryURL+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), + images: imageList(image("sha256:0000000000000000000000000000000000000000000000000000000000000000", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), + dcs: dcList(dc("foo", "rc1", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), expectedImageDeletions: []string{}, }, + "referenced by bc - sti - ImageStreamImage - don't prune": { - images: imageList(image("sha256:0000000000000000000000000000000000000000000000000000000000000000", registryURL+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), + images: imageList(image("sha256:0000000000000000000000000000000000000000000000000000000000000000", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), bcs: bcList(bc("foo", "bc1", "source", "ImageStreamImage", "foo", "bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), expectedImageDeletions: []string{}, }, + "referenced by bc - docker - ImageStreamImage - don't prune": { - images: imageList(image("sha256:0000000000000000000000000000000000000000000000000000000000000000", registryURL+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), + images: imageList(image("sha256:0000000000000000000000000000000000000000000000000000000000000000", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), bcs: bcList(bc("foo", "bc1", "docker", "ImageStreamImage", "foo", "bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), expectedImageDeletions: []string{}, }, + "referenced by bc - custom - ImageStreamImage - don't prune": { - images: imageList(image("sha256:0000000000000000000000000000000000000000000000000000000000000000", registryURL+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), + images: imageList(image("sha256:0000000000000000000000000000000000000000000000000000000000000000", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), bcs: bcList(bc("foo", "bc1", "custom", "ImageStreamImage", "foo", "bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), expectedImageDeletions: []string{}, }, + "referenced by bc - sti - DockerImage - don't prune": { - images: imageList(image("sha256:0000000000000000000000000000000000000000000000000000000000000000", registryURL+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), - bcs: bcList(bc("foo", "bc1", "source", "DockerImage", "foo", registryURL+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), + images: imageList(image("sha256:0000000000000000000000000000000000000000000000000000000000000000", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), + bcs: bcList(bc("foo", "bc1", "source", "DockerImage", "foo", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), expectedImageDeletions: []string{}, }, + "referenced by bc - docker - DockerImage - don't prune": { - images: imageList(image("sha256:0000000000000000000000000000000000000000000000000000000000000000", registryURL+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), - bcs: bcList(bc("foo", "bc1", "docker", "DockerImage", "foo", registryURL+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), + images: imageList(image("sha256:0000000000000000000000000000000000000000000000000000000000000000", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), + bcs: bcList(bc("foo", "bc1", "docker", "DockerImage", "foo", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), expectedImageDeletions: []string{}, }, + "referenced by bc - custom - DockerImage - don't prune": { - images: imageList(image("sha256:0000000000000000000000000000000000000000000000000000000000000000", registryURL+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), - bcs: bcList(bc("foo", "bc1", "custom", "DockerImage", "foo", registryURL+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), + images: imageList(image("sha256:0000000000000000000000000000000000000000000000000000000000000000", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), + bcs: bcList(bc("foo", "bc1", "custom", "DockerImage", "foo", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), expectedImageDeletions: []string{}, }, + "referenced by build - sti - ImageStreamImage - don't prune": { - images: imageList(image("sha256:0000000000000000000000000000000000000000000000000000000000000000", registryURL+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), + images: imageList(image("sha256:0000000000000000000000000000000000000000000000000000000000000000", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), builds: buildList(build("foo", "build1", "source", "ImageStreamImage", "foo", "bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), expectedImageDeletions: []string{}, }, + "referenced by build - docker - ImageStreamImage - don't prune": { - images: imageList(image("sha256:0000000000000000000000000000000000000000000000000000000000000000", registryURL+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), + images: imageList(image("sha256:0000000000000000000000000000000000000000000000000000000000000000", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), builds: buildList(build("foo", "build1", "docker", "ImageStreamImage", "foo", "bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), expectedImageDeletions: []string{}, }, + "referenced by build - custom - ImageStreamImage - don't prune": { - images: imageList(image("sha256:0000000000000000000000000000000000000000000000000000000000000000", registryURL+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), + images: imageList(image("sha256:0000000000000000000000000000000000000000000000000000000000000000", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), builds: buildList(build("foo", "build1", "custom", "ImageStreamImage", "foo", "bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), expectedImageDeletions: []string{}, }, + "referenced by build - sti - DockerImage - don't prune": { - images: imageList(image("sha256:0000000000000000000000000000000000000000000000000000000000000000", registryURL+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), - builds: buildList(build("foo", "build1", "source", "DockerImage", "foo", registryURL+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), + images: imageList(image("sha256:0000000000000000000000000000000000000000000000000000000000000000", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), + builds: buildList(build("foo", "build1", "source", "DockerImage", "foo", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), expectedImageDeletions: []string{}, }, + "referenced by build - docker - DockerImage - don't prune": { - images: imageList(image("sha256:0000000000000000000000000000000000000000000000000000000000000000", registryURL+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), - builds: buildList(build("foo", "build1", "docker", "DockerImage", "foo", registryURL+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), + images: imageList(image("sha256:0000000000000000000000000000000000000000000000000000000000000000", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), + builds: buildList(build("foo", "build1", "docker", "DockerImage", "foo", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), expectedImageDeletions: []string{}, }, + "referenced by build - custom - DockerImage - don't prune": { - images: imageList(image("sha256:0000000000000000000000000000000000000000000000000000000000000000", registryURL+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), - builds: buildList(build("foo", "build1", "custom", "DockerImage", "foo", registryURL+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), + images: imageList(image("sha256:0000000000000000000000000000000000000000000000000000000000000000", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), + builds: buildList(build("foo", "build1", "custom", "DockerImage", "foo", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), expectedImageDeletions: []string{}, }, + "image stream - keep most recent n images": { images: imageList( unmanagedImage("sha256:0000000000000000000000000000000000000000000000000000000000000000", "otherregistry/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000", false, "", ""), - image("sha256:0000000000000000000000000000000000000000000000000000000000000002", registryURL+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000002"), - image("sha256:0000000000000000000000000000000000000000000000000000000000000003", registryURL+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000003"), - image("sha256:0000000000000000000000000000000000000000000000000000000000000004", registryURL+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000004"), + image("sha256:0000000000000000000000000000000000000000000000000000000000000002", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000002"), + image("sha256:0000000000000000000000000000000000000000000000000000000000000003", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000003"), + image("sha256:0000000000000000000000000000000000000000000000000000000000000004", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000004"), ), streams: streamList( - stream(registryURL, "foo", "bar", tags( + stream(registryHost, "foo", "bar", tags( tag("latest", tagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000000", "otherregistry/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000"), - tagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000002", registryURL+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000002"), - tagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000003", registryURL+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000003"), - tagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000004", registryURL+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000004"), + tagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000002", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000002"), + tagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000003", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000003"), + tagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000004", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000004"), ), )), ), expectedImageDeletions: []string{"sha256:0000000000000000000000000000000000000000000000000000000000000004"}, expectedStreamUpdates: []string{"foo/bar|sha256:0000000000000000000000000000000000000000000000000000000000000004"}, }, + "image stream - same manifest listed multiple times in tag history": { images: imageList( - image("sha256:0000000000000000000000000000000000000000000000000000000000000001", registryURL+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000001"), - image("sha256:0000000000000000000000000000000000000000000000000000000000000002", registryURL+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000002"), + image("sha256:0000000000000000000000000000000000000000000000000000000000000001", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000001"), + image("sha256:0000000000000000000000000000000000000000000000000000000000000002", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000002"), ), streams: streamList( - stream(registryURL, "foo", "bar", tags( + stream(registryHost, "foo", "bar", tags( tag("latest", - tagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000001", registryURL+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000001"), - tagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000002", registryURL+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000002"), - tagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000001", registryURL+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000001"), - tagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000002", registryURL+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000002"), + tagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000001", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000001"), + tagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000002", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000002"), + tagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000001", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000001"), + tagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000002", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000002"), ), )), ), }, + "image stream age less than min pruning age - don't prune": { images: imageList( - image("sha256:0000000000000000000000000000000000000000000000000000000000000000", registryURL+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000"), - image("sha256:0000000000000000000000000000000000000000000000000000000000000002", registryURL+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000002"), - image("sha256:0000000000000000000000000000000000000000000000000000000000000003", registryURL+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000003"), - image("sha256:0000000000000000000000000000000000000000000000000000000000000004", registryURL+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000004"), + image("sha256:0000000000000000000000000000000000000000000000000000000000000000", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000"), + image("sha256:0000000000000000000000000000000000000000000000000000000000000002", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000002"), + image("sha256:0000000000000000000000000000000000000000000000000000000000000003", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000003"), + image("sha256:0000000000000000000000000000000000000000000000000000000000000004", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000004"), ), streams: streamList( - agedStream(registryURL, "foo", "bar", 5, tags( + agedStream(registryHost, "foo", "bar", 5, tags( tag("latest", - tagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000000", registryURL+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000"), - tagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000002", registryURL+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000002"), - tagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000003", registryURL+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000003"), - tagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000004", registryURL+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000004"), + tagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000000", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000"), + tagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000002", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000002"), + tagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000003", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000003"), + tagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000004", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000004"), ), )), ), expectedImageDeletions: []string{}, expectedStreamUpdates: []string{}, }, + "multiple resources pointing to image - don't prune": { images: imageList( - image("sha256:0000000000000000000000000000000000000000000000000000000000000000", registryURL+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000"), - image("sha256:0000000000000000000000000000000000000000000000000000000000000002", registryURL+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000002"), + image("sha256:0000000000000000000000000000000000000000000000000000000000000000", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000"), + image("sha256:0000000000000000000000000000000000000000000000000000000000000002", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000002"), ), streams: streamList( - stream(registryURL, "foo", "bar", tags( + stream(registryHost, "foo", "bar", tags( tag("latest", - tagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000000", registryURL+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000"), - tagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000002", registryURL+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000002"), + tagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000000", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000"), + tagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000002", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000002"), ), )), ), - rcs: rcList(rc("foo", "rc1", registryURL+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000002")), - pods: podList(pod("foo", "pod1", kapi.PodRunning, registryURL+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000002")), - dcs: dcList(dc("foo", "rc1", registryURL+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), - bcs: bcList(bc("foo", "bc1", "source", "DockerImage", "foo", registryURL+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), + rcs: rcList(rc("foo", "rc1", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000002")), + pods: podList(pod("foo", "pod1", kapi.PodRunning, registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000002")), + dcs: dcList(dc("foo", "rc1", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), + bcs: bcList(bc("foo", "bc1", "source", "DockerImage", "foo", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), builds: buildList(build("foo", "build1", "custom", "ImageStreamImage", "foo", "bar@sha256:0000000000000000000000000000000000000000000000000000000000000000")), expectedImageDeletions: []string{}, expectedStreamUpdates: []string{}, }, + "image with nil annotations": { + images: imageList( + unmanagedImage("sha256:0000000000000000000000000000000000000000000000000000000000000000", "someregistry/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000", false, "", ""), + ), + expectedImageDeletions: []string{"sha256:0000000000000000000000000000000000000000000000000000000000000000"}, + expectedStreamUpdates: []string{}, + }, + + "prune all-images=true image with nil annotations": { + allImages: newBool(true), + images: imageList( + unmanagedImage("sha256:0000000000000000000000000000000000000000000000000000000000000000", "someregistry/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000", false, "", ""), + ), + expectedImageDeletions: []string{"sha256:0000000000000000000000000000000000000000000000000000000000000000"}, + expectedStreamUpdates: []string{}, + }, + + "prune all-images=false image with nil annotations": { + allImages: newBool(false), images: imageList( unmanagedImage("sha256:0000000000000000000000000000000000000000000000000000000000000000", "someregistry/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000", false, "", ""), ), expectedImageDeletions: []string{}, expectedStreamUpdates: []string{}, }, + "image missing managed annotation": { images: imageList( unmanagedImage("sha256:0000000000000000000000000000000000000000000000000000000000000000", "someregistry/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000", true, "foo", "bar"), ), - expectedImageDeletions: []string{}, + expectedImageDeletions: []string{"sha256:0000000000000000000000000000000000000000000000000000000000000000"}, expectedStreamUpdates: []string{}, }, + "image with managed annotation != true": { images: imageList( unmanagedImage("sha256:0000000000000000000000000000000000000000000000000000000000000000", "someregistry/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000", true, imageapi.ManagedByOpenShiftAnnotation, "false"), - unmanagedImage("sha256:0000000000000000000000000000000000000000000000000000000000000000", "someregistry/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000", true, imageapi.ManagedByOpenShiftAnnotation, "0"), - unmanagedImage("sha256:0000000000000000000000000000000000000000000000000000000000000000", "someregistry/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000", true, imageapi.ManagedByOpenShiftAnnotation, "1"), - unmanagedImage("sha256:0000000000000000000000000000000000000000000000000000000000000000", "someregistry/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000", true, imageapi.ManagedByOpenShiftAnnotation, "True"), - unmanagedImage("sha256:0000000000000000000000000000000000000000000000000000000000000000", "someregistry/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000", true, imageapi.ManagedByOpenShiftAnnotation, "yes"), - unmanagedImage("sha256:0000000000000000000000000000000000000000000000000000000000000000", "someregistry/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000", true, imageapi.ManagedByOpenShiftAnnotation, "Yes"), + unmanagedImage("sha256:0000000000000000000000000000000000000000000000000000000000000001", "someregistry/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000", true, imageapi.ManagedByOpenShiftAnnotation, "0"), + unmanagedImage("sha256:0000000000000000000000000000000000000000000000000000000000000002", "someregistry/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000", true, imageapi.ManagedByOpenShiftAnnotation, "1"), + unmanagedImage("sha256:0000000000000000000000000000000000000000000000000000000000000003", "someregistry/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000", true, imageapi.ManagedByOpenShiftAnnotation, "True"), + unmanagedImage("sha256:0000000000000000000000000000000000000000000000000000000000000004", "someregistry/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000", true, imageapi.ManagedByOpenShiftAnnotation, "yes"), + unmanagedImage("sha256:0000000000000000000000000000000000000000000000000000000000000005", "someregistry/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000", true, imageapi.ManagedByOpenShiftAnnotation, "Yes"), + ), + expectedImageDeletions: []string{ + "sha256:0000000000000000000000000000000000000000000000000000000000000000", + "sha256:0000000000000000000000000000000000000000000000000000000000000001", + "sha256:0000000000000000000000000000000000000000000000000000000000000002", + "sha256:0000000000000000000000000000000000000000000000000000000000000003", + "sha256:0000000000000000000000000000000000000000000000000000000000000004", + "sha256:0000000000000000000000000000000000000000000000000000000000000005", + }, + expectedStreamUpdates: []string{}, + }, + + "prune all-images=true with image missing managed annotation": { + allImages: newBool(true), + images: imageList( + unmanagedImage("sha256:0000000000000000000000000000000000000000000000000000000000000000", "someregistry/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000", true, "foo", "bar"), + ), + expectedImageDeletions: []string{"sha256:0000000000000000000000000000000000000000000000000000000000000000"}, + expectedStreamUpdates: []string{}, + }, + + "prune all-images=true with image with managed annotation != true": { + allImages: newBool(true), + images: imageList( + unmanagedImage("sha256:0000000000000000000000000000000000000000000000000000000000000000", "someregistry/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000", true, imageapi.ManagedByOpenShiftAnnotation, "false"), + unmanagedImage("sha256:0000000000000000000000000000000000000000000000000000000000000001", "someregistry/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000", true, imageapi.ManagedByOpenShiftAnnotation, "0"), + unmanagedImage("sha256:0000000000000000000000000000000000000000000000000000000000000002", "someregistry/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000", true, imageapi.ManagedByOpenShiftAnnotation, "1"), + unmanagedImage("sha256:0000000000000000000000000000000000000000000000000000000000000003", "someregistry/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000", true, imageapi.ManagedByOpenShiftAnnotation, "True"), + unmanagedImage("sha256:0000000000000000000000000000000000000000000000000000000000000004", "someregistry/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000", true, imageapi.ManagedByOpenShiftAnnotation, "yes"), + unmanagedImage("sha256:0000000000000000000000000000000000000000000000000000000000000005", "someregistry/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000", true, imageapi.ManagedByOpenShiftAnnotation, "Yes"), + ), + expectedImageDeletions: []string{ + "sha256:0000000000000000000000000000000000000000000000000000000000000000", + "sha256:0000000000000000000000000000000000000000000000000000000000000001", + "sha256:0000000000000000000000000000000000000000000000000000000000000002", + "sha256:0000000000000000000000000000000000000000000000000000000000000003", + "sha256:0000000000000000000000000000000000000000000000000000000000000004", + "sha256:0000000000000000000000000000000000000000000000000000000000000005", + }, + expectedStreamUpdates: []string{}, + }, + + "prune all-images=false with image missing managed annotation": { + allImages: newBool(false), + images: imageList( + unmanagedImage("sha256:0000000000000000000000000000000000000000000000000000000000000000", "someregistry/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000", true, "foo", "bar"), + ), + expectedImageDeletions: []string{}, + expectedStreamUpdates: []string{}, + }, + + "prune all-images=false with image with managed annotation != true": { + allImages: newBool(false), + images: imageList( + unmanagedImage("sha256:0000000000000000000000000000000000000000000000000000000000000000", "someregistry/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000", true, imageapi.ManagedByOpenShiftAnnotation, "false"), + unmanagedImage("sha256:0000000000000000000000000000000000000000000000000000000000000001", "someregistry/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000", true, imageapi.ManagedByOpenShiftAnnotation, "0"), + unmanagedImage("sha256:0000000000000000000000000000000000000000000000000000000000000002", "someregistry/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000", true, imageapi.ManagedByOpenShiftAnnotation, "1"), + unmanagedImage("sha256:0000000000000000000000000000000000000000000000000000000000000003", "someregistry/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000", true, imageapi.ManagedByOpenShiftAnnotation, "True"), + unmanagedImage("sha256:0000000000000000000000000000000000000000000000000000000000000004", "someregistry/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000", true, imageapi.ManagedByOpenShiftAnnotation, "yes"), + unmanagedImage("sha256:0000000000000000000000000000000000000000000000000000000000000005", "someregistry/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000", true, imageapi.ManagedByOpenShiftAnnotation, "Yes"), ), expectedImageDeletions: []string{}, expectedStreamUpdates: []string{}, }, + "image with layers": { images: imageList( - imageWithLayers("sha256:0000000000000000000000000000000000000000000000000000000000000001", registryURL+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000001", &config1, "layer1", "layer2", "layer3", "layer4"), - imageWithLayers("sha256:0000000000000000000000000000000000000000000000000000000000000002", registryURL+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000002", &config2, "layer1", "layer2", "layer3", "layer4"), - imageWithLayers("sha256:0000000000000000000000000000000000000000000000000000000000000003", registryURL+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000003", nil, "layer1", "layer2", "layer3", "layer4"), - imageWithLayers("sha256:0000000000000000000000000000000000000000000000000000000000000004", registryURL+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000004", nil, "layer5", "layer6", "layer7", "layer8"), + imageWithLayers("sha256:0000000000000000000000000000000000000000000000000000000000000001", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000001", &config1, "layer1", "layer2", "layer3", "layer4"), + imageWithLayers("sha256:0000000000000000000000000000000000000000000000000000000000000002", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000002", &config2, "layer1", "layer2", "layer3", "layer4"), + imageWithLayers("sha256:0000000000000000000000000000000000000000000000000000000000000003", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000003", nil, "layer1", "layer2", "layer3", "layer4"), + imageWithLayers("sha256:0000000000000000000000000000000000000000000000000000000000000004", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000004", nil, "layer5", "layer6", "layer7", "layer8"), ), streams: streamList( - stream(registryURL, "foo", "bar", tags( + stream(registryHost, "foo", "bar", tags( tag("latest", - tagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000001", registryURL+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000001"), - tagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000002", registryURL+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000002"), - tagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000003", registryURL+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000003"), - tagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000004", registryURL+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000004"), + tagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000001", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000001"), + tagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000002", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000002"), + tagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000003", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000003"), + tagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000004", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000004"), ), )), ), @@ -768,21 +875,22 @@ func TestImagePruning(t *testing.T) { registryURL + "|layer8", }, }, + "images with duplicate layers and configs": { images: imageList( - imageWithLayers("sha256:0000000000000000000000000000000000000000000000000000000000000001", registryURL+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000001", &config1, "layer1", "layer2", "layer3", "layer4"), - imageWithLayers("sha256:0000000000000000000000000000000000000000000000000000000000000002", registryURL+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000002", &config1, "layer1", "layer2", "layer3", "layer4"), - imageWithLayers("sha256:0000000000000000000000000000000000000000000000000000000000000003", registryURL+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000003", &config1, "layer1", "layer2", "layer3", "layer4"), - imageWithLayers("sha256:0000000000000000000000000000000000000000000000000000000000000004", registryURL+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000004", &config2, "layer5", "layer6", "layer7", "layer8"), - imageWithLayers("sha256:0000000000000000000000000000000000000000000000000000000000000005", registryURL+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000005", &config2, "layer5", "layer6", "layer9", "layerX"), + imageWithLayers("sha256:0000000000000000000000000000000000000000000000000000000000000001", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000001", &config1, "layer1", "layer2", "layer3", "layer4"), + imageWithLayers("sha256:0000000000000000000000000000000000000000000000000000000000000002", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000002", &config1, "layer1", "layer2", "layer3", "layer4"), + imageWithLayers("sha256:0000000000000000000000000000000000000000000000000000000000000003", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000003", &config1, "layer1", "layer2", "layer3", "layer4"), + imageWithLayers("sha256:0000000000000000000000000000000000000000000000000000000000000004", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000004", &config2, "layer5", "layer6", "layer7", "layer8"), + imageWithLayers("sha256:0000000000000000000000000000000000000000000000000000000000000005", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000005", &config2, "layer5", "layer6", "layer9", "layerX"), ), streams: streamList( - stream(registryURL, "foo", "bar", tags( + stream(registryHost, "foo", "bar", tags( tag("latest", - tagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000001", registryURL+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000001"), - tagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000002", registryURL+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000002"), - tagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000003", registryURL+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000003"), - tagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000004", registryURL+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000004"), + tagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000001", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000001"), + tagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000002", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000002"), + tagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000003", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000003"), + tagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000004", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000004"), ), )), ), @@ -805,19 +913,20 @@ func TestImagePruning(t *testing.T) { registryURL + "|layerX", }, }, + "image exceeding limits": { pruneOverSizeLimit: newBool(true), images: imageList( unmanagedImage("sha256:0000000000000000000000000000000000000000000000000000000000000000", "otherregistry/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000", false, "", ""), - sizedImage("sha256:0000000000000000000000000000000000000000000000000000000000000002", registryURL+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000002", 100, nil), - sizedImage("sha256:0000000000000000000000000000000000000000000000000000000000000003", registryURL+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000003", 200, nil), + sizedImage("sha256:0000000000000000000000000000000000000000000000000000000000000002", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000002", 100, nil), + sizedImage("sha256:0000000000000000000000000000000000000000000000000000000000000003", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000003", 200, nil), ), streams: streamList( - stream(registryURL, "foo", "bar", tags( + stream(registryHost, "foo", "bar", tags( tag("latest", tagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000000", "otherregistry/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000"), - tagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000002", registryURL+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000002"), - tagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000003", registryURL+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000003"), + tagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000002", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000002"), + tagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000003", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000003"), ), )), ), @@ -827,25 +936,26 @@ func TestImagePruning(t *testing.T) { expectedImageDeletions: []string{"sha256:0000000000000000000000000000000000000000000000000000000000000003"}, expectedStreamUpdates: []string{"foo/bar|sha256:0000000000000000000000000000000000000000000000000000000000000003"}, }, + "multiple images in different namespaces exceeding different limits": { pruneOverSizeLimit: newBool(true), images: imageList( - sizedImage("sha256:0000000000000000000000000000000000000000000000000000000000000001", registryURL+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000001", 100, nil), - sizedImage("sha256:0000000000000000000000000000000000000000000000000000000000000002", registryURL+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000002", 200, nil), - sizedImage("sha256:0000000000000000000000000000000000000000000000000000000000000003", registryURL+"/bar/foo@sha256:0000000000000000000000000000000000000000000000000000000000000003", 500, nil), - sizedImage("sha256:0000000000000000000000000000000000000000000000000000000000000004", registryURL+"/bar/foo@sha256:0000000000000000000000000000000000000000000000000000000000000004", 600, nil), + sizedImage("sha256:0000000000000000000000000000000000000000000000000000000000000001", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000001", 100, nil), + sizedImage("sha256:0000000000000000000000000000000000000000000000000000000000000002", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000002", 200, nil), + sizedImage("sha256:0000000000000000000000000000000000000000000000000000000000000003", registryHost+"/bar/foo@sha256:0000000000000000000000000000000000000000000000000000000000000003", 500, nil), + sizedImage("sha256:0000000000000000000000000000000000000000000000000000000000000004", registryHost+"/bar/foo@sha256:0000000000000000000000000000000000000000000000000000000000000004", 600, nil), ), streams: streamList( - stream(registryURL, "foo", "bar", tags( + stream(registryHost, "foo", "bar", tags( tag("latest", - tagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000001", registryURL+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000001"), - tagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000002", registryURL+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000002"), + tagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000001", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000001"), + tagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000002", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000002"), ), )), - stream(registryURL, "bar", "foo", tags( + stream(registryHost, "bar", "foo", tags( tag("latest", - tagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000003", registryURL+"/bar/foo@sha256:0000000000000000000000000000000000000000000000000000000000000003"), - tagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000004", registryURL+"/bar/foo@sha256:0000000000000000000000000000000000000000000000000000000000000004"), + tagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000003", registryHost+"/bar/foo@sha256:0000000000000000000000000000000000000000000000000000000000000003"), + tagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000004", registryHost+"/bar/foo@sha256:0000000000000000000000000000000000000000000000000000000000000004"), ), )), ), @@ -856,19 +966,20 @@ func TestImagePruning(t *testing.T) { expectedImageDeletions: []string{"sha256:0000000000000000000000000000000000000000000000000000000000000002", "sha256:0000000000000000000000000000000000000000000000000000000000000004"}, expectedStreamUpdates: []string{"foo/bar|sha256:0000000000000000000000000000000000000000000000000000000000000002", "bar/foo|sha256:0000000000000000000000000000000000000000000000000000000000000004"}, }, + "image within allowed limits": { pruneOverSizeLimit: newBool(true), images: imageList( unmanagedImage("sha256:0000000000000000000000000000000000000000000000000000000000000000", "otherregistry/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000", false, "", ""), - sizedImage("sha256:0000000000000000000000000000000000000000000000000000000000000002", registryURL+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000002", 100, nil), - sizedImage("sha256:0000000000000000000000000000000000000000000000000000000000000003", registryURL+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000003", 200, nil), + sizedImage("sha256:0000000000000000000000000000000000000000000000000000000000000002", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000002", 100, nil), + sizedImage("sha256:0000000000000000000000000000000000000000000000000000000000000003", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000003", 200, nil), ), streams: streamList( - stream(registryURL, "foo", "bar", tags( + stream(registryHost, "foo", "bar", tags( tag("latest", tagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000000", "otherregistry/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000"), - tagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000002", registryURL+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000002"), - tagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000003", registryURL+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000003"), + tagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000002", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000002"), + tagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000003", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000003"), ), )), ), @@ -878,20 +989,21 @@ func TestImagePruning(t *testing.T) { expectedImageDeletions: []string{}, expectedStreamUpdates: []string{}, }, + "image exceeding limits with namespace specified": { pruneOverSizeLimit: newBool(true), namespace: "foo", images: imageList( unmanagedImage("sha256:0000000000000000000000000000000000000000000000000000000000000000", "otherregistry/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000", false, "", ""), - sizedImage("sha256:0000000000000000000000000000000000000000000000000000000000000002", registryURL+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000002", 100, nil), - sizedImage("sha256:0000000000000000000000000000000000000000000000000000000000000003", registryURL+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000003", 200, nil), + sizedImage("sha256:0000000000000000000000000000000000000000000000000000000000000002", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000002", 100, nil), + sizedImage("sha256:0000000000000000000000000000000000000000000000000000000000000003", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000003", 200, nil), ), streams: streamList( - stream(registryURL, "foo", "bar", tags( + stream(registryHost, "foo", "bar", tags( tag("latest", tagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000000", "otherregistry/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000000"), - tagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000002", registryURL+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000002"), - tagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000003", registryURL+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000003"), + tagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000002", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000002"), + tagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000003", registryHost+"/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000003"), ), )), ), @@ -910,6 +1022,7 @@ func TestImagePruning(t *testing.T) { options := PrunerOptions{ Namespace: test.namespace, + AllImages: test.allImages, Images: &test.images, Streams: &test.streams, Pods: &test.pods, @@ -918,6 +1031,7 @@ func TestImagePruning(t *testing.T) { Builds: &test.builds, DCs: &test.dcs, LimitRanges: test.limits, + RegistryURL: &url.URL{Scheme: "https", Host: registryHost}, } if test.pruneOverSizeLimit != nil { options.PruneOverSizeLimit = test.pruneOverSizeLimit @@ -928,7 +1042,6 @@ func TestImagePruning(t *testing.T) { options.KeepTagRevisions = &keepTagRevisions } p := NewPruner(options) - p.(*pruner).registryPinger = &fakeRegistryPinger{} imageDeleter := &fakeImageDeleter{invocations: sets.NewString()} streamDeleter := &fakeImageStreamDeleter{invocations: sets.NewString()} @@ -939,23 +1052,23 @@ func TestImagePruning(t *testing.T) { p.Prune(imageDeleter, streamDeleter, layerLinkDeleter, blobDeleter, manifestDeleter) expectedImageDeletions := sets.NewString(test.expectedImageDeletions...) - if !reflect.DeepEqual(expectedImageDeletions, imageDeleter.invocations) { - t.Errorf("%s: expected image deletions %q, got %q", name, expectedImageDeletions.List(), imageDeleter.invocations.List()) + if a, e := imageDeleter.invocations, expectedImageDeletions; !reflect.DeepEqual(a, e) { + t.Errorf("%s: unexpected image deletions: %s", name, diff.ObjectDiff(a, e)) } expectedStreamUpdates := sets.NewString(test.expectedStreamUpdates...) - if !reflect.DeepEqual(expectedStreamUpdates, streamDeleter.invocations) { - t.Errorf("%s: expected stream updates %q, got %q", name, expectedStreamUpdates.List(), streamDeleter.invocations.List()) + if a, e := streamDeleter.invocations, expectedStreamUpdates; !reflect.DeepEqual(a, e) { + t.Errorf("%s: unexpected stream updates: %s", name, diff.ObjectDiff(a, e)) } expectedLayerLinkDeletions := sets.NewString(test.expectedLayerLinkDeletions...) - if !reflect.DeepEqual(expectedLayerLinkDeletions, layerLinkDeleter.invocations) { - t.Errorf("%s: expected layer link deletions %q, got %q", name, expectedLayerLinkDeletions.List(), layerLinkDeleter.invocations.List()) + if a, e := layerLinkDeleter.invocations, expectedLayerLinkDeletions; !reflect.DeepEqual(a, e) { + t.Errorf("%s: unexpected layer link deletions: %s", name, diff.ObjectDiff(a, e)) } expectedBlobDeletions := sets.NewString(test.expectedBlobDeletions...) - if !reflect.DeepEqual(expectedBlobDeletions, blobDeleter.invocations) { - t.Errorf("%s: expected blob deletions %q, got %q", name, expectedBlobDeletions.List(), blobDeleter.invocations.List()) + if a, e := blobDeleter.invocations, expectedBlobDeletions; !reflect.DeepEqual(a, e) { + t.Errorf("%s: unexpected blob deletions: %s", name, diff.ObjectDiff(a, e)) } } } @@ -1006,11 +1119,10 @@ func TestLayerDeleter(t *testing.T) { return &http.Response{StatusCode: http.StatusServiceUnavailable, Body: ioutil.NopCloser(bytes.NewReader([]byte{}))}, nil }) layerLinkDeleter := NewLayerLinkDeleter() - layerLinkDeleter.DeleteLayerLink(client, "registry1", "repo", "layer1") + layerLinkDeleter.DeleteLayerLink(client, &url.URL{Scheme: "http", Host: "registry1"}, "repo", "layer1") - if !reflect.DeepEqual(actions, []string{"DELETE:https://registry1/v2/repo/blobs/layer1", - "DELETE:http://registry1/v2/repo/blobs/layer1"}) { - t.Errorf("Unexpected actions %v", actions) + if e := []string{"DELETE:http://registry1/v2/repo/blobs/layer1"}; !reflect.DeepEqual(actions, e) { + t.Errorf("unexpected actions: %s", diff.ObjectDiff(actions, e)) } } @@ -1023,10 +1135,10 @@ func TestNotFoundLayerDeleter(t *testing.T) { return &http.Response{StatusCode: http.StatusNotFound, Body: ioutil.NopCloser(bytes.NewReader([]byte{}))}, nil }) layerLinkDeleter := NewLayerLinkDeleter() - layerLinkDeleter.DeleteLayerLink(client, "registry1", "repo", "layer1") + layerLinkDeleter.DeleteLayerLink(client, &url.URL{Scheme: "https", Host: "registry1"}, "repo", "layer1") - if !reflect.DeepEqual(actions, []string{"DELETE:https://registry1/v2/repo/blobs/layer1"}) { - t.Errorf("Unexpected actions %v", actions) + if e := []string{"DELETE:https://registry1/v2/repo/blobs/layer1"}; !reflect.DeepEqual(actions, e) { + t.Errorf("unexpected actions: %s", diff.ObjectDiff(actions, e)) } } @@ -1060,17 +1172,17 @@ func TestRegistryPruning(t *testing.T) { )), ), expectedLayerLinkDeletions: sets.NewString( - "registry1.io|foo/bar|"+config1, - "registry1.io|foo/bar|layer1", - "registry1.io|foo/bar|layer2", + "https://registry1.io|foo/bar|"+config1, + "https://registry1.io|foo/bar|layer1", + "https://registry1.io|foo/bar|layer2", ), expectedBlobDeletions: sets.NewString( - "registry1.io|"+config1, - "registry1.io|layer1", - "registry1.io|layer2", + "https://registry1.io|"+config1, + "https://registry1.io|layer1", + "https://registry1.io|layer2", ), expectedManifestDeletions: sets.NewString( - "registry1.io|foo/bar|sha256:0000000000000000000000000000000000000000000000000000000000000001", + "https://registry1.io|foo/bar|sha256:0000000000000000000000000000000000000000000000000000000000000001", ), }, "no pruning when no images are pruned": { @@ -1095,40 +1207,17 @@ func TestRegistryPruning(t *testing.T) { ), expectedLayerLinkDeletions: sets.NewString(), expectedBlobDeletions: sets.NewString( - "registry1.io|"+config1, - "registry1.io|"+config2, - "registry1.io|layer1", - "registry1.io|layer2", - "registry1.io|layer3", - "registry1.io|layer4", - "registry1.io|layer5", - "registry1.io|layer6", + "https://registry1.io|"+config1, + "https://registry1.io|"+config2, + "https://registry1.io|layer1", + "https://registry1.io|layer2", + "https://registry1.io|layer3", + "https://registry1.io|layer4", + "https://registry1.io|layer5", + "https://registry1.io|layer6", ), expectedManifestDeletions: sets.NewString(), }, - "ping error": { - images: imageList( - imageWithLayers("sha256:0000000000000000000000000000000000000000000000000000000000000001", "registry1.io/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000001", &config1, "layer1", "layer2", "layer3", "layer4"), - imageWithLayers("sha256:0000000000000000000000000000000000000000000000000000000000000002", "registry1.io/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000002", &config2, "layer3", "layer4", "layer5", "layer6"), - ), - streams: streamList( - stream("registry1.io", "foo", "bar", tags( - tag("latest", - tagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000002", "registry1.io/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000002"), - tagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000001", "registry1.io/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000001"), - ), - )), - stream("registry1.io", "foo", "other", tags( - tag("latest", - tagEvent("sha256:0000000000000000000000000000000000000000000000000000000000000002", "registry1.io/foo/other@sha256:0000000000000000000000000000000000000000000000000000000000000002"), - ), - )), - ), - expectedLayerLinkDeletions: sets.NewString(), - expectedBlobDeletions: sets.NewString(), - expectedManifestDeletions: sets.NewString(), - pingErr: errors.New("foo"), - }, "config used as a layer": { images: imageList( imageWithLayers("sha256:0000000000000000000000000000000000000000000000000000000000000001", "registry1.io/foo/bar@sha256:0000000000000000000000000000000000000000000000000000000000000001", &config1, "layer1", "layer2", "layer3", config1), @@ -1150,16 +1239,16 @@ func TestRegistryPruning(t *testing.T) { )), ), expectedLayerLinkDeletions: sets.NewString( - "registry1.io|foo/bar|layer1", - "registry1.io|foo/bar|layer2", + "https://registry1.io|foo/bar|layer1", + "https://registry1.io|foo/bar|layer2", // TODO: ideally, pruner should remove layers of id2 from foo/bar as well ), expectedBlobDeletions: sets.NewString( - "registry1.io|layer1", - "registry1.io|layer2", + "https://registry1.io|layer1", + "https://registry1.io|layer2", ), expectedManifestDeletions: sets.NewString( - "registry1.io|foo/bar|sha256:0000000000000000000000000000000000000000000000000000000000000001", + "https://registry1.io|foo/bar|sha256:0000000000000000000000000000000000000000000000000000000000000001", ), }, } @@ -1184,9 +1273,9 @@ func TestRegistryPruning(t *testing.T) { BCs: &buildapi.BuildConfigList{}, Builds: &buildapi.BuildList{}, DCs: &deployapi.DeploymentConfigList{}, + RegistryURL: &url.URL{Scheme: "https", Host: "registry1.io"}, } p := NewPruner(options) - p.(*pruner).registryPinger = &fakeRegistryPinger{err: test.pingErr} imageDeleter := &fakeImageDeleter{invocations: sets.NewString()} streamDeleter := &fakeImageStreamDeleter{invocations: sets.NewString()} @@ -1196,14 +1285,14 @@ func TestRegistryPruning(t *testing.T) { p.Prune(imageDeleter, streamDeleter, layerLinkDeleter, blobDeleter, manifestDeleter) - if !reflect.DeepEqual(test.expectedLayerLinkDeletions, layerLinkDeleter.invocations) { - t.Errorf("%s: expected layer link deletions %#v, got %#v", name, test.expectedLayerLinkDeletions.List(), layerLinkDeleter.invocations.List()) + if a, e := layerLinkDeleter.invocations, test.expectedLayerLinkDeletions; !reflect.DeepEqual(a, e) { + t.Errorf("%s: unexpected layer link deletions: %s", name, diff.ObjectDiff(a, e)) } - if !reflect.DeepEqual(test.expectedBlobDeletions, blobDeleter.invocations) { - t.Errorf("%s: expected blob deletions %#v, got %#v", name, test.expectedBlobDeletions.List(), blobDeleter.invocations.List()) + if a, e := blobDeleter.invocations, test.expectedBlobDeletions; !reflect.DeepEqual(a, e) { + t.Errorf("%s: unexpected blob deletions: %s", name, diff.ObjectDiff(a, e)) } - if !reflect.DeepEqual(test.expectedManifestDeletions, manifestDeleter.invocations) { - t.Errorf("%s: expected manifest deletions %#v, got %#v", name, test.expectedManifestDeletions.List(), manifestDeleter.invocations.List()) + if a, e := manifestDeleter.invocations, test.expectedManifestDeletions; !reflect.DeepEqual(a, e) { + t.Errorf("%s: unexpected manifest deletions: %s", name, diff.ObjectDiff(a, e)) } } } @@ -1254,7 +1343,6 @@ func TestImageWithStrongAndWeakRefsIsNotPruned(t *testing.T) { options.KeepYoungerThan = &keepYoungerThan options.KeepTagRevisions = &keepTagRevisions p := NewPruner(options) - p.(*pruner).registryPinger = &fakeRegistryPinger{} imageDeleter := &fakeImageDeleter{invocations: sets.NewString()} streamDeleter := &fakeImageStreamDeleter{invocations: sets.NewString()} diff --git a/vendor/github.com/openshift/origin/pkg/oauth/apis/oauth/v1/conversion.go b/vendor/github.com/openshift/origin/pkg/oauth/apis/oauth/v1/conversion.go index c594c17ee..c1ac5f73d 100644 --- a/vendor/github.com/openshift/origin/pkg/oauth/apis/oauth/v1/conversion.go +++ b/vendor/github.com/openshift/origin/pkg/oauth/apis/oauth/v1/conversion.go @@ -13,23 +13,43 @@ func addConversionFuncs(scheme *runtime.Scheme) error { ); err != nil { return err } + if err := scheme.AddFieldLabelConversionFunc(SchemeGroupVersion.String(), "OAuthAccessToken", + oapi.GetFieldLabelConversionFunc(oauthapi.OAuthAccessTokenToSelectableFields(&oauthapi.OAuthAccessToken{}), nil), + ); err != nil { + return err + } if err := scheme.AddFieldLabelConversionFunc("v1", "OAuthAuthorizeToken", oapi.GetFieldLabelConversionFunc(oauthapi.OAuthAuthorizeTokenToSelectableFields(&oauthapi.OAuthAuthorizeToken{}), nil), ); err != nil { return err } + if err := scheme.AddFieldLabelConversionFunc(SchemeGroupVersion.String(), "OAuthAuthorizeToken", + oapi.GetFieldLabelConversionFunc(oauthapi.OAuthAuthorizeTokenToSelectableFields(&oauthapi.OAuthAuthorizeToken{}), nil), + ); err != nil { + return err + } if err := scheme.AddFieldLabelConversionFunc("v1", "OAuthClient", oapi.GetFieldLabelConversionFunc(oauthapi.OAuthClientToSelectableFields(&oauthapi.OAuthClient{}), nil), ); err != nil { return err } + if err := scheme.AddFieldLabelConversionFunc(SchemeGroupVersion.String(), "OAuthClient", + oapi.GetFieldLabelConversionFunc(oauthapi.OAuthClientToSelectableFields(&oauthapi.OAuthClient{}), nil), + ); err != nil { + return err + } if err := scheme.AddFieldLabelConversionFunc("v1", "OAuthClientAuthorization", oapi.GetFieldLabelConversionFunc(oauthapi.OAuthClientAuthorizationToSelectableFields(&oauthapi.OAuthClientAuthorization{}), nil), ); err != nil { return err } + if err := scheme.AddFieldLabelConversionFunc(SchemeGroupVersion.String(), "OAuthClientAuthorization", + oapi.GetFieldLabelConversionFunc(oauthapi.OAuthClientAuthorizationToSelectableFields(&oauthapi.OAuthClientAuthorization{}), nil), + ); err != nil { + return err + } return nil } diff --git a/vendor/github.com/openshift/origin/pkg/project/apis/project/v1/conversion.go b/vendor/github.com/openshift/origin/pkg/project/apis/project/v1/conversion.go index 51fcf88d7..f138153f9 100644 --- a/vendor/github.com/openshift/origin/pkg/project/apis/project/v1/conversion.go +++ b/vendor/github.com/openshift/origin/pkg/project/apis/project/v1/conversion.go @@ -9,7 +9,12 @@ import ( ) func addConversionFuncs(scheme *runtime.Scheme) error { - return scheme.AddFieldLabelConversionFunc("v1", "Project", + if err := scheme.AddFieldLabelConversionFunc("v1", "Project", + oapi.GetFieldLabelConversionFunc(namespace.NamespaceToSelectableFields(&kapi.Namespace{}), nil), + ); err != nil { + return err + } + return scheme.AddFieldLabelConversionFunc(SchemeGroupVersion.String(), "Project", oapi.GetFieldLabelConversionFunc(namespace.NamespaceToSelectableFields(&kapi.Namespace{}), nil), ) } diff --git a/vendor/github.com/openshift/origin/pkg/project/registry/projectrequest/delegated/sample_template.go b/vendor/github.com/openshift/origin/pkg/project/registry/projectrequest/delegated/sample_template.go index 030fa3938..fb529b6cd 100644 --- a/vendor/github.com/openshift/origin/pkg/project/registry/projectrequest/delegated/sample_template.go +++ b/vendor/github.com/openshift/origin/pkg/project/registry/projectrequest/delegated/sample_template.go @@ -40,13 +40,13 @@ func DefaultTemplate() *templateapi.Template { oapi.OpenShiftDisplayName: "${" + ProjectDisplayNameParam + "}", projectapi.ProjectRequester: "${" + ProjectRequesterParam + "}", } - if err := templateapi.AddObjectsToTemplate(ret, []runtime.Object{project}, projectapiv1.SchemeGroupVersion); err != nil { + if err := templateapi.AddObjectsToTemplate(ret, []runtime.Object{project}, projectapiv1.LegacySchemeGroupVersion); err != nil { panic(err) } serviceAccountRoleBindings := bootstrappolicy.GetBootstrapServiceAccountProjectRoleBindings(ns) for i := range serviceAccountRoleBindings { - if err := templateapi.AddObjectsToTemplate(ret, []runtime.Object{&serviceAccountRoleBindings[i]}, authorizationapiv1.SchemeGroupVersion); err != nil { + if err := templateapi.AddObjectsToTemplate(ret, []runtime.Object{&serviceAccountRoleBindings[i]}, authorizationapiv1.LegacySchemeGroupVersion); err != nil { panic(err) } } @@ -56,7 +56,7 @@ func DefaultTemplate() *templateapi.Template { binding.Namespace = ns binding.Subjects = []kapi.ObjectReference{{Kind: authorizationapi.UserKind, Name: "${" + ProjectAdminUserParam + "}"}} binding.RoleRef.Name = bootstrappolicy.AdminRoleName - if err := templateapi.AddObjectsToTemplate(ret, []runtime.Object{binding}, authorizationapiv1.SchemeGroupVersion); err != nil { + if err := templateapi.AddObjectsToTemplate(ret, []runtime.Object{binding}, authorizationapiv1.LegacySchemeGroupVersion); err != nil { // this should never happen because we're tightly controlling what goes in. panic(err) } diff --git a/vendor/github.com/openshift/origin/pkg/route/apis/route/v1/conversion.go b/vendor/github.com/openshift/origin/pkg/route/apis/route/v1/conversion.go index d29809f9a..6b02bdd28 100644 --- a/vendor/github.com/openshift/origin/pkg/route/apis/route/v1/conversion.go +++ b/vendor/github.com/openshift/origin/pkg/route/apis/route/v1/conversion.go @@ -8,7 +8,12 @@ import ( ) func addConversionFuncs(scheme *runtime.Scheme) error { - return scheme.AddFieldLabelConversionFunc("v1", "Route", + if err := scheme.AddFieldLabelConversionFunc("v1", "Route", + oapi.GetFieldLabelConversionFunc(routeapi.RouteToSelectableFields(&routeapi.Route{}), nil), + ); err != nil { + return err + } + return scheme.AddFieldLabelConversionFunc(SchemeGroupVersion.String(), "Route", oapi.GetFieldLabelConversionFunc(routeapi.RouteToSelectableFields(&routeapi.Route{}), nil), ) } diff --git a/vendor/github.com/openshift/origin/pkg/template/apis/template/v1/conversion.go b/vendor/github.com/openshift/origin/pkg/template/apis/template/v1/conversion.go index 146cb4f03..d3ede3889 100644 --- a/vendor/github.com/openshift/origin/pkg/template/apis/template/v1/conversion.go +++ b/vendor/github.com/openshift/origin/pkg/template/apis/template/v1/conversion.go @@ -15,18 +15,33 @@ func addConversionFuncs(scheme *runtime.Scheme) error { ); err != nil { return err } + if err := scheme.AddFieldLabelConversionFunc(SchemeGroupVersion.String(), "Template", + oapi.GetFieldLabelConversionFunc(templateapi.TemplateToSelectableFields(&templateapi.Template{}), nil), + ); err != nil { + return err + } if err := scheme.AddFieldLabelConversionFunc("v1", "TemplateInstance", oapi.GetFieldLabelConversionFunc(templateapi.TemplateInstanceToSelectableFields(&templateapi.TemplateInstance{}), nil), ); err != nil { return err } + if err := scheme.AddFieldLabelConversionFunc(SchemeGroupVersion.String(), "TemplateInstance", + oapi.GetFieldLabelConversionFunc(templateapi.TemplateInstanceToSelectableFields(&templateapi.TemplateInstance{}), nil), + ); err != nil { + return err + } if err := scheme.AddFieldLabelConversionFunc("v1", "BrokerTemplateInstance", oapi.GetFieldLabelConversionFunc(templateapi.BrokerTemplateInstanceToSelectableFields(&templateapi.BrokerTemplateInstance{}), nil), ); err != nil { return err } + if err := scheme.AddFieldLabelConversionFunc(SchemeGroupVersion.String(), "BrokerTemplateInstance", + oapi.GetFieldLabelConversionFunc(templateapi.BrokerTemplateInstanceToSelectableFields(&templateapi.BrokerTemplateInstance{}), nil), + ); err != nil { + return err + } return nil diff --git a/vendor/github.com/openshift/origin/pkg/user/apis/user/v1/conversion.go b/vendor/github.com/openshift/origin/pkg/user/apis/user/v1/conversion.go index 28da9f8ec..82f3ec3d7 100644 --- a/vendor/github.com/openshift/origin/pkg/user/apis/user/v1/conversion.go +++ b/vendor/github.com/openshift/origin/pkg/user/apis/user/v1/conversion.go @@ -13,17 +13,32 @@ func addConversionFuncs(scheme *runtime.Scheme) error { ); err != nil { return err } + if err := scheme.AddFieldLabelConversionFunc(SchemeGroupVersion.String(), "Group", + oapi.GetFieldLabelConversionFunc(userapi.GroupToSelectableFields(&userapi.Group{}), nil), + ); err != nil { + return err + } if err := scheme.AddFieldLabelConversionFunc("v1", "Identity", oapi.GetFieldLabelConversionFunc(userapi.IdentityToSelectableFields(&userapi.Identity{}), nil), ); err != nil { return err } + if err := scheme.AddFieldLabelConversionFunc(SchemeGroupVersion.String(), "Identity", + oapi.GetFieldLabelConversionFunc(userapi.IdentityToSelectableFields(&userapi.Identity{}), nil), + ); err != nil { + return err + } if err := scheme.AddFieldLabelConversionFunc("v1", "User", oapi.GetFieldLabelConversionFunc(userapi.UserToSelectableFields(&userapi.User{}), nil), ); err != nil { return err } + if err := scheme.AddFieldLabelConversionFunc(SchemeGroupVersion.String(), "User", + oapi.GetFieldLabelConversionFunc(userapi.UserToSelectableFields(&userapi.User{}), nil), + ); err != nil { + return err + } return nil } diff --git a/vendor/github.com/openshift/origin/test/cmd/admin.sh b/vendor/github.com/openshift/origin/test/cmd/admin.sh index 482c90f81..3531ae9a9 100755 --- a/vendor/github.com/openshift/origin/test/cmd/admin.sh +++ b/vendor/github.com/openshift/origin/test/cmd/admin.sh @@ -78,6 +78,8 @@ os::cmd::expect_success_and_not_text 'oadm manage-node --selector= --schedulable os::cmd::expect_success_and_not_text 'oc get node -o yaml' 'unschedulable: true' os::cmd::expect_success_and_text 'oadm manage-node --selector= --schedulable=false' 'SchedulingDisabled' os::cmd::expect_success_and_text 'oc get node -o yaml' 'unschedulable: true' +# ensure correct serialization of podList output +os::cmd::expect_success_and_text "oadm manage-node --list-pods --selector= -o jsonpath='{ .kind }'" 'List' echo "manage-node: ok" os::test::junit::declare_suite_end diff --git a/vendor/github.com/openshift/origin/test/integration/router_test.go b/vendor/github.com/openshift/origin/test/end-to-end/router_test.go similarity index 99% rename from vendor/github.com/openshift/origin/test/integration/router_test.go rename to vendor/github.com/openshift/origin/test/end-to-end/router_test.go index bfc2de648..a8fbdf903 100644 --- a/vendor/github.com/openshift/origin/test/integration/router_test.go +++ b/vendor/github.com/openshift/origin/test/end-to-end/router_test.go @@ -1,4 +1,4 @@ -package integration +package endtoend import ( "crypto/tls" diff --git a/vendor/github.com/openshift/origin/test/extended/builds/start.go b/vendor/github.com/openshift/origin/test/extended/builds/start.go index 570cae576..e6abc9f44 100644 --- a/vendor/github.com/openshift/origin/test/extended/builds/start.go +++ b/vendor/github.com/openshift/origin/test/extended/builds/start.go @@ -308,14 +308,14 @@ var _ = g.Describe("[builds][Slow] starting a build using CLI", func() { g.By("verifying the build output contains the changes.") o.Expect(buildLog).To(o.ContainSubstring("bar")) }) - g.It("Should fail on non-existent build-arg", func() { + g.It("Should complete with a warning on non-existent build-arg", func() { g.By("starting the build with --build-arg flag") br, _ := exutil.StartBuildAndWait(oc, "sample-build-docker-args", "--build-arg=bar=foo") - br.AssertFailure() + br.AssertSuccess() buildLog, err := br.Logs() o.Expect(err).NotTo(o.HaveOccurred()) - g.By("verifying the build failed due to Docker.") - o.Expect(buildLog).To(o.ContainSubstring("One or more build-args [bar] were not consumed, failing build")) + g.By("verifying the build completed with a warning.") + o.Expect(buildLog).To(o.ContainSubstring("One or more build-args [bar] were not consumed")) }) }) diff --git a/vendor/github.com/openshift/origin/test/extended/imageapis/limitrange_admission.go b/vendor/github.com/openshift/origin/test/extended/imageapis/limitrange_admission.go index eba177834..d9a343c39 100644 --- a/vendor/github.com/openshift/origin/test/extended/imageapis/limitrange_admission.go +++ b/vendor/github.com/openshift/origin/test/extended/imageapis/limitrange_admission.go @@ -184,10 +184,14 @@ var _ = g.Describe("[Feature:ImageQuota] Image limit range", func() { g.It(fmt.Sprintf("should deny an import of a repository exceeding limit on %s resource", imageapi.ResourceImageStreamTags), func() { oc.SetOutputDir(exutil.TestContext.OutputDir) - defer tearDown(oc) maxBulkImport, err := getMaxImagesBulkImportedPerRepository() - o.Expect(err).NotTo(o.HaveOccurred()) + if err != nil { + g.Skip(err.Error()) + return + } + + defer tearDown(oc) s1tag2Image, err := buildAndPushTestImagesTo(oc, "src1st", "tag", maxBulkImport+1) s2tag2Image, err := buildAndPushTestImagesTo(oc, "src2nd", "t", 2) @@ -235,7 +239,7 @@ func buildAndPushTestImagesTo(oc *exutil.CLI, isName string, tagPrefix string, n for i := 1; i <= numberOfImages; i++ { tag := fmt.Sprintf("%s%d", tagPrefix, i) - dgst, err := imagesutil.BuildAndPushImageOfSizeWithDocker(oc, dClient, isName, tag, imageSize, 2, g.GinkgoWriter, true) + dgst, _, err := imagesutil.BuildAndPushImageOfSizeWithDocker(oc, dClient, isName, tag, imageSize, 2, g.GinkgoWriter, true, true) if err != nil { return nil, err } @@ -309,7 +313,7 @@ func bumpLimit(oc *exutil.CLI, resourceName kapi.ResourceName, limit string) (ka func getMaxImagesBulkImportedPerRepository() (int, error) { max := os.Getenv("MAX_IMAGES_BULK_IMPORTED_PER_REPOSITORY") if len(max) == 0 { - return 0, fmt.Errorf("MAX_IMAGES_BULK_IMAGES_IMPORTED_PER_REPOSITORY needs to be set") + return 0, fmt.Errorf("MAX_IMAGES_BULK_IMAGES_IMPORTED_PER_REPOSITORY is not set") } return strconv.Atoi(max) } diff --git a/vendor/github.com/openshift/origin/test/extended/imageapis/quota_admission.go b/vendor/github.com/openshift/origin/test/extended/imageapis/quota_admission.go index fda559a03..a8d78452c 100644 --- a/vendor/github.com/openshift/origin/test/extended/imageapis/quota_admission.go +++ b/vendor/github.com/openshift/origin/test/extended/imageapis/quota_admission.go @@ -60,25 +60,25 @@ var _ = g.Describe("[Feature:ImageQuota] Image resource quota", func() { o.Expect(err).NotTo(o.HaveOccurred()) g.By(fmt.Sprintf("trying to push image exceeding quota %v", quota)) - _, err = imagesutil.BuildAndPushImageOfSizeWithDocker(oc, dClient, "first", "refused", imageSize, 1, outSink, false) + _, _, err = imagesutil.BuildAndPushImageOfSizeWithDocker(oc, dClient, "first", "refused", imageSize, 1, outSink, false, true) o.Expect(err).NotTo(o.HaveOccurred()) quota, err = bumpQuota(oc, imageapi.ResourceImageStreams, 1) o.Expect(err).NotTo(o.HaveOccurred()) g.By(fmt.Sprintf("trying to push image below quota %v", quota)) - _, err = imagesutil.BuildAndPushImageOfSizeWithDocker(oc, dClient, "first", "tag1", imageSize, 1, outSink, true) + _, _, err = imagesutil.BuildAndPushImageOfSizeWithDocker(oc, dClient, "first", "tag1", imageSize, 1, outSink, true, true) o.Expect(err).NotTo(o.HaveOccurred()) used, err := waitForResourceQuotaSync(oc, quotaName, quota) o.Expect(err).NotTo(o.HaveOccurred()) o.Expect(assertQuotasEqual(used, quota)).NotTo(o.HaveOccurred()) g.By(fmt.Sprintf("trying to push image to existing image stream %v", quota)) - _, err = imagesutil.BuildAndPushImageOfSizeWithDocker(oc, dClient, "first", "tag2", imageSize, 1, outSink, true) + _, _, err = imagesutil.BuildAndPushImageOfSizeWithDocker(oc, dClient, "first", "tag2", imageSize, 1, outSink, true, true) o.Expect(err).NotTo(o.HaveOccurred()) g.By(fmt.Sprintf("trying to push image exceeding quota %v", quota)) - _, err = imagesutil.BuildAndPushImageOfSizeWithDocker(oc, dClient, "second", "refused", imageSize, 1, outSink, false) + _, _, err = imagesutil.BuildAndPushImageOfSizeWithDocker(oc, dClient, "second", "refused", imageSize, 1, outSink, false, true) quota, err = bumpQuota(oc, imageapi.ResourceImageStreams, 2) o.Expect(err).NotTo(o.HaveOccurred()) @@ -86,14 +86,14 @@ var _ = g.Describe("[Feature:ImageQuota] Image resource quota", func() { o.Expect(err).NotTo(o.HaveOccurred()) g.By(fmt.Sprintf("trying to push image below quota %v", quota)) - _, err = imagesutil.BuildAndPushImageOfSizeWithDocker(oc, dClient, "second", "tag1", imageSize, 1, outSink, true) + _, _, err = imagesutil.BuildAndPushImageOfSizeWithDocker(oc, dClient, "second", "tag1", imageSize, 1, outSink, true, true) o.Expect(err).NotTo(o.HaveOccurred()) used, err = waitForResourceQuotaSync(oc, quotaName, quota) o.Expect(err).NotTo(o.HaveOccurred()) o.Expect(assertQuotasEqual(used, quota)).NotTo(o.HaveOccurred()) g.By(fmt.Sprintf("trying to push image exceeding quota %v", quota)) - _, err = imagesutil.BuildAndPushImageOfSizeWithDocker(oc, dClient, "third", "refused", imageSize, 1, outSink, false) + _, _, err = imagesutil.BuildAndPushImageOfSizeWithDocker(oc, dClient, "third", "refused", imageSize, 1, outSink, false, true) o.Expect(err).NotTo(o.HaveOccurred()) g.By("deleting first image stream") @@ -110,7 +110,7 @@ var _ = g.Describe("[Feature:ImageQuota] Image resource quota", func() { o.Expect(assertQuotasEqual(used, kapi.ResourceList{imageapi.ResourceImageStreams: resource.MustParse("1")})).NotTo(o.HaveOccurred()) g.By(fmt.Sprintf("trying to push image below quota %v", quota)) - _, err = imagesutil.BuildAndPushImageOfSizeWithDocker(oc, dClient, "third", "tag", imageSize, 1, outSink, true) + _, _, err = imagesutil.BuildAndPushImageOfSizeWithDocker(oc, dClient, "third", "tag", imageSize, 1, outSink, true, true) o.Expect(err).NotTo(o.HaveOccurred()) used, err = waitForResourceQuotaSync(oc, quotaName, quota) o.Expect(err).NotTo(o.HaveOccurred()) diff --git a/vendor/github.com/openshift/origin/test/extended/images/hardprune.go b/vendor/github.com/openshift/origin/test/extended/images/hardprune.go new file mode 100644 index 000000000..47ec66868 --- /dev/null +++ b/vendor/github.com/openshift/origin/test/extended/images/hardprune.go @@ -0,0 +1,361 @@ +package images + +import ( + "fmt" + "strings" + + g "github.com/onsi/ginkgo" + o "github.com/onsi/gomega" + + "github.com/docker/distribution/manifest/schema2" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + imageapi "github.com/openshift/origin/pkg/image/apis/image" + registryutil "github.com/openshift/origin/test/extended/registry/util" + exutil "github.com/openshift/origin/test/extended/util" + testutil "github.com/openshift/origin/test/util" +) + +var _ = g.Describe("[Feature:ImagePrune] Image hard prune", func() { + defer g.GinkgoRecover() + var oc = exutil.NewCLI("prune-images", exutil.KubeConfigPath()) + var originalAcceptSchema2 *bool + + g.JustBeforeEach(func() { + if originalAcceptSchema2 == nil { + accepts, err := registryutil.DoesRegistryAcceptSchema2(oc) + o.Expect(err).NotTo(o.HaveOccurred()) + originalAcceptSchema2 = &accepts + } + + readOnly := false + acceptSchema2 := true + err := registryutil.ConfigureRegistry(oc, + registryutil.RegistryConfiguration{ + ReadOnly: &readOnly, + AcceptSchema2: &acceptSchema2, + }) + o.Expect(err).NotTo(o.HaveOccurred()) + + err = exutil.WaitForBuilderAccount(oc.KubeClient().Core().ServiceAccounts(oc.Namespace())) + o.Expect(err).NotTo(o.HaveOccurred()) + + defer func(ns string) { oc.SetNamespace(ns) }(oc.Namespace()) + g.By(fmt.Sprintf("give a user %s a right to prune images with %s role", oc.Username(), "system:image-pruner")) + err = oc.AsAdmin().WithoutNamespace().Run("adm").Args("policy", "add-cluster-role-to-user", "system:image-pruner", oc.Username()).Execute() + o.Expect(err).NotTo(o.HaveOccurred()) + err = oc.AsAdmin().SetNamespace(metav1.NamespaceDefault).Run("adm"). + Args("policy", "add-cluster-role-to-user", "system:image-pruner", + fmt.Sprintf("system:serviceaccount:%s:registry", metav1.NamespaceDefault)).Execute() + o.Expect(err).NotTo(o.HaveOccurred()) + + // make sure to remove all unreferenced blobs from the storage + _, err = RunHardPrune(oc, false) + o.Expect(err).NotTo(o.HaveOccurred()) + }) + + g.AfterEach(func() { + readOnly := false + err := registryutil.ConfigureRegistry(oc, + registryutil.RegistryConfiguration{ + ReadOnly: &readOnly, + AcceptSchema2: originalAcceptSchema2, + }) + o.Expect(err).NotTo(o.HaveOccurred()) + }) + + mergeOrSetExpectedDeletions := func(expected, new *RegistryStorageFiles, merge bool) *RegistryStorageFiles { + if !merge { + return new + } + for _, repo := range new.Repos { + expected.Repos = append(expected.Repos, repo) + } + for name, links := range new.ManifestLinks { + expected.ManifestLinks.Add(name, links...) + } + for name, links := range new.LayerLinks { + expected.LayerLinks.Add(name, links...) + } + for _, blob := range new.Blobs { + expected.Blobs = append(expected.Blobs, blob) + } + return expected + } + + testHardPrune := func(dryRun bool) { + oc.SetOutputDir(exutil.TestContext.OutputDir) + outSink := g.GinkgoWriter + registryURL, err := registryutil.GetDockerRegistryURL(oc) + o.Expect(err).NotTo(o.HaveOccurred()) + + cleanUp := NewCleanUpContainer(oc) + defer cleanUp.Run() + + dClient, err := testutil.NewDockerClient() + o.Expect(err).NotTo(o.HaveOccurred()) + + baseImg1, imageId, err := BuildAndPushImageOfSizeWithDocker(oc, dClient, "a", "latest", testImageSize, 2, outSink, true, false) + o.Expect(err).NotTo(o.HaveOccurred()) + cleanUp.AddImage(baseImg1, imageId, "") + baseImg1Spec := fmt.Sprintf("%s/%s/a:latest", registryURL, oc.Namespace()) + + baseImg2, imageId, err := BuildAndPushImageOfSizeWithDocker(oc, dClient, "b", "latest", testImageSize, 2, outSink, true, false) + o.Expect(err).NotTo(o.HaveOccurred()) + cleanUp.AddImage(baseImg2, imageId, "") + baseImg2Spec := fmt.Sprintf("%s/%s/b:latest", registryURL, oc.Namespace()) + + baseImg3, imageId, err := BuildAndPushImageOfSizeWithDocker(oc, dClient, "c", "latest", testImageSize, 2, outSink, true, false) + o.Expect(err).NotTo(o.HaveOccurred()) + cleanUp.AddImage(baseImg3, imageId, "") + baseImg3Spec := fmt.Sprintf("%s/%s/c:latest", registryURL, oc.Namespace()) + + baseImg4, imageId, err := BuildAndPushImageOfSizeWithDocker(oc, dClient, "a", "img4", testImageSize, 2, outSink, true, false) + o.Expect(err).NotTo(o.HaveOccurred()) + cleanUp.AddImage(baseImg4, imageId, "") + + childImg1, imageId, err := BuildAndPushChildImage(oc, dClient, baseImg1Spec, "c", "latest", 1, outSink, true) + o.Expect(err).NotTo(o.HaveOccurred()) + cleanUp.AddImage(childImg1, "", "") + childImg2, imageId, err := BuildAndPushChildImage(oc, dClient, baseImg2Spec, "b", "latest", 1, outSink, true) + o.Expect(err).NotTo(o.HaveOccurred()) + cleanUp.AddImage(childImg2, "", "") + childImg3, imageId, err := BuildAndPushChildImage(oc, dClient, baseImg3Spec, "c", "latest", 1, outSink, true) + o.Expect(err).NotTo(o.HaveOccurred()) + cleanUp.AddImage(childImg3, "", "") + + err = oc.Run("tag").Args("--source=istag", "a:latest", "a-tagged:latest").Execute() + o.Expect(err).NotTo(o.HaveOccurred()) + + imgs := map[string]*imageapi.Image{} + for _, imgName := range []string{baseImg1, baseImg2, baseImg3, baseImg4, childImg1, childImg2, childImg3} { + img, err := oc.AsAdmin().Client().Images().Get(imgName, metav1.GetOptions{}) + o.Expect(err).NotTo(o.HaveOccurred()) + imgs[imgName] = img + o.Expect(img.DockerImageManifestMediaType).To(o.Equal(schema2.MediaTypeManifest)) + } + + // this shouldn't delete anything + deleted, err := RunHardPrune(oc, dryRun) + o.Expect(err).NotTo(o.HaveOccurred()) + o.Expect(deleted.Len()).To(o.Equal(0)) + + /* TODO: use a persistent storage for the registry to preserve data across re-deployments + readOnly := true + err = registryutil.ConfigureRegistry(oc, registryutil.RegistryConfiguration{ReadOnly: &readOnly}) + o.Expect(err).NotTo(o.HaveOccurred()) + */ + + /* imageName | parent | layers | imagestreams + * ---------- | -------- | ------ | ------------ + * baseImg1 | | 1 2 | a a-tagged + * baseImg2 | | 4 5 | b + * baseImg3 | | 7 8 | c + * baseImg4 | | 11 12 | a + * childImg1 | baseImg1 | 1 2 3 | c + * childImg2 | baseImg2 | 4 5 6 | b + * childImg3 | baseImg3 | 7 8 9 | c + */ + + err = oc.AsAdmin().Client().ImageStreamTags(oc.Namespace()).Delete("a", "latest") + o.Expect(err).NotTo(o.HaveOccurred()) + deleted, err = RunHardPrune(oc, dryRun) + o.Expect(err).NotTo(o.HaveOccurred()) + expectedDeletions := &RegistryStorageFiles{ + /* TODO: reenable once we delete layer links as well + LayerLinks: RepoLinks{oc.Namespace()+"/a": []string{ + imgs[baseImg1].DockerImageMetadata.ID, + imgs[baseImg1].DockerImageLayers[0].Name, + imgs[baseImg1].DockerImageLayers[1].Name, + }}, + */ + ManifestLinks: RepoLinks{oc.Namespace() + "/a": []string{baseImg1}}, + } + err = AssertDeletedStorageFiles(deleted, expectedDeletions) + o.Expect(err).NotTo(o.HaveOccurred()) + + err = oc.AsAdmin().Client().Images().Delete(childImg1) + o.Expect(err).NotTo(o.HaveOccurred()) + // The repository a-tagged will not be removed even though it has no tags anymore. + // For the repository to be removed, the image stream itself needs to be deleted. + err = oc.AsAdmin().Client().ImageStreamTags(oc.Namespace()).Delete("a-tagged", "latest") + o.Expect(err).NotTo(o.HaveOccurred()) + deleted, err = RunHardPrune(oc, dryRun) + o.Expect(err).NotTo(o.HaveOccurred()) + expectedDeletions = mergeOrSetExpectedDeletions(expectedDeletions, + &RegistryStorageFiles{ + /* TODO: reenable once we delete layer links as well + LayerLinks: RepoLinks{oc.Namespace()+"/c": []string{ + imgs[childImg1].DockerImageMetadata.ID, + imgs[childImg1].DockerImageLayers[0].Name, + }}, + */ + ManifestLinks: RepoLinks{oc.Namespace() + "/c": []string{childImg1}}, + Blobs: []string{ + childImg1, // manifest blob + imgs[childImg1].DockerImageMetadata.ID, // manifest config + imgs[childImg1].DockerImageLayers[0].Name, + }, + }, + dryRun) + err = AssertDeletedStorageFiles(deleted, expectedDeletions) + o.Expect(err).NotTo(o.HaveOccurred()) + + err = oc.AsAdmin().Client().Images().Delete(baseImg1) + o.Expect(err).NotTo(o.HaveOccurred()) + deleted, err = RunHardPrune(oc, dryRun) + o.Expect(err).NotTo(o.HaveOccurred()) + expectedDeletions = mergeOrSetExpectedDeletions(expectedDeletions, + &RegistryStorageFiles{ + Blobs: []string{ + baseImg1, // manifest blob + imgs[baseImg1].DockerImageMetadata.ID, // manifest config + imgs[baseImg1].DockerImageLayers[0].Name, + imgs[baseImg1].DockerImageLayers[1].Name, + }, + }, + dryRun) + err = AssertDeletedStorageFiles(deleted, expectedDeletions) + o.Expect(err).NotTo(o.HaveOccurred()) + + err = oc.AsAdmin().Client().Images().Delete(childImg2) + o.Expect(err).NotTo(o.HaveOccurred()) + deleted, err = RunHardPrune(oc, dryRun) + o.Expect(err).NotTo(o.HaveOccurred()) + expectedDeletions = mergeOrSetExpectedDeletions(expectedDeletions, + &RegistryStorageFiles{ + /* TODO: reenable once we delete layer links as well + LayerLinks: RepoLinks{oc.Namespace()+"/b": []string{ + imgs[childImg2].DockerImageMetadata.ID, + imgs[childImg2].DockerImageLayers[0].Name, + }}, + */ + ManifestLinks: RepoLinks{oc.Namespace() + "/b": []string{childImg2}}, + Blobs: []string{ + childImg2, // manifest blob + imgs[childImg2].DockerImageMetadata.ID, // manifest config + imgs[childImg2].DockerImageLayers[0].Name, + }, + }, + dryRun) + err = AssertDeletedStorageFiles(deleted, expectedDeletions) + o.Expect(err).NotTo(o.HaveOccurred()) + + // untag both baseImg2 and childImg2 + err = oc.AsAdmin().Client().ImageStreams(oc.Namespace()).Delete("b") + o.Expect(err).NotTo(o.HaveOccurred()) + delete(expectedDeletions.ManifestLinks, oc.Namespace()+"/b") + err = oc.AsAdmin().Client().Images().Delete(baseImg2) + o.Expect(err).NotTo(o.HaveOccurred()) + deleted, err = RunHardPrune(oc, dryRun) + o.Expect(err).NotTo(o.HaveOccurred()) + expectedDeletions = mergeOrSetExpectedDeletions(expectedDeletions, + &RegistryStorageFiles{ + /* TODO: reenable once we delete layer links as well + LayerLinks: RepoLinks{oc.Namespace()+"/b": []string{ + imgs[baseImg2].DockerImageMetadata.ID, + imgs[baseImg2].DockerImageLayers[0].Name, + imgs[baseImg2].DockerImageLayers[1].Name, + }}, + */ + Repos: []string{oc.Namespace() + "/b"}, + Blobs: []string{ + baseImg2, // manifest blob + imgs[baseImg2].DockerImageMetadata.ID, // manifest config + imgs[baseImg2].DockerImageLayers[0].Name, + imgs[baseImg2].DockerImageLayers[1].Name, + }, + }, + dryRun) + err = AssertDeletedStorageFiles(deleted, expectedDeletions) + o.Expect(err).NotTo(o.HaveOccurred()) + + /* updated is/image table + * imageName | parent | layers | imagestreams + * ---------- | -------- | ------ | ------------ + * baseImg3 | | 7 8 | c + * baseImg4 | | 11 12 | a + * childImg3 | baseImg3 | 7 8 9 | c + */ + + // delete baseImg3 using soft prune + output, err := oc.WithoutNamespace().Run("adm").Args( + "prune", "images", "--keep-tag-revisions=1", "--keep-younger-than=0").Output() + o.Expect(err).NotTo(o.HaveOccurred()) + o.Expect(output).To(o.ContainSubstring(baseImg3)) + o.Expect(output).To(o.ContainSubstring(imgs[baseImg3].DockerImageMetadata.ID)) + for _, layer := range imgs[baseImg3].DockerImageLayers { + o.Expect(output).To(o.ContainSubstring(layer.Name)) + } + o.Expect(output).NotTo(o.ContainSubstring(baseImg4)) + o.Expect(output).NotTo(o.ContainSubstring(childImg3)) + + // there should be nothing left for hard pruner to delete + deleted, err = RunHardPrune(oc, dryRun) + o.Expect(err).NotTo(o.HaveOccurred()) + if !dryRun { + expectedDeletions = &RegistryStorageFiles{} + } + err = AssertDeletedStorageFiles(deleted, expectedDeletions) + o.Expect(err).NotTo(o.HaveOccurred()) + + err = oc.AsAdmin().Client().Images().Delete(childImg3) + o.Expect(err).NotTo(o.HaveOccurred()) + deleted, err = RunHardPrune(oc, dryRun) + o.Expect(err).NotTo(o.HaveOccurred()) + expectedDeletions = mergeOrSetExpectedDeletions(expectedDeletions, + &RegistryStorageFiles{ + /* TODO: reenable once we delete layer links as well + LayerLinks: RepoLinks{oc.Namespace()+"/b": []string{ + imgs[baseImg2].DockerImageMetadata.ID, + imgs[baseImg2].DockerImageLayers[0].Name, + imgs[baseImg2].DockerImageLayers[1].Name, + }}, + */ + ManifestLinks: RepoLinks{oc.Namespace() + "/c": []string{childImg3}}, + Blobs: []string{ + childImg3, + imgs[childImg3].DockerImageMetadata.ID, // manifest config + imgs[childImg3].DockerImageLayers[0].Name, + }, + }, + dryRun) + err = AssertDeletedStorageFiles(deleted, expectedDeletions) + o.Expect(err).NotTo(o.HaveOccurred()) + + /* updated is/image table + * imageName | parent | layers | imagestreams + * ---------- | -------- | ------ | ------------ + * baseImg3 | | 7 8 | c + * baseImg4 | | 11 12 | a + */ + + assertImageBlobsPresent := func(present bool, img *imageapi.Image) { + for _, layer := range img.DockerImageLayers { + o.Expect(pathExistsInRegistry(oc, strings.Split(blobToPath("", layer.Name), "/")...)). + To(o.Equal(present)) + } + o.Expect(pathExistsInRegistry(oc, strings.Split(blobToPath("", img.DockerImageMetadata.ID), "/")...)). + To(o.Equal(present)) + o.Expect(pathExistsInRegistry(oc, strings.Split(blobToPath("", img.Name), "/")...)). + To(o.Equal(present)) + } + + for _, img := range []string{baseImg1, childImg1, baseImg2, childImg2} { + assertImageBlobsPresent(dryRun, imgs[img]) + } + for _, img := range []string{baseImg3, baseImg4} { + assertImageBlobsPresent(true, imgs[img]) + } + } + + g.It("should show orphaned blob deletions in dry-run mode", func() { + testHardPrune(true) + }) + + g.It("should delete orphaned blobs", func() { + testHardPrune(false) + }) + +}) diff --git a/vendor/github.com/openshift/origin/test/extended/images/helper.go b/vendor/github.com/openshift/origin/test/extended/images/helper.go index 43f220bc1..1bbf40de4 100644 --- a/vendor/github.com/openshift/origin/test/extended/images/helper.go +++ b/vendor/github.com/openshift/origin/test/extended/images/helper.go @@ -3,6 +3,7 @@ package images import ( "bytes" cryptorand "crypto/rand" + "crypto/tls" "fmt" "io" "io/ioutil" @@ -14,14 +15,21 @@ import ( "strings" "time" - "github.com/docker/distribution/digest" dockerclient "github.com/fsouza/go-dockerclient" + g "github.com/onsi/ginkgo" + + "github.com/docker/distribution/digest" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + kerrors "k8s.io/apimachinery/pkg/util/errors" + knet "k8s.io/apimachinery/pkg/util/net" + "k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/util/wait" + "k8s.io/kubernetes/pkg/client/retry" "github.com/openshift/origin/pkg/client" imageapi "github.com/openshift/origin/pkg/image/apis/image" + registryutil "github.com/openshift/origin/test/extended/registry/util" exutil "github.com/openshift/origin/test/extended/util" testutil "github.com/openshift/origin/test/util" ) @@ -30,10 +38,92 @@ const ( // There are coefficients used to multiply layer data size to get a rough size of uploaded blob. layerSizeMultiplierForDocker18 = 2.0 layerSizeMultiplierForLatestDocker = 0.8 + defaultLayerSize = 1024 digestSHA256GzippedEmptyTar = digest.Digest("sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4") digestSha256EmptyTar = digest.Digest("sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855") + + dockerRegistryBinary = "dockerregistry" + registryGCLauncherScript = `#!/bin/sh +bin="$(which %[1]s 2>/dev/null)" +if [ -z "${bin}" -a -e "/usr/bin/%[1]s" ]; then + bin="/usr/bin/%[1]s" +elif [ -z "${bin}" -a -e "/%[1]s" ]; then + bin="/%[1]s" +fi +export REGISTRY_LOG_LEVEL=info +exec "${bin}" -prune=%[2]s +` ) +// RepoLinks maps digests of layer links to a repository +type RepoLinks map[string][]string + +func (rl RepoLinks) Add(repo string, links ...string) { + rl[repo] = append(rl[repo], links...) +} + +type RegistryStorageFiles struct { + Repos []string + ManifestLinks RepoLinks + LayerLinks RepoLinks + Blobs []string +} + +// ToPaths returns a list of paths of files contained in _sfs_ corresponding to their location in registry +// pod's storage under _root_ directory. +func (sfs *RegistryStorageFiles) ToPaths(root string) []string { + result := []string{} + if sfs == nil { + return result + } + for _, repo := range sfs.Repos { + result = append(result, repoToPath(root, repo)) + } + for repo, links := range sfs.ManifestLinks { + for _, link := range links { + result = append(result, repoLinkToPath(root, "manifest", repo, link)) + } + } + for repo, links := range sfs.LayerLinks { + for _, link := range links { + result = append(result, repoLinkToPath(root, "layer", repo, link)) + } + } + for _, blob := range sfs.Blobs { + result = append(result, blobToPath(root, blob)) + } + return result +} + +// Len returns a number of files contained in the sfs container. +func (sfs *RegistryStorageFiles) Len() int { + if sfs == nil { + return 0 + } + count := len(sfs.Blobs) + len(sfs.Repos) + for _, links := range sfs.ManifestLinks { + count += len(links) + } + for _, links := range sfs.LayerLinks { + count += len(links) + } + return count +} + +func repoToPath(root, repository string) string { + return path.Join(root, fmt.Sprintf("repositories/%s", repository)) +} +func repoLinkToPath(root, fileType, repository, dgst string) string { + d := digest.Digest(dgst) + return path.Join(root, fmt.Sprintf("repositories/%s/_%ss/%s/%s/link", + repository, fileType, d.Algorithm(), d.Hex())) +} +func blobToPath(root, dgst string) string { + d := digest.Digest(dgst) + return path.Join(root, fmt.Sprintf("blobs/%s/%s/%s/data", + d.Algorithm(), d.Hex()[0:2], d.Hex())) +} + var ( pushDeniedErrorMessages []string = []string{ // docker < 1.10 @@ -143,6 +233,7 @@ func BuildAndPushImageOfSizeWithBuilder( // Docker daemon directly. Built image is stored as an image stream tag :. If shouldSucceed is // false, a push is expected to fail with a denied error. Note the size is only approximate. Resulting image // size will be different depending on used compression algorithm and metadata overhead. +// Returned is an image digest, its ID (docker daemon's internal representation) and an error if any. func BuildAndPushImageOfSizeWithDocker( oc *exutil.CLI, dClient *dockerclient.Client, @@ -151,14 +242,85 @@ func BuildAndPushImageOfSizeWithDocker( numberOfLayers int, outSink io.Writer, shouldSucceed bool, -) (imageDigest string, err error) { - registryURL, err := GetDockerRegistryURL(oc) + removeBuiltImage bool, +) (string, string, error) { + imageName, image, err := buildImageOfSizeWithDocker( + oc, + dClient, + "scratch", + name, tag, + size, + numberOfLayers, + outSink) if err != nil { - return "", err + return "", "", err + } + + digest, err := pushImageWithDocker( + oc, + dClient, + image, + imageName, tag, + outSink, + shouldSucceed, + removeBuiltImage) + if err != nil { + return "", "", err + } + + return digest, image.ID, nil +} + +// BuildAndPushChildImage tries to build and push an image of given name and number of layers. It instructs +// Docker daemon directly. Built image is stored as an image stream tag :. +// Returned is an image digest, its ID (docker daemon's internal representation) and an error if any. +func BuildAndPushChildImage( + oc *exutil.CLI, + dClient *dockerclient.Client, + parent string, + name, tag string, + numberOfNewLayers int, + outSink io.Writer, + removeBuiltImage bool, +) (string, string, error) { + imageName, image, err := buildImageOfSizeWithDocker( + oc, dClient, + parent, name, tag, + defaultLayerSize, + numberOfNewLayers, + outSink) + if err != nil { + return "", "", err + } + + digest, err := pushImageWithDocker( + oc, dClient, + image, imageName, tag, + outSink, + true, + removeBuiltImage) + if err != nil { + return "", "", err + } + + return digest, image.ID, nil +} + +func buildImageOfSizeWithDocker( + oc *exutil.CLI, + dClient *dockerclient.Client, + parent, name, tag string, + size uint64, + numberOfLayers int, + outSink io.Writer, +) (string, *dockerclient.Image, error) { + registryURL, err := registryutil.GetDockerRegistryURL(oc) + if err != nil { + return "", nil, err } tempDir, err := ioutil.TempDir("", "name-build") if err != nil { - return "", err + return "", nil, err } dataSize := calculateRoughDataSize(oc.Stdout(), size, numberOfLayers) @@ -168,12 +330,12 @@ func BuildAndPushImageOfSizeWithDocker( for i := 1; i <= numberOfLayers; i++ { blobName := fmt.Sprintf("data%d", i) if err := createRandomBlob(path.Join(tempDir, blobName), dataSize); err != nil { - return "", err + return "", nil, err } lines[i] = fmt.Sprintf("COPY %s /%s", blobName, blobName) } if err := ioutil.WriteFile(path.Join(tempDir, "Dockerfile"), []byte(strings.Join(lines, "\n")+"\n"), 0644); err != nil { - return "", err + return "", nil, err } imageName := fmt.Sprintf("%s/%s/%s", registryURL, oc.Namespace(), name) @@ -187,28 +349,49 @@ func BuildAndPushImageOfSizeWithDocker( OutputStream: outSink, }) if err != nil { - return "", fmt.Errorf("failed to build %q image: %v", taggedName, err) + return "", nil, fmt.Errorf("failed to build %q image: %v", taggedName, err) } image, err := dClient.InspectImage(taggedName) if err != nil { - return + return "", nil, err + } + + return imageName, image, nil +} + +func pushImageWithDocker( + oc *exutil.CLI, + dClient *dockerclient.Client, + image *dockerclient.Image, + name, tag string, + outSink io.Writer, + shouldSucceed bool, + removeBuiltImage bool, +) (string, error) { + if removeBuiltImage { + defer dClient.RemoveImageExtended(image.ID, dockerclient.RemoveImageOptions{Force: true}) } - defer dClient.RemoveImageExtended(image.ID, dockerclient.RemoveImageOptions{Force: true}) + var imageDigest string if len(image.RepoDigests) == 1 { imageDigest = image.RepoDigests[0] } out, err := oc.Run("whoami").Args("-t").Output() if err != nil { - return + return "", err } token := strings.TrimSpace(out) + registryURL, err := registryutil.GetDockerRegistryURL(oc) + if err != nil { + return "", err + } + var buf bytes.Buffer err = dClient.PushImage(dockerclient.PushImageOptions{ - Name: imageName, + Name: name, Tag: tag, Registry: registryURL, OutputStream: &buf, @@ -221,42 +404,30 @@ func BuildAndPushImageOfSizeWithDocker( out = buf.String() outSink.Write([]byte(out)) - if shouldSucceed { - if err != nil { - return "", fmt.Errorf("Got unexpected push error: %v", err) + if !shouldSucceed { + if err == nil { + return "", fmt.Errorf("Push unexpectedly succeeded") } - if len(imageDigest) == 0 { - match := rePushedImageDigest.FindStringSubmatch(out) - if len(match) < 2 { - return imageDigest, fmt.Errorf("Failed to parse digest") - } - imageDigest = match[1] + if !reExpectedDeniedError.MatchString(err.Error()) { + return "", fmt.Errorf("Failed to match expected %q in: %q", reExpectedDeniedError.String(), err.Error()) } - return - } - if err == nil { - return "", fmt.Errorf("Push unexpectedly succeeded") - } - if !reExpectedDeniedError.MatchString(err.Error()) { - return "", fmt.Errorf("Failed to match expected %q in: %q", reExpectedDeniedError.String(), err.Error()) + // push failed with expected error -> no results + return "", nil } - return "", nil -} - -// GetDockerRegistryURL returns a cluster URL of internal docker registry if available. -func GetDockerRegistryURL(oc *exutil.CLI) (string, error) { - svc, err := oc.AdminKubeClient().Core().Services("default").Get("docker-registry", metav1.GetOptions{}) if err != nil { - return "", err + return "", fmt.Errorf("Got unexpected push error: %v", err) } - url := svc.Spec.ClusterIP - for _, p := range svc.Spec.Ports { - url = fmt.Sprintf("%s:%d", url, p.Port) - break + if len(imageDigest) == 0 { + match := rePushedImageDigest.FindStringSubmatch(out) + if len(match) < 2 { + return imageDigest, fmt.Errorf("Failed to parse digest") + } + imageDigest = match[1] } - return url, nil + + return imageDigest, nil } // createRandomBlob creates a random data with bytes from `letters` in order to let docker take advantage of @@ -344,11 +515,7 @@ func MirrorBlobInRegistry(oc *exutil.CLI, dgst digest.Digest, repository string, if presentGlobally || inRepository { return fmt.Errorf("blob %q is already present in the registry", dgst.String()) } - registryURL, err := GetDockerRegistryURL(oc) - if err != nil { - return err - } - req, err := http.NewRequest("GET", fmt.Sprintf("http://%s/v2/%s/blobs/%s", registryURL, repository, dgst.String()), nil) + registryURL, err := registryutil.GetDockerRegistryURL(oc) if err != nil { return err } @@ -356,12 +523,37 @@ func MirrorBlobInRegistry(oc *exutil.CLI, dgst digest.Digest, repository string, if err != nil { return err } - req.Header.Set("range", "bytes=0-1") - req.Header.Set("Authorization", "Bearer "+token) - c := http.Client{} - resp, err := c.Do(req) - if err != nil { - return err + + c := http.Client{ + Transport: knet.SetTransportDefaults(&http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + }), + } + + peekAtBlob := func(schema string) (*http.Request, *http.Response, error) { + req, err := http.NewRequest("GET", fmt.Sprintf("%s://%s/v2/%s/blobs/%s", schema, registryURL, repository, dgst.String()), nil) + if err != nil { + return nil, nil, err + } + req.Header.Set("range", "bytes=0-1") + req.Header.Set("Authorization", "Bearer "+token) + resp, err := c.Do(req) + if err != nil { + fmt.Fprintf(g.GinkgoWriter, "failed to %s %s: %v (%#+v)\n", req.Method, req.URL, err, err) + return nil, nil, err + } + return req, resp, nil + } + + var ( + req *http.Request + resp *http.Response + getErr error + ) + if req, resp, getErr = peekAtBlob("https"); getErr != nil { + if req, resp, getErr = peekAtBlob("http"); getErr != nil { + return getErr + } } defer resp.Body.Close() @@ -382,7 +574,8 @@ func IsEmptyDigest(dgst digest.Digest) bool { func pathExistsInRegistry(oc *exutil.CLI, pthComponents ...string) (bool, error) { pth := path.Join(append([]string{"/registry/docker/registry/v2"}, pthComponents...)...) - cmd := fmt.Sprintf("[ -e %s ] && echo exists || echo missing", pth) + cmd := fmt.Sprintf("test -e '%s' && echo exists || echo missing", pth) + defer func(ns string) { oc.SetNamespace(ns) }(oc.Namespace()) out, err := oc.SetNamespace(metav1.NamespaceDefault).AsAdmin().Run("rsh").Args( "dc/docker-registry", "/bin/sh", "-c", cmd).Output() if err != nil { @@ -423,3 +616,207 @@ func IsBlobStoredInRegistry( } return present, presentInRepository, err } + +// RunHardPrune executes into a docker-registry pod and runs a garbage collector. The docker-registry is +// assumed to be in a read-only mode and using filesystem as a storage driver. It returns lists of deleted +// files. +func RunHardPrune(oc *exutil.CLI, dryRun bool) (*RegistryStorageFiles, error) { + pod, err := registryutil.GetRegistryPod(oc.AsAdmin().KubeClient().Core()) + if err != nil { + return nil, err + } + + defer func(ns string) { oc.SetNamespace(ns) }(oc.Namespace()) + output, err := oc.AsAdmin().SetNamespace(metav1.NamespaceDefault).Run("env").Args("--list", "dc/docker-registry").Output() + if err != nil { + return nil, err + } + + deleted := &RegistryStorageFiles{ + Repos: []string{}, + ManifestLinks: make(RepoLinks), + LayerLinks: make(RepoLinks), + Blobs: []string{}, + } + + err = wait.ExponentialBackoff(retry.DefaultBackoff, func() (bool, error) { + pruneType := "delete" + if dryRun { + pruneType = "check" + } + out, err := oc.SetNamespace(metav1.NamespaceDefault).AsAdmin(). + Run("exec").Args("--stdin", pod.Name, "--", "/bin/sh", "-s"). + InputString(fmt.Sprintf(registryGCLauncherScript, dockerRegistryBinary, pruneType)).Output() + if exitError, ok := err.(*exutil.ExitError); ok && strings.Contains(exitError.StdErr, "unable to upgrade connection") { + fmt.Fprintf(g.GinkgoWriter, "failed to execute into registry pod %s: %v\n", pod.Name, err) + return false, nil + } + output = out + return true, err + }) + if len(output) > 0 { + fmt.Fprintf(g.GinkgoWriter, "prune output: \n%s\n\n", output) + } + + if err != nil { + return nil, err + } + + const reCommon = `(?im)\bmsg="(?:would\s+)?delet(?:e|ing)\s+(?:the\s+)?` + var reDeleteRepository = regexp.MustCompile(reCommon + `repo(?:sitory)?:\s+` + + `(?:[^"]+/)?` /* root path of the repository */ + + `([^"/]+/[^"/]+)/?"` /* repository */) + var reDeleteRepositoryLink = regexp.MustCompile(reCommon + + `(manifest|layer)(?:\s+link)?:\s+` /* type of link's destination file */ + + `([^#@]+)[#@]` /* repository */ + + `([^"]+)"` /* digest */) + var reDeleteBlob = regexp.MustCompile(reCommon + `blob:\s+` + + `(?:[^"]+/)?` /* root path to the blob */ + + `([^":/]+)` /* digest algorithm */ + + `(?:/[^"/]{2}/|:)` /* directory whose name matches the first two characters of digest hex */ + + `([^":/]+)"` /* digest hex */) + var reDeletedBlobs = regexp.MustCompile(`(?im)^(?:would\s+)?deleted?\s+(\d+)\s+blobs$`) + + for _, match := range reDeleteRepository.FindAllStringSubmatch(output, -1) { + deleted.Repos = append(deleted.Repos, match[1]) + } + for _, match := range reDeleteRepositoryLink.FindAllStringSubmatch(output, -1) { + fileType, repository, digest := match[1], match[2], match[3] + + switch strings.ToLower(fileType) { + case "manifest": + deleted.ManifestLinks.Add(repository, digest) + case "link": + deleted.LayerLinks.Add(repository, digest) + default: + fmt.Fprintf(g.GinkgoWriter, "unrecognized type of deleted file: %s\n", match[1]) + continue + } + } + for _, match := range reDeleteBlob.FindAllStringSubmatch(output, -1) { + deleted.Blobs = append(deleted.Blobs, fmt.Sprintf("%s:%s", match[1], match[2])) + } + + match := reDeletedBlobs.FindStringSubmatch(output) + if match == nil { + return nil, fmt.Errorf("missing the number of deleted blobs in the output") + } + + deletedBlobCount, err := strconv.Atoi(match[1]) + if err != nil { + return nil, fmt.Errorf("failed to parse deleted number of blobs %q: %v", match[1], err) + } + if deletedBlobCount != len(deleted.Blobs) { + return nil, fmt.Errorf("numbers of deleted blobs doesn't match %d != %d", len(deleted.Blobs), deletedBlobCount) + } + + return deleted, nil +} + +// AssertDeletedStorageFiles compares lists of deleted files against expected. An error will be generated for +// each entry present in just one of these sets. +func AssertDeletedStorageFiles(deleted, expected *RegistryStorageFiles) error { + var errors []error + deletedSet := sets.NewString(deleted.ToPaths("")...) + expectedPaths := sets.NewString(expected.ToPaths("")...) + verifiedSet := sets.NewString() + + for pth := range expectedPaths { + if deletedSet.Has(pth) { + verifiedSet.Insert(pth) + } else { + errors = append(errors, fmt.Errorf("expected path %s was not deleted", pth)) + } + } + for pth := range deletedSet { + if !expectedPaths.Has(pth) { + errors = append(errors, fmt.Errorf("path %s got unexpectedly deleted", pth)) + } + } + + return kerrors.NewAggregate(errors) +} + +// CleanUpContainer holds names of image names, docker image IDs, imagestreamtags and imagestreams that shall +// be deleted at the end of the test. +type CleanUpContainer struct { + OC *exutil.CLI + + imageNames sets.String + imageIDs sets.String + isTags sets.String + isNames sets.String +} + +// NewCleanUpContainer creates a new instance of CleanUpContainer. +func NewCleanUpContainer(oc *exutil.CLI) *CleanUpContainer { + return &CleanUpContainer{ + OC: oc, + imageNames: sets.NewString(), + imageIDs: sets.NewString(), + isTags: sets.NewString(), + isNames: sets.NewString(), + } +} + +// AddImage marks given image name, docker image id and imagestreamtag as candidates for deletion. +func (c *CleanUpContainer) AddImage(name, id, isTag string) { + if len(name) > 0 { + c.imageNames.Insert(name) + } + if len(id) > 0 { + c.imageIDs.Insert(id) + } + if len(isTag) > 0 { + c.isNames.Insert(isTag) + } +} + +// AddImageStream marks the given image stream name for removal. +func (c *CleanUpContainer) AddImageStream(isName string) { + c.isNames.Insert(isName) +} + +// Run deletes all the marked objects. +func (c *CleanUpContainer) Run() { + for image := range c.imageNames { + err := c.OC.AsAdmin().Client().Images().Delete(image) + if err != nil { + fmt.Fprintf(g.GinkgoWriter, "clean up of image %q failed: %v\n", image, err) + } + } + for isName := range c.isNames { + err := c.OC.AsAdmin().Client().ImageStreams(c.OC.Namespace()).Delete(isName) + if err != nil { + fmt.Fprintf(g.GinkgoWriter, "clean up of image stream %q failed: %v\n", isName, err) + } + } + for isTag := range c.isTags { + parts := strings.SplitN(isTag, ":", 2) + if len(parts) != 2 { + fmt.Fprintf(g.GinkgoWriter, "cannot remove invalid istag %q", isTag) + continue + } + err := c.OC.Client().ImageStreamTags(c.OC.Namespace()).Delete(parts[0], parts[1]) + if err != nil { + fmt.Fprintf(g.GinkgoWriter, "clean up of image stream tag %q failed: %v\n", isTag, err) + } + } + + if len(c.imageIDs) == 0 { + return + } + + dClient, err := testutil.NewDockerClient() + if err != nil { + fmt.Fprintf(g.GinkgoWriter, "failed to create a new docker client: %v\n", err) + return + } + + for id := range c.imageIDs { + err := dClient.RemoveImageExtended(id, dockerclient.RemoveImageOptions{Force: true}) + if err != nil { + fmt.Fprintf(g.GinkgoWriter, "failed to remove image %q: %v\n", id, err) + } + } +} diff --git a/vendor/github.com/openshift/origin/test/extended/images/prune.go b/vendor/github.com/openshift/origin/test/extended/images/prune.go index 5173dea01..f48eb1d50 100644 --- a/vendor/github.com/openshift/origin/test/extended/images/prune.go +++ b/vendor/github.com/openshift/origin/test/extended/images/prune.go @@ -2,9 +2,7 @@ package images import ( "fmt" - "regexp" "sort" - "strconv" "strings" "time" @@ -17,8 +15,8 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - dockerregistryserver "github.com/openshift/origin/pkg/dockerregistry/server" imageapi "github.com/openshift/origin/pkg/image/apis/image" + registryutil "github.com/openshift/origin/test/extended/registry/util" exutil "github.com/openshift/origin/test/extended/util" testutil "github.com/openshift/origin/test/util" ) @@ -30,19 +28,15 @@ const ( externalImageReference = "docker.io/openshift/origin-release:golang-1.4" ) -type cleanUpContainer struct { - imageNames []string - isNames []string -} - var _ = g.Describe("[Feature:ImagePrune] Image prune", func() { defer g.GinkgoRecover() var oc = exutil.NewCLI("prune-images", exutil.KubeConfigPath()) + var originalAcceptSchema2 *bool g.JustBeforeEach(func() { if originalAcceptSchema2 == nil { - accepts, err := doesRegistryAcceptSchema2(oc) + accepts, err := registryutil.DoesRegistryAcceptSchema2(oc) o.Expect(err).NotTo(o.HaveOccurred()) originalAcceptSchema2 = &accepts } @@ -59,14 +53,14 @@ var _ = g.Describe("[Feature:ImagePrune] Image prune", func() { g.JustBeforeEach(func() { if *originalAcceptSchema2 { g.By("ensure the registry does not accept schema 2") - err := ensureRegistryAcceptsSchema2(oc, false) + err := registryutil.EnsureRegistryAcceptsSchema2(oc, false) o.Expect(err).NotTo(o.HaveOccurred()) } }) g.AfterEach(func() { if *originalAcceptSchema2 { - err := ensureRegistryAcceptsSchema2(oc, true) + err := registryutil.EnsureRegistryAcceptsSchema2(oc, true) o.Expect(err).NotTo(o.HaveOccurred()) } }) @@ -78,14 +72,14 @@ var _ = g.Describe("[Feature:ImagePrune] Image prune", func() { g.JustBeforeEach(func() { if !*originalAcceptSchema2 { g.By("ensure the registry accepts schema 2") - err := ensureRegistryAcceptsSchema2(oc, true) + err := registryutil.EnsureRegistryAcceptsSchema2(oc, true) o.Expect(err).NotTo(o.HaveOccurred()) } }) g.AfterEach(func() { if !*originalAcceptSchema2 { - err := ensureRegistryAcceptsSchema2(oc, false) + err := registryutil.EnsureRegistryAcceptsSchema2(oc, false) o.Expect(err).NotTo(o.HaveOccurred()) } }) @@ -94,9 +88,17 @@ var _ = g.Describe("[Feature:ImagePrune] Image prune", func() { }) g.Describe("with default --all flag", func() { + g.JustBeforeEach(func() { + if !*originalAcceptSchema2 { + g.By("ensure the registry accepts schema 2") + err := registryutil.EnsureRegistryAcceptsSchema2(oc, true) + o.Expect(err).NotTo(o.HaveOccurred()) + } + }) + g.AfterEach(func() { if !*originalAcceptSchema2 { - err := ensureRegistryAcceptsSchema2(oc, false) + err := registryutil.EnsureRegistryAcceptsSchema2(oc, false) o.Expect(err).NotTo(o.HaveOccurred()) } }) @@ -105,9 +107,17 @@ var _ = g.Describe("[Feature:ImagePrune] Image prune", func() { }) g.Describe("with --all=false flag", func() { + g.JustBeforeEach(func() { + if !*originalAcceptSchema2 { + g.By("ensure the registry accepts schema 2") + err := registryutil.EnsureRegistryAcceptsSchema2(oc, true) + o.Expect(err).NotTo(o.HaveOccurred()) + } + }) + g.AfterEach(func() { if !*originalAcceptSchema2 { - err := ensureRegistryAcceptsSchema2(oc, false) + err := registryutil.EnsureRegistryAcceptsSchema2(oc, false) o.Expect(err).NotTo(o.HaveOccurred()) } }) @@ -133,23 +143,23 @@ func testPruneImages(oc *exutil.CLI, schemaVersion int) { oc.SetOutputDir(exutil.TestContext.OutputDir) outSink := g.GinkgoWriter - cleanUp := cleanUpContainer{} - defer tearDownPruneImagesTest(oc, &cleanUp) + cleanUp := NewCleanUpContainer(oc) + defer cleanUp.Run() dClient, err := testutil.NewDockerClient() o.Expect(err).NotTo(o.HaveOccurred()) g.By(fmt.Sprintf("build two images using Docker and push them as schema %d", schemaVersion)) - imgPruneName, err := BuildAndPushImageOfSizeWithDocker(oc, dClient, isName, "latest", testImageSize, 2, outSink, true) + imgPruneName, _, err := BuildAndPushImageOfSizeWithDocker(oc, dClient, isName, "latest", testImageSize, 2, outSink, true, true) o.Expect(err).NotTo(o.HaveOccurred()) - cleanUp.imageNames = append(cleanUp.imageNames, imgPruneName) - cleanUp.isNames = append(cleanUp.isNames, isName) - pruneSize, err := getRegistryStorageSize(oc) + cleanUp.AddImage(imgPruneName, "", "") + cleanUp.AddImageStream(isName) + pruneSize, err := registryutil.GetRegistryStorageSize(oc) o.Expect(err).NotTo(o.HaveOccurred()) - imgKeepName, err := BuildAndPushImageOfSizeWithDocker(oc, dClient, isName, "latest", testImageSize, 2, outSink, true) + imgKeepName, _, err := BuildAndPushImageOfSizeWithDocker(oc, dClient, isName, "latest", testImageSize, 2, outSink, true, true) o.Expect(err).NotTo(o.HaveOccurred()) - cleanUp.imageNames = append(cleanUp.imageNames, imgKeepName) - keepSize, err := getRegistryStorageSize(oc) + cleanUp.AddImage(imgKeepName, "", "") + keepSize, err := registryutil.GetRegistryStorageSize(oc) o.Expect(err).NotTo(o.HaveOccurred()) o.Expect(pruneSize < keepSize).To(o.BeTrue()) @@ -185,7 +195,7 @@ func testPruneImages(oc *exutil.CLI, schemaVersion int) { } } - noConfirmSize, err := getRegistryStorageSize(oc) + noConfirmSize, err := registryutil.GetRegistryStorageSize(oc) o.Expect(err).NotTo(o.HaveOccurred()) o.Expect(noConfirmSize).To(o.Equal(keepSize)) @@ -221,7 +231,7 @@ func testPruneImages(oc *exutil.CLI, schemaVersion int) { o.Expect(inRepository).To(o.BeTrue()) } - confirmSize, err := getRegistryStorageSize(oc) + confirmSize, err := registryutil.GetRegistryStorageSize(oc) o.Expect(err).NotTo(o.HaveOccurred()) g.By(fmt.Sprintf("confirming storage size: sizeOfKeepImage=%d <= sizeAfterPrune=%d < beforePruneSize=%d", imgKeep.DockerImageMetadata.Size, confirmSize, keepSize)) o.Expect(confirmSize >= imgKeep.DockerImageMetadata.Size).To(o.BeTrue()) @@ -231,23 +241,23 @@ func testPruneImages(oc *exutil.CLI, schemaVersion int) { } func testPruneAllImages(oc *exutil.CLI, setAllImagesToFalse bool, schemaVersion int) { - isName := "prune" + isName := fmt.Sprintf("prune-schema%d-all-images-%t", schemaVersion, setAllImagesToFalse) repository := oc.Namespace() + "/" + isName oc.SetOutputDir(exutil.TestContext.OutputDir) outSink := g.GinkgoWriter - cleanUp := cleanUpContainer{} - defer tearDownPruneImagesTest(oc, &cleanUp) + cleanUp := NewCleanUpContainer(oc) + defer cleanUp.Run() dClient, err := testutil.NewDockerClient() o.Expect(err).NotTo(o.HaveOccurred()) g.By("build one image using Docker and push it") - managedImageName, err := BuildAndPushImageOfSizeWithDocker(oc, dClient, isName, "latest", testImageSize, 2, outSink, true) + managedImageName, _, err := BuildAndPushImageOfSizeWithDocker(oc, dClient, isName, "latest", testImageSize, 2, outSink, true, true) o.Expect(err).NotTo(o.HaveOccurred()) - cleanUp.imageNames = append(cleanUp.imageNames, managedImageName) - cleanUp.isNames = append(cleanUp.isNames, isName) + cleanUp.AddImage(managedImageName, "", "") + cleanUp.AddImageStream(isName) o.Expect(err).NotTo(o.HaveOccurred()) managedImage, err := oc.AsAdmin().Client().Images().Get(managedImageName, metav1.GetOptions{}) @@ -255,8 +265,8 @@ func testPruneAllImages(oc *exutil.CLI, setAllImagesToFalse bool, schemaVersion externalImage, blobdgst, err := importImageAndMirrorItsSmallestBlob(oc, externalImageReference, "origin-release:latest") o.Expect(err).NotTo(o.HaveOccurred()) - cleanUp.imageNames = append(cleanUp.imageNames, externalImage.Name) - cleanUp.isNames = append(cleanUp.isNames, "origin-release") + cleanUp.AddImage(externalImage.Name, "", "") + cleanUp.AddImageStream("origin-release") checkAdminPruneOutput := func(output string, dryRun bool) { o.Expect(output).To(o.ContainSubstring(managedImage.Name)) @@ -315,88 +325,6 @@ func testPruneAllImages(oc *exutil.CLI, setAllImagesToFalse bool, schemaVersion checkAdminPruneOutput(output, false) } -func tearDownPruneImagesTest(oc *exutil.CLI, cleanUp *cleanUpContainer) { - for _, image := range cleanUp.imageNames { - err := oc.AsAdmin().Client().Images().Delete(image) - if err != nil { - fmt.Fprintf(g.GinkgoWriter, "clean up of image %q failed: %v\n", image, err) - } - } - for _, isName := range cleanUp.isNames { - err := oc.AsAdmin().Client().ImageStreams(oc.Namespace()).Delete(isName) - if err != nil { - fmt.Fprintf(g.GinkgoWriter, "clean up of image stream %q failed: %v\n", isName, err) - } - } -} - -func getRegistryStorageSize(oc *exutil.CLI) (int64, error) { - ns := oc.Namespace() - defer oc.SetNamespace(ns) - out, err := oc.SetNamespace(metav1.NamespaceDefault).AsAdmin().Run("rsh").Args("dc/docker-registry", "du", "--bytes", "--summarize", "/registry/docker/registry").Output() - if err != nil { - return 0, err - } - m := regexp.MustCompile(`^\d+`).FindString(out) - if len(m) == 0 { - return 0, fmt.Errorf("failed to parse du output: %s", out) - } - - size, err := strconv.ParseInt(m, 10, 64) - if err != nil { - return 0, fmt.Errorf("failed to parse du output: %s", m) - } - - return size, nil -} - -func doesRegistryAcceptSchema2(oc *exutil.CLI) (bool, error) { - ns := oc.Namespace() - defer oc.SetNamespace(ns) - env, err := oc.SetNamespace(metav1.NamespaceDefault).AsAdmin().Run("env").Args("dc/docker-registry", "--list").Output() - if err != nil { - return false, err - } - - return strings.Contains(env, fmt.Sprintf("%s=true", dockerregistryserver.AcceptSchema2EnvVar)), nil -} - -// ensureRegistryAcceptsSchema2 checks whether the registry is configured to accept manifests V2 schema 2 or -// not. If the result doesn't match given accept argument, registry's deployment config is updated accordingly -// and the function blocks until the registry is re-deployed and ready for new requests. -func ensureRegistryAcceptsSchema2(oc *exutil.CLI, accept bool) error { - ns := oc.Namespace() - oc = oc.SetNamespace(metav1.NamespaceDefault).AsAdmin() - defer oc.SetNamespace(ns) - env, err := oc.Run("env").Args("dc/docker-registry", "--list").Output() - if err != nil { - return err - } - - value := fmt.Sprintf("%s=%t", dockerregistryserver.AcceptSchema2EnvVar, accept) - if strings.Contains(env, value) { - if accept { - g.By("docker-registry is already configured to accept schema 2") - } else { - g.By("docker-registry is already configured to refuse schema 2") - } - return nil - } - - dc, err := oc.Client().DeploymentConfigs(metav1.NamespaceDefault).Get("docker-registry", metav1.GetOptions{}) - if err != nil { - return err - } - - g.By("configuring Docker registry to accept schema 2") - err = oc.Run("env").Args("dc/docker-registry", value).Execute() - if err != nil { - return fmt.Errorf("failed to update registry environment: %v", err) - } - - return exutil.WaitForDeploymentConfig(oc.AdminKubeClient(), oc.AdminClient(), metav1.NamespaceDefault, "docker-registry", dc.Status.LatestVersion+1, oc) -} - type byLayerSize []imageapi.ImageLayer func (bls byLayerSize) Len() int { return len(bls) } diff --git a/vendor/github.com/openshift/origin/test/extended/registry/registry.go b/vendor/github.com/openshift/origin/test/extended/registry/registry.go index 9ae8b6ca4..c892dcf23 100644 --- a/vendor/github.com/openshift/origin/test/extended/registry/registry.go +++ b/vendor/github.com/openshift/origin/test/extended/registry/registry.go @@ -1,7 +1,6 @@ package registry import ( - "fmt" "time" g "github.com/onsi/ginkgo" @@ -15,6 +14,7 @@ import ( regclient "github.com/openshift/origin/pkg/dockerregistry" imageapi "github.com/openshift/origin/pkg/image/apis/image" imagesutil "github.com/openshift/origin/test/extended/images" + registryutil "github.com/openshift/origin/test/extended/registry/util" exutil "github.com/openshift/origin/test/extended/util" testutil "github.com/openshift/origin/test/util" ) @@ -29,15 +29,10 @@ var _ = g.Describe("[Conformance][registry][migration] manifest migration from e defer g.GinkgoRecover() var oc = exutil.NewCLI("registry-migration", exutil.KubeConfigPath()) - // needs to be run at the top of each It; cannot be run in AfterEach which is run after the project - // is destroyed - tearDown := func(oc *exutil.CLI) { - deleteTestImages(oc) - } - g.It("registry can get access to manifest [local]", func() { oc.SetOutputDir(exutil.TestContext.OutputDir) - defer tearDown(oc) + cleanUp := imagesutil.NewCleanUpContainer(oc) + defer cleanUp.Run() g.By("set up policy for registry to have anonymous access to images") err := oc.Run("policy").Args("add-role-to-user", "registry-viewer", "system:anonymous").Execute() @@ -46,12 +41,13 @@ var _ = g.Describe("[Conformance][registry][migration] manifest migration from e dClient, err := testutil.NewDockerClient() o.Expect(err).NotTo(o.HaveOccurred()) - registryURL, err := imagesutil.GetDockerRegistryURL(oc) + registryURL, err := registryutil.GetDockerRegistryURL(oc) o.Expect(err).NotTo(o.HaveOccurred()) g.By("pushing image...") - imageDigest, err := imagesutil.BuildAndPushImageOfSizeWithDocker(oc, dClient, repoName, tagName, imageSize, 1, g.GinkgoWriter, true) + imageDigest, _, err := imagesutil.BuildAndPushImageOfSizeWithDocker(oc, dClient, repoName, tagName, imageSize, 1, g.GinkgoWriter, true, true) o.Expect(err).NotTo(o.HaveOccurred()) + cleanUp.AddImage(imageDigest, "", "") g.By("checking that the image converted...") image, err := oc.AsAdmin().Client().Images().Get(imageDigest, metav1.GetOptions{}) @@ -139,20 +135,3 @@ func waitForImageUpdate(oc *exutil.CLI, image *imageapi.Image) error { return (image.ResourceVersion < newImage.ResourceVersion), nil }) } - -// deleteTestImages deletes test images built in current and shared -// namespaces. It also deletes shared projects. -func deleteTestImages(oc *exutil.CLI) { - g.By(fmt.Sprintf("Deleting images and image streams in project %q", oc.Namespace())) - iss, err := oc.AdminClient().ImageStreams(oc.Namespace()).List(metav1.ListOptions{}) - if err != nil { - return - } - for _, is := range iss.Items { - for _, history := range is.Status.Tags { - for i := range history.Items { - oc.AdminClient().Images().Delete(history.Items[i].Image) - } - } - } -} diff --git a/vendor/github.com/openshift/origin/test/extended/registry/signature.go b/vendor/github.com/openshift/origin/test/extended/registry/signature.go index 34c418c4e..5b323a46e 100644 --- a/vendor/github.com/openshift/origin/test/extended/registry/signature.go +++ b/vendor/github.com/openshift/origin/test/extended/registry/signature.go @@ -6,7 +6,7 @@ import ( g "github.com/onsi/ginkgo" o "github.com/onsi/gomega" - imagesutil "github.com/openshift/origin/test/extended/images" + registryutil "github.com/openshift/origin/test/extended/registry/util" exutil "github.com/openshift/origin/test/extended/util" e2e "k8s.io/kubernetes/test/e2e/framework" @@ -33,7 +33,7 @@ var _ = g.Describe("[imageapis][registry] image signature workflow", func() { o.Expect(err).NotTo(o.HaveOccurred()) g.By("looking up the openshift registry URL") - registryURL, err := imagesutil.GetDockerRegistryURL(oc) + registryURL, err := registryutil.GetDockerRegistryURL(oc) signerImage := fmt.Sprintf("%s/%s/signer:latest", registryURL, oc.Namespace()) signedImage := fmt.Sprintf("%s/%s/signed:latest", registryURL, oc.Namespace()) o.Expect(err).NotTo(o.HaveOccurred()) diff --git a/vendor/github.com/openshift/origin/test/extended/registry/util/util.go b/vendor/github.com/openshift/origin/test/extended/registry/util/util.go new file mode 100644 index 000000000..a6c3b6542 --- /dev/null +++ b/vendor/github.com/openshift/origin/test/extended/registry/util/util.go @@ -0,0 +1,172 @@ +package images + +import ( + "fmt" + "regexp" + "sort" + "strconv" + "strings" + + g "github.com/onsi/ginkgo" + //o "github.com/onsi/gomega" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" + kapiv1 "k8s.io/kubernetes/pkg/api/v1" + kcoreclient "k8s.io/kubernetes/pkg/client/clientset_generated/clientset/typed/core/v1" + + dockerregistryserver "github.com/openshift/origin/pkg/dockerregistry/server" + exutil "github.com/openshift/origin/test/extended/util" +) + +const ( + readOnlyEnvVar = "REGISTRY_STORAGE_MAINTENANCE_READONLY" + defaultAcceptSchema2 = true +) + +// GetDockerRegistryURL returns a cluster URL of internal docker registry if available. +func GetDockerRegistryURL(oc *exutil.CLI) (string, error) { + svc, err := oc.AdminKubeClient().Core().Services("default").Get("docker-registry", metav1.GetOptions{}) + if err != nil { + return "", err + } + url := svc.Spec.ClusterIP + for _, p := range svc.Spec.Ports { + url = fmt.Sprintf("%s:%d", url, p.Port) + break + } + return url, nil +} + +// GetRegistryStorageSize returns a number of bytes occupied by registry's data on its filesystem. +func GetRegistryStorageSize(oc *exutil.CLI) (int64, error) { + defer func(ns string) { oc.SetNamespace(ns) }(oc.Namespace()) + out, err := oc.SetNamespace(metav1.NamespaceDefault).AsAdmin().Run("rsh").Args( + "dc/docker-registry", "du", "--bytes", "--summarize", "/registry/docker/registry").Output() + if err != nil { + return 0, err + } + m := regexp.MustCompile(`^\d+`).FindString(out) + if len(m) == 0 { + return 0, fmt.Errorf("failed to parse du output: %s", out) + } + + size, err := strconv.ParseInt(m, 10, 64) + if err != nil { + return 0, fmt.Errorf("failed to parse du output: %s", m) + } + + return size, nil +} + +// DoesRegistryAcceptSchema2 returns true if the integrated registry is configured to accept manifest V2 +// schema 2. +func DoesRegistryAcceptSchema2(oc *exutil.CLI) (bool, error) { + defer func(ns string) { oc.SetNamespace(ns) }(oc.Namespace()) + env, err := oc.SetNamespace(metav1.NamespaceDefault).AsAdmin().Run("env").Args("dc/docker-registry", "--list").Output() + if err != nil { + return defaultAcceptSchema2, err + } + + if strings.Contains(env, fmt.Sprintf("%s=", dockerregistryserver.AcceptSchema2EnvVar)) { + return strings.Contains(env, fmt.Sprintf("%s=true", dockerregistryserver.AcceptSchema2EnvVar)), nil + } + + return defaultAcceptSchema2, nil +} + +// RegistriConfiguration holds desired configuration options for the integrated registry. *nil* stands for +// "no change". +type RegistryConfiguration struct { + ReadOnly *bool + AcceptSchema2 *bool +} + +type byAgeDesc []kapiv1.Pod + +func (ba byAgeDesc) Len() int { return len(ba) } +func (ba byAgeDesc) Swap(i, j int) { ba[i], ba[j] = ba[j], ba[i] } +func (ba byAgeDesc) Less(i, j int) bool { + return ba[j].CreationTimestamp.Before(ba[i].CreationTimestamp) +} + +// GetRegistryPod returns the youngest registry pod deployed. +func GetRegistryPod(podsGetter kcoreclient.PodsGetter) (*kapiv1.Pod, error) { + podList, err := podsGetter.Pods(metav1.NamespaceDefault).List(metav1.ListOptions{ + LabelSelector: labels.SelectorFromSet(labels.Set{"deploymentconfig": "docker-registry"}).String(), + }) + if err != nil { + return nil, err + } + if len(podList.Items) == 0 { + return nil, fmt.Errorf("failed to find any docker-registry pod") + } + + sort.Sort(byAgeDesc(podList.Items)) + + return &podList.Items[0], nil +} + +// ConfigureRegistry re-deploys the registry pod if its configuration doesn't match the desiredState. The +// function blocks until the registry is ready. +func ConfigureRegistry(oc *exutil.CLI, desiredState RegistryConfiguration) error { + defer func(ns string) { oc.SetNamespace(ns) }(oc.Namespace()) + oc = oc.SetNamespace(metav1.NamespaceDefault).AsAdmin() + env, err := oc.Run("env").Args("dc/docker-registry", "--list").Output() + if err != nil { + return err + } + + envOverrides := []string{} + + if desiredState.AcceptSchema2 != nil { + current := defaultAcceptSchema2 + if strings.Contains(env, fmt.Sprintf("%s=%t", dockerregistryserver.AcceptSchema2EnvVar, !defaultAcceptSchema2)) { + current = !defaultAcceptSchema2 + } + if current != *desiredState.AcceptSchema2 { + new := fmt.Sprintf("%s=%t", dockerregistryserver.AcceptSchema2EnvVar, *desiredState.AcceptSchema2) + envOverrides = append(envOverrides, new) + } + } + if desiredState.ReadOnly != nil { + value := fmt.Sprintf("%s=%s", readOnlyEnvVar, makeReadonlyEnvValue(*desiredState.ReadOnly)) + if !strings.Contains(env, value) { + envOverrides = append(envOverrides, value) + } + } + if len(envOverrides) == 0 { + g.By("docker-registry is already in the desired state of configuration") + return nil + } + + dc, err := oc.Client().DeploymentConfigs(metav1.NamespaceDefault).Get("docker-registry", metav1.GetOptions{}) + if err != nil { + return err + } + waitForVersion := dc.Status.LatestVersion + 1 + + err = oc.Run("env").Args(append([]string{"dc/docker-registry"}, envOverrides...)...).Execute() + if err != nil { + return fmt.Errorf("failed to update registry's environment with %s: %v", &waitForVersion, err) + } + return exutil.WaitForDeploymentConfig( + oc.AdminKubeClient(), + oc.AdminClient(), + metav1.NamespaceDefault, + "docker-registry", + waitForVersion, + oc) +} + +// EnsureRegistryAcceptsSchema2 checks whether the registry is configured to accept manifests V2 schema 2 or +// not. If the result doesn't match given accept argument, registry's deployment config will be updated +// accordingly and the function will block until the registry have been re-deployed and ready for new +// requests. +func EnsureRegistryAcceptsSchema2(oc *exutil.CLI, accept bool) error { + return ConfigureRegistry(oc, RegistryConfiguration{AcceptSchema2: &accept}) +} + +func makeReadonlyEnvValue(on bool) string { + return fmt.Sprintf(`{"enabled":%t}`, on) +} diff --git a/vendor/github.com/openshift/origin/test/extended/util/cli.go b/vendor/github.com/openshift/origin/test/extended/util/cli.go index f88e97058..5088c2fe1 100644 --- a/vendor/github.com/openshift/origin/test/extended/util/cli.go +++ b/vendor/github.com/openshift/origin/test/extended/util/cli.go @@ -134,9 +134,9 @@ func (c *CLI) SetNamespace(ns string) *CLI { } // WithoutNamespace instructs the command should be invoked without adding --namespace parameter -func (c *CLI) WithoutNamespace() *CLI { +func (c CLI) WithoutNamespace() *CLI { c.withoutNamespace = true - return c + return &c } // SetOutputDir change the default output directory for temporary files diff --git a/vendor/github.com/openshift/origin/test/integration/imageimporter_test.go b/vendor/github.com/openshift/origin/test/integration/imageimporter_test.go index e735ccd56..54dec98e7 100644 --- a/vendor/github.com/openshift/origin/test/integration/imageimporter_test.go +++ b/vendor/github.com/openshift/origin/test/integration/imageimporter_test.go @@ -32,6 +32,7 @@ import ( ) func TestImageStreamImport(t *testing.T) { + t.Skip("This test was disabled until https://github.com/openshift/origin/issues/16323 is fixed!") testutil.RequireEtcd(t) defer testutil.DumpEtcdOnFailure(t) _, clusterAdminKubeConfig, err := testserver.StartTestMaster() @@ -803,6 +804,8 @@ func TestImageStreamImportScheduled(t *testing.T) { } func TestImageStreamImportDockerHub(t *testing.T) { + t.Skip("This test was disabled until https://github.com/openshift/origin/issues/16323 is fixed!") + rt, _ := restclient.TransportFor(&restclient.Config{}) importCtx := importer.NewContext(rt, nil).WithCredentials(importer.NoCredentials) diff --git a/vendor/k8s.io/kubernetes/cmd/kube-controller-manager/app/core.go b/vendor/k8s.io/kubernetes/cmd/kube-controller-manager/app/core.go index 69466bbad..2338f90ab 100644 --- a/vendor/k8s.io/kubernetes/cmd/kube-controller-manager/app/core.go +++ b/vendor/k8s.io/kubernetes/cmd/kube-controller-manager/app/core.go @@ -49,6 +49,7 @@ func startEndpointController(ctx ControllerContext) (bool, error) { go endpointcontroller.NewEndpointController( ctx.InformerFactory.Core().V1().Pods(), ctx.InformerFactory.Core().V1().Services(), + ctx.InformerFactory.Core().V1().Endpoints(), ctx.ClientBuilder.ClientOrDie("endpoint-controller"), ).Run(int(ctx.Options.ConcurrentEndpointSyncs), ctx.Stop) return true, nil diff --git a/vendor/k8s.io/kubernetes/pkg/client/unversioned/helper.go b/vendor/k8s.io/kubernetes/pkg/client/unversioned/helper.go index c8d09ef41..74baf75d0 100644 --- a/vendor/k8s.io/kubernetes/pkg/client/unversioned/helper.go +++ b/vendor/k8s.io/kubernetes/pkg/client/unversioned/helper.go @@ -17,6 +17,7 @@ limitations under the License. package unversioned import ( + "k8s.io/apimachinery/pkg/runtime/schema" restclient "k8s.io/client-go/rest" "k8s.io/kubernetes/pkg/api" // Import solely to initialize client auth plugins. @@ -35,13 +36,9 @@ func SetKubernetesDefaults(config *restclient.Config) error { if config.APIPath == "" { config.APIPath = legacyAPIPath } - if config.GroupVersion == nil || config.GroupVersion.Group != api.GroupName { - g, err := api.Registry.Group(api.GroupName) - if err != nil { - return err - } - copyGroupVersion := g.GroupVersion - config.GroupVersion = ©GroupVersion + // TODO chase down uses and tolerate nil + if config.GroupVersion == nil { + config.GroupVersion = &schema.GroupVersion{} } if config.NegotiatedSerializer == nil { config.NegotiatedSerializer = api.Codecs diff --git a/vendor/k8s.io/kubernetes/pkg/client/unversioned/helper_test.go b/vendor/k8s.io/kubernetes/pkg/client/unversioned/helper_test.go index 3e884acca..529661015 100644 --- a/vendor/k8s.io/kubernetes/pkg/client/unversioned/helper_test.go +++ b/vendor/k8s.io/kubernetes/pkg/client/unversioned/helper_test.go @@ -42,7 +42,7 @@ func TestSetKubernetesDefaults(t *testing.T) { restclient.Config{ APIPath: "/api", ContentConfig: restclient.ContentConfig{ - GroupVersion: &api.Registry.GroupOrDie(api.GroupName).GroupVersion, + GroupVersion: &schema.GroupVersion{}, NegotiatedSerializer: testapi.Default.NegotiatedSerializer(), }, }, diff --git a/vendor/k8s.io/kubernetes/pkg/controller/endpoint/BUILD b/vendor/k8s.io/kubernetes/pkg/controller/endpoint/BUILD index bd1ea282e..e432af34f 100644 --- a/vendor/k8s.io/kubernetes/pkg/controller/endpoint/BUILD +++ b/vendor/k8s.io/kubernetes/pkg/controller/endpoint/BUILD @@ -16,6 +16,7 @@ go_library( ], tags = ["automanaged"], deps = [ + "//pkg/api:go_default_library", "//pkg/api/v1:go_default_library", "//pkg/api/v1/endpoints:go_default_library", "//pkg/api/v1/pod:go_default_library", @@ -50,7 +51,9 @@ go_test( "//pkg/controller:go_default_library", "//vendor:k8s.io/apimachinery/pkg/apis/meta/v1", "//vendor:k8s.io/apimachinery/pkg/runtime", + "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", "//vendor:k8s.io/apimachinery/pkg/util/intstr", + "//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library", "//vendor:k8s.io/client-go/rest", "//vendor:k8s.io/client-go/tools/cache", "//vendor:k8s.io/client-go/util/testing", diff --git a/vendor/k8s.io/kubernetes/pkg/controller/endpoint/endpoints_controller.go b/vendor/k8s.io/kubernetes/pkg/controller/endpoint/endpoints_controller.go index 54c8b9c33..a4d1ecf14 100644 --- a/vendor/k8s.io/kubernetes/pkg/controller/endpoint/endpoints_controller.go +++ b/vendor/k8s.io/kubernetes/pkg/controller/endpoint/endpoints_controller.go @@ -22,6 +22,7 @@ import ( "strconv" "time" + apiequality "k8s.io/apimachinery/pkg/api/equality" "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" @@ -30,6 +31,7 @@ import ( "k8s.io/apimachinery/pkg/util/wait" "k8s.io/client-go/tools/cache" "k8s.io/client-go/util/workqueue" + "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/v1" "k8s.io/kubernetes/pkg/api/v1/endpoints" podutil "k8s.io/kubernetes/pkg/api/v1/pod" @@ -43,11 +45,6 @@ import ( ) const ( - // We'll attempt to recompute EVERY service's endpoints at least this - // often. Higher numbers = lower CPU/network load; lower numbers = - // shorter amount of time before a mistaken endpoint is corrected. - FullServiceResyncPeriod = 30 * time.Second - // An annotation on the Service denoting if the endpoints controller should // go ahead and create endpoints for unready pods. This annotation is // currently only used by StatefulSets, where we need the pod to be DNS @@ -66,26 +63,24 @@ var ( ) // NewEndpointController returns a new *EndpointController. -func NewEndpointController(podInformer coreinformers.PodInformer, serviceInformer coreinformers.ServiceInformer, client clientset.Interface) *EndpointController { +func NewEndpointController(podInformer coreinformers.PodInformer, serviceInformer coreinformers.ServiceInformer, + endpointsInformer coreinformers.EndpointsInformer, client clientset.Interface) *EndpointController { if client != nil && client.Core().RESTClient().GetRateLimiter() != nil { metrics.RegisterMetricAndTrackRateLimiterUsage("endpoint_controller", client.Core().RESTClient().GetRateLimiter()) } e := &EndpointController{ - client: client, - queue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "endpoint"), + client: client, + queue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "endpoint"), + workerLoopPeriod: time.Second, } - serviceInformer.Informer().AddEventHandlerWithResyncPeriod( - cache.ResourceEventHandlerFuncs{ - AddFunc: e.enqueueService, - UpdateFunc: func(old, cur interface{}) { - e.enqueueService(cur) - }, - DeleteFunc: e.enqueueService, + serviceInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ + AddFunc: e.enqueueService, + UpdateFunc: func(old, cur interface{}) { + e.enqueueService(cur) }, - // TODO: Can we have much longer period here? - FullServiceResyncPeriod, - ) + DeleteFunc: e.enqueueService, + }) e.serviceLister = serviceInformer.Lister() e.servicesSynced = serviceInformer.Informer().HasSynced @@ -97,6 +92,9 @@ func NewEndpointController(podInformer coreinformers.PodInformer, serviceInforme e.podLister = podInformer.Lister() e.podsSynced = podInformer.Informer().HasSynced + e.endpointsLister = endpointsInformer.Lister() + e.endpointsSynced = endpointsInformer.Informer().HasSynced + return e } @@ -118,12 +116,22 @@ type EndpointController struct { // Added as a member to the struct to allow injection for testing. podsSynced cache.InformerSynced + // endpointsLister is able to list/get endpoints and is populated by the shared informer passed to + // NewEndpointController. + endpointsLister corelisters.EndpointsLister + // endpointsSynced returns true if the endpoints shared informer has been synced at least once. + // Added as a member to the struct to allow injection for testing. + endpointsSynced cache.InformerSynced + // Services that need to be updated. A channel is inappropriate here, // because it allows services with lots of pods to be serviced much // more often than services with few pods; it also would cause a // service that's inserted multiple times to be processed more than // necessary. queue workqueue.RateLimitingInterface + + // workerLoopPeriod is the time between worker runs. The workers process the queue of service and pod changes. + workerLoopPeriod time.Duration } // Runs e; will not return until stopCh is closed. workers determines how many @@ -132,13 +140,13 @@ func (e *EndpointController) Run(workers int, stopCh <-chan struct{}) { defer utilruntime.HandleCrash() defer e.queue.ShutDown() - if !cache.WaitForCacheSync(stopCh, e.podsSynced, e.servicesSynced) { + if !cache.WaitForCacheSync(stopCh, e.podsSynced, e.servicesSynced, e.endpointsSynced) { utilruntime.HandleError(fmt.Errorf("timed out waiting for caches to sync")) return } for i := 0; i < workers; i++ { - go wait.Until(e.worker, time.Second, stopCh) + go wait.Until(e.worker, e.workerLoopPeriod, stopCh) } go func() { defer utilruntime.HandleCrash() @@ -181,6 +189,56 @@ func (e *EndpointController) addPod(obj interface{}) { } } +func podToEndpointAddress(pod *v1.Pod) *v1.EndpointAddress { + return &v1.EndpointAddress{ + IP: pod.Status.PodIP, + NodeName: &pod.Spec.NodeName, + TargetRef: &v1.ObjectReference{ + Kind: "Pod", + Namespace: pod.ObjectMeta.Namespace, + Name: pod.ObjectMeta.Name, + UID: pod.ObjectMeta.UID, + ResourceVersion: pod.ObjectMeta.ResourceVersion, + }} +} + +func podChanged(oldPod, newPod *v1.Pod) bool { + // If the pod's readiness has changed, the associated endpoint address + // will move from the unready endpoints set to the ready endpoints. + // So for the purposes of an endpoint, a readiness change on a pod + // means we have a changed pod. + if v1.IsPodReady(oldPod) != v1.IsPodReady(newPod) { + return true + } + // Convert the pod to an EndpointAddress, clear inert fields, + // and see if they are the same. + newEndpointAddress := podToEndpointAddress(newPod) + oldEndpointAddress := podToEndpointAddress(oldPod) + // Ignore the ResourceVersion because it changes + // with every pod update. This allows the comparison to + // show equality if all other relevant fields match. + newEndpointAddress.TargetRef.ResourceVersion = "" + oldEndpointAddress.TargetRef.ResourceVersion = "" + if reflect.DeepEqual(newEndpointAddress, oldEndpointAddress) { + // The pod has not changed in any way that impacts the endpoints + return false + } + return true +} + +func determineNeededServiceUpdates(oldServices, services sets.String, podChanged bool) sets.String { + if podChanged { + // if the labels and pod changed, all services need to be updated + services = services.Union(oldServices) + } else { + // if only the labels changed, services not common to + // both the new and old service set (i.e the disjunctive union) + // need to be updated + services = services.Difference(oldServices).Union(oldServices.Difference(services)) + } + return services +} + // When a pod is updated, figure out what services it used to be a member of // and what services it will be a member of, and enqueue the union of these. // old and cur must be *v1.Pod types. @@ -192,22 +250,37 @@ func (e *EndpointController) updatePod(old, cur interface{}) { // Two different versions of the same pod will always have different RVs. return } + + podChangedFlag := podChanged(oldPod, newPod) + + // Check if the pod labels have changed, indicating a possibe + // change in the service membership + labelsChanged := false + if !reflect.DeepEqual(newPod.Labels, oldPod.Labels) || + !hostNameAndDomainAreEqual(newPod, oldPod) { + labelsChanged = true + } + + // If both the pod and labels are unchanged, no update is needed + if !podChangedFlag && !labelsChanged { + return + } + services, err := e.getPodServiceMemberships(newPod) if err != nil { utilruntime.HandleError(fmt.Errorf("Unable to get pod %v/%v's service memberships: %v", newPod.Namespace, newPod.Name, err)) return } - // Only need to get the old services if the labels changed. - if !reflect.DeepEqual(newPod.Labels, oldPod.Labels) || - !hostNameAndDomainAreEqual(newPod, oldPod) { + if labelsChanged { oldServices, err := e.getPodServiceMemberships(oldPod) if err != nil { utilruntime.HandleError(fmt.Errorf("Unable to get pod %v/%v's service memberships: %v", oldPod.Namespace, oldPod.Name, err)) return } - services = services.Union(oldServices) + services = determineNeededServiceUpdates(oldServices, services, podChangedFlag) } + for key := range services { e.queue.Add(key) } @@ -248,14 +321,19 @@ func (e *EndpointController) deletePod(obj interface{}) { e.addPod(obj) return } - podKey, err := keyFunc(obj) - if err != nil { - utilruntime.HandleError(fmt.Errorf("Couldn't get key for object %#v: %v", obj, err)) + // If we reached here it means the pod was deleted but its final state is unrecorded. + tombstone, ok := obj.(cache.DeletedFinalStateUnknown) + if !ok { + utilruntime.HandleError(fmt.Errorf("Couldn't get object from tombstone %#v", obj)) return } - glog.V(4).Infof("Pod %q was deleted but we don't have a record of its final state, so it will take up to %v before it will be removed from all endpoint records.", podKey, FullServiceResyncPeriod) - - // TODO: keep a map of pods to services to handle this condition. + pod, ok := tombstone.Obj.(*v1.Pod) + if !ok { + utilruntime.HandleError(fmt.Errorf("Tombstone contained object that is not a Pod: %#v", obj)) + return + } + glog.V(4).Infof("Enqueuing services of deleted pod %s having final state unrecorded", pod.Name) + e.addPod(pod) } // obj could be an *v1.Service, or a DeletionFinalStateUnknown marker item. @@ -379,16 +457,7 @@ func (e *EndpointController) syncService(key string) error { } epp := v1.EndpointPort{Name: portName, Port: int32(portNum), Protocol: portProto} - epa := v1.EndpointAddress{ - IP: pod.Status.PodIP, - NodeName: &pod.Spec.NodeName, - TargetRef: &v1.ObjectReference{ - Kind: "Pod", - Namespace: pod.ObjectMeta.Namespace, - Name: pod.ObjectMeta.Name, - UID: pod.ObjectMeta.UID, - ResourceVersion: pod.ObjectMeta.ResourceVersion, - }} + epa := *podToEndpointAddress(pod) hostname := getHostname(pod) if len(hostname) > 0 && @@ -416,7 +485,7 @@ func (e *EndpointController) syncService(key string) error { subsets = endpoints.RepackSubsets(subsets) // See if there's actually an update here. - currentEndpoints, err := e.client.Core().Endpoints(service.Namespace).Get(service.Name, metav1.GetOptions{}) + currentEndpoints, err := e.endpointsLister.Endpoints(service.Namespace).Get(service.Name) if err != nil { if errors.IsNotFound(err) { currentEndpoints = &v1.Endpoints{ @@ -430,12 +499,19 @@ func (e *EndpointController) syncService(key string) error { } } - if reflect.DeepEqual(currentEndpoints.Subsets, subsets) && - reflect.DeepEqual(currentEndpoints.Labels, service.Labels) { + createEndpoints := len(currentEndpoints.ResourceVersion) == 0 + + if !createEndpoints && + apiequality.Semantic.DeepEqual(currentEndpoints.Subsets, subsets) && + apiequality.Semantic.DeepEqual(currentEndpoints.Labels, service.Labels) { glog.V(5).Infof("endpoints are equal for %s/%s, skipping update", service.Namespace, service.Name) return nil } - newEndpoints := currentEndpoints + copy, err := api.Scheme.DeepCopy(currentEndpoints) + if err != nil { + return err + } + newEndpoints := copy.(*v1.Endpoints) newEndpoints.Subsets = subsets newEndpoints.Labels = service.Labels if newEndpoints.Annotations == nil { @@ -443,7 +519,6 @@ func (e *EndpointController) syncService(key string) error { } glog.V(4).Infof("Update endpoints for %v/%v, ready: %d not ready: %d", service.Namespace, service.Name, readyEps, notReadyEps) - createEndpoints := len(currentEndpoints.ResourceVersion) == 0 if createEndpoints { // No previous endpoints, create them _, err = e.client.Core().Endpoints(service.Namespace).Create(newEndpoints) @@ -471,13 +546,12 @@ func (e *EndpointController) syncService(key string) error { // some stragglers could have been left behind if the endpoint controller // reboots). func (e *EndpointController) checkLeftoverEndpoints() { - list, err := e.client.Core().Endpoints(metav1.NamespaceAll).List(metav1.ListOptions{}) + list, err := e.endpointsLister.List(labels.Everything()) if err != nil { utilruntime.HandleError(fmt.Errorf("Unable to list endpoints (%v); orphaned endpoints will not be cleaned up. (They're pretty harmless, but you can restart this component if you want another attempt made.)", err)) return } - for i := range list.Items { - ep := &list.Items[i] + for _, ep := range list { key, err := keyFunc(ep) if err != nil { utilruntime.HandleError(fmt.Errorf("Unable to get key for endpoint %#v", ep)) diff --git a/vendor/k8s.io/kubernetes/pkg/controller/endpoint/endpoints_controller_test.go b/vendor/k8s.io/kubernetes/pkg/controller/endpoint/endpoints_controller_test.go index 570e23ca7..dda86d8a2 100644 --- a/vendor/k8s.io/kubernetes/pkg/controller/endpoint/endpoints_controller_test.go +++ b/vendor/k8s.io/kubernetes/pkg/controller/endpoint/endpoints_controller_test.go @@ -21,10 +21,13 @@ import ( "net/http" "net/http/httptest" "testing" + "time" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/intstr" + "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/apimachinery/pkg/util/wait" restclient "k8s.io/client-go/rest" "k8s.io/client-go/tools/cache" utiltesting "k8s.io/client-go/util/testing" @@ -38,6 +41,7 @@ import ( ) var alwaysReady = func() bool { return true } +var neverReady = func() bool { return false } var emptyNodeName string func addPods(store cache.Store, namespace string, nPods int, nPorts int, nNotReady int) { @@ -78,10 +82,10 @@ type serverResponse struct { obj interface{} } -func makeTestServer(t *testing.T, namespace string, endpointsResponse serverResponse) (*httptest.Server, *utiltesting.FakeHandler) { +func makeTestServer(t *testing.T, namespace string) (*httptest.Server, *utiltesting.FakeHandler) { fakeEndpointsHandler := utiltesting.FakeHandler{ - StatusCode: endpointsResponse.statusCode, - ResponseBody: runtime.EncodeOrDie(testapi.Default.Codec(), endpointsResponse.obj.(runtime.Object)), + StatusCode: http.StatusOK, + ResponseBody: runtime.EncodeOrDie(testapi.Default.Codec(), &v1.Endpoints{}), } mux := http.NewServeMux() mux.Handle(testapi.Default.ResourcePath("endpoints", namespace, ""), &fakeEndpointsHandler) @@ -95,39 +99,43 @@ func makeTestServer(t *testing.T, namespace string, endpointsResponse serverResp type endpointController struct { *EndpointController - podStore cache.Store - serviceStore cache.Store + podStore cache.Store + serviceStore cache.Store + endpointsStore cache.Store } func newController(url string) *endpointController { client := clientset.NewForConfigOrDie(&restclient.Config{Host: url, ContentConfig: restclient.ContentConfig{GroupVersion: &api.Registry.GroupOrDie(v1.GroupName).GroupVersion}}) informerFactory := informers.NewSharedInformerFactory(client, controller.NoResyncPeriodFunc()) - endpoints := NewEndpointController(informerFactory.Core().V1().Pods(), informerFactory.Core().V1().Services(), client) + endpoints := NewEndpointController(informerFactory.Core().V1().Pods(), informerFactory.Core().V1().Services(), + informerFactory.Core().V1().Endpoints(), client) endpoints.podsSynced = alwaysReady endpoints.servicesSynced = alwaysReady + endpoints.endpointsSynced = alwaysReady return &endpointController{ endpoints, informerFactory.Core().V1().Pods().Informer().GetStore(), informerFactory.Core().V1().Services().Informer().GetStore(), + informerFactory.Core().V1().Endpoints().Informer().GetStore(), } } func TestSyncEndpointsItemsPreserveNoSelector(t *testing.T) { ns := metav1.NamespaceDefault - testServer, endpointsHandler := makeTestServer(t, ns, - serverResponse{http.StatusOK, &v1.Endpoints{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Namespace: ns, - ResourceVersion: "1", - }, - Subsets: []v1.EndpointSubset{{ - Addresses: []v1.EndpointAddress{{IP: "6.7.8.9", NodeName: &emptyNodeName}}, - Ports: []v1.EndpointPort{{Port: 1000}}, - }}, - }}) + testServer, endpointsHandler := makeTestServer(t, ns) defer testServer.Close() endpoints := newController(testServer.URL) + endpoints.endpointsStore.Add(&v1.Endpoints{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Namespace: ns, + ResourceVersion: "1", + }, + Subsets: []v1.EndpointSubset{{ + Addresses: []v1.EndpointAddress{{IP: "6.7.8.9", NodeName: &emptyNodeName}}, + Ports: []v1.EndpointPort{{Port: 1000}}, + }}, + }) endpoints.serviceStore.Add(&v1.Service{ ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: ns}, Spec: v1.ServiceSpec{Ports: []v1.ServicePort{{Port: 80}}}, @@ -136,31 +144,87 @@ func TestSyncEndpointsItemsPreserveNoSelector(t *testing.T) { endpointsHandler.ValidateRequestCount(t, 0) } +func TestSyncEndpointsExistingNilSubsets(t *testing.T) { + ns := metav1.NamespaceDefault + testServer, endpointsHandler := makeTestServer(t, ns) + defer testServer.Close() + endpoints := newController(testServer.URL) + endpoints.endpointsStore.Add(&v1.Endpoints{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Namespace: ns, + ResourceVersion: "1", + }, + Subsets: nil, + }) + endpoints.serviceStore.Add(&v1.Service{ + ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: ns}, + Spec: v1.ServiceSpec{ + Selector: map[string]string{"foo": "bar"}, + Ports: []v1.ServicePort{{Port: 80}}, + }, + }) + endpoints.syncService(ns + "/foo") + endpointsHandler.ValidateRequestCount(t, 0) +} + +func TestSyncEndpointsExistingEmptySubsets(t *testing.T) { + ns := metav1.NamespaceDefault + testServer, endpointsHandler := makeTestServer(t, ns) + defer testServer.Close() + endpoints := newController(testServer.URL) + endpoints.endpointsStore.Add(&v1.Endpoints{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Namespace: ns, + ResourceVersion: "1", + }, + Subsets: []v1.EndpointSubset{}, + }) + endpoints.serviceStore.Add(&v1.Service{ + ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: ns}, + Spec: v1.ServiceSpec{ + Selector: map[string]string{"foo": "bar"}, + Ports: []v1.ServicePort{{Port: 80}}, + }, + }) + endpoints.syncService(ns + "/foo") + endpointsHandler.ValidateRequestCount(t, 0) +} + +func TestSyncEndpointsNewNoSubsets(t *testing.T) { + ns := metav1.NamespaceDefault + testServer, endpointsHandler := makeTestServer(t, ns) + defer testServer.Close() + endpoints := newController(testServer.URL) + endpoints.serviceStore.Add(&v1.Service{ + ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: ns}, + Spec: v1.ServiceSpec{ + Selector: map[string]string{"foo": "bar"}, + Ports: []v1.ServicePort{{Port: 80}}, + }, + }) + endpoints.syncService(ns + "/foo") + endpointsHandler.ValidateRequestCount(t, 1) +} + func TestCheckLeftoverEndpoints(t *testing.T) { ns := metav1.NamespaceDefault - // Note that this requests *all* endpoints, therefore metav1.NamespaceAll - // below. - testServer, _ := makeTestServer(t, metav1.NamespaceAll, - serverResponse{http.StatusOK, &v1.EndpointsList{ - ListMeta: metav1.ListMeta{ - ResourceVersion: "1", - }, - Items: []v1.Endpoints{{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Namespace: ns, - ResourceVersion: "1", - }, - Subsets: []v1.EndpointSubset{{ - Addresses: []v1.EndpointAddress{{IP: "6.7.8.9", NodeName: &emptyNodeName}}, - Ports: []v1.EndpointPort{{Port: 1000}}, - }}, - }}, - }}) + testServer, _ := makeTestServer(t, ns) defer testServer.Close() endpoints := newController(testServer.URL) + endpoints.endpointsStore.Add(&v1.Endpoints{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Namespace: ns, + ResourceVersion: "1", + }, + Subsets: []v1.EndpointSubset{{ + Addresses: []v1.EndpointAddress{{IP: "6.7.8.9", NodeName: &emptyNodeName}}, + Ports: []v1.EndpointPort{{Port: 1000}}, + }}, + }) endpoints.checkLeftoverEndpoints() - if e, a := 1, endpoints.queue.Len(); e != a { t.Fatalf("Expected %v, got %v", e, a) } @@ -172,21 +236,20 @@ func TestCheckLeftoverEndpoints(t *testing.T) { func TestSyncEndpointsProtocolTCP(t *testing.T) { ns := "other" - testServer, endpointsHandler := makeTestServer(t, ns, - serverResponse{http.StatusOK, &v1.Endpoints{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Namespace: ns, - ResourceVersion: "1", - }, - Subsets: []v1.EndpointSubset{{ - Addresses: []v1.EndpointAddress{{IP: "6.7.8.9", NodeName: &emptyNodeName}}, - Ports: []v1.EndpointPort{{Port: 1000, Protocol: "TCP"}}, - }}, - }}) + testServer, endpointsHandler := makeTestServer(t, ns) defer testServer.Close() endpoints := newController(testServer.URL) - + endpoints.endpointsStore.Add(&v1.Endpoints{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Namespace: ns, + ResourceVersion: "1", + }, + Subsets: []v1.EndpointSubset{{ + Addresses: []v1.EndpointAddress{{IP: "6.7.8.9", NodeName: &emptyNodeName}}, + Ports: []v1.EndpointPort{{Port: 1000, Protocol: "TCP"}}, + }}, + }) addPods(endpoints.podStore, ns, 1, 1, 0) endpoints.serviceStore.Add(&v1.Service{ ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: ns}, @@ -196,7 +259,8 @@ func TestSyncEndpointsProtocolTCP(t *testing.T) { }, }) endpoints.syncService(ns + "/foo") - endpointsHandler.ValidateRequestCount(t, 2) + + endpointsHandler.ValidateRequestCount(t, 1) data := runtime.EncodeOrDie(testapi.Default.Codec(), &v1.Endpoints{ ObjectMeta: metav1.ObjectMeta{ Name: "foo", @@ -213,20 +277,20 @@ func TestSyncEndpointsProtocolTCP(t *testing.T) { func TestSyncEndpointsProtocolUDP(t *testing.T) { ns := "other" - testServer, endpointsHandler := makeTestServer(t, ns, - serverResponse{http.StatusOK, &v1.Endpoints{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Namespace: ns, - ResourceVersion: "1", - }, - Subsets: []v1.EndpointSubset{{ - Addresses: []v1.EndpointAddress{{IP: "6.7.8.9", NodeName: &emptyNodeName}}, - Ports: []v1.EndpointPort{{Port: 1000, Protocol: "UDP"}}, - }}, - }}) + testServer, endpointsHandler := makeTestServer(t, ns) defer testServer.Close() endpoints := newController(testServer.URL) + endpoints.endpointsStore.Add(&v1.Endpoints{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Namespace: ns, + ResourceVersion: "1", + }, + Subsets: []v1.EndpointSubset{{ + Addresses: []v1.EndpointAddress{{IP: "6.7.8.9", NodeName: &emptyNodeName}}, + Ports: []v1.EndpointPort{{Port: 1000, Protocol: "UDP"}}, + }}, + }) addPods(endpoints.podStore, ns, 1, 1, 0) endpoints.serviceStore.Add(&v1.Service{ ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: ns}, @@ -236,7 +300,8 @@ func TestSyncEndpointsProtocolUDP(t *testing.T) { }, }) endpoints.syncService(ns + "/foo") - endpointsHandler.ValidateRequestCount(t, 2) + + endpointsHandler.ValidateRequestCount(t, 1) data := runtime.EncodeOrDie(testapi.Default.Codec(), &v1.Endpoints{ ObjectMeta: metav1.ObjectMeta{ Name: "foo", @@ -253,17 +318,17 @@ func TestSyncEndpointsProtocolUDP(t *testing.T) { func TestSyncEndpointsItemsEmptySelectorSelectsAll(t *testing.T) { ns := "other" - testServer, endpointsHandler := makeTestServer(t, ns, - serverResponse{http.StatusOK, &v1.Endpoints{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Namespace: ns, - ResourceVersion: "1", - }, - Subsets: []v1.EndpointSubset{}, - }}) + testServer, endpointsHandler := makeTestServer(t, ns) defer testServer.Close() endpoints := newController(testServer.URL) + endpoints.endpointsStore.Add(&v1.Endpoints{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Namespace: ns, + ResourceVersion: "1", + }, + Subsets: []v1.EndpointSubset{}, + }) addPods(endpoints.podStore, ns, 1, 1, 0) endpoints.serviceStore.Add(&v1.Service{ ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: ns}, @@ -273,6 +338,7 @@ func TestSyncEndpointsItemsEmptySelectorSelectsAll(t *testing.T) { }, }) endpoints.syncService(ns + "/foo") + data := runtime.EncodeOrDie(testapi.Default.Codec(), &v1.Endpoints{ ObjectMeta: metav1.ObjectMeta{ Name: "foo", @@ -289,17 +355,17 @@ func TestSyncEndpointsItemsEmptySelectorSelectsAll(t *testing.T) { func TestSyncEndpointsItemsEmptySelectorSelectsAllNotReady(t *testing.T) { ns := "other" - testServer, endpointsHandler := makeTestServer(t, ns, - serverResponse{http.StatusOK, &v1.Endpoints{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Namespace: ns, - ResourceVersion: "1", - }, - Subsets: []v1.EndpointSubset{}, - }}) + testServer, endpointsHandler := makeTestServer(t, ns) defer testServer.Close() endpoints := newController(testServer.URL) + endpoints.endpointsStore.Add(&v1.Endpoints{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Namespace: ns, + ResourceVersion: "1", + }, + Subsets: []v1.EndpointSubset{}, + }) addPods(endpoints.podStore, ns, 0, 1, 1) endpoints.serviceStore.Add(&v1.Service{ ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: ns}, @@ -309,6 +375,7 @@ func TestSyncEndpointsItemsEmptySelectorSelectsAllNotReady(t *testing.T) { }, }) endpoints.syncService(ns + "/foo") + data := runtime.EncodeOrDie(testapi.Default.Codec(), &v1.Endpoints{ ObjectMeta: metav1.ObjectMeta{ Name: "foo", @@ -325,17 +392,17 @@ func TestSyncEndpointsItemsEmptySelectorSelectsAllNotReady(t *testing.T) { func TestSyncEndpointsItemsEmptySelectorSelectsAllMixed(t *testing.T) { ns := "other" - testServer, endpointsHandler := makeTestServer(t, ns, - serverResponse{http.StatusOK, &v1.Endpoints{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Namespace: ns, - ResourceVersion: "1", - }, - Subsets: []v1.EndpointSubset{}, - }}) + testServer, endpointsHandler := makeTestServer(t, ns) defer testServer.Close() endpoints := newController(testServer.URL) + endpoints.endpointsStore.Add(&v1.Endpoints{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Namespace: ns, + ResourceVersion: "1", + }, + Subsets: []v1.EndpointSubset{}, + }) addPods(endpoints.podStore, ns, 1, 1, 1) endpoints.serviceStore.Add(&v1.Service{ ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: ns}, @@ -345,6 +412,7 @@ func TestSyncEndpointsItemsEmptySelectorSelectsAllMixed(t *testing.T) { }, }) endpoints.syncService(ns + "/foo") + data := runtime.EncodeOrDie(testapi.Default.Codec(), &v1.Endpoints{ ObjectMeta: metav1.ObjectMeta{ Name: "foo", @@ -362,20 +430,20 @@ func TestSyncEndpointsItemsEmptySelectorSelectsAllMixed(t *testing.T) { func TestSyncEndpointsItemsPreexisting(t *testing.T) { ns := "bar" - testServer, endpointsHandler := makeTestServer(t, ns, - serverResponse{http.StatusOK, &v1.Endpoints{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Namespace: ns, - ResourceVersion: "1", - }, - Subsets: []v1.EndpointSubset{{ - Addresses: []v1.EndpointAddress{{IP: "6.7.8.9", NodeName: &emptyNodeName}}, - Ports: []v1.EndpointPort{{Port: 1000}}, - }}, - }}) + testServer, endpointsHandler := makeTestServer(t, ns) defer testServer.Close() endpoints := newController(testServer.URL) + endpoints.endpointsStore.Add(&v1.Endpoints{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Namespace: ns, + ResourceVersion: "1", + }, + Subsets: []v1.EndpointSubset{{ + Addresses: []v1.EndpointAddress{{IP: "6.7.8.9", NodeName: &emptyNodeName}}, + Ports: []v1.EndpointPort{{Port: 1000}}, + }}, + }) addPods(endpoints.podStore, ns, 1, 1, 0) endpoints.serviceStore.Add(&v1.Service{ ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: ns}, @@ -385,6 +453,7 @@ func TestSyncEndpointsItemsPreexisting(t *testing.T) { }, }) endpoints.syncService(ns + "/foo") + data := runtime.EncodeOrDie(testapi.Default.Codec(), &v1.Endpoints{ ObjectMeta: metav1.ObjectMeta{ Name: "foo", @@ -401,20 +470,20 @@ func TestSyncEndpointsItemsPreexisting(t *testing.T) { func TestSyncEndpointsItemsPreexistingIdentical(t *testing.T) { ns := metav1.NamespaceDefault - testServer, endpointsHandler := makeTestServer(t, metav1.NamespaceDefault, - serverResponse{http.StatusOK, &v1.Endpoints{ - ObjectMeta: metav1.ObjectMeta{ - ResourceVersion: "1", - Name: "foo", - Namespace: ns, - }, - Subsets: []v1.EndpointSubset{{ - Addresses: []v1.EndpointAddress{{IP: "1.2.3.4", NodeName: &emptyNodeName, TargetRef: &v1.ObjectReference{Kind: "Pod", Name: "pod0", Namespace: ns}}}, - Ports: []v1.EndpointPort{{Port: 8080, Protocol: "TCP"}}, - }}, - }}) + testServer, endpointsHandler := makeTestServer(t, ns) defer testServer.Close() endpoints := newController(testServer.URL) + endpoints.endpointsStore.Add(&v1.Endpoints{ + ObjectMeta: metav1.ObjectMeta{ + ResourceVersion: "1", + Name: "foo", + Namespace: ns, + }, + Subsets: []v1.EndpointSubset{{ + Addresses: []v1.EndpointAddress{{IP: "1.2.3.4", NodeName: &emptyNodeName, TargetRef: &v1.ObjectReference{Kind: "Pod", Name: "pod0", Namespace: ns}}}, + Ports: []v1.EndpointPort{{Port: 8080, Protocol: "TCP"}}, + }}, + }) addPods(endpoints.podStore, metav1.NamespaceDefault, 1, 1, 0) endpoints.serviceStore.Add(&v1.Service{ ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: metav1.NamespaceDefault}, @@ -424,13 +493,12 @@ func TestSyncEndpointsItemsPreexistingIdentical(t *testing.T) { }, }) endpoints.syncService(ns + "/foo") - endpointsHandler.ValidateRequest(t, testapi.Default.ResourcePath("endpoints", metav1.NamespaceDefault, "foo"), "GET", nil) + endpointsHandler.ValidateRequestCount(t, 0) } func TestSyncEndpointsItems(t *testing.T) { ns := "other" - testServer, endpointsHandler := makeTestServer(t, ns, - serverResponse{http.StatusOK, &v1.Endpoints{}}) + testServer, endpointsHandler := makeTestServer(t, ns) defer testServer.Close() endpoints := newController(testServer.URL) addPods(endpoints.podStore, ns, 3, 2, 0) @@ -446,6 +514,7 @@ func TestSyncEndpointsItems(t *testing.T) { }, }) endpoints.syncService("other/foo") + expectedSubsets := []v1.EndpointSubset{{ Addresses: []v1.EndpointAddress{ {IP: "1.2.3.4", NodeName: &emptyNodeName, TargetRef: &v1.ObjectReference{Kind: "Pod", Name: "pod0", Namespace: ns}}, @@ -460,18 +529,17 @@ func TestSyncEndpointsItems(t *testing.T) { data := runtime.EncodeOrDie(testapi.Default.Codec(), &v1.Endpoints{ ObjectMeta: metav1.ObjectMeta{ ResourceVersion: "", + Name: "foo", }, Subsets: endptspkg.SortSubsets(expectedSubsets), }) - // endpointsHandler should get 2 requests - one for "GET" and the next for "POST". - endpointsHandler.ValidateRequestCount(t, 2) + endpointsHandler.ValidateRequestCount(t, 1) endpointsHandler.ValidateRequest(t, testapi.Default.ResourcePath("endpoints", ns, ""), "POST", &data) } func TestSyncEndpointsItemsWithLabels(t *testing.T) { ns := "other" - testServer, endpointsHandler := makeTestServer(t, ns, - serverResponse{http.StatusOK, &v1.Endpoints{}}) + testServer, endpointsHandler := makeTestServer(t, ns) defer testServer.Close() endpoints := newController(testServer.URL) addPods(endpoints.podStore, ns, 3, 2, 0) @@ -491,6 +559,7 @@ func TestSyncEndpointsItemsWithLabels(t *testing.T) { }, }) endpoints.syncService(ns + "/foo") + expectedSubsets := []v1.EndpointSubset{{ Addresses: []v1.EndpointAddress{ {IP: "1.2.3.4", NodeName: &emptyNodeName, TargetRef: &v1.ObjectReference{Kind: "Pod", Name: "pod0", Namespace: ns}}, @@ -505,34 +574,34 @@ func TestSyncEndpointsItemsWithLabels(t *testing.T) { data := runtime.EncodeOrDie(testapi.Default.Codec(), &v1.Endpoints{ ObjectMeta: metav1.ObjectMeta{ ResourceVersion: "", + Name: "foo", Labels: serviceLabels, }, Subsets: endptspkg.SortSubsets(expectedSubsets), }) - // endpointsHandler should get 2 requests - one for "GET" and the next for "POST". - endpointsHandler.ValidateRequestCount(t, 2) + endpointsHandler.ValidateRequestCount(t, 1) endpointsHandler.ValidateRequest(t, testapi.Default.ResourcePath("endpoints", ns, ""), "POST", &data) } func TestSyncEndpointsItemsPreexistingLabelsChange(t *testing.T) { ns := "bar" - testServer, endpointsHandler := makeTestServer(t, ns, - serverResponse{http.StatusOK, &v1.Endpoints{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Namespace: ns, - ResourceVersion: "1", - Labels: map[string]string{ - "foo": "bar", - }, - }, - Subsets: []v1.EndpointSubset{{ - Addresses: []v1.EndpointAddress{{IP: "6.7.8.9", NodeName: &emptyNodeName}}, - Ports: []v1.EndpointPort{{Port: 1000}}, - }}, - }}) + testServer, endpointsHandler := makeTestServer(t, ns) defer testServer.Close() endpoints := newController(testServer.URL) + endpoints.endpointsStore.Add(&v1.Endpoints{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Namespace: ns, + ResourceVersion: "1", + Labels: map[string]string{ + "foo": "bar", + }, + }, + Subsets: []v1.EndpointSubset{{ + Addresses: []v1.EndpointAddress{{IP: "6.7.8.9", NodeName: &emptyNodeName}}, + Ports: []v1.EndpointPort{{Port: 1000}}, + }}, + }) addPods(endpoints.podStore, ns, 1, 1, 0) serviceLabels := map[string]string{"baz": "blah"} endpoints.serviceStore.Add(&v1.Service{ @@ -547,6 +616,7 @@ func TestSyncEndpointsItemsPreexistingLabelsChange(t *testing.T) { }, }) endpoints.syncService(ns + "/foo") + data := runtime.EncodeOrDie(testapi.Default.Codec(), &v1.Endpoints{ ObjectMeta: metav1.ObjectMeta{ Name: "foo", @@ -561,3 +631,211 @@ func TestSyncEndpointsItemsPreexistingLabelsChange(t *testing.T) { }) endpointsHandler.ValidateRequest(t, testapi.Default.ResourcePath("endpoints", ns, "foo"), "PUT", &data) } + +func TestPodToEndpointAddress(t *testing.T) { + podStore := cache.NewStore(cache.DeletionHandlingMetaNamespaceKeyFunc) + ns := "test" + addPods(podStore, ns, 1, 1, 0) + pods := podStore.List() + if len(pods) != 1 { + t.Errorf("podStore size: expected: %d, got: %d", 1, len(pods)) + return + } + pod := pods[0].(*v1.Pod) + epa := podToEndpointAddress(pod) + if epa.IP != pod.Status.PodIP { + t.Errorf("IP: expected: %s, got: %s", pod.Status.PodIP, epa.IP) + } + if *(epa.NodeName) != pod.Spec.NodeName { + t.Errorf("NodeName: expected: %s, got: %s", pod.Spec.NodeName, *(epa.NodeName)) + } + if epa.TargetRef.Kind != "Pod" { + t.Errorf("TargetRef.Kind: expected: %s, got: %s", "Pod", epa.TargetRef.Kind) + } + if epa.TargetRef.Namespace != pod.ObjectMeta.Namespace { + t.Errorf("TargetRef.Kind: expected: %s, got: %s", pod.ObjectMeta.Namespace, epa.TargetRef.Namespace) + } + if epa.TargetRef.Name != pod.ObjectMeta.Name { + t.Errorf("TargetRef.Kind: expected: %s, got: %s", pod.ObjectMeta.Name, epa.TargetRef.Name) + } + if epa.TargetRef.UID != pod.ObjectMeta.UID { + t.Errorf("TargetRef.Kind: expected: %s, got: %s", pod.ObjectMeta.UID, epa.TargetRef.UID) + } + if epa.TargetRef.ResourceVersion != pod.ObjectMeta.ResourceVersion { + t.Errorf("TargetRef.Kind: expected: %s, got: %s", pod.ObjectMeta.ResourceVersion, epa.TargetRef.ResourceVersion) + } +} + +func TestPodChanged(t *testing.T) { + podStore := cache.NewStore(cache.DeletionHandlingMetaNamespaceKeyFunc) + ns := "test" + addPods(podStore, ns, 1, 1, 0) + pods := podStore.List() + if len(pods) != 1 { + t.Errorf("podStore size: expected: %d, got: %d", 1, len(pods)) + return + } + oldPod := pods[0].(*v1.Pod) + objCopy, err := api.Scheme.DeepCopy(oldPod) + if err != nil { + t.Errorf("error copying Pod: %v ", err) + } + copied, ok := objCopy.(*v1.Pod) + if !ok { + t.Errorf("expected pod, got %#v", objCopy) + } + newPod := copied + + if podChanged(oldPod, newPod) { + t.Errorf("Expected pod to be unchanged for copied pod") + } + + newPod.Spec.NodeName = "changed" + if !podChanged(oldPod, newPod) { + t.Errorf("Expected pod to be changed for pod with NodeName changed") + } + newPod.Spec.NodeName = oldPod.Spec.NodeName + + newPod.ObjectMeta.ResourceVersion = "changed" + if podChanged(oldPod, newPod) { + t.Errorf("Expected pod to be unchanged for pod with only ResourceVersion changed") + } + newPod.ObjectMeta.ResourceVersion = oldPod.ObjectMeta.ResourceVersion + + newPod.Status.PodIP = "1.2.3.1" + if !podChanged(oldPod, newPod) { + t.Errorf("Expected pod to be changed with pod IP address change") + } + newPod.Status.PodIP = oldPod.Status.PodIP + + newPod.ObjectMeta.Name = "wrong-name" + if !podChanged(oldPod, newPod) { + t.Errorf("Expected pod to be changed with pod name change") + } + newPod.ObjectMeta.Name = oldPod.ObjectMeta.Name + + saveConditions := oldPod.Status.Conditions + oldPod.Status.Conditions = nil + if !podChanged(oldPod, newPod) { + t.Errorf("Expected pod to be changed with pod readiness change") + } + oldPod.Status.Conditions = saveConditions +} + +func TestDetermineNeededServiceUpdates(t *testing.T) { + testCases := []struct { + name string + a sets.String + b sets.String + union sets.String + xor sets.String + }{ + { + name: "no services changed", + a: sets.NewString("a", "b", "c"), + b: sets.NewString("a", "b", "c"), + xor: sets.NewString(), + union: sets.NewString("a", "b", "c"), + }, + { + name: "all old services removed, new services added", + a: sets.NewString("a", "b", "c"), + b: sets.NewString("d", "e", "f"), + xor: sets.NewString("a", "b", "c", "d", "e", "f"), + union: sets.NewString("a", "b", "c", "d", "e", "f"), + }, + { + name: "all old services removed, no new services added", + a: sets.NewString("a", "b", "c"), + b: sets.NewString(), + xor: sets.NewString("a", "b", "c"), + union: sets.NewString("a", "b", "c"), + }, + { + name: "no old services, but new services added", + a: sets.NewString(), + b: sets.NewString("a", "b", "c"), + xor: sets.NewString("a", "b", "c"), + union: sets.NewString("a", "b", "c"), + }, + { + name: "one service removed, one service added, two unchanged", + a: sets.NewString("a", "b", "c"), + b: sets.NewString("b", "c", "d"), + xor: sets.NewString("a", "d"), + union: sets.NewString("a", "b", "c", "d"), + }, + { + name: "no services", + a: sets.NewString(), + b: sets.NewString(), + xor: sets.NewString(), + union: sets.NewString(), + }, + } + for _, testCase := range testCases { + retval := determineNeededServiceUpdates(testCase.a, testCase.b, false) + if !retval.Equal(testCase.xor) { + t.Errorf("%s (with podChanged=false): expected: %v got: %v", testCase.name, testCase.xor.List(), retval.List()) + } + + retval = determineNeededServiceUpdates(testCase.a, testCase.b, true) + if !retval.Equal(testCase.union) { + t.Errorf("%s (with podChanged=true): expected: %v got: %v", testCase.name, testCase.union.List(), retval.List()) + } + } +} + +func TestWaitsForAllInformersToBeSynced2(t *testing.T) { + var tests = []struct { + podsSynced func() bool + servicesSynced func() bool + endpointsSynced func() bool + shouldUpdateEndpoints bool + }{ + {neverReady, alwaysReady, alwaysReady, false}, + {alwaysReady, neverReady, alwaysReady, false}, + {alwaysReady, alwaysReady, neverReady, false}, + {alwaysReady, alwaysReady, alwaysReady, true}, + } + + for _, test := range tests { + func() { + ns := "other" + testServer, endpointsHandler := makeTestServer(t, ns) + defer testServer.Close() + endpoints := newController(testServer.URL) + addPods(endpoints.podStore, ns, 1, 1, 0) + service := &v1.Service{ + ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: ns}, + Spec: v1.ServiceSpec{ + Selector: map[string]string{}, + Ports: []v1.ServicePort{{Port: 80, TargetPort: intstr.FromInt(8080), Protocol: "TCP"}}, + }, + } + endpoints.serviceStore.Add(service) + endpoints.enqueueService(service) + endpoints.podsSynced = test.podsSynced + endpoints.servicesSynced = test.servicesSynced + endpoints.endpointsSynced = test.endpointsSynced + endpoints.workerLoopPeriod = 10 * time.Millisecond + stopCh := make(chan struct{}) + defer close(stopCh) + go endpoints.Run(1, stopCh) + + // cache.WaitForCacheSync has a 100ms poll period, and the endpoints worker has a 10ms period. + // To ensure we get all updates, including unexpected ones, we need to wait at least as long as + // a single cache sync period and worker period, with some fudge room. + time.Sleep(150 * time.Millisecond) + if test.shouldUpdateEndpoints { + // Ensure the work queue has been processed by looping for up to a second to prevent flakes. + wait.PollImmediate(50*time.Millisecond, 1*time.Second, func() (bool, error) { + return endpoints.queue.Len() == 0, nil + }) + endpointsHandler.ValidateRequestCount(t, 1) + } else { + endpointsHandler.ValidateRequestCount(t, 0) + } + }() + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubeapiserver/authenticator/config.go b/vendor/k8s.io/kubernetes/pkg/kubeapiserver/authenticator/config.go index 1e892078f..e308d64dc 100644 --- a/vendor/k8s.io/kubernetes/pkg/kubeapiserver/authenticator/config.go +++ b/vendor/k8s.io/kubernetes/pkg/kubeapiserver/authenticator/config.go @@ -30,7 +30,9 @@ import ( "k8s.io/apiserver/pkg/authentication/request/union" "k8s.io/apiserver/pkg/authentication/request/websocket" "k8s.io/apiserver/pkg/authentication/request/x509" + tokencache "k8s.io/apiserver/pkg/authentication/token/cache" "k8s.io/apiserver/pkg/authentication/token/tokenfile" + tokenunion "k8s.io/apiserver/pkg/authentication/token/union" "k8s.io/apiserver/plugin/pkg/authenticator/password/keystone" "k8s.io/apiserver/plugin/pkg/authenticator/password/passwordfile" "k8s.io/apiserver/plugin/pkg/authenticator/request/basicauth" @@ -64,6 +66,9 @@ type AuthenticatorConfig struct { WebhookTokenAuthnConfigFile string WebhookTokenAuthnCacheTTL time.Duration + TokenSuccessCacheTTL time.Duration + TokenFailureCacheTTL time.Duration + RequestHeaderConfig *authenticatorfactory.RequestHeaderConfig // TODO, this is the only non-serializable part of the entire config. Factor it out into a clientconfig @@ -75,9 +80,9 @@ type AuthenticatorConfig struct { // Kubernetes authentication mechanisms. func (config AuthenticatorConfig) New() (authenticator.Request, *spec.SecurityDefinitions, error) { var authenticators []authenticator.Request + var tokenAuthenticators []authenticator.Token securityDefinitions := spec.SecurityDefinitions{} hasBasicAuth := false - hasTokenAuth := false // front-proxy, BasicAuth methods, local first, then remote // Add the front proxy authenticator if requested @@ -127,22 +132,19 @@ func (config AuthenticatorConfig) New() (authenticator.Request, *spec.SecurityDe if err != nil { return nil, nil, err } - authenticators = append(authenticators, bearertoken.New(tokenAuth), websocket.NewProtocolAuthenticator(tokenAuth)) - hasTokenAuth = true + tokenAuthenticators = append(tokenAuthenticators, tokenAuth) } if len(config.ServiceAccountKeyFiles) > 0 { serviceAccountAuth, err := newServiceAccountAuthenticator(config.ServiceAccountKeyFiles, config.ServiceAccountLookup, config.ServiceAccountTokenGetter) if err != nil { return nil, nil, err } - authenticators = append(authenticators, bearertoken.New(serviceAccountAuth), websocket.NewProtocolAuthenticator(serviceAccountAuth)) - hasTokenAuth = true + tokenAuthenticators = append(tokenAuthenticators, serviceAccountAuth) } if config.BootstrapToken { if config.BootstrapTokenAuthenticator != nil { // TODO: This can sometimes be nil because of - authenticators = append(authenticators, bearertoken.New(config.BootstrapTokenAuthenticator), websocket.NewProtocolAuthenticator(config.BootstrapTokenAuthenticator)) - hasTokenAuth = true + tokenAuthenticators = append(tokenAuthenticators, config.BootstrapTokenAuthenticator) } } // NOTE(ericchiang): Keep the OpenID Connect after Service Accounts. @@ -156,22 +158,19 @@ func (config AuthenticatorConfig) New() (authenticator.Request, *spec.SecurityDe if err != nil { return nil, nil, err } - authenticators = append(authenticators, bearertoken.New(oidcAuth), websocket.NewProtocolAuthenticator(oidcAuth)) - hasTokenAuth = true + tokenAuthenticators = append(tokenAuthenticators, oidcAuth) } if len(config.WebhookTokenAuthnConfigFile) > 0 { webhookTokenAuth, err := newWebhookTokenAuthenticator(config.WebhookTokenAuthnConfigFile, config.WebhookTokenAuthnCacheTTL) if err != nil { return nil, nil, err } - authenticators = append(authenticators, bearertoken.New(webhookTokenAuth), websocket.NewProtocolAuthenticator(webhookTokenAuth)) - hasTokenAuth = true + tokenAuthenticators = append(tokenAuthenticators, webhookTokenAuth) } // always add anytoken last, so that every other token authenticator gets to try first if config.AnyToken { - authenticators = append(authenticators, bearertoken.New(anytoken.AnyTokenAuthenticator{}), websocket.NewProtocolAuthenticator(anytoken.AnyTokenAuthenticator{})) - hasTokenAuth = true + tokenAuthenticators = append(tokenAuthenticators, anytoken.AnyTokenAuthenticator{}) } if hasBasicAuth { @@ -183,7 +182,14 @@ func (config AuthenticatorConfig) New() (authenticator.Request, *spec.SecurityDe } } - if hasTokenAuth { + if len(tokenAuthenticators) > 0 { + // Union the token authenticators + tokenAuth := tokenunion.New(tokenAuthenticators...) + // Optionally cache authentication results + if config.TokenSuccessCacheTTL > 0 || config.TokenFailureCacheTTL > 0 { + tokenAuth = tokencache.New(tokenAuth, config.TokenSuccessCacheTTL, config.TokenFailureCacheTTL) + } + authenticators = append(authenticators, bearertoken.New(tokenAuth), websocket.NewProtocolAuthenticator(tokenAuth)) securityDefinitions["BearerToken"] = &spec.SecurityScheme{ SecuritySchemeProps: spec.SecuritySchemeProps{ Type: "apiKey", diff --git a/vendor/k8s.io/kubernetes/pkg/kubeapiserver/options/authentication.go b/vendor/k8s.io/kubernetes/pkg/kubeapiserver/options/authentication.go index 0bdc8da2f..0447d569c 100644 --- a/vendor/k8s.io/kubernetes/pkg/kubeapiserver/options/authentication.go +++ b/vendor/k8s.io/kubernetes/pkg/kubeapiserver/options/authentication.go @@ -42,6 +42,9 @@ type BuiltInAuthenticationOptions struct { ServiceAccounts *ServiceAccountAuthenticationOptions TokenFile *TokenFileAuthenticationOptions WebHook *WebHookAuthenticationOptions + + TokenSuccessCacheTTL time.Duration + TokenFailureCacheTTL time.Duration } type AnyTokenAuthenticationOptions struct { @@ -88,7 +91,10 @@ type WebHookAuthenticationOptions struct { } func NewBuiltInAuthenticationOptions() *BuiltInAuthenticationOptions { - return &BuiltInAuthenticationOptions{} + return &BuiltInAuthenticationOptions{ + TokenSuccessCacheTTL: 10 * time.Second, + TokenFailureCacheTTL: 0 * time.Second, + } } func (s *BuiltInAuthenticationOptions) WithAll() *BuiltInAuthenticationOptions { @@ -262,7 +268,10 @@ func (s *BuiltInAuthenticationOptions) AddFlags(fs *pflag.FlagSet) { } func (s *BuiltInAuthenticationOptions) ToAuthenticationConfig() authenticator.AuthenticatorConfig { - ret := authenticator.AuthenticatorConfig{} + ret := authenticator.AuthenticatorConfig{ + TokenSuccessCacheTTL: s.TokenSuccessCacheTTL, + TokenFailureCacheTTL: s.TokenFailureCacheTTL, + } if s.Anonymous != nil { ret.Anonymous = s.Anonymous.Allow diff --git a/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/util/clientcache.go b/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/util/clientcache.go index 115304321..1475e3a6f 100644 --- a/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/util/clientcache.go +++ b/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/util/clientcache.go @@ -24,7 +24,6 @@ import ( restclient "k8s.io/client-go/rest" "k8s.io/client-go/tools/clientcmd" fedclientset "k8s.io/kubernetes/federation/client/clientset_generated/federation_internalclientset" - "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" oldclient "k8s.io/kubernetes/pkg/client/unversioned" "k8s.io/kubernetes/pkg/version" @@ -92,19 +91,7 @@ func (c *ClientCache) getDefaultConfig() (restclient.Config, discovery.Discovery // ClientConfigForVersion returns the correct config for a server func (c *ClientCache) ClientConfigForVersion(requiredVersion *schema.GroupVersion) (*restclient.Config, error) { - // TODO: have a better config copy method - config, discoveryClient, err := c.getDefaultConfig() - if err != nil { - return nil, err - } - if requiredVersion == nil && config.GroupVersion != nil { - // if someone has set the values via flags, our config will have the groupVersion set - // that means it is required. - requiredVersion = config.GroupVersion - } - - // required version may still be nil, since config.GroupVersion may have been nil. Do the check - // before looking up from the cache + // only lookup in the cache if the requiredVersion is set if requiredVersion != nil { if config, ok := c.configs[*requiredVersion]; ok { return copyConfig(config), nil @@ -113,11 +100,21 @@ func (c *ClientCache) ClientConfigForVersion(requiredVersion *schema.GroupVersio return copyConfig(c.noVersionConfig), nil } - negotiatedVersion, err := discovery.NegotiateVersion(discoveryClient, requiredVersion, api.Registry.EnabledVersions()) + // this returns a shallow copy to work with + config, discoveryClient, err := c.getDefaultConfig() if err != nil { return nil, err } - config.GroupVersion = negotiatedVersion + + if requiredVersion != nil { + if err := discovery.ServerSupportsVersion(discoveryClient, *requiredVersion); err != nil { + return nil, err + } + config.GroupVersion = requiredVersion + } else { + // TODO remove this hack. This is allowing the GetOptions to be serialized. + config.GroupVersion = &schema.GroupVersion{Group: "", Version: "v1"} + } // TODO this isn't what we want. Each clientset should be setting defaults as it sees fit. oldclient.SetKubernetesDefaults(&config) diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/cm/qos_container_manager_linux.go b/vendor/k8s.io/kubernetes/pkg/kubelet/cm/qos_container_manager_linux.go index fa9951902..9df0cb87f 100644 --- a/vendor/k8s.io/kubernetes/pkg/kubelet/cm/qos_container_manager_linux.go +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/cm/qos_container_manager_linux.go @@ -282,7 +282,7 @@ func (m *qosContainerManagerImpl) UpdateCgroups() error { } } if updateSuccess { - glog.V(2).Infof("[ContainerManager]: Updated QoS cgroup configuration") + glog.V(4).Infof("[ContainerManager]: Updated QoS cgroup configuration") return nil } @@ -299,12 +299,12 @@ func (m *qosContainerManagerImpl) UpdateCgroups() error { for _, config := range qosConfigs { err := m.cgroupManager.Update(config) if err != nil { - glog.V(2).Infof("[ContainerManager]: Failed to update QoS cgroup configuration") + glog.Errorf("[ContainerManager]: Failed to update QoS cgroup configuration") return err } } - glog.V(2).Infof("[ContainerManager]: Updated QoS cgroup configuration on retry") + glog.V(4).Infof("[ContainerManager]: Updated QoS cgroup configuration on retry") return nil } diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/dockershim/helpers.go b/vendor/k8s.io/kubernetes/pkg/kubelet/dockershim/helpers.go index 0ea5123c1..82833a341 100644 --- a/vendor/k8s.io/kubernetes/pkg/kubelet/dockershim/helpers.go +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/dockershim/helpers.go @@ -18,6 +18,8 @@ package dockershim import ( "fmt" + "os" + "path/filepath" "regexp" "strconv" "strings" @@ -29,6 +31,7 @@ import ( "github.com/golang/glog" "k8s.io/kubernetes/pkg/api/v1" + "k8s.io/kubernetes/pkg/credentialprovider" runtimeapi "k8s.io/kubernetes/pkg/kubelet/api/v1alpha1/runtime" "k8s.io/kubernetes/pkg/kubelet/dockertools" "k8s.io/kubernetes/pkg/kubelet/types" @@ -340,6 +343,11 @@ func getSecurityOptSeparator(v *semver.Version) rune { // ensureSandboxImageExists pulls the sandbox image when it's not present. func ensureSandboxImageExists(client dockertools.DockerInterface, image string) error { + dockerCfgSearchPath := []string{"/.docker", filepath.Join(os.Getenv("HOME"), ".docker")} + return ensureSandboxImageExistsDockerCfg(client, image, dockerCfgSearchPath) +} + +func ensureSandboxImageExistsDockerCfg(client dockertools.DockerInterface, image string, dockerCfgSearchPath []string) error { _, err := client.InspectImageByRef(image) if err == nil { return nil @@ -347,8 +355,32 @@ func ensureSandboxImageExists(client dockertools.DockerInterface, image string) if !dockertools.IsImageNotFoundError(err) { return fmt.Errorf("failed to inspect sandbox image %q: %v", image, err) } - err = client.PullImage(image, dockertypes.AuthConfig{}, dockertypes.ImagePullOptions{}) + + // To support images in private registries, try to read docker config + authConfig := dockertypes.AuthConfig{} + keyring := &credentialprovider.BasicDockerKeyring{} + var cfgLoadErr error + if cfg, err := credentialprovider.ReadDockerConfigJSONFile(dockerCfgSearchPath); err == nil { + keyring.Add(cfg) + } else if cfg, err := credentialprovider.ReadDockercfgFile(dockerCfgSearchPath); err == nil { + keyring.Add(cfg) + } else { + cfgLoadErr = err + } + if creds, withCredentials := keyring.Lookup(image); withCredentials { + // Use the first one that matched our image + for _, cred := range creds { + authConfig.Username = cred.Username + authConfig.Password = cred.Password + break + } + } + + err = client.PullImage(image, authConfig, dockertypes.ImagePullOptions{}) if err != nil { + if cfgLoadErr != nil { + glog.Warningf("Couldn't load Docker cofig. If sandbox image %q is in a private registry, this will cause further errors. Error: %v", image, cfgLoadErr) + } return fmt.Errorf("unable to pull sandbox image %q: %v", image, err) } return nil diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/dockershim/helpers_test.go b/vendor/k8s.io/kubernetes/pkg/kubelet/dockershim/helpers_test.go index 74e238578..72aa4ca51 100644 --- a/vendor/k8s.io/kubernetes/pkg/kubelet/dockershim/helpers_test.go +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/dockershim/helpers_test.go @@ -17,7 +17,11 @@ limitations under the License. package dockershim import ( + "encoding/base64" "fmt" + "io/ioutil" + "os" + "path/filepath" "testing" "github.com/blang/semver" @@ -266,13 +270,33 @@ func TestGetSecurityOptSeparator(t *testing.T) { } } +// writeDockerConfig will write a config file into a temporary dir, and return that dir. +// Caller is responsible for deleting the dir and its contents. +func writeDockerConfig(cfg string) (string, error) { + tmpdir, err := ioutil.TempDir("", "dockershim=helpers_test.go=") + if err != nil { + return "", err + } + dir := filepath.Join(tmpdir, ".docker") + if err := os.Mkdir(dir, 0755); err != nil { + return "", err + } + return tmpdir, ioutil.WriteFile(filepath.Join(dir, "config.json"), []byte(cfg), 0644) +} + func TestEnsureSandboxImageExists(t *testing.T) { sandboxImage := "gcr.io/test/image" + registryHost := "https://gcr.io/" + authConfig := dockertypes.AuthConfig{Username: "user", Password: "pass"} + authB64 := base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s:%s", authConfig.Username, authConfig.Password))) + authJSON := fmt.Sprintf("{\"auths\": {\"%s\": {\"auth\": \"%s\"} } }", registryHost, authB64) for desc, test := range map[string]struct { - injectImage bool - injectErr error - calls []string - err bool + injectImage bool + imgNeedsAuth bool + injectErr error + calls []string + err bool + configJSON string }{ "should not pull image when it already exists": { injectImage: true, @@ -290,14 +314,42 @@ func TestEnsureSandboxImageExists(t *testing.T) { calls: []string{"inspect_image"}, err: true, }, + "should return error when image pull needs private auth, but none provided": { + injectImage: true, + imgNeedsAuth: true, + injectErr: dockertools.ImageNotFoundError{ID: "image_id"}, + calls: []string{"inspect_image", "pull"}, + err: true, + }, + "should pull private image using dockerauth if image doesn't exist": { + injectImage: true, + imgNeedsAuth: true, + injectErr: dockertools.ImageNotFoundError{ID: "image_id"}, + calls: []string{"inspect_image", "pull"}, + configJSON: authJSON, + err: false, + }, } { t.Logf("TestCase: %q", desc) _, fakeDocker, _ := newTestDockerService() if test.injectImage { - fakeDocker.InjectImages([]dockertypes.Image{{ID: sandboxImage}}) + images := []dockertypes.Image{{ID: sandboxImage}} + fakeDocker.InjectImages(images) + if test.imgNeedsAuth { + fakeDocker.MakeImagesPrivate(images, authConfig) + } } fakeDocker.InjectError("inspect_image", test.injectErr) - err := ensureSandboxImageExists(fakeDocker, sandboxImage) + + var dockerCfgSearchPath []string + if test.configJSON != "" { + tmpdir, err := writeDockerConfig(test.configJSON) + require.NoError(t, err, "could not create a temp docker config file") + dockerCfgSearchPath = append(dockerCfgSearchPath, filepath.Join(tmpdir, ".docker")) + defer os.RemoveAll(tmpdir) + } + + err := ensureSandboxImageExistsDockerCfg(fakeDocker, sandboxImage, dockerCfgSearchPath) assert.NoError(t, fakeDocker.AssertCalls(test.calls)) assert.Equal(t, test.err, err != nil) } diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/dockertools/fake_docker_client.go b/vendor/k8s.io/kubernetes/pkg/kubelet/dockertools/fake_docker_client.go index 218677918..7aa641037 100644 --- a/vendor/k8s.io/kubernetes/pkg/kubelet/dockertools/fake_docker_client.go +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/dockertools/fake_docker_client.go @@ -55,6 +55,7 @@ type FakeDockerClient struct { ContainerMap map[string]*dockertypes.ContainerJSON ImageInspects map[string]*dockertypes.ImageInspect Images []dockertypes.Image + ImageIDsNeedingAuth map[string]dockertypes.AuthConfig Errors map[string]error called []calledDetail pulled []string @@ -91,8 +92,9 @@ func NewFakeDockerClient() *FakeDockerClient { ContainerMap: make(map[string]*dockertypes.ContainerJSON), Clock: clock.RealClock{}, // default this to true, so that we trace calls, image pulls and container lifecycle - EnableTrace: true, - ImageInspects: make(map[string]*dockertypes.ImageInspect), + EnableTrace: true, + ImageInspects: make(map[string]*dockertypes.ImageInspect), + ImageIDsNeedingAuth: make(map[string]dockertypes.AuthConfig), } } @@ -624,6 +626,14 @@ func (f *FakeDockerClient) Logs(id string, opts dockertypes.ContainerLogsOptions return f.popError("logs") } +func (f *FakeDockerClient) isAuthorizedForImage(image string, auth dockertypes.AuthConfig) bool { + if reqd, exists := f.ImageIDsNeedingAuth[image]; !exists { + return true // no auth needed + } else { + return auth.Username == reqd.Username && auth.Password == reqd.Password + } +} + // PullImage is a test-spy implementation of DockerInterface.PullImage. // It adds an entry "pull" to the internal method call record. func (f *FakeDockerClient) PullImage(image string, auth dockertypes.AuthConfig, opts dockertypes.ImagePullOptions) error { @@ -632,6 +642,10 @@ func (f *FakeDockerClient) PullImage(image string, auth dockertypes.AuthConfig, f.appendCalled(calledDetail{name: "pull"}) err := f.popError("pull") if err == nil { + if !f.isAuthorizedForImage(image, auth) { + return ImageNotFoundError{ID: image} + } + authJson, _ := json.Marshal(auth) inspect := createImageInspectFromRef(image) f.ImageInspects[image] = inspect @@ -712,11 +726,20 @@ func (f *FakeDockerClient) InjectImages(images []dockertypes.Image) { } } +func (f *FakeDockerClient) MakeImagesPrivate(images []dockertypes.Image, auth dockertypes.AuthConfig) { + f.Lock() + defer f.Unlock() + for _, i := range images { + f.ImageIDsNeedingAuth[i.ID] = auth + } +} + func (f *FakeDockerClient) ResetImages() { f.Lock() defer f.Unlock() f.Images = []dockertypes.Image{} f.ImageInspects = make(map[string]*dockertypes.ImageInspect) + f.ImageIDsNeedingAuth = make(map[string]dockertypes.AuthConfig) } func (f *FakeDockerClient) InjectImageInspects(inspects []dockertypes.ImageInspect) { diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/kubelet.go b/vendor/k8s.io/kubernetes/pkg/kubelet/kubelet.go index 97025b82e..ab122c032 100644 --- a/vendor/k8s.io/kubernetes/pkg/kubelet/kubelet.go +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/kubelet.go @@ -1548,11 +1548,14 @@ func (kl *Kubelet) syncPod(o syncPodOptions) error { return err } - // Wait for volumes to attach/mount - if err := kl.volumeManager.WaitForAttachAndMount(pod); err != nil { - kl.recorder.Eventf(pod, v1.EventTypeWarning, events.FailedMountVolume, "Unable to mount volumes for pod %q: %v", format.Pod(pod), err) - glog.Errorf("Unable to mount volumes for pod %q: %v; skipping pod", format.Pod(pod), err) - return err + // Volume manager will not mount volumes for terminated pods + if !kl.podIsTerminated(pod) { + // Wait for volumes to attach/mount + if err := kl.volumeManager.WaitForAttachAndMount(pod); err != nil { + kl.recorder.Eventf(pod, v1.EventTypeWarning, events.FailedMountVolume, "Unable to mount volumes for pod %q: %v", format.Pod(pod), err) + glog.Errorf("Unable to mount volumes for pod %q: %v; skipping pod", format.Pod(pod), err) + return err + } } // Fetch the pull secrets for the pod diff --git a/vendor/k8s.io/kubernetes/plugin/pkg/scheduler/algorithm/predicates/predicates.go b/vendor/k8s.io/kubernetes/plugin/pkg/scheduler/algorithm/predicates/predicates.go index d255374d9..53bca12d6 100644 --- a/vendor/k8s.io/kubernetes/plugin/pkg/scheduler/algorithm/predicates/predicates.go +++ b/vendor/k8s.io/kubernetes/plugin/pkg/scheduler/algorithm/predicates/predicates.go @@ -1074,14 +1074,13 @@ func getMatchingAntiAffinityTerms(pod *v1.Pod, nodeInfoMap map[string]*scheduler continue } for _, term := range getPodAntiAffinityTerms(affinity.PodAntiAffinity) { - namespaces := priorityutil.GetNamespacesFromPodAffinityTerm(pod, &term) + namespaces := priorityutil.GetNamespacesFromPodAffinityTerm(existingPod, &term) selector, err := metav1.LabelSelectorAsSelector(term.LabelSelector) if err != nil { catchError(err) return } - match := priorityutil.PodMatchesTermsNamespaceAndSelector(pod, namespaces, selector) - if match { + if priorityutil.PodMatchesTermsNamespaceAndSelector(pod, namespaces, selector) { nodeResult = append(nodeResult, matchingPodAntiAffinityTerm{term: &term, node: node}) } } @@ -1109,8 +1108,7 @@ func (c *PodAffinityChecker) getMatchingAntiAffinityTerms(pod *v1.Pod, allPods [ if err != nil { return nil, err } - match := priorityutil.PodMatchesTermsNamespaceAndSelector(pod, namespaces, selector) - if match { + if priorityutil.PodMatchesTermsNamespaceAndSelector(pod, namespaces, selector) { result = append(result, matchingPodAntiAffinityTerm{term: &term, node: existingPodNode}) } } @@ -1128,17 +1126,17 @@ func (c *PodAffinityChecker) satisfiesExistingPodsAntiAffinity(pod *v1.Pod, meta } else { allPods, err := c.podLister.List(labels.Everything()) if err != nil { - glog.V(10).Infof("Failed to get all pods, %+v", err) + glog.Errorf("Failed to get all pods, %+v", err) return false } if matchingTerms, err = c.getMatchingAntiAffinityTerms(pod, allPods); err != nil { - glog.V(10).Infof("Failed to get all terms that pod %+v matches, err: %+v", podName(pod), err) + glog.Errorf("Failed to get all terms that pod %+v matches, err: %+v", podName(pod), err) return false } } for _, term := range matchingTerms { if len(term.term.TopologyKey) == 0 { - glog.V(10).Infof("Empty topologyKey is not allowed except for PreferredDuringScheduling pod anti-affinity") + glog.Errorf("Empty topologyKey is not allowed except for PreferredDuringScheduling pod anti-affinity") return false } if priorityutil.NodesHaveSameTopologyKey(node, term.node, term.term.TopologyKey) { @@ -1167,7 +1165,7 @@ func (c *PodAffinityChecker) satisfiesPodsAffinityAntiAffinity(pod *v1.Pod, node for _, term := range getPodAffinityTerms(affinity.PodAffinity) { termMatches, matchingPodExists, err := c.anyPodMatchesPodAffinityTerm(pod, allPods, node, &term) if err != nil { - glog.V(10).Infof("Cannot schedule pod %+v onto node %v,because of PodAffinityTerm %v, err: %v", + glog.Errorf("Cannot schedule pod %+v onto node %v,because of PodAffinityTerm %v, err: %v", podName(pod), node.Name, term, err) return false } @@ -1183,7 +1181,7 @@ func (c *PodAffinityChecker) satisfiesPodsAffinityAntiAffinity(pod *v1.Pod, node namespaces := priorityutil.GetNamespacesFromPodAffinityTerm(pod, &term) selector, err := metav1.LabelSelectorAsSelector(term.LabelSelector) if err != nil { - glog.V(10).Infof("Cannot parse selector on term %v for pod %v. Details %v", + glog.Errorf("Cannot parse selector on term %v for pod %v. Details %v", term, podName(pod), err) return false } diff --git a/vendor/k8s.io/kubernetes/plugin/pkg/scheduler/algorithm/predicates/predicates_test.go b/vendor/k8s.io/kubernetes/plugin/pkg/scheduler/algorithm/predicates/predicates_test.go index a2b2f2ffd..99564cae5 100644 --- a/vendor/k8s.io/kubernetes/plugin/pkg/scheduler/algorithm/predicates/predicates_test.go +++ b/vendor/k8s.io/kubernetes/plugin/pkg/scheduler/algorithm/predicates/predicates_test.go @@ -2886,6 +2886,78 @@ func TestInterPodAffinityWithMultipleNodes(t *testing.T) { test: "NodeA and nodeB have same topologyKey and label value. NodeA has an existing pod that match the inter pod affinity rule. NodeC has an existing pod that match the inter pod affinity rule. The pod can not be scheduled onto nodeA, nodeB and nodeC but can be schedulerd onto nodeD", nometa: true, }, + { + pod: &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{"foo": "123"}, + Namespace: "NS1", + }, + Spec: v1.PodSpec{ + Affinity: &v1.Affinity{ + PodAntiAffinity: &v1.PodAntiAffinity{ + RequiredDuringSchedulingIgnoredDuringExecution: []v1.PodAffinityTerm{ + { + LabelSelector: &metav1.LabelSelector{ + MatchExpressions: []metav1.LabelSelectorRequirement{ + { + Key: "foo", + Operator: metav1.LabelSelectorOpIn, + Values: []string{"bar"}, + }, + }, + }, + TopologyKey: "region", + }, + }, + }, + }, + }, + }, + pods: []*v1.Pod{ + { + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{"foo": "bar"}, + Namespace: "NS1", + }, + Spec: v1.PodSpec{NodeName: "nodeA"}, + }, + { + ObjectMeta: metav1.ObjectMeta{Namespace: "NS2"}, + Spec: v1.PodSpec{ + NodeName: "nodeC", + Affinity: &v1.Affinity{ + PodAntiAffinity: &v1.PodAntiAffinity{ + RequiredDuringSchedulingIgnoredDuringExecution: []v1.PodAffinityTerm{ + { + LabelSelector: &metav1.LabelSelector{ + MatchExpressions: []metav1.LabelSelectorRequirement{ + { + Key: "foo", + Operator: metav1.LabelSelectorOpIn, + Values: []string{"123"}, + }, + }, + }, + TopologyKey: "region", + }, + }, + }, + }, + }, + }, + }, + nodes: []v1.Node{ + {ObjectMeta: metav1.ObjectMeta{Name: "nodeA", Labels: labelRgChina}}, + {ObjectMeta: metav1.ObjectMeta{Name: "nodeB", Labels: labelRgChinaAzAz1}}, + {ObjectMeta: metav1.ObjectMeta{Name: "nodeC", Labels: labelRgIndia}}, + }, + fits: map[string]bool{ + "nodeA": false, + "nodeB": false, + "nodeC": true, + }, + test: "NodeA and nodeB have same topologyKey and label value. NodeA has an existing pod that match the inter pod affinity rule. The pod can not be scheduled onto nodeA, nodeB, but can be schedulerd onto nodeC (NodeC has an existing pod that match the inter pod affinity rule but in different namespace)", + }, } affinityExpectedFailureReasons := []algorithm.PredicateFailureReason{ErrPodAffinityNotMatch} selectorExpectedFailureReasons := []algorithm.PredicateFailureReason{ErrNodeSelectorNotMatch} diff --git a/vendor/k8s.io/kubernetes/staging/src/k8s.io/apiserver/pkg/authentication/group/token_group_adder.go b/vendor/k8s.io/kubernetes/staging/src/k8s.io/apiserver/pkg/authentication/group/token_group_adder.go new file mode 100644 index 000000000..4f60d522f --- /dev/null +++ b/vendor/k8s.io/kubernetes/staging/src/k8s.io/apiserver/pkg/authentication/group/token_group_adder.go @@ -0,0 +1,48 @@ +/* +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 group + +import ( + "k8s.io/apiserver/pkg/authentication/authenticator" + "k8s.io/apiserver/pkg/authentication/user" +) + +// TokenGroupAdder adds groups to an authenticated user.Info +type TokenGroupAdder struct { + // Authenticator is delegated to make the authentication decision + Authenticator authenticator.Token + // Groups are additional groups to add to the user.Info from a successful authentication + Groups []string +} + +// NewTokenGroupAdder wraps a token authenticator, and adds the specified groups to the returned user when authentication succeeds +func NewTokenGroupAdder(auth authenticator.Token, groups []string) authenticator.Token { + return &TokenGroupAdder{auth, groups} +} + +func (g *TokenGroupAdder) AuthenticateToken(token string) (user.Info, bool, error) { + u, ok, err := g.Authenticator.AuthenticateToken(token) + if err != nil || !ok { + return nil, ok, err + } + return &user.DefaultInfo{ + Name: u.GetName(), + UID: u.GetUID(), + Groups: append(u.GetGroups(), g.Groups...), + Extra: u.GetExtra(), + }, true, nil +} diff --git a/vendor/k8s.io/kubernetes/staging/src/k8s.io/apiserver/pkg/authentication/group/token_group_adder_test.go b/vendor/k8s.io/kubernetes/staging/src/k8s.io/apiserver/pkg/authentication/group/token_group_adder_test.go new file mode 100644 index 000000000..cb5a0a65e --- /dev/null +++ b/vendor/k8s.io/kubernetes/staging/src/k8s.io/apiserver/pkg/authentication/group/token_group_adder_test.go @@ -0,0 +1,41 @@ +/* +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 group + +import ( + "reflect" + "testing" + + "k8s.io/apiserver/pkg/authentication/authenticator" + "k8s.io/apiserver/pkg/authentication/user" +) + +func TestTokenGroupAdder(t *testing.T) { + adder := authenticator.Token( + NewTokenGroupAdder( + authenticator.TokenFunc(func(token string) (user.Info, bool, error) { + return &user.DefaultInfo{Name: "user", Groups: []string{"original"}}, true, nil + }), + []string{"added"}, + ), + ) + + user, _, _ := adder.AuthenticateToken("") + if !reflect.DeepEqual(user.GetGroups(), []string{"original", "added"}) { + t.Errorf("Expected original,added groups, got %#v", user.GetGroups()) + } +} diff --git a/vendor/k8s.io/kubernetes/staging/src/k8s.io/apiserver/pkg/authentication/token/cache/BUILD b/vendor/k8s.io/kubernetes/staging/src/k8s.io/apiserver/pkg/authentication/token/cache/BUILD new file mode 100644 index 000000000..3384859f7 --- /dev/null +++ b/vendor/k8s.io/kubernetes/staging/src/k8s.io/apiserver/pkg/authentication/token/cache/BUILD @@ -0,0 +1,54 @@ +package(default_visibility = ["//visibility:public"]) + +licenses(["notice"]) + +load( + "@io_bazel_rules_go//go:def.bzl", + "go_library", + "go_test", +) + +go_test( + name = "go_default_test", + srcs = [ + "cache_test.go", + "cached_token_authenticator_test.go", + ], + library = ":go_default_library", + tags = ["automanaged"], + deps = [ + "//vendor/github.com/pborman/uuid:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/clock:go_default_library", + "//vendor/k8s.io/apiserver/pkg/authentication/authenticator:go_default_library", + "//vendor/k8s.io/apiserver/pkg/authentication/user:go_default_library", + ], +) + +go_library( + name = "go_default_library", + srcs = [ + "cache_simple.go", + "cache_striped.go", + "cached_token_authenticator.go", + ], + tags = ["automanaged"], + deps = [ + "//vendor/k8s.io/apimachinery/pkg/util/cache:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/clock:go_default_library", + "//vendor/k8s.io/apiserver/pkg/authentication/authenticator:go_default_library", + "//vendor/k8s.io/apiserver/pkg/authentication/user:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], +) diff --git a/vendor/k8s.io/kubernetes/staging/src/k8s.io/apiserver/pkg/authentication/token/cache/cache_simple.go b/vendor/k8s.io/kubernetes/staging/src/k8s.io/apiserver/pkg/authentication/token/cache/cache_simple.go new file mode 100644 index 000000000..18d5692d7 --- /dev/null +++ b/vendor/k8s.io/kubernetes/staging/src/k8s.io/apiserver/pkg/authentication/token/cache/cache_simple.go @@ -0,0 +1,49 @@ +/* +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 cache + +import ( + "time" + + lrucache "k8s.io/apimachinery/pkg/util/cache" + "k8s.io/apimachinery/pkg/util/clock" +) + +type simpleCache struct { + lru *lrucache.LRUExpireCache +} + +func newSimpleCache(size int, clock clock.Clock) cache { + return &simpleCache{lru: lrucache.NewLRUExpireCacheWithClock(size, clock)} +} + +func (c *simpleCache) get(key string) (*cacheRecord, bool) { + record, ok := c.lru.Get(key) + if !ok { + return nil, false + } + value, ok := record.(*cacheRecord) + return value, ok +} + +func (c *simpleCache) set(key string, value *cacheRecord, ttl time.Duration) { + c.lru.Add(key, value, ttl) +} + +func (c *simpleCache) remove(key string) { + c.lru.Remove(key) +} diff --git a/vendor/k8s.io/kubernetes/staging/src/k8s.io/apiserver/pkg/authentication/token/cache/cache_striped.go b/vendor/k8s.io/kubernetes/staging/src/k8s.io/apiserver/pkg/authentication/token/cache/cache_striped.go new file mode 100644 index 000000000..b791260fc --- /dev/null +++ b/vendor/k8s.io/kubernetes/staging/src/k8s.io/apiserver/pkg/authentication/token/cache/cache_striped.go @@ -0,0 +1,60 @@ +/* +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 cache + +import ( + "hash/fnv" + "time" +) + +// split cache lookups across N striped caches +type stripedCache struct { + stripeCount uint32 + keyFunc func(string) uint32 + caches []cache +} + +type keyFunc func(string) uint32 +type newCacheFunc func() cache + +func newStripedCache(stripeCount int, keyFunc keyFunc, newCacheFunc newCacheFunc) cache { + caches := []cache{} + for i := 0; i < stripeCount; i++ { + caches = append(caches, newCacheFunc()) + } + return &stripedCache{ + stripeCount: uint32(stripeCount), + keyFunc: keyFunc, + caches: caches, + } +} + +func (c *stripedCache) get(key string) (*cacheRecord, bool) { + return c.caches[c.keyFunc(key)%c.stripeCount].get(key) +} +func (c *stripedCache) set(key string, value *cacheRecord, ttl time.Duration) { + c.caches[c.keyFunc(key)%c.stripeCount].set(key, value, ttl) +} +func (c *stripedCache) remove(key string) { + c.caches[c.keyFunc(key)%c.stripeCount].remove(key) +} + +func fnvKeyFunc(key string) uint32 { + f := fnv.New32() + f.Write([]byte(key)) + return f.Sum32() +} diff --git a/vendor/k8s.io/kubernetes/staging/src/k8s.io/apiserver/pkg/authentication/token/cache/cache_test.go b/vendor/k8s.io/kubernetes/staging/src/k8s.io/apiserver/pkg/authentication/token/cache/cache_test.go new file mode 100644 index 000000000..d4e9adff7 --- /dev/null +++ b/vendor/k8s.io/kubernetes/staging/src/k8s.io/apiserver/pkg/authentication/token/cache/cache_test.go @@ -0,0 +1,94 @@ +/* +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 cache + +import ( + "math/rand" + "testing" + "time" + + "k8s.io/apimachinery/pkg/util/clock" + "k8s.io/apiserver/pkg/authentication/user" + + "github.com/pborman/uuid" +) + +func TestSimpleCache(t *testing.T) { + testCache(newSimpleCache(4096, clock.RealClock{}), t) +} + +func BenchmarkSimpleCache(b *testing.B) { + benchmarkCache(newSimpleCache(4096, clock.RealClock{}), b) +} + +func TestStripedCache(t *testing.T) { + testCache(newStripedCache(32, fnvKeyFunc, func() cache { return newSimpleCache(128, clock.RealClock{}) }), t) +} + +func BenchmarkStripedCache(b *testing.B) { + benchmarkCache(newStripedCache(32, fnvKeyFunc, func() cache { return newSimpleCache(128, clock.RealClock{}) }), b) +} + +func benchmarkCache(cache cache, b *testing.B) { + keys := []string{} + for i := 0; i < b.N; i++ { + key := uuid.NewRandom().String() + keys = append(keys, key) + } + + b.ResetTimer() + + b.SetParallelism(500) + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + key := keys[rand.Intn(b.N)] + _, ok := cache.get(key) + if ok { + cache.remove(key) + } else { + cache.set(key, &cacheRecord{}, time.Second) + } + } + }) +} + +func testCache(cache cache, t *testing.T) { + if result, ok := cache.get("foo"); ok || result != nil { + t.Errorf("Expected null, false, got %#v, %v", result, ok) + } + + record1 := &cacheRecord{user: &user.DefaultInfo{Name: "bob"}} + record2 := &cacheRecord{user: &user.DefaultInfo{Name: "alice"}} + + // when empty, record is stored + cache.set("foo", record1, time.Hour) + if result, ok := cache.get("foo"); !ok || result != record1 { + t.Errorf("Expected %#v, true, got %#v, %v", record1, ok) + } + + // newer record overrides + cache.set("foo", record2, time.Hour) + if result, ok := cache.get("foo"); !ok || result != record2 { + t.Errorf("Expected %#v, true, got %#v, %v", record2, ok) + } + + // removing the current value removes + cache.remove("foo") + if result, ok := cache.get("foo"); ok || result != nil { + t.Errorf("Expected null, false, got %#v, %v", result, ok) + } +} diff --git a/vendor/k8s.io/kubernetes/staging/src/k8s.io/apiserver/pkg/authentication/token/cache/cached_token_authenticator.go b/vendor/k8s.io/kubernetes/staging/src/k8s.io/apiserver/pkg/authentication/token/cache/cached_token_authenticator.go new file mode 100644 index 000000000..088c271f9 --- /dev/null +++ b/vendor/k8s.io/kubernetes/staging/src/k8s.io/apiserver/pkg/authentication/token/cache/cached_token_authenticator.go @@ -0,0 +1,81 @@ +/* +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 cache + +import ( + "time" + + utilclock "k8s.io/apimachinery/pkg/util/clock" + "k8s.io/apiserver/pkg/authentication/authenticator" + "k8s.io/apiserver/pkg/authentication/user" +) + +type cacheRecord struct { + user user.Info + ok bool + err error +} + +type cachedTokenAuthenticator struct { + authenticator authenticator.Token + + successTTL time.Duration + failureTTL time.Duration + + cache cache +} + +type cache interface { + // given a key, return the record, and whether or not it existed + get(key string) (value *cacheRecord, exists bool) + // caches the record for the key + set(key string, value *cacheRecord, ttl time.Duration) + // removes the record for the key + remove(key string) +} + +// New returns a token authenticator that caches the results of the specified authenticator. A ttl of 0 bypasses the cache. +func New(authenticator authenticator.Token, successTTL, failureTTL time.Duration) authenticator.Token { + return newWithClock(authenticator, successTTL, failureTTL, utilclock.RealClock{}) +} + +func newWithClock(authenticator authenticator.Token, successTTL, failureTTL time.Duration, clock utilclock.Clock) authenticator.Token { + return &cachedTokenAuthenticator{ + authenticator: authenticator, + successTTL: successTTL, + failureTTL: failureTTL, + cache: newStripedCache(32, fnvKeyFunc, func() cache { return newSimpleCache(128, clock) }), + } +} + +// AuthenticateToken implements authenticator.Token +func (a *cachedTokenAuthenticator) AuthenticateToken(token string) (user.Info, bool, error) { + if record, ok := a.cache.get(token); ok { + return record.user, record.ok, record.err + } + + user, ok, err := a.authenticator.AuthenticateToken(token) + + switch { + case ok && a.successTTL > 0: + a.cache.set(token, &cacheRecord{user: user, ok: ok, err: err}, a.successTTL) + case !ok && a.failureTTL > 0: + a.cache.set(token, &cacheRecord{user: user, ok: ok, err: err}, a.failureTTL) + } + + return user, ok, err +} diff --git a/vendor/k8s.io/kubernetes/staging/src/k8s.io/apiserver/pkg/authentication/token/cache/cached_token_authenticator_test.go b/vendor/k8s.io/kubernetes/staging/src/k8s.io/apiserver/pkg/authentication/token/cache/cached_token_authenticator_test.go new file mode 100644 index 000000000..200d11478 --- /dev/null +++ b/vendor/k8s.io/kubernetes/staging/src/k8s.io/apiserver/pkg/authentication/token/cache/cached_token_authenticator_test.go @@ -0,0 +1,105 @@ +/* +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 cache + +import ( + "reflect" + "testing" + "time" + + utilclock "k8s.io/apimachinery/pkg/util/clock" + "k8s.io/apiserver/pkg/authentication/authenticator" + "k8s.io/apiserver/pkg/authentication/user" +) + +func TestCachedTokenAuthenticator(t *testing.T) { + var ( + calledWithToken []string + + resultUsers map[string]user.Info + resultOk bool + resultErr error + ) + fakeAuth := authenticator.TokenFunc(func(token string) (user.Info, bool, error) { + calledWithToken = append(calledWithToken, token) + return resultUsers[token], resultOk, resultErr + }) + fakeClock := utilclock.NewFakeClock(time.Now()) + + a := newWithClock(fakeAuth, time.Minute, 0, fakeClock) + + calledWithToken, resultUsers, resultOk, resultErr = []string{}, nil, false, nil + a.AuthenticateToken("bad1") + a.AuthenticateToken("bad2") + a.AuthenticateToken("bad3") + a.AuthenticateToken("bad1") + a.AuthenticateToken("bad2") + a.AuthenticateToken("bad3") + if !reflect.DeepEqual(calledWithToken, []string{"bad1", "bad2", "bad3", "bad1", "bad2", "bad3"}) { + t.Errorf("Expected failing calls to bypass cache, got %v", calledWithToken) + } + + // reset calls, make the backend return success for three user tokens + calledWithToken = []string{} + resultUsers, resultOk, resultErr = map[string]user.Info{}, true, nil + resultUsers["usertoken1"] = &user.DefaultInfo{Name: "user1"} + resultUsers["usertoken2"] = &user.DefaultInfo{Name: "user2"} + resultUsers["usertoken3"] = &user.DefaultInfo{Name: "user3"} + + // populate cache + if user, ok, err := a.AuthenticateToken("usertoken1"); err != nil || !ok || user.GetName() != "user1" { + t.Errorf("Expected user1") + } + if user, ok, err := a.AuthenticateToken("usertoken2"); err != nil || !ok || user.GetName() != "user2" { + t.Errorf("Expected user2") + } + if user, ok, err := a.AuthenticateToken("usertoken3"); err != nil || !ok || user.GetName() != "user3" { + t.Errorf("Expected user3") + } + if !reflect.DeepEqual(calledWithToken, []string{"usertoken1", "usertoken2", "usertoken3"}) { + t.Errorf("Expected token calls, got %v", calledWithToken) + } + + // reset calls, make the backend return failures + calledWithToken = []string{} + resultUsers, resultOk, resultErr = nil, false, nil + + // authenticate calls still succeed and backend is not hit + if user, ok, err := a.AuthenticateToken("usertoken1"); err != nil || !ok || user.GetName() != "user1" { + t.Errorf("Expected user1") + } + if user, ok, err := a.AuthenticateToken("usertoken2"); err != nil || !ok || user.GetName() != "user2" { + t.Errorf("Expected user2") + } + if user, ok, err := a.AuthenticateToken("usertoken3"); err != nil || !ok || user.GetName() != "user3" { + t.Errorf("Expected user3") + } + if !reflect.DeepEqual(calledWithToken, []string{}) { + t.Errorf("Expected no token calls, got %v", calledWithToken) + } + + // skip forward in time + fakeClock.Step(2 * time.Minute) + + // backend is consulted again and fails + a.AuthenticateToken("usertoken1") + a.AuthenticateToken("usertoken2") + a.AuthenticateToken("usertoken3") + if !reflect.DeepEqual(calledWithToken, []string{"usertoken1", "usertoken2", "usertoken3"}) { + t.Errorf("Expected token calls, got %v", calledWithToken) + } +} diff --git a/vendor/k8s.io/kubernetes/staging/src/k8s.io/apiserver/pkg/authentication/token/union/BUILD b/vendor/k8s.io/kubernetes/staging/src/k8s.io/apiserver/pkg/authentication/token/union/BUILD new file mode 100644 index 000000000..7a6b23db9 --- /dev/null +++ b/vendor/k8s.io/kubernetes/staging/src/k8s.io/apiserver/pkg/authentication/token/union/BUILD @@ -0,0 +1,41 @@ +package(default_visibility = ["//visibility:public"]) + +licenses(["notice"]) + +load( + "@io_bazel_rules_go//go:def.bzl", + "go_library", + "go_test", +) + +go_test( + name = "go_default_test", + srcs = ["unionauth_test.go"], + library = ":go_default_library", + tags = ["automanaged"], + deps = ["//vendor/k8s.io/apiserver/pkg/authentication/user:go_default_library"], +) + +go_library( + name = "go_default_library", + srcs = ["union.go"], + tags = ["automanaged"], + deps = [ + "//vendor/k8s.io/apimachinery/pkg/util/errors:go_default_library", + "//vendor/k8s.io/apiserver/pkg/authentication/authenticator:go_default_library", + "//vendor/k8s.io/apiserver/pkg/authentication/user:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], +) diff --git a/vendor/k8s.io/kubernetes/staging/src/k8s.io/apiserver/pkg/authentication/token/union/union.go b/vendor/k8s.io/kubernetes/staging/src/k8s.io/apiserver/pkg/authentication/token/union/union.go new file mode 100644 index 000000000..7cc391bc4 --- /dev/null +++ b/vendor/k8s.io/kubernetes/staging/src/k8s.io/apiserver/pkg/authentication/token/union/union.go @@ -0,0 +1,70 @@ +/* +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 union + +import ( + utilerrors "k8s.io/apimachinery/pkg/util/errors" + "k8s.io/apiserver/pkg/authentication/authenticator" + "k8s.io/apiserver/pkg/authentication/user" +) + +// unionAuthTokenHandler authenticates tokens using a chain of authenticator.Token objects +type unionAuthTokenHandler struct { + // Handlers is a chain of request authenticators to delegate to + Handlers []authenticator.Token + // FailOnError determines whether an error returns short-circuits the chain + FailOnError bool +} + +// New returns a token authenticator that validates credentials using a chain of authenticator.Token objects. +// The entire chain is tried until one succeeds. If all fail, an aggregate error is returned. +func New(authTokenHandlers ...authenticator.Token) authenticator.Token { + if len(authTokenHandlers) == 1 { + return authTokenHandlers[0] + } + return &unionAuthTokenHandler{Handlers: authTokenHandlers, FailOnError: false} +} + +// NewFailOnError returns a token authenticator that validates credentials using a chain of authenticator.Token objects. +// The first error short-circuits the chain. +func NewFailOnError(authTokenHandlers ...authenticator.Token) authenticator.Token { + if len(authTokenHandlers) == 1 { + return authTokenHandlers[0] + } + return &unionAuthTokenHandler{Handlers: authTokenHandlers, FailOnError: true} +} + +// AuthenticateToken authenticates the token using a chain of authenticator.Token objects. +func (authHandler *unionAuthTokenHandler) AuthenticateToken(token string) (user.Info, bool, error) { + var errlist []error + for _, currAuthRequestHandler := range authHandler.Handlers { + info, ok, err := currAuthRequestHandler.AuthenticateToken(token) + if err != nil { + if authHandler.FailOnError { + return info, ok, err + } + errlist = append(errlist, err) + continue + } + + if ok { + return info, ok, err + } + } + + return nil, false, utilerrors.NewAggregate(errlist) +} diff --git a/vendor/k8s.io/kubernetes/staging/src/k8s.io/apiserver/pkg/authentication/token/union/unionauth_test.go b/vendor/k8s.io/kubernetes/staging/src/k8s.io/apiserver/pkg/authentication/token/union/unionauth_test.go new file mode 100644 index 000000000..1107c5754 --- /dev/null +++ b/vendor/k8s.io/kubernetes/staging/src/k8s.io/apiserver/pkg/authentication/token/union/unionauth_test.go @@ -0,0 +1,158 @@ +/* +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 union + +import ( + "errors" + "reflect" + "strings" + "testing" + + "k8s.io/apiserver/pkg/authentication/user" +) + +type mockAuthRequestHandler struct { + returnUser user.Info + isAuthenticated bool + err error +} + +var ( + user1 = &user.DefaultInfo{Name: "fresh_ferret", UID: "alfa"} + user2 = &user.DefaultInfo{Name: "elegant_sheep", UID: "bravo"} +) + +func (mock *mockAuthRequestHandler) AuthenticateToken(token string) (user.Info, bool, error) { + return mock.returnUser, mock.isAuthenticated, mock.err +} + +func TestAuthenticateTokenSecondPasses(t *testing.T) { + handler1 := &mockAuthRequestHandler{returnUser: user1} + handler2 := &mockAuthRequestHandler{returnUser: user2, isAuthenticated: true} + authRequestHandler := New(handler1, handler2) + + authenticatedUser, isAuthenticated, err := authRequestHandler.AuthenticateToken("foo") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + if !isAuthenticated { + t.Errorf("Unexpectedly unauthenticated: %v", isAuthenticated) + } + if !reflect.DeepEqual(user2, authenticatedUser) { + t.Errorf("Expected %v, got %v", user2, authenticatedUser) + } +} + +func TestAuthenticateTokenFirstPasses(t *testing.T) { + handler1 := &mockAuthRequestHandler{returnUser: user1, isAuthenticated: true} + handler2 := &mockAuthRequestHandler{returnUser: user2} + authRequestHandler := New(handler1, handler2) + + authenticatedUser, isAuthenticated, err := authRequestHandler.AuthenticateToken("foo") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + if !isAuthenticated { + t.Errorf("Unexpectedly unauthenticated: %v", isAuthenticated) + } + if !reflect.DeepEqual(user1, authenticatedUser) { + t.Errorf("Expected %v, got %v", user1, authenticatedUser) + } +} + +func TestAuthenticateTokenSuppressUnnecessaryErrors(t *testing.T) { + handler1 := &mockAuthRequestHandler{err: errors.New("first")} + handler2 := &mockAuthRequestHandler{isAuthenticated: true} + authRequestHandler := New(handler1, handler2) + + _, isAuthenticated, err := authRequestHandler.AuthenticateToken("foo") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + if !isAuthenticated { + t.Errorf("Unexpectedly unauthenticated: %v", isAuthenticated) + } +} + +func TestAuthenticateTokenNoAuthenticators(t *testing.T) { + authRequestHandler := New() + + authenticatedUser, isAuthenticated, err := authRequestHandler.AuthenticateToken("foo") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + if isAuthenticated { + t.Errorf("Unexpectedly authenticated: %v", isAuthenticated) + } + if authenticatedUser != nil { + t.Errorf("Unexpected authenticatedUser: %v", authenticatedUser) + } +} + +func TestAuthenticateTokenNonePass(t *testing.T) { + handler1 := &mockAuthRequestHandler{} + handler2 := &mockAuthRequestHandler{} + authRequestHandler := New(handler1, handler2) + + _, isAuthenticated, err := authRequestHandler.AuthenticateToken("foo") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + if isAuthenticated { + t.Errorf("Unexpectedly authenticated: %v", isAuthenticated) + } +} + +func TestAuthenticateTokenAdditiveErrors(t *testing.T) { + handler1 := &mockAuthRequestHandler{err: errors.New("first")} + handler2 := &mockAuthRequestHandler{err: errors.New("second")} + authRequestHandler := New(handler1, handler2) + + _, isAuthenticated, err := authRequestHandler.AuthenticateToken("foo") + if err == nil { + t.Errorf("Expected an error") + } + if !strings.Contains(err.Error(), "first") { + t.Errorf("Expected error containing %v, got %v", "first", err) + } + if !strings.Contains(err.Error(), "second") { + t.Errorf("Expected error containing %v, got %v", "second", err) + } + if isAuthenticated { + t.Errorf("Unexpectedly authenticated: %v", isAuthenticated) + } +} + +func TestAuthenticateTokenFailEarly(t *testing.T) { + handler1 := &mockAuthRequestHandler{err: errors.New("first")} + handler2 := &mockAuthRequestHandler{err: errors.New("second")} + authRequestHandler := NewFailOnError(handler1, handler2) + + _, isAuthenticated, err := authRequestHandler.AuthenticateToken("foo") + if err == nil { + t.Errorf("Expected an error") + } + if !strings.Contains(err.Error(), "first") { + t.Errorf("Expected error containing %v, got %v", "first", err) + } + if strings.Contains(err.Error(), "second") { + t.Errorf("Did not expect second error, got %v", err) + } + if isAuthenticated { + t.Errorf("Unexpectedly authenticated: %v", isAuthenticated) + } +} diff --git a/vendor/k8s.io/kubernetes/staging/src/k8s.io/apiserver/pkg/storage/tests/cacher_test.go b/vendor/k8s.io/kubernetes/staging/src/k8s.io/apiserver/pkg/storage/tests/cacher_test.go new file mode 100644 index 000000000..3637c3fae --- /dev/null +++ b/vendor/k8s.io/kubernetes/staging/src/k8s.io/apiserver/pkg/storage/tests/cacher_test.go @@ -0,0 +1,644 @@ +/* +Copyright 2015 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 tests + +import ( + "fmt" + "reflect" + goruntime "runtime" + "strconv" + "testing" + "time" + + apiequality "k8s.io/apimachinery/pkg/api/equality" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/api/meta" + apitesting "k8s.io/apimachinery/pkg/api/testing" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/fields" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/apimachinery/pkg/util/wait" + "k8s.io/apimachinery/pkg/watch" + "k8s.io/apiserver/pkg/apis/example" + examplev1 "k8s.io/apiserver/pkg/apis/example/v1" + "k8s.io/apiserver/pkg/storage" + etcdstorage "k8s.io/apiserver/pkg/storage/etcd" + "k8s.io/apiserver/pkg/storage/etcd/etcdtest" + etcdtesting "k8s.io/apiserver/pkg/storage/etcd/testing" + "k8s.io/apiserver/pkg/storage/etcd3" + "k8s.io/apiserver/pkg/storage/value" + + "golang.org/x/net/context" + + "k8s.io/apimachinery/pkg/runtime/serializer" + _ "k8s.io/client-go/pkg/api/install" +) + +var ( + scheme = runtime.NewScheme() + codecs = serializer.NewCodecFactory(scheme) +) + +func init() { + metav1.AddToGroupVersion(scheme, metav1.SchemeGroupVersion) + example.AddToScheme(scheme) + examplev1.AddToScheme(scheme) +} + +// GetAttrs returns labels and fields of a given object for filtering purposes. +func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, error) { + pod, ok := obj.(*example.Pod) + if !ok { + return nil, nil, fmt.Errorf("not a pod") + } + return labels.Set(pod.ObjectMeta.Labels), PodToSelectableFields(pod), nil +} + +// PodToSelectableFields returns a field set that represents the object +// TODO: fields are not labels, and the validation rules for them do not apply. +func PodToSelectableFields(pod *example.Pod) fields.Set { + // The purpose of allocation with a given number of elements is to reduce + // amount of allocations needed to create the fields.Set. If you add any + // field here or the number of object-meta related fields changes, this should + // be adjusted. + podSpecificFieldsSet := make(fields.Set, 5) + podSpecificFieldsSet["spec.nodeName"] = pod.Spec.NodeName + podSpecificFieldsSet["spec.restartPolicy"] = string(pod.Spec.RestartPolicy) + podSpecificFieldsSet["status.phase"] = string(pod.Status.Phase) + return AddObjectMetaFieldsSet(podSpecificFieldsSet, &pod.ObjectMeta, true) +} + +func AddObjectMetaFieldsSet(source fields.Set, objectMeta *metav1.ObjectMeta, hasNamespaceField bool) fields.Set { + source["metadata.name"] = objectMeta.Name + if hasNamespaceField { + source["metadata.namespace"] = objectMeta.Namespace + } + return source +} + +func newEtcdTestStorage(t *testing.T, prefix string) (*etcdtesting.EtcdTestServer, storage.Interface) { + server, _ := etcdtesting.NewUnsecuredEtcd3TestClientServer(t, scheme) + storage := etcd3.New(server.V3Client, apitesting.TestCodec(codecs, examplev1.SchemeGroupVersion), prefix, value.IdentityTransformer) + return server, storage +} + +func newTestCacher(s storage.Interface, cap int) *storage.Cacher { + prefix := "pods" + config := storage.CacherConfig{ + CacheCapacity: cap, + Storage: s, + Versioner: etcdstorage.APIObjectVersioner{}, + Copier: scheme, + Type: &example.Pod{}, + ResourcePrefix: prefix, + KeyFunc: func(obj runtime.Object) (string, error) { return storage.NamespaceKeyFunc(prefix, obj) }, + GetAttrsFunc: GetAttrs, + NewListFunc: func() runtime.Object { return &example.PodList{} }, + Codec: codecs.LegacyCodec(examplev1.SchemeGroupVersion), + } + return storage.NewCacherFromConfig(config) +} + +func makeTestPod(name string) *example.Pod { + return &example.Pod{ + ObjectMeta: metav1.ObjectMeta{Namespace: "ns", Name: name}, + Spec: DeepEqualSafePodSpec(), + } +} + +func updatePod(t *testing.T, s storage.Interface, obj, old *example.Pod) *example.Pod { + updateFn := func(input runtime.Object, res storage.ResponseMeta) (runtime.Object, *uint64, error) { + newObj, err := scheme.DeepCopy(obj) + if err != nil { + t.Errorf("unexpected error: %v", err) + return nil, nil, err + } + return newObj.(*example.Pod), nil, nil + } + key := "pods/" + obj.Namespace + "/" + obj.Name + if err := s.GuaranteedUpdate(context.TODO(), key, &example.Pod{}, old == nil, nil, updateFn); err != nil { + t.Errorf("unexpected error: %v", err) + } + obj.ResourceVersion = "" + result := &example.Pod{} + if err := s.Get(context.TODO(), key, "", result, false); err != nil { + t.Errorf("unexpected error: %v", err) + } + return result +} + +func TestGet(t *testing.T) { + server, etcdStorage := newEtcdTestStorage(t, etcdtest.PathPrefix()) + defer server.Terminate(t) + cacher := newTestCacher(etcdStorage, 10) + defer cacher.Stop() + + podFoo := makeTestPod("foo") + fooCreated := updatePod(t, etcdStorage, podFoo, nil) + + // We pass the ResourceVersion from the above Create() operation. + result := &example.Pod{} + if err := cacher.Get(context.TODO(), "pods/ns/foo", fooCreated.ResourceVersion, result, true); err != nil { + t.Errorf("Unexpected error: %v", err) + } + if e, a := *fooCreated, *result; !reflect.DeepEqual(e, a) { + t.Errorf("Expected: %#v, got: %#v", e, a) + } + + if err := cacher.Get(context.TODO(), "pods/ns/bar", fooCreated.ResourceVersion, result, true); err != nil { + t.Errorf("Unexpected error: %v", err) + } + emptyPod := example.Pod{} + if e, a := emptyPod, *result; !reflect.DeepEqual(e, a) { + t.Errorf("Expected: %#v, got: %#v", e, a) + } + + if err := cacher.Get(context.TODO(), "pods/ns/bar", fooCreated.ResourceVersion, result, false); !storage.IsNotFound(err) { + t.Errorf("Unexpected error: %v", err) + } +} + +func TestList(t *testing.T) { + server, etcdStorage := newEtcdTestStorage(t, etcdtest.PathPrefix()) + defer server.Terminate(t) + cacher := newTestCacher(etcdStorage, 10) + defer cacher.Stop() + + podFoo := makeTestPod("foo") + podBar := makeTestPod("bar") + podBaz := makeTestPod("baz") + + podFooPrime := makeTestPod("foo") + podFooPrime.Spec.NodeName = "fakeNode" + + fooCreated := updatePod(t, etcdStorage, podFoo, nil) + _ = updatePod(t, etcdStorage, podBar, nil) + _ = updatePod(t, etcdStorage, podBaz, nil) + + _ = updatePod(t, etcdStorage, podFooPrime, fooCreated) + + // Create a pod in a namespace that contains "ns" as a prefix + // Make sure it is not returned in a watch of "ns" + podFooNS2 := makeTestPod("foo") + podFooNS2.Namespace += "2" + updatePod(t, etcdStorage, podFooNS2, nil) + + deleted := example.Pod{} + if err := etcdStorage.Delete(context.TODO(), "pods/ns/bar", &deleted, nil); err != nil { + t.Errorf("Unexpected error: %v", err) + } + + // We first List directly from etcd by passing empty resourceVersion, + // to get the current etcd resourceVersion. + rvResult := &example.PodList{} + if err := cacher.List(context.TODO(), "pods/ns", "", storage.Everything, rvResult); err != nil { + t.Errorf("Unexpected error: %v", err) + } + deletedPodRV := rvResult.ListMeta.ResourceVersion + + result := &example.PodList{} + // We pass the current etcd ResourceVersion received from the above List() operation, + // since there is not easy way to get ResourceVersion of barPod deletion operation. + if err := cacher.List(context.TODO(), "pods/ns", deletedPodRV, storage.Everything, result); err != nil { + t.Errorf("Unexpected error: %v", err) + } + if result.ListMeta.ResourceVersion != deletedPodRV { + t.Errorf("Incorrect resource version: %v", result.ListMeta.ResourceVersion) + } + if len(result.Items) != 2 { + t.Errorf("Unexpected list result: %d", len(result.Items)) + } + keys := sets.String{} + for _, item := range result.Items { + keys.Insert(item.Name) + } + if !keys.HasAll("foo", "baz") { + t.Errorf("Unexpected list result: %#v", result) + } + for _, item := range result.Items { + // unset fields that are set by the infrastructure + item.ResourceVersion = "" + item.CreationTimestamp = metav1.Time{} + + if item.Namespace != "ns" { + t.Errorf("Unexpected namespace: %s", item.Namespace) + } + + var expected *example.Pod + switch item.Name { + case "foo": + expected = podFooPrime + case "baz": + expected = podBaz + default: + t.Errorf("Unexpected item: %v", item) + } + if e, a := *expected, item; !reflect.DeepEqual(e, a) { + t.Errorf("Expected: %#v, got: %#v", e, a) + } + } +} + +func TestInfiniteList(t *testing.T) { + server, etcdStorage := newEtcdTestStorage(t, etcdtest.PathPrefix()) + defer server.Terminate(t) + cacher := newTestCacher(etcdStorage, 10) + defer cacher.Stop() + + podFoo := makeTestPod("foo") + fooCreated := updatePod(t, etcdStorage, podFoo, nil) + + // Set up List at fooCreated.ResourceVersion + 10 + rv, err := storage.ParseWatchResourceVersion(fooCreated.ResourceVersion) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + listRV := strconv.Itoa(int(rv + 10)) + + result := &example.PodList{} + err = cacher.List(context.TODO(), "pods/ns", listRV, storage.Everything, result) + if !errors.IsTimeout(err) { + t.Errorf("Unexpected error: %v", err) + } +} + +func verifyWatchEvent(t *testing.T, w watch.Interface, eventType watch.EventType, eventObject runtime.Object) { + _, _, line, _ := goruntime.Caller(1) + select { + case event := <-w.ResultChan(): + if e, a := eventType, event.Type; e != a { + t.Logf("(called from line %d)", line) + t.Errorf("Expected: %s, got: %s", eventType, event.Type) + } + if e, a := eventObject, event.Object; !apiequality.Semantic.DeepDerivative(e, a) { + t.Logf("(called from line %d)", line) + t.Errorf("Expected (%s): %#v, got: %#v", eventType, e, a) + } + case <-time.After(wait.ForeverTestTimeout): + t.Logf("(called from line %d)", line) + t.Errorf("Timed out waiting for an event") + } +} + +type injectListError struct { + errors int + storage.Interface +} + +func (self *injectListError) List(ctx context.Context, key string, resourceVersion string, p storage.SelectionPredicate, listObj runtime.Object) error { + if self.errors > 0 { + self.errors-- + return fmt.Errorf("injected error") + } + return self.Interface.List(ctx, key, resourceVersion, p, listObj) +} + +func TestWatch(t *testing.T) { + server, etcdStorage := newEtcdTestStorage(t, etcdtest.PathPrefix()) + // Inject one list error to make sure we test the relist case. + etcdStorage = &injectListError{errors: 1, Interface: etcdStorage} + defer server.Terminate(t) + cacher := newTestCacher(etcdStorage, 3) // small capacity to trigger "too old version" error + defer cacher.Stop() + + podFoo := makeTestPod("foo") + podBar := makeTestPod("bar") + + podFooPrime := makeTestPod("foo") + podFooPrime.Spec.NodeName = "fakeNode" + + podFooBis := makeTestPod("foo") + podFooBis.Spec.NodeName = "anotherFakeNode" + + podFooNS2 := makeTestPod("foo") + podFooNS2.Namespace += "2" + + // initialVersion is used to initate the watcher at the beginning of the world, + // which is not defined precisely in etcd. + initialVersion, err := cacher.LastSyncResourceVersion() + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + startVersion := strconv.Itoa(int(initialVersion)) + + // Set up Watch for object "podFoo". + watcher, err := cacher.Watch(context.TODO(), "pods/ns/foo", startVersion, storage.Everything) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + defer watcher.Stop() + + // Create in another namespace first to make sure events from other namespaces don't get delivered + updatePod(t, etcdStorage, podFooNS2, nil) + + fooCreated := updatePod(t, etcdStorage, podFoo, nil) + _ = updatePod(t, etcdStorage, podBar, nil) + fooUpdated := updatePod(t, etcdStorage, podFooPrime, fooCreated) + + verifyWatchEvent(t, watcher, watch.Added, podFoo) + verifyWatchEvent(t, watcher, watch.Modified, podFooPrime) + + // Check whether we get too-old error via the watch channel + tooOldWatcher, err := cacher.Watch(context.TODO(), "pods/ns/foo", "1", storage.Everything) + if err != nil { + t.Fatalf("Expected no direct error, got %v", err) + } + defer tooOldWatcher.Stop() + // Ensure we get a "Gone" error + expectedGoneError := errors.NewGone("").ErrStatus + verifyWatchEvent(t, tooOldWatcher, watch.Error, &expectedGoneError) + + initialWatcher, err := cacher.Watch(context.TODO(), "pods/ns/foo", fooCreated.ResourceVersion, storage.Everything) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + defer initialWatcher.Stop() + + verifyWatchEvent(t, initialWatcher, watch.Modified, podFooPrime) + + // Now test watch from "now". + nowWatcher, err := cacher.Watch(context.TODO(), "pods/ns/foo", "0", storage.Everything) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + defer nowWatcher.Stop() + + verifyWatchEvent(t, nowWatcher, watch.Added, podFooPrime) + + _ = updatePod(t, etcdStorage, podFooBis, fooUpdated) + + verifyWatchEvent(t, nowWatcher, watch.Modified, podFooBis) +} + +func TestWatcherTimeout(t *testing.T) { + server, etcdStorage := newEtcdTestStorage(t, etcdtest.PathPrefix()) + defer server.Terminate(t) + cacher := newTestCacher(etcdStorage, 10) + defer cacher.Stop() + + // initialVersion is used to initate the watcher at the beginning of the world, + // which is not defined precisely in etcd. + initialVersion, err := cacher.LastSyncResourceVersion() + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + startVersion := strconv.Itoa(int(initialVersion)) + + // Create a number of watchers that will not be reading any result. + nonReadingWatchers := 50 + for i := 0; i < nonReadingWatchers; i++ { + watcher, err := cacher.WatchList(context.TODO(), "pods/ns", startVersion, storage.Everything) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + defer watcher.Stop() + } + + // Create a second watcher that will be reading result. + readingWatcher, err := cacher.WatchList(context.TODO(), "pods/ns", startVersion, storage.Everything) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + defer readingWatcher.Stop() + + startTime := time.Now() + for i := 1; i <= 22; i++ { + pod := makeTestPod(strconv.Itoa(i)) + _ = updatePod(t, etcdStorage, pod, nil) + verifyWatchEvent(t, readingWatcher, watch.Added, pod) + } + if time.Since(startTime) > time.Duration(250*nonReadingWatchers)*time.Millisecond { + t.Errorf("waiting for events took too long: %v", time.Since(startTime)) + } +} + +func TestFiltering(t *testing.T) { + server, etcdStorage := newEtcdTestStorage(t, etcdtest.PathPrefix()) + defer server.Terminate(t) + cacher := newTestCacher(etcdStorage, 10) + defer cacher.Stop() + + // Ensure that the cacher is initialized, before creating any pods, + // so that we are sure that all events will be present in cacher. + syncWatcher, err := cacher.Watch(context.TODO(), "pods/ns/foo", "0", storage.Everything) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + syncWatcher.Stop() + + podFoo := makeTestPod("foo") + podFoo.Labels = map[string]string{"filter": "foo"} + podFooFiltered := makeTestPod("foo") + podFooPrime := makeTestPod("foo") + podFooPrime.Labels = map[string]string{"filter": "foo"} + podFooPrime.Spec.NodeName = "fakeNode" + + podFooNS2 := makeTestPod("foo") + podFooNS2.Namespace += "2" + podFooNS2.Labels = map[string]string{"filter": "foo"} + + // Create in another namespace first to make sure events from other namespaces don't get delivered + updatePod(t, etcdStorage, podFooNS2, nil) + + fooCreated := updatePod(t, etcdStorage, podFoo, nil) + fooFiltered := updatePod(t, etcdStorage, podFooFiltered, fooCreated) + fooUnfiltered := updatePod(t, etcdStorage, podFoo, fooFiltered) + _ = updatePod(t, etcdStorage, podFooPrime, fooUnfiltered) + + deleted := example.Pod{} + if err := etcdStorage.Delete(context.TODO(), "pods/ns/foo", &deleted, nil); err != nil { + t.Errorf("Unexpected error: %v", err) + } + + // Set up Watch for object "podFoo" with label filter set. + pred := storage.SelectionPredicate{ + Label: labels.SelectorFromSet(labels.Set{"filter": "foo"}), + Field: fields.Everything(), + GetAttrs: func(obj runtime.Object) (label labels.Set, field fields.Set, err error) { + metadata, err := meta.Accessor(obj) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + return labels.Set(metadata.GetLabels()), nil, nil + }, + } + watcher, err := cacher.Watch(context.TODO(), "pods/ns/foo", fooCreated.ResourceVersion, pred) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + defer watcher.Stop() + + verifyWatchEvent(t, watcher, watch.Deleted, podFooFiltered) + verifyWatchEvent(t, watcher, watch.Added, podFoo) + verifyWatchEvent(t, watcher, watch.Modified, podFooPrime) + verifyWatchEvent(t, watcher, watch.Deleted, podFooPrime) +} + +func TestStartingResourceVersion(t *testing.T) { + server, etcdStorage := newEtcdTestStorage(t, etcdtest.PathPrefix()) + defer server.Terminate(t) + cacher := newTestCacher(etcdStorage, 10) + defer cacher.Stop() + + // add 1 object + podFoo := makeTestPod("foo") + fooCreated := updatePod(t, etcdStorage, podFoo, nil) + + // Set up Watch starting at fooCreated.ResourceVersion + 10 + rv, err := storage.ParseWatchResourceVersion(fooCreated.ResourceVersion) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + rv += 10 + startVersion := strconv.Itoa(int(rv)) + + watcher, err := cacher.Watch(context.TODO(), "pods/ns/foo", startVersion, storage.Everything) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + defer watcher.Stop() + + lastFoo := fooCreated + for i := 0; i < 11; i++ { + podFooForUpdate := makeTestPod("foo") + podFooForUpdate.Labels = map[string]string{"foo": strconv.Itoa(i)} + lastFoo = updatePod(t, etcdStorage, podFooForUpdate, lastFoo) + } + + select { + case e := <-watcher.ResultChan(): + pod := e.Object.(*example.Pod) + podRV, err := storage.ParseWatchResourceVersion(pod.ResourceVersion) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + // event should have at least rv + 1, since we're starting the watch at rv + if podRV <= rv { + t.Errorf("expected event with resourceVersion of at least %d, got %d", rv+1, podRV) + } + case <-time.After(wait.ForeverTestTimeout): + t.Errorf("timed out waiting for event") + } +} + +func TestEmptyWatchEventCache(t *testing.T) { + server, etcdStorage := newEtcdTestStorage(t, etcdtest.PathPrefix()) + defer server.Terminate(t) + + // add a few objects + updatePod(t, etcdStorage, makeTestPod("pod1"), nil) + updatePod(t, etcdStorage, makeTestPod("pod2"), nil) + updatePod(t, etcdStorage, makeTestPod("pod3"), nil) + updatePod(t, etcdStorage, makeTestPod("pod4"), nil) + updatePod(t, etcdStorage, makeTestPod("pod5"), nil) + + fooCreated := updatePod(t, etcdStorage, makeTestPod("foo"), nil) + + // get rv of last pod created + rv, err := storage.ParseWatchResourceVersion(fooCreated.ResourceVersion) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + + cacher := newTestCacher(etcdStorage, 10) + defer cacher.Stop() + + // We now have a cacher with an empty cache of watch events and a resourceVersion of rv. + // It should support establishing watches from rv and higher, but not older. + + { + watcher, err := cacher.Watch(context.TODO(), "pods/ns", strconv.Itoa(int(rv-1)), storage.Everything) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + defer watcher.Stop() + expectedGoneError := errors.NewGone("").ErrStatus + verifyWatchEvent(t, watcher, watch.Error, &expectedGoneError) + } + + { + watcher, err := cacher.Watch(context.TODO(), "pods/ns", strconv.Itoa(int(rv+1)), storage.Everything) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + defer watcher.Stop() + select { + case e := <-watcher.ResultChan(): + t.Errorf("unexpected event %#v", e) + case <-time.After(3 * time.Second): + // watch from rv+1 remained established successfully + } + } + + { + watcher, err := cacher.Watch(context.TODO(), "pods/ns", strconv.Itoa(int(rv)), storage.Everything) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + defer watcher.Stop() + select { + case e := <-watcher.ResultChan(): + t.Errorf("unexpected event %#v", e) + case <-time.After(3 * time.Second): + // watch from rv remained established successfully + } + } +} + +func TestRandomWatchDeliver(t *testing.T) { + server, etcdStorage := newEtcdTestStorage(t, etcdtest.PathPrefix()) + defer server.Terminate(t) + cacher := newTestCacher(etcdStorage, 10) + defer cacher.Stop() + + fooCreated := updatePod(t, etcdStorage, makeTestPod("foo"), nil) + rv, err := storage.ParseWatchResourceVersion(fooCreated.ResourceVersion) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + startVersion := strconv.Itoa(int(rv)) + + watcher, err := cacher.WatchList(context.TODO(), "pods/ns", startVersion, storage.Everything) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + + // Now we can create exactly 21 events that should be delivered + // to the watcher, before it will completely block cacher and as + // a result will be dropped. + for i := 0; i < 21; i++ { + updatePod(t, etcdStorage, makeTestPod(fmt.Sprintf("foo-%d", i)), nil) + } + + // Now stop the watcher and check if the consecutive events are being delivered. + watcher.Stop() + + watched := 0 + for { + event, ok := <-watcher.ResultChan() + if !ok { + break + } + if a, e := event.Object.(*example.Pod).Name, fmt.Sprintf("foo-%d", watched); e != a { + t.Errorf("Unexpected object watched: %s, expected %s", a, e) + } + watched++ + } +} diff --git a/vendor/k8s.io/kubernetes/staging/src/k8s.io/apiserver/pkg/storage/watch_cache.go b/vendor/k8s.io/kubernetes/staging/src/k8s.io/apiserver/pkg/storage/watch_cache.go index b2d8f4a82..16a0604dd 100644 --- a/vendor/k8s.io/kubernetes/staging/src/k8s.io/apiserver/pkg/storage/watch_cache.go +++ b/vendor/k8s.io/kubernetes/staging/src/k8s.io/apiserver/pkg/storage/watch_cache.go @@ -407,7 +407,9 @@ func (w *watchCache) SetOnEvent(onEvent func(*watchCacheEvent)) { func (w *watchCache) GetAllEventsSinceThreadUnsafe(resourceVersion uint64) ([]*watchCacheEvent, error) { size := w.endIndex - w.startIndex - oldest := w.resourceVersion + // if we have no watch events in our cache, the oldest one we can successfully deliver to a watcher + // is the *next* event we'll receive, which will be at least one greater than our current resourceVersion + oldest := w.resourceVersion + 1 if size > 0 { oldest = w.cache[w.startIndex%w.capacity].resourceVersion } diff --git a/vendor/k8s.io/kubernetes/staging/src/k8s.io/client-go/discovery/helper.go b/vendor/k8s.io/kubernetes/staging/src/k8s.io/client-go/discovery/helper.go index f184bc929..603b0ee3a 100644 --- a/vendor/k8s.io/kubernetes/staging/src/k8s.io/client-go/discovery/helper.go +++ b/vendor/k8s.io/kubernetes/staging/src/k8s.io/client-go/discovery/helper.go @@ -41,25 +41,13 @@ func MatchesServerVersion(clientVersion apimachineryversion.Info, client Discove return nil } -// NegotiateVersion queries the server's supported api versions to find -// a version that both client and server support. -// - If no version is provided, try registered client versions in order of -// preference. -// - If version is provided and the server does not support it, -// return an error. -// TODO negotiation should be reserved for cases where we need a version for a given group. In those cases, it should return an ordered list of -// server preferences. From that list, a separate function can match from an ordered list of client versions. -// This is not what the function has ever done before, but it makes more logical sense. -func NegotiateVersion(client DiscoveryInterface, requiredGV *schema.GroupVersion, clientRegisteredGVs []schema.GroupVersion) (*schema.GroupVersion, error) { - clientVersions := sets.String{} - for _, gv := range clientRegisteredGVs { - clientVersions.Insert(gv.String()) - } +// ServerSupportsVersion returns an error if the server doesn't have the required version +func ServerSupportsVersion(client DiscoveryInterface, requiredGV schema.GroupVersion) error { groups, err := client.ServerGroups() if err != nil { // This is almost always a connection error, and higher level code should treat this as a generic error, // not a negotiation specific error. - return nil, err + return err } versions := metav1.ExtractGroupVersions(groups) serverVersions := sets.String{} @@ -67,46 +55,18 @@ func NegotiateVersion(client DiscoveryInterface, requiredGV *schema.GroupVersion serverVersions.Insert(v) } - // If version explicitly requested verify that both client and server support it. - // If server does not support warn, but try to negotiate a lower version. - if requiredGV != nil { - if !clientVersions.Has(requiredGV.String()) { - return nil, fmt.Errorf("client does not support API version %q; client supported API versions: %v", requiredGV, clientVersions) - - } - // If the server supports no versions, then we should just use the preferredGV - // This can happen because discovery fails due to 403 Forbidden errors - if len(serverVersions) == 0 { - return requiredGV, nil - } - if serverVersions.Has(requiredGV.String()) { - return requiredGV, nil - } - // If we are using an explicit config version the server does not support, fail. - return nil, fmt.Errorf("server does not support API version %q", requiredGV) - } - - for _, clientGV := range clientRegisteredGVs { - if serverVersions.Has(clientGV.String()) { - // Version was not explicitly requested in command config (--api-version). - // Ok to fall back to a supported version with a warning. - // TODO: caesarxuchao: enable the warning message when we have - // proper fix. Please refer to issue #14895. - // if len(version) != 0 { - // glog.Warningf("Server does not support API version '%s'. Falling back to '%s'.", version, clientVersion) - // } - t := clientGV - return &t, nil - } + if serverVersions.Has(requiredGV.String()) { + return nil } - // if we have no server versions and we have no required version, choose the first clientRegisteredVersion - if len(serverVersions) == 0 && len(clientRegisteredGVs) > 0 { - return &clientRegisteredGVs[0], nil + // If the server supports no versions, then we should pretend it has the version because of old servers. + // This can happen because discovery fails due to 403 Forbidden errors + if len(serverVersions) == 0 { + return nil } // fall back to an empty GroupVersion. Most client commands no longer respect a GroupVersion anyway - return &schema.GroupVersion{}, nil + return fmt.Errorf("server does not support API version %q", requiredGV) } // GroupVersionResources converts APIResourceLists to the GroupVersionResources. diff --git a/vendor/k8s.io/kubernetes/staging/src/k8s.io/client-go/discovery/helper_blackbox_test.go b/vendor/k8s.io/kubernetes/staging/src/k8s.io/client-go/discovery/helper_blackbox_test.go index 9093316e8..56c6c7224 100644 --- a/vendor/k8s.io/kubernetes/staging/src/k8s.io/client-go/discovery/helper_blackbox_test.go +++ b/vendor/k8s.io/kubernetes/staging/src/k8s.io/client-go/discovery/helper_blackbox_test.go @@ -47,81 +47,40 @@ func objBody(object interface{}) io.ReadCloser { return ioutil.NopCloser(bytes.NewReader([]byte(output))) } -func TestNegotiateVersion(t *testing.T) { +func TestServerSupportsVersion(t *testing.T) { tests := []struct { name string - requiredVersion *schema.GroupVersion - expectedVersion *schema.GroupVersion + requiredVersion schema.GroupVersion serverVersions []string - clientVersions []schema.GroupVersion expectErr func(err error) bool sendErr error statusCode int }{ - { - name: "server supports client default", - serverVersions: []string{"version1", api.Registry.GroupOrDie(api.GroupName).GroupVersion.String()}, - clientVersions: []schema.GroupVersion{{Version: "version1"}, api.Registry.GroupOrDie(api.GroupName).GroupVersion}, - expectedVersion: &schema.GroupVersion{Version: "version1"}, - statusCode: http.StatusOK, - }, - { - name: "server falls back to client supported", - serverVersions: []string{"version1"}, - clientVersions: []schema.GroupVersion{{Version: "version1"}, api.Registry.GroupOrDie(api.GroupName).GroupVersion}, - expectedVersion: &schema.GroupVersion{Version: "version1"}, - statusCode: http.StatusOK, - }, { name: "explicit version supported", - requiredVersion: &schema.GroupVersion{Version: "v1"}, + requiredVersion: schema.GroupVersion{Version: "v1"}, serverVersions: []string{"/version1", api.Registry.GroupOrDie(api.GroupName).GroupVersion.String()}, - clientVersions: []schema.GroupVersion{{Version: "version1"}, api.Registry.GroupOrDie(api.GroupName).GroupVersion}, - expectedVersion: &schema.GroupVersion{Version: "v1"}, statusCode: http.StatusOK, }, { name: "explicit version not supported on server", - requiredVersion: &schema.GroupVersion{Version: "v1"}, + requiredVersion: schema.GroupVersion{Version: "v1"}, serverVersions: []string{"version1"}, - clientVersions: []schema.GroupVersion{{Version: "version1"}, api.Registry.GroupOrDie(api.GroupName).GroupVersion}, expectErr: func(err error) bool { return strings.Contains(err.Error(), `server does not support API version "v1"`) }, statusCode: http.StatusOK, }, - { - name: "explicit version not supported on client", - requiredVersion: &schema.GroupVersion{Version: "v1"}, - serverVersions: []string{"v1"}, - clientVersions: []schema.GroupVersion{{Version: "version1"}}, - expectErr: func(err error) bool { return strings.Contains(err.Error(), `client does not support API version "v1"`) }, - statusCode: http.StatusOK, - }, { name: "connection refused error", serverVersions: []string{"version1"}, - clientVersions: []schema.GroupVersion{{Version: "version1"}, api.Registry.GroupOrDie(api.GroupName).GroupVersion}, sendErr: errors.New("connection refused"), expectErr: func(err error) bool { return strings.Contains(err.Error(), "connection refused") }, statusCode: http.StatusOK, }, - { - name: "discovery fails due to 403 Forbidden errors and thus serverVersions is empty, use default GroupVersion", - clientVersions: []schema.GroupVersion{{Version: "version1"}, api.Registry.GroupOrDie(api.GroupName).GroupVersion}, - expectedVersion: &schema.GroupVersion{Version: "version1"}, - statusCode: http.StatusForbidden, - }, { name: "discovery fails due to 404 Not Found errors and thus serverVersions is empty, use requested GroupVersion", - requiredVersion: &schema.GroupVersion{Version: "version1"}, - clientVersions: []schema.GroupVersion{{Version: "version1"}, api.Registry.GroupOrDie(api.GroupName).GroupVersion}, - expectedVersion: &schema.GroupVersion{Version: "version1"}, + requiredVersion: schema.GroupVersion{Version: "version1"}, statusCode: http.StatusNotFound, }, - { - name: "discovery fails due to 403 Forbidden errors and thus serverVersions is empty, fallback to empty GroupVersion", - expectedVersion: &schema.GroupVersion{}, - statusCode: http.StatusForbidden, - }, } for _, test := range tests { @@ -143,7 +102,7 @@ func TestNegotiateVersion(t *testing.T) { } c := discovery.NewDiscoveryClientForConfigOrDie(&restclient.Config{}) c.RESTClient().(*restclient.RESTClient).Client = fakeClient.Client - response, err := discovery.NegotiateVersion(c, test.requiredVersion, test.clientVersions) + err := discovery.ServerSupportsVersion(c, test.requiredVersion) if err == nil && test.expectErr != nil { t.Errorf("expected error, got nil for [%s].", test.name) } @@ -153,9 +112,6 @@ func TestNegotiateVersion(t *testing.T) { } continue } - if *response != *test.expectedVersion { - t.Errorf("%s: expected version %s, got %s.", test.name, test.expectedVersion, response) - } } }