Merge pull request #33 from cfryanr/discovery_doc
Adding discovery document object
This commit is contained in:
commit
09571d1117
@ -63,7 +63,7 @@ func TestRun(t *testing.T) {
|
|||||||
err := run(envGetter, tokenExchanger, buffer, 30*time.Second)
|
err := run(envGetter, tokenExchanger, buffer, 30*time.Second)
|
||||||
r.EqualError(err, "failed to login: environment variable not set: PLACEHOLDER_NAME_K8S_API_ENDPOINT")
|
r.EqualError(err, "failed to login: environment variable not set: PLACEHOLDER_NAME_K8S_API_ENDPOINT")
|
||||||
})
|
})
|
||||||
}, spec.Parallel())
|
})
|
||||||
|
|
||||||
when("the token exchange fails", func() {
|
when("the token exchange fails", func() {
|
||||||
it.Before(func() {
|
it.Before(func() {
|
||||||
@ -76,7 +76,7 @@ func TestRun(t *testing.T) {
|
|||||||
err := run(envGetter, tokenExchanger, buffer, 30*time.Second)
|
err := run(envGetter, tokenExchanger, buffer, 30*time.Second)
|
||||||
r.EqualError(err, "failed to login: some error")
|
r.EqualError(err, "failed to login: some error")
|
||||||
})
|
})
|
||||||
}, spec.Parallel())
|
})
|
||||||
|
|
||||||
when("the JSON encoder fails", func() {
|
when("the JSON encoder fails", func() {
|
||||||
it.Before(func() {
|
it.Before(func() {
|
||||||
@ -89,7 +89,7 @@ func TestRun(t *testing.T) {
|
|||||||
err := run(envGetter, tokenExchanger, &library.ErrorWriter{ReturnError: fmt.Errorf("some IO error")}, 30*time.Second)
|
err := run(envGetter, tokenExchanger, &library.ErrorWriter{ReturnError: fmt.Errorf("some IO error")}, 30*time.Second)
|
||||||
r.EqualError(err, "failed to marshal response to stdout: some IO error")
|
r.EqualError(err, "failed to marshal response to stdout: some IO error")
|
||||||
})
|
})
|
||||||
}, spec.Parallel())
|
})
|
||||||
|
|
||||||
when("the token exchange times out", func() {
|
when("the token exchange times out", func() {
|
||||||
it.Before(func() {
|
it.Before(func() {
|
||||||
@ -107,7 +107,7 @@ func TestRun(t *testing.T) {
|
|||||||
err := run(envGetter, tokenExchanger, buffer, 1*time.Millisecond)
|
err := run(envGetter, tokenExchanger, buffer, 1*time.Millisecond)
|
||||||
r.EqualError(err, "failed to login: context deadline exceeded")
|
r.EqualError(err, "failed to login: context deadline exceeded")
|
||||||
})
|
})
|
||||||
}, spec.Parallel())
|
})
|
||||||
|
|
||||||
when("the token exchange succeeds", func() {
|
when("the token exchange succeeds", func() {
|
||||||
var actualToken, actualCaBundle, actualAPIEndpoint string
|
var actualToken, actualCaBundle, actualAPIEndpoint string
|
||||||
@ -144,6 +144,6 @@ func TestRun(t *testing.T) {
|
|||||||
}`
|
}`
|
||||||
r.JSONEq(expected, buffer.String())
|
r.JSONEq(expected, buffer.String())
|
||||||
})
|
})
|
||||||
}, spec.Parallel())
|
})
|
||||||
}, spec.Report(report.Terminal{}))
|
}, spec.Parallel(), spec.Report(report.Terminal{}))
|
||||||
}
|
}
|
||||||
|
49
deploy/crd.yaml
Normal file
49
deploy/crd.yaml
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
#@ load("@ytt:data", "data")
|
||||||
|
|
||||||
|
#! Example of valid LoginDiscoveryConfig object:
|
||||||
|
#! ---
|
||||||
|
#! apiVersion: suzerain-io.github.io/v1alpha1
|
||||||
|
#! kind: LoginDiscoveryConfig
|
||||||
|
#! metadata:
|
||||||
|
#! name: login-discovery
|
||||||
|
#! namespace: integration
|
||||||
|
#! spec:
|
||||||
|
#! server: https://foo
|
||||||
|
#! certificateAuthorityData: bar
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: apiextensions.k8s.io/v1
|
||||||
|
kind: CustomResourceDefinition
|
||||||
|
metadata:
|
||||||
|
name: logindiscoveryconfigs.crds.placeholder.suzerain-io.github.io
|
||||||
|
spec:
|
||||||
|
group: crds.placeholder.suzerain-io.github.io
|
||||||
|
versions:
|
||||||
|
#! Any changes to these schemas should also be reflected in the types.go file(s)
|
||||||
|
#! in https://github.com/suzerain-io/placeholder-name-api/tree/main/pkg/apis/placeholder
|
||||||
|
- name: v1alpha1
|
||||||
|
served: true
|
||||||
|
storage: true
|
||||||
|
schema:
|
||||||
|
openAPIV3Schema:
|
||||||
|
type: object
|
||||||
|
required: [spec]
|
||||||
|
properties:
|
||||||
|
spec:
|
||||||
|
type: object
|
||||||
|
required: [server, certificateAuthorityData]
|
||||||
|
properties:
|
||||||
|
server:
|
||||||
|
type: string
|
||||||
|
minLength: 1
|
||||||
|
pattern: '^https://'
|
||||||
|
certificateAuthorityData:
|
||||||
|
type: string
|
||||||
|
minLength: 1
|
||||||
|
scope: Namespaced
|
||||||
|
names:
|
||||||
|
plural: logindiscoveryconfigs
|
||||||
|
singular: logindiscoveryconfig
|
||||||
|
kind: LoginDiscoveryConfig
|
||||||
|
shortNames:
|
||||||
|
- ldc
|
@ -24,6 +24,8 @@ metadata:
|
|||||||
data:
|
data:
|
||||||
#@yaml/text-templated-strings
|
#@yaml/text-templated-strings
|
||||||
placeholder-name.yaml: |
|
placeholder-name.yaml: |
|
||||||
|
discovery:
|
||||||
|
url: (@= data.values.discovery_url or "null" @)
|
||||||
webhook:
|
webhook:
|
||||||
url: (@= data.values.webhook_url @)
|
url: (@= data.values.webhook_url @)
|
||||||
caBundle: (@= data.values.webhook_ca_bundle @)
|
caBundle: (@= data.values.webhook_ca_bundle @)
|
||||||
|
@ -38,6 +38,9 @@ rules:
|
|||||||
- apiGroups: [""]
|
- apiGroups: [""]
|
||||||
resources: [services]
|
resources: [services]
|
||||||
verbs: [create, get, list, patch, update, watch]
|
verbs: [create, get, list, patch, update, watch]
|
||||||
|
- apiGroups: [crds.placeholder.suzerain-io.github.io]
|
||||||
|
resources: [logindiscoveryconfigs]
|
||||||
|
verbs: [create, get, list, update, watch]
|
||||||
---
|
---
|
||||||
kind: RoleBinding
|
kind: RoleBinding
|
||||||
apiVersion: rbac.authorization.k8s.io/v1
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
@ -108,3 +111,27 @@ roleRef:
|
|||||||
#! give permissions for a special configmap of CA bundles that is needed by aggregated api servers
|
#! give permissions for a special configmap of CA bundles that is needed by aggregated api servers
|
||||||
name: extension-apiserver-authentication-reader
|
name: extension-apiserver-authentication-reader
|
||||||
apiGroup: rbac.authorization.k8s.io
|
apiGroup: rbac.authorization.k8s.io
|
||||||
|
---
|
||||||
|
kind: Role
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
metadata:
|
||||||
|
name: #@ data.values.app_name + "-cluster-info-lister-watcher-role"
|
||||||
|
namespace: kube-public
|
||||||
|
rules:
|
||||||
|
- apiGroups: [""]
|
||||||
|
resources: [configmaps]
|
||||||
|
verbs: [list, watch]
|
||||||
|
---
|
||||||
|
kind: RoleBinding
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
metadata:
|
||||||
|
name: #@ data.values.app_name + "-cluster-info-lister-watcher-role-binding"
|
||||||
|
namespace: kube-public
|
||||||
|
subjects:
|
||||||
|
- kind: ServiceAccount
|
||||||
|
name: #@ data.values.app_name + "-service-account"
|
||||||
|
namespace: #@ data.values.namespace
|
||||||
|
roleRef:
|
||||||
|
kind: Role
|
||||||
|
name: #@ data.values.app_name + "-cluster-info-lister-watcher-role"
|
||||||
|
apiGroup: rbac.authorization.k8s.io
|
||||||
|
5
go.mod
5
go.mod
@ -10,8 +10,9 @@ require (
|
|||||||
github.com/sclevine/spec v1.4.0
|
github.com/sclevine/spec v1.4.0
|
||||||
github.com/spf13/cobra v1.0.0
|
github.com/spf13/cobra v1.0.0
|
||||||
github.com/stretchr/testify v1.6.1
|
github.com/stretchr/testify v1.6.1
|
||||||
github.com/suzerain-io/placeholder-name-api v0.0.0-20200731022217-d7e4c306f7fd
|
github.com/suzerain-io/controller-go v0.0.0-20200730212956-7f99b569ca9f
|
||||||
github.com/suzerain-io/placeholder-name-client-go v0.0.0-20200731022627-a9c34c8413ac
|
github.com/suzerain-io/placeholder-name-api v0.0.0-20200731224558-ff85679d3364
|
||||||
|
github.com/suzerain-io/placeholder-name-client-go v0.0.0-20200731225637-b994efe19486
|
||||||
github.com/suzerain-io/placeholder-name/pkg/client v0.0.0-00010101000000-000000000000
|
github.com/suzerain-io/placeholder-name/pkg/client v0.0.0-00010101000000-000000000000
|
||||||
k8s.io/api v0.19.0-rc.0
|
k8s.io/api v0.19.0-rc.0
|
||||||
k8s.io/apimachinery v0.19.0-rc.0
|
k8s.io/apimachinery v0.19.0-rc.0
|
||||||
|
16
go.sum
16
go.sum
@ -44,8 +44,10 @@ github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV
|
|||||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
|
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
|
||||||
github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
|
github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
|
||||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||||
|
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM=
|
||||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||||
|
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4 h1:Hs82Z41s6SdL1CELW+XaDYmOH4hkBN4/N9og/AsOv7E=
|
||||||
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||||
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
|
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
|
||||||
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
|
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
|
||||||
@ -528,10 +530,12 @@ github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd
|
|||||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
|
github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
|
||||||
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
||||||
github.com/suzerain-io/placeholder-name-api v0.0.0-20200731022217-d7e4c306f7fd h1:rJT8jn+6g1Wy40nGOGULz9hQvuDq0MZSA+3JaTaqVgQ=
|
github.com/suzerain-io/controller-go v0.0.0-20200730212956-7f99b569ca9f h1:gZ6rAdl+VE9DT0yE52xY/kJZ/hOJYxwtsgGoPr5vItI=
|
||||||
github.com/suzerain-io/placeholder-name-api v0.0.0-20200731022217-d7e4c306f7fd/go.mod h1:OuYBJDpMMnvMUoBn+XeMWtHghuYk0cq9bNkNa3T8j/g=
|
github.com/suzerain-io/controller-go v0.0.0-20200730212956-7f99b569ca9f/go.mod h1:+v9upryFWBJac6KXKlheGHr7e3kqpk1ldH1iIMFopMs=
|
||||||
github.com/suzerain-io/placeholder-name-client-go v0.0.0-20200731022627-a9c34c8413ac h1:3QfNymuRno/01062Ns/xv70m8TxQjiJQKTYCjIztGJw=
|
github.com/suzerain-io/placeholder-name-api v0.0.0-20200731224558-ff85679d3364 h1:5NaQExCSh8+6YP3QNhWsWyMrEd+7zsnrznRlisydlZo=
|
||||||
github.com/suzerain-io/placeholder-name-client-go v0.0.0-20200731022627-a9c34c8413ac/go.mod h1:EFFPGm0xrUidCxQkF1g2pGdKifwo4U0Dwi70TTluNGM=
|
github.com/suzerain-io/placeholder-name-api v0.0.0-20200731224558-ff85679d3364/go.mod h1:OuYBJDpMMnvMUoBn+XeMWtHghuYk0cq9bNkNa3T8j/g=
|
||||||
|
github.com/suzerain-io/placeholder-name-client-go v0.0.0-20200731225637-b994efe19486 h1:JOPwCoRjsUu8E/E81BolafPfovnTmxGAgFnEV0R8T3U=
|
||||||
|
github.com/suzerain-io/placeholder-name-client-go v0.0.0-20200731225637-b994efe19486/go.mod h1:0lk6jYt88I1632/5TIwpBPhQAewKuesNK+rKhfoegRk=
|
||||||
github.com/tdakkota/asciicheck v0.0.0-20200416190851-d7f85be797a2 h1:Xr9gkxfOP0KQWXKNqmwe8vEeSUiUj4Rlee9CMVX2ZUQ=
|
github.com/tdakkota/asciicheck v0.0.0-20200416190851-d7f85be797a2 h1:Xr9gkxfOP0KQWXKNqmwe8vEeSUiUj4Rlee9CMVX2ZUQ=
|
||||||
github.com/tdakkota/asciicheck v0.0.0-20200416190851-d7f85be797a2/go.mod h1:yHp0ai0Z9gUljN3o0xMhYJnH/IcvkdTBOX2fmJ93JEM=
|
github.com/tdakkota/asciicheck v0.0.0-20200416190851-d7f85be797a2/go.mod h1:yHp0ai0Z9gUljN3o0xMhYJnH/IcvkdTBOX2fmJ93JEM=
|
||||||
github.com/tetafro/godot v0.4.2 h1:Dib7un+rYJFUi8vN0Bk6EHheKy6fv6ZzFURHw75g6m8=
|
github.com/tetafro/godot v0.4.2 h1:Dib7un+rYJFUi8vN0Bk6EHheKy6fv6ZzFURHw75g6m8=
|
||||||
@ -740,7 +744,10 @@ golang.org/x/tools v0.0.0-20200519015757-0d0afa43d58a/go.mod h1:EkVYQZoAsY45+roY
|
|||||||
golang.org/x/tools v0.0.0-20200602230032-c00d67ef29d0/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20200602230032-c00d67ef29d0/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/tools v0.0.0-20200625211823-6506e20df31f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
golang.org/x/tools v0.0.0-20200625211823-6506e20df31f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||||
golang.org/x/tools v0.0.0-20200626171337-aa94e735be7f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
golang.org/x/tools v0.0.0-20200626171337-aa94e735be7f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||||
|
golang.org/x/tools v0.0.0-20200626171337-aa94e735be7f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||||
golang.org/x/tools v0.0.0-20200710042808-f1c4188a97a1 h1:rD1FcWVsRaMY+l8biE9jbWP5MS/CJJ/90a9TMkMgNrM=
|
golang.org/x/tools v0.0.0-20200710042808-f1c4188a97a1 h1:rD1FcWVsRaMY+l8biE9jbWP5MS/CJJ/90a9TMkMgNrM=
|
||||||
|
golang.org/x/tools v0.0.0-20200710042808-f1c4188a97a1 h1:rD1FcWVsRaMY+l8biE9jbWP5MS/CJJ/90a9TMkMgNrM=
|
||||||
|
golang.org/x/tools v0.0.0-20200710042808-f1c4188a97a1/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||||
golang.org/x/tools v0.0.0-20200710042808-f1c4188a97a1/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
golang.org/x/tools v0.0.0-20200710042808-f1c4188a97a1/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
@ -788,6 +795,7 @@ google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2
|
|||||||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||||
google.golang.org/protobuf v1.24.0 h1:UhZDfRO8JRQru4/+LlLE0BRKGF8L+PICnvYZmx/fEGA=
|
google.golang.org/protobuf v1.24.0 h1:UhZDfRO8JRQru4/+LlLE0BRKGF8L+PICnvYZmx/fEGA=
|
||||||
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
|
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
|
||||||
|
gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc=
|
||||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
@ -6,6 +6,7 @@ SPDX-License-Identifier: Apache-2.0
|
|||||||
package apiserver
|
package apiserver
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
@ -58,6 +59,7 @@ type Config struct {
|
|||||||
type ExtraConfig struct {
|
type ExtraConfig struct {
|
||||||
Webhook authenticator.Token
|
Webhook authenticator.Token
|
||||||
Issuer loginrequest.CertIssuer
|
Issuer loginrequest.CertIssuer
|
||||||
|
StartControllersPostStartHook func(ctx context.Context)
|
||||||
}
|
}
|
||||||
|
|
||||||
type PlaceHolderServer struct {
|
type PlaceHolderServer struct {
|
||||||
@ -122,9 +124,17 @@ func (c completedConfig) New() (*PlaceHolderServer, error) {
|
|||||||
return nil, fmt.Errorf("install API group error: %w", err)
|
return nil, fmt.Errorf("install API group error: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
s.GenericAPIServer.AddPostStartHookOrDie("place-holder-post-start-hook",
|
s.GenericAPIServer.AddPostStartHookOrDie("start-controllers",
|
||||||
func(context genericapiserver.PostStartHookContext) error {
|
func(postStartContext genericapiserver.PostStartHookContext) error {
|
||||||
klog.InfoS("post start hook", "foo", "bar")
|
klog.InfoS("start-controllers post start hook starting")
|
||||||
|
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
go func() {
|
||||||
|
<-postStartContext.StopCh
|
||||||
|
cancel()
|
||||||
|
}()
|
||||||
|
c.ExtraConfig.StartControllersPostStartHook(ctx)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
7
internal/controller/logindiscovery/doc.go
Normal file
7
internal/controller/logindiscovery/doc.go
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2020 VMware, Inc.
|
||||||
|
SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Package logindiscovery contains controller(s) for reconciling LoginDiscoveryConfig's.
|
||||||
|
package logindiscovery
|
192
internal/controller/logindiscovery/publisher.go
Normal file
192
internal/controller/logindiscovery/publisher.go
Normal file
@ -0,0 +1,192 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2020 VMware, Inc.
|
||||||
|
SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package logindiscovery
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
k8serrors "k8s.io/apimachinery/pkg/api/errors"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
corev1informers "k8s.io/client-go/informers/core/v1"
|
||||||
|
"k8s.io/client-go/tools/clientcmd"
|
||||||
|
"k8s.io/klog/v2"
|
||||||
|
|
||||||
|
"github.com/suzerain-io/controller-go"
|
||||||
|
crdsplaceholderv1alpha1 "github.com/suzerain-io/placeholder-name-api/pkg/apis/crdsplaceholder/v1alpha1"
|
||||||
|
placeholderclientset "github.com/suzerain-io/placeholder-name-client-go/pkg/generated/clientset/versioned"
|
||||||
|
crdsplaceholderv1alpha1informers "github.com/suzerain-io/placeholder-name-client-go/pkg/generated/informers/externalversions/crdsplaceholder/v1alpha1"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
ClusterInfoNamespace = "kube-public"
|
||||||
|
|
||||||
|
clusterInfoName = "cluster-info"
|
||||||
|
clusterInfoConfigMapKey = "kubeconfig"
|
||||||
|
|
||||||
|
configName = "placeholder-name-config"
|
||||||
|
)
|
||||||
|
|
||||||
|
func nameAndNamespaceExactMatchFilterFactory(name, namespace string) controller.FilterFuncs {
|
||||||
|
objMatchesFunc := func(obj metav1.Object) bool {
|
||||||
|
return obj.GetName() == name && obj.GetNamespace() == namespace
|
||||||
|
}
|
||||||
|
return controller.FilterFuncs{
|
||||||
|
AddFunc: objMatchesFunc,
|
||||||
|
UpdateFunc: func(oldObj, newObj metav1.Object) bool {
|
||||||
|
return objMatchesFunc(oldObj) || objMatchesFunc(newObj)
|
||||||
|
},
|
||||||
|
DeleteFunc: objMatchesFunc,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Same signature as controller.WithInformer().
|
||||||
|
type withInformerOptionFunc func(
|
||||||
|
getter controller.InformerGetter,
|
||||||
|
filter controller.Filter,
|
||||||
|
opt controller.InformerOption) controller.Option
|
||||||
|
|
||||||
|
type publisherController struct {
|
||||||
|
namespace string
|
||||||
|
serverOverride *string
|
||||||
|
placeholderClient placeholderclientset.Interface
|
||||||
|
configMapInformer corev1informers.ConfigMapInformer
|
||||||
|
loginDiscoveryConfigInformer crdsplaceholderv1alpha1informers.LoginDiscoveryConfigInformer
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewPublisherController(
|
||||||
|
namespace string,
|
||||||
|
serverOverride *string,
|
||||||
|
placeholderClient placeholderclientset.Interface,
|
||||||
|
configMapInformer corev1informers.ConfigMapInformer,
|
||||||
|
loginDiscoveryConfigInformer crdsplaceholderv1alpha1informers.LoginDiscoveryConfigInformer,
|
||||||
|
withInformer withInformerOptionFunc,
|
||||||
|
) controller.Controller {
|
||||||
|
return controller.New(
|
||||||
|
controller.Config{
|
||||||
|
Name: "publisher-controller",
|
||||||
|
Syncer: &publisherController{
|
||||||
|
namespace: namespace,
|
||||||
|
serverOverride: serverOverride,
|
||||||
|
placeholderClient: placeholderClient,
|
||||||
|
configMapInformer: configMapInformer,
|
||||||
|
loginDiscoveryConfigInformer: loginDiscoveryConfigInformer,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
withInformer(
|
||||||
|
configMapInformer,
|
||||||
|
nameAndNamespaceExactMatchFilterFactory(clusterInfoName, ClusterInfoNamespace),
|
||||||
|
controller.InformerOption{},
|
||||||
|
),
|
||||||
|
withInformer(
|
||||||
|
loginDiscoveryConfigInformer,
|
||||||
|
nameAndNamespaceExactMatchFilterFactory(configName, namespace),
|
||||||
|
controller.InformerOption{},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *publisherController) Sync(ctx controller.Context) error {
|
||||||
|
configMap, err := c.configMapInformer.
|
||||||
|
Lister().
|
||||||
|
ConfigMaps(ClusterInfoNamespace).
|
||||||
|
Get(clusterInfoName)
|
||||||
|
notFound := k8serrors.IsNotFound(err)
|
||||||
|
if err != nil && !notFound {
|
||||||
|
return fmt.Errorf("failed to get %s configmap: %w", clusterInfoName, err)
|
||||||
|
}
|
||||||
|
if notFound {
|
||||||
|
klog.InfoS(
|
||||||
|
"could not find config map",
|
||||||
|
"configmap",
|
||||||
|
klog.KRef(ClusterInfoNamespace, clusterInfoName),
|
||||||
|
)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
kubeConfig, kubeConfigPresent := configMap.Data[clusterInfoConfigMapKey]
|
||||||
|
if !kubeConfigPresent {
|
||||||
|
klog.InfoS("could not find kubeconfig configmap key")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
config, _ := clientcmd.Load([]byte(kubeConfig))
|
||||||
|
|
||||||
|
var certificateAuthorityData, server string
|
||||||
|
for _, v := range config.Clusters {
|
||||||
|
certificateAuthorityData = base64.StdEncoding.EncodeToString(v.CertificateAuthorityData)
|
||||||
|
server = v.Server
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.serverOverride != nil {
|
||||||
|
server = *c.serverOverride
|
||||||
|
}
|
||||||
|
|
||||||
|
discoveryConfig := crdsplaceholderv1alpha1.LoginDiscoveryConfig{
|
||||||
|
TypeMeta: metav1.TypeMeta{},
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: configName,
|
||||||
|
Namespace: c.namespace,
|
||||||
|
},
|
||||||
|
Spec: crdsplaceholderv1alpha1.LoginDiscoveryConfigSpec{
|
||||||
|
Server: server,
|
||||||
|
CertificateAuthorityData: certificateAuthorityData,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if err := c.createOrUpdateLoginDiscoveryConfig(ctx.Context, &discoveryConfig); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *publisherController) createOrUpdateLoginDiscoveryConfig(
|
||||||
|
ctx context.Context,
|
||||||
|
discoveryConfig *crdsplaceholderv1alpha1.LoginDiscoveryConfig,
|
||||||
|
) error {
|
||||||
|
existingDiscoveryConfig, err := c.loginDiscoveryConfigInformer.
|
||||||
|
Lister().
|
||||||
|
LoginDiscoveryConfigs(c.namespace).
|
||||||
|
Get(discoveryConfig.Name)
|
||||||
|
notFound := k8serrors.IsNotFound(err)
|
||||||
|
if err != nil && !notFound {
|
||||||
|
return fmt.Errorf("could not get logindiscoveryconfig: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
loginDiscoveryConfigs := c.placeholderClient.
|
||||||
|
CrdsV1alpha1().
|
||||||
|
LoginDiscoveryConfigs(c.namespace)
|
||||||
|
if notFound {
|
||||||
|
if _, err := loginDiscoveryConfigs.Create(
|
||||||
|
ctx,
|
||||||
|
discoveryConfig,
|
||||||
|
metav1.CreateOptions{},
|
||||||
|
); err != nil {
|
||||||
|
return fmt.Errorf("could not create logindiscoveryconfig: %w", err)
|
||||||
|
}
|
||||||
|
} else if !equal(existingDiscoveryConfig, discoveryConfig) {
|
||||||
|
// Update just the fields we care about.
|
||||||
|
existingDiscoveryConfig.Spec.Server = discoveryConfig.Spec.Server
|
||||||
|
existingDiscoveryConfig.Spec.CertificateAuthorityData = discoveryConfig.Spec.CertificateAuthorityData
|
||||||
|
|
||||||
|
if _, err := loginDiscoveryConfigs.Update(
|
||||||
|
ctx,
|
||||||
|
existingDiscoveryConfig,
|
||||||
|
metav1.UpdateOptions{},
|
||||||
|
); err != nil {
|
||||||
|
return fmt.Errorf("could not update logindiscoveryconfig: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func equal(a, b *crdsplaceholderv1alpha1.LoginDiscoveryConfig) bool {
|
||||||
|
return a.Spec.Server == b.Spec.Server &&
|
||||||
|
a.Spec.CertificateAuthorityData == b.Spec.CertificateAuthorityData
|
||||||
|
}
|
475
internal/controller/logindiscovery/publisher_test.go
Normal file
475
internal/controller/logindiscovery/publisher_test.go
Normal file
@ -0,0 +1,475 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2020 VMware, Inc.
|
||||||
|
SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package logindiscovery
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/sclevine/spec"
|
||||||
|
"github.com/sclevine/spec/report"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
corev1 "k8s.io/api/core/v1"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
kubeinformers "k8s.io/client-go/informers"
|
||||||
|
kubernetesfake "k8s.io/client-go/kubernetes/fake"
|
||||||
|
coretesting "k8s.io/client-go/testing"
|
||||||
|
|
||||||
|
"github.com/suzerain-io/controller-go"
|
||||||
|
crdsplaceholderv1alpha1 "github.com/suzerain-io/placeholder-name-api/pkg/apis/crdsplaceholder/v1alpha1"
|
||||||
|
placeholderfake "github.com/suzerain-io/placeholder-name-client-go/pkg/generated/clientset/versioned/fake"
|
||||||
|
placeholderinformers "github.com/suzerain-io/placeholder-name-client-go/pkg/generated/informers/externalversions"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ObservableWithInformerOption struct {
|
||||||
|
InformerToFilterMap map[controller.InformerGetter]controller.Filter
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewObservableWithInformerOption() *ObservableWithInformerOption {
|
||||||
|
return &ObservableWithInformerOption{
|
||||||
|
InformerToFilterMap: make(map[controller.InformerGetter]controller.Filter),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (owi *ObservableWithInformerOption) WithInformer(
|
||||||
|
getter controller.InformerGetter,
|
||||||
|
filter controller.Filter,
|
||||||
|
opt controller.InformerOption) controller.Option {
|
||||||
|
owi.InformerToFilterMap[getter] = filter
|
||||||
|
return controller.WithInformer(getter, filter, opt)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInformerFilters(t *testing.T) {
|
||||||
|
spec.Run(t, "informer filters", func(t *testing.T, when spec.G, it spec.S) {
|
||||||
|
const installedInNamespace = "some-namespace"
|
||||||
|
|
||||||
|
var r *require.Assertions
|
||||||
|
var observableWithInformerOption *ObservableWithInformerOption
|
||||||
|
var configMapInformerFilter controller.Filter
|
||||||
|
var loginDiscoveryConfigInformerFilter controller.Filter
|
||||||
|
|
||||||
|
it.Before(func() {
|
||||||
|
r = require.New(t)
|
||||||
|
observableWithInformerOption = NewObservableWithInformerOption()
|
||||||
|
configMapInformer := kubeinformers.NewSharedInformerFactory(nil, 0).Core().V1().ConfigMaps()
|
||||||
|
loginDiscoveryConfigInformer := placeholderinformers.NewSharedInformerFactory(nil, 0).Crds().V1alpha1().LoginDiscoveryConfigs()
|
||||||
|
_ = NewPublisherController(
|
||||||
|
installedInNamespace,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
configMapInformer,
|
||||||
|
loginDiscoveryConfigInformer,
|
||||||
|
observableWithInformerOption.WithInformer, // make it possible to observe the behavior of the Filters
|
||||||
|
)
|
||||||
|
configMapInformerFilter = observableWithInformerOption.InformerToFilterMap[configMapInformer]
|
||||||
|
loginDiscoveryConfigInformerFilter = observableWithInformerOption.InformerToFilterMap[loginDiscoveryConfigInformer]
|
||||||
|
})
|
||||||
|
|
||||||
|
when("watching ConfigMap objects", func() {
|
||||||
|
var subject controller.Filter
|
||||||
|
var target, wrongNamespace, wrongName, unrelated *corev1.ConfigMap
|
||||||
|
|
||||||
|
it.Before(func() {
|
||||||
|
subject = configMapInformerFilter
|
||||||
|
target = &corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{Name: "cluster-info", Namespace: "kube-public"}}
|
||||||
|
wrongNamespace = &corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{Name: "cluster-info", Namespace: "wrong-namespace"}}
|
||||||
|
wrongName = &corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{Name: "wrong-name", Namespace: "kube-public"}}
|
||||||
|
unrelated = &corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{Name: "wrong-name", Namespace: "wrong-namespace"}}
|
||||||
|
})
|
||||||
|
|
||||||
|
when("the target ConfigMap changes", func() {
|
||||||
|
it("returns true to trigger the sync method", func() {
|
||||||
|
r.True(subject.Add(target))
|
||||||
|
r.True(subject.Update(target, unrelated))
|
||||||
|
r.True(subject.Update(unrelated, target))
|
||||||
|
r.True(subject.Delete(target))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
when("a ConfigMap from another namespace changes", func() {
|
||||||
|
it("returns false to avoid triggering the sync method", func() {
|
||||||
|
r.False(subject.Add(wrongNamespace))
|
||||||
|
r.False(subject.Update(wrongNamespace, unrelated))
|
||||||
|
r.False(subject.Update(unrelated, wrongNamespace))
|
||||||
|
r.False(subject.Delete(wrongNamespace))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
when("a ConfigMap with a different name changes", func() {
|
||||||
|
it("returns false to avoid triggering the sync method", func() {
|
||||||
|
r.False(subject.Add(wrongName))
|
||||||
|
r.False(subject.Update(wrongName, unrelated))
|
||||||
|
r.False(subject.Update(unrelated, wrongName))
|
||||||
|
r.False(subject.Delete(wrongName))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
when("a ConfigMap with a different name and a different namespace changes", func() {
|
||||||
|
it("returns false to avoid triggering the sync method", func() {
|
||||||
|
r.False(subject.Add(unrelated))
|
||||||
|
r.False(subject.Update(unrelated, unrelated))
|
||||||
|
r.False(subject.Delete(unrelated))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
when("watching LoginDiscoveryConfig objects", func() {
|
||||||
|
var subject controller.Filter
|
||||||
|
var target, wrongNamespace, wrongName, unrelated *crdsplaceholderv1alpha1.LoginDiscoveryConfig
|
||||||
|
|
||||||
|
it.Before(func() {
|
||||||
|
subject = loginDiscoveryConfigInformerFilter
|
||||||
|
target = &crdsplaceholderv1alpha1.LoginDiscoveryConfig{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{Name: "placeholder-name-config", Namespace: installedInNamespace},
|
||||||
|
}
|
||||||
|
wrongNamespace = &crdsplaceholderv1alpha1.LoginDiscoveryConfig{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{Name: "placeholder-name-config", Namespace: "wrong-namespace"},
|
||||||
|
}
|
||||||
|
wrongName = &crdsplaceholderv1alpha1.LoginDiscoveryConfig{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{Name: "wrong-name", Namespace: installedInNamespace},
|
||||||
|
}
|
||||||
|
unrelated = &crdsplaceholderv1alpha1.LoginDiscoveryConfig{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{Name: "wrong-name", Namespace: "wrong-namespace"},
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
when("the target LoginDiscoveryConfig changes", func() {
|
||||||
|
it("returns true to trigger the sync method", func() {
|
||||||
|
r.True(subject.Add(target))
|
||||||
|
r.True(subject.Update(target, unrelated))
|
||||||
|
r.True(subject.Update(unrelated, target))
|
||||||
|
r.True(subject.Delete(target))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
when("a LoginDiscoveryConfig from another namespace changes", func() {
|
||||||
|
it("returns false to avoid triggering the sync method", func() {
|
||||||
|
r.False(subject.Add(wrongNamespace))
|
||||||
|
r.False(subject.Update(wrongNamespace, unrelated))
|
||||||
|
r.False(subject.Update(unrelated, wrongNamespace))
|
||||||
|
r.False(subject.Delete(wrongNamespace))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
when("a LoginDiscoveryConfig with a different name changes", func() {
|
||||||
|
it("returns false to avoid triggering the sync method", func() {
|
||||||
|
r.False(subject.Add(wrongName))
|
||||||
|
r.False(subject.Update(wrongName, unrelated))
|
||||||
|
r.False(subject.Update(unrelated, wrongName))
|
||||||
|
r.False(subject.Delete(wrongName))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
when("a LoginDiscoveryConfig with a different name and a different namespace changes", func() {
|
||||||
|
it("returns false to avoid triggering the sync method", func() {
|
||||||
|
r.False(subject.Add(unrelated))
|
||||||
|
r.False(subject.Update(unrelated, unrelated))
|
||||||
|
r.False(subject.Delete(unrelated))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}, spec.Parallel(), spec.Report(report.Terminal{}))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSync(t *testing.T) {
|
||||||
|
spec.Run(t, "Sync", func(t *testing.T, when spec.G, it spec.S) {
|
||||||
|
const installedInNamespace = "some-namespace"
|
||||||
|
|
||||||
|
var r *require.Assertions
|
||||||
|
|
||||||
|
var subject controller.Controller
|
||||||
|
var serverOverride *string
|
||||||
|
var kubeInformerClient *kubernetesfake.Clientset
|
||||||
|
var placeholderInformerClient *placeholderfake.Clientset
|
||||||
|
var kubeInformers kubeinformers.SharedInformerFactory
|
||||||
|
var placeholderInformers placeholderinformers.SharedInformerFactory
|
||||||
|
var placeholderAPIClient *placeholderfake.Clientset
|
||||||
|
var timeoutContext context.Context
|
||||||
|
var timeoutContextCancel context.CancelFunc
|
||||||
|
var syncContext *controller.Context
|
||||||
|
|
||||||
|
var expectedLoginDiscoveryConfig = func(expectedNamespace, expectedServerURL, expectedCAData string) (schema.GroupVersionResource, *crdsplaceholderv1alpha1.LoginDiscoveryConfig) {
|
||||||
|
expectedLoginDiscoveryConfigGVR := schema.GroupVersionResource{
|
||||||
|
Group: crdsplaceholderv1alpha1.GroupName,
|
||||||
|
Version: "v1alpha1",
|
||||||
|
Resource: "logindiscoveryconfigs",
|
||||||
|
}
|
||||||
|
expectedLoginDiscoveryConfig := &crdsplaceholderv1alpha1.LoginDiscoveryConfig{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "placeholder-name-config",
|
||||||
|
Namespace: expectedNamespace,
|
||||||
|
},
|
||||||
|
Spec: crdsplaceholderv1alpha1.LoginDiscoveryConfigSpec{
|
||||||
|
Server: expectedServerURL,
|
||||||
|
CertificateAuthorityData: expectedCAData,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return expectedLoginDiscoveryConfigGVR, expectedLoginDiscoveryConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
// Defer starting the informers until the last possible moment so that the
|
||||||
|
// nested Before's can keep adding things to the informer caches.
|
||||||
|
var startInformersAndController = func() {
|
||||||
|
// Set this at the last second to allow for injection of server override.
|
||||||
|
subject = NewPublisherController(
|
||||||
|
installedInNamespace,
|
||||||
|
serverOverride,
|
||||||
|
placeholderAPIClient,
|
||||||
|
kubeInformers.Core().V1().ConfigMaps(),
|
||||||
|
placeholderInformers.Crds().V1alpha1().LoginDiscoveryConfigs(),
|
||||||
|
controller.WithInformer,
|
||||||
|
)
|
||||||
|
|
||||||
|
// Set this at the last second to support calling subject.Name().
|
||||||
|
syncContext = &controller.Context{
|
||||||
|
Context: timeoutContext,
|
||||||
|
Name: subject.Name(),
|
||||||
|
Key: controller.Key{
|
||||||
|
Namespace: "kube-public",
|
||||||
|
Name: "cluster-info",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Must start informers before calling TestRunSynchronously()
|
||||||
|
kubeInformers.Start(timeoutContext.Done())
|
||||||
|
placeholderInformers.Start(timeoutContext.Done())
|
||||||
|
controller.TestRunSynchronously(t, subject)
|
||||||
|
}
|
||||||
|
|
||||||
|
it.Before(func() {
|
||||||
|
r = require.New(t)
|
||||||
|
|
||||||
|
timeoutContext, timeoutContextCancel = context.WithTimeout(context.Background(), time.Second*3)
|
||||||
|
|
||||||
|
kubeInformerClient = kubernetesfake.NewSimpleClientset()
|
||||||
|
kubeInformers = kubeinformers.NewSharedInformerFactory(kubeInformerClient, 0)
|
||||||
|
placeholderAPIClient = placeholderfake.NewSimpleClientset()
|
||||||
|
placeholderInformerClient = placeholderfake.NewSimpleClientset()
|
||||||
|
placeholderInformers = placeholderinformers.NewSharedInformerFactory(placeholderInformerClient, 0)
|
||||||
|
})
|
||||||
|
|
||||||
|
it.After(func() {
|
||||||
|
timeoutContextCancel()
|
||||||
|
})
|
||||||
|
|
||||||
|
when("there is a cluster-info ConfigMap in the kube-public namespace", func() {
|
||||||
|
const caData = "c29tZS1jZXJ0aWZpY2F0ZS1hdXRob3JpdHktZGF0YQo=" // "some-certificate-authority-data" base64 encoded
|
||||||
|
const kubeServerURL = "https://some-server"
|
||||||
|
|
||||||
|
when("the ConfigMap has the expected `kubeconfig` top-level data key", func() {
|
||||||
|
it.Before(func() {
|
||||||
|
clusterInfoConfigMap := &corev1.ConfigMap{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{Name: "cluster-info", Namespace: "kube-public"},
|
||||||
|
// Note that go fmt puts tabs in our file, which we must remove from our configmap yaml below.
|
||||||
|
Data: map[string]string{
|
||||||
|
"kubeconfig": strings.ReplaceAll(`
|
||||||
|
kind: Config
|
||||||
|
apiVersion: v1
|
||||||
|
clusters:
|
||||||
|
- name: ""
|
||||||
|
cluster:
|
||||||
|
certificate-authority-data: "`+caData+`"
|
||||||
|
server: "`+kubeServerURL+`"`, "\t", " "),
|
||||||
|
"uninteresting-key": "uninteresting-value",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
err := kubeInformerClient.Tracker().Add(clusterInfoConfigMap)
|
||||||
|
r.NoError(err)
|
||||||
|
})
|
||||||
|
|
||||||
|
when("the LoginDiscoveryConfig does not already exist", func() {
|
||||||
|
it("creates a LoginDiscoveryConfig", func() {
|
||||||
|
startInformersAndController()
|
||||||
|
err := controller.TestSync(t, subject, *syncContext)
|
||||||
|
r.NoError(err)
|
||||||
|
|
||||||
|
expectedLoginDiscoveryConfigGVR, expectedLoginDiscoveryConfig := expectedLoginDiscoveryConfig(
|
||||||
|
installedInNamespace,
|
||||||
|
kubeServerURL,
|
||||||
|
caData,
|
||||||
|
)
|
||||||
|
|
||||||
|
r.Equal(
|
||||||
|
[]coretesting.Action{
|
||||||
|
coretesting.NewCreateAction(
|
||||||
|
expectedLoginDiscoveryConfigGVR,
|
||||||
|
installedInNamespace,
|
||||||
|
expectedLoginDiscoveryConfig,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
placeholderAPIClient.Actions(),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
when("creating the LoginDiscoveryConfig fails", func() {
|
||||||
|
it.Before(func() {
|
||||||
|
placeholderAPIClient.PrependReactor(
|
||||||
|
"create",
|
||||||
|
"logindiscoveryconfigs",
|
||||||
|
func(_ coretesting.Action) (bool, runtime.Object, error) {
|
||||||
|
return true, nil, errors.New("create failed")
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns the create error", func() {
|
||||||
|
startInformersAndController()
|
||||||
|
err := controller.TestSync(t, subject, *syncContext)
|
||||||
|
r.EqualError(err, "could not create logindiscoveryconfig: create failed")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
when("a server override is passed to the controller", func() {
|
||||||
|
it("uses the server override field", func() {
|
||||||
|
serverOverride = new(string)
|
||||||
|
*serverOverride = "https://some-server-override"
|
||||||
|
|
||||||
|
startInformersAndController()
|
||||||
|
err := controller.TestSync(t, subject, *syncContext)
|
||||||
|
r.NoError(err)
|
||||||
|
|
||||||
|
expectedLoginDiscoveryConfigGVR, expectedLoginDiscoveryConfig := expectedLoginDiscoveryConfig(
|
||||||
|
installedInNamespace,
|
||||||
|
kubeServerURL,
|
||||||
|
caData,
|
||||||
|
)
|
||||||
|
expectedLoginDiscoveryConfig.Spec.Server = "https://some-server-override"
|
||||||
|
|
||||||
|
r.Equal(
|
||||||
|
[]coretesting.Action{
|
||||||
|
coretesting.NewCreateAction(
|
||||||
|
expectedLoginDiscoveryConfigGVR,
|
||||||
|
installedInNamespace,
|
||||||
|
expectedLoginDiscoveryConfig,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
placeholderAPIClient.Actions(),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
when("the LoginDiscoveryConfig already exists", func() {
|
||||||
|
when("the LoginDiscoveryConfig is already up to date according to the data in the ConfigMap", func() {
|
||||||
|
it.Before(func() {
|
||||||
|
_, expectedLoginDiscoveryConfig := expectedLoginDiscoveryConfig(
|
||||||
|
installedInNamespace,
|
||||||
|
kubeServerURL,
|
||||||
|
caData,
|
||||||
|
)
|
||||||
|
err := placeholderInformerClient.Tracker().Add(expectedLoginDiscoveryConfig)
|
||||||
|
r.NoError(err)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("does not update the LoginDiscoveryConfig to avoid unnecessary etcd writes/api calls", func() {
|
||||||
|
startInformersAndController()
|
||||||
|
err := controller.TestSync(t, subject, *syncContext)
|
||||||
|
r.NoError(err)
|
||||||
|
|
||||||
|
r.Empty(placeholderAPIClient.Actions())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
when("the LoginDiscoveryConfig is stale compared to the data in the ConfigMap", func() {
|
||||||
|
it.Before(func() {
|
||||||
|
_, expectedLoginDiscoveryConfig := expectedLoginDiscoveryConfig(
|
||||||
|
installedInNamespace,
|
||||||
|
kubeServerURL,
|
||||||
|
caData,
|
||||||
|
)
|
||||||
|
expectedLoginDiscoveryConfig.Spec.Server = "https://some-other-server"
|
||||||
|
r.NoError(placeholderInformerClient.Tracker().Add(expectedLoginDiscoveryConfig))
|
||||||
|
r.NoError(placeholderAPIClient.Tracker().Add(expectedLoginDiscoveryConfig))
|
||||||
|
})
|
||||||
|
|
||||||
|
it("updates the existing LoginDiscoveryConfig", func() {
|
||||||
|
startInformersAndController()
|
||||||
|
err := controller.TestSync(t, subject, *syncContext)
|
||||||
|
r.NoError(err)
|
||||||
|
|
||||||
|
expectedLoginDiscoveryConfigGVR, expectedLoginDiscoveryConfig := expectedLoginDiscoveryConfig(
|
||||||
|
installedInNamespace,
|
||||||
|
kubeServerURL,
|
||||||
|
caData,
|
||||||
|
)
|
||||||
|
expectedActions := []coretesting.Action{
|
||||||
|
coretesting.NewUpdateAction(
|
||||||
|
expectedLoginDiscoveryConfigGVR,
|
||||||
|
installedInNamespace,
|
||||||
|
expectedLoginDiscoveryConfig,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
r.Equal(expectedActions, placeholderAPIClient.Actions())
|
||||||
|
})
|
||||||
|
|
||||||
|
when("updating the LoginDiscoveryConfig fails", func() {
|
||||||
|
it.Before(func() {
|
||||||
|
placeholderAPIClient.PrependReactor(
|
||||||
|
"update",
|
||||||
|
"logindiscoveryconfigs",
|
||||||
|
func(_ coretesting.Action) (bool, runtime.Object, error) {
|
||||||
|
return true, nil, errors.New("update failed")
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns the update error", func() {
|
||||||
|
startInformersAndController()
|
||||||
|
err := controller.TestSync(t, subject, *syncContext)
|
||||||
|
r.EqualError(err, "could not update logindiscoveryconfig: update failed")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
when("the ConfigMap is missing the expected `kubeconfig` top-level data key", func() {
|
||||||
|
it.Before(func() {
|
||||||
|
clusterInfoConfigMap := &corev1.ConfigMap{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{Name: "cluster-info", Namespace: "kube-public"},
|
||||||
|
Data: map[string]string{
|
||||||
|
"these are not the droids you're looking for": "uninteresting-value",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
err := kubeInformerClient.Tracker().Add(clusterInfoConfigMap)
|
||||||
|
r.NoError(err)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("keeps waiting for it to exist", func() {
|
||||||
|
startInformersAndController()
|
||||||
|
err := controller.TestSync(t, subject, *syncContext)
|
||||||
|
r.NoError(err)
|
||||||
|
r.Empty(placeholderAPIClient.Actions())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
when("there is not a cluster-info ConfigMap in the kube-public namespace", func() {
|
||||||
|
it.Before(func() {
|
||||||
|
unrelatedConfigMap := &corev1.ConfigMap{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "oops this is not the cluster-info ConfigMap",
|
||||||
|
Namespace: "kube-public",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
err := kubeInformerClient.Tracker().Add(unrelatedConfigMap)
|
||||||
|
r.NoError(err)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("keeps waiting for one", func() {
|
||||||
|
startInformersAndController()
|
||||||
|
err := controller.TestSync(t, subject, *syncContext)
|
||||||
|
r.NoError(err)
|
||||||
|
r.Empty(placeholderAPIClient.Actions())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}, spec.Parallel(), spec.Report(report.Terminal{}))
|
||||||
|
}
|
@ -25,20 +25,29 @@ import (
|
|||||||
"k8s.io/apiserver/pkg/server/dynamiccertificates"
|
"k8s.io/apiserver/pkg/server/dynamiccertificates"
|
||||||
genericoptions "k8s.io/apiserver/pkg/server/options"
|
genericoptions "k8s.io/apiserver/pkg/server/options"
|
||||||
"k8s.io/apiserver/plugin/pkg/authenticator/token/webhook"
|
"k8s.io/apiserver/plugin/pkg/authenticator/token/webhook"
|
||||||
|
k8sinformers "k8s.io/client-go/informers"
|
||||||
"k8s.io/client-go/kubernetes"
|
"k8s.io/client-go/kubernetes"
|
||||||
corev1client "k8s.io/client-go/kubernetes/typed/core/v1"
|
|
||||||
restclient "k8s.io/client-go/rest"
|
restclient "k8s.io/client-go/rest"
|
||||||
apiregistrationv1 "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1"
|
apiregistrationv1 "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1"
|
||||||
aggregationv1client "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset"
|
aggregationv1client "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset"
|
||||||
|
|
||||||
|
"github.com/suzerain-io/controller-go"
|
||||||
placeholderv1alpha1 "github.com/suzerain-io/placeholder-name-api/pkg/apis/placeholder/v1alpha1"
|
placeholderv1alpha1 "github.com/suzerain-io/placeholder-name-api/pkg/apis/placeholder/v1alpha1"
|
||||||
|
placeholderclientset "github.com/suzerain-io/placeholder-name-client-go/pkg/generated/clientset/versioned"
|
||||||
|
placeholderinformers "github.com/suzerain-io/placeholder-name-client-go/pkg/generated/informers/externalversions"
|
||||||
"github.com/suzerain-io/placeholder-name/internal/apiserver"
|
"github.com/suzerain-io/placeholder-name/internal/apiserver"
|
||||||
"github.com/suzerain-io/placeholder-name/internal/autoregistration"
|
"github.com/suzerain-io/placeholder-name/internal/autoregistration"
|
||||||
"github.com/suzerain-io/placeholder-name/internal/certauthority"
|
"github.com/suzerain-io/placeholder-name/internal/certauthority"
|
||||||
|
"github.com/suzerain-io/placeholder-name/internal/controller/logindiscovery"
|
||||||
"github.com/suzerain-io/placeholder-name/internal/downward"
|
"github.com/suzerain-io/placeholder-name/internal/downward"
|
||||||
"github.com/suzerain-io/placeholder-name/pkg/config"
|
"github.com/suzerain-io/placeholder-name/pkg/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
singletonWorker = 1
|
||||||
|
defaultResyncInterval = 3 * time.Minute
|
||||||
|
)
|
||||||
|
|
||||||
// App is an object that represents the placeholder-name-server application.
|
// App is an object that represents the placeholder-name-server application.
|
||||||
type App struct {
|
type App struct {
|
||||||
cmd *cobra.Command
|
cmd *cobra.Command
|
||||||
@ -83,18 +92,26 @@ authenticating to the Kubernetes API.`,
|
|||||||
protoKubeConfig := createProtoKubeConfig(kubeConfig)
|
protoKubeConfig := createProtoKubeConfig(kubeConfig)
|
||||||
|
|
||||||
// Connect to the core Kubernetes API.
|
// Connect to the core Kubernetes API.
|
||||||
k8s, err := kubernetes.NewForConfig(protoKubeConfig)
|
k8sClient, err := kubernetes.NewForConfig(protoKubeConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("could not initialize Kubernetes client: %w", err)
|
return fmt.Errorf("could not initialize Kubernetes client: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Connect to the Kubernetes aggregation API.
|
// Connect to the Kubernetes aggregation API.
|
||||||
aggregation, err := aggregationv1client.NewForConfig(protoKubeConfig)
|
aggregationClient, err := aggregationv1client.NewForConfig(protoKubeConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("could not initialize Kubernetes client: %w", err)
|
return fmt.Errorf("could not initialize Kubernetes client: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return a.run(ctx, k8s.CoreV1(), aggregation)
|
// Connect to the placeholder API.
|
||||||
|
// I think we can't use protobuf encoding here because we are using CRDs
|
||||||
|
// (for which protobuf encoding is not supported).
|
||||||
|
placeholderClient, err := placeholderclientset.NewForConfig(kubeConfig)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not initialize placeholder client: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return a.run(ctx, k8sClient, aggregationClient, placeholderClient)
|
||||||
},
|
},
|
||||||
Args: cobra.NoArgs,
|
Args: cobra.NoArgs,
|
||||||
}
|
}
|
||||||
@ -143,8 +160,9 @@ func (a *App) Run() error {
|
|||||||
|
|
||||||
func (a *App) run(
|
func (a *App) run(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
k8s corev1client.CoreV1Interface,
|
k8sClient kubernetes.Interface,
|
||||||
aggregation aggregationv1client.Interface,
|
aggregationClient aggregationv1client.Interface,
|
||||||
|
placeholderClient placeholderclientset.Interface,
|
||||||
) error {
|
) error {
|
||||||
cfg, err := config.FromPath(a.configPath)
|
cfg, err := config.FromPath(a.configPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -166,6 +184,7 @@ func (a *App) run(
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("could not read pod metadata: %w", err)
|
return fmt.Errorf("could not read pod metadata: %w", err)
|
||||||
}
|
}
|
||||||
|
serverInstallationNamespace := podinfo.Namespace
|
||||||
|
|
||||||
// TODO use the postStart hook to generate certs?
|
// TODO use the postStart hook to generate certs?
|
||||||
|
|
||||||
@ -177,7 +196,7 @@ func (a *App) run(
|
|||||||
const serviceName = "placeholder-name-api"
|
const serviceName = "placeholder-name-api"
|
||||||
|
|
||||||
cert, err := apiCA.Issue(
|
cert, err := apiCA.Issue(
|
||||||
pkix.Name{CommonName: serviceName + "." + podinfo.Namespace + ".svc"},
|
pkix.Name{CommonName: serviceName + "." + serverInstallationNamespace + ".svc"},
|
||||||
[]string{},
|
[]string{},
|
||||||
24*365*time.Hour,
|
24*365*time.Hour,
|
||||||
)
|
)
|
||||||
@ -213,16 +232,27 @@ func (a *App) run(
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
if err := autoregistration.Setup(ctx, autoregistration.SetupOptions{
|
if err := autoregistration.Setup(ctx, autoregistration.SetupOptions{
|
||||||
CoreV1: k8s,
|
CoreV1: k8sClient.CoreV1(),
|
||||||
AggregationV1: aggregation,
|
AggregationV1: aggregationClient,
|
||||||
Namespace: podinfo.Namespace,
|
Namespace: serverInstallationNamespace,
|
||||||
ServiceTemplate: service,
|
ServiceTemplate: service,
|
||||||
APIServiceTemplate: apiService,
|
APIServiceTemplate: apiService,
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return fmt.Errorf("could not register API service: %w", err)
|
return fmt.Errorf("could not register API service: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
apiServerConfig, err := a.ConfigServer(cert, webhookTokenAuthenticator, clientCA)
|
cmrf := wireControllerManagerRunFunc(
|
||||||
|
serverInstallationNamespace,
|
||||||
|
cfg.DiscoveryConfig.URL,
|
||||||
|
k8sClient,
|
||||||
|
placeholderClient,
|
||||||
|
)
|
||||||
|
apiServerConfig, err := a.configServer(
|
||||||
|
cert,
|
||||||
|
webhookTokenAuthenticator,
|
||||||
|
clientCA,
|
||||||
|
cmrf,
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -235,7 +265,12 @@ func (a *App) run(
|
|||||||
return server.GenericAPIServer.PrepareRun().Run(ctx.Done())
|
return server.GenericAPIServer.PrepareRun().Run(ctx.Done())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) ConfigServer(cert *tls.Certificate, webhookTokenAuthenticator *webhook.WebhookTokenAuthenticator, ca *certauthority.CA) (*apiserver.Config, error) {
|
func (a *App) configServer(
|
||||||
|
cert *tls.Certificate,
|
||||||
|
webhookTokenAuthenticator *webhook.WebhookTokenAuthenticator,
|
||||||
|
ca *certauthority.CA,
|
||||||
|
startControllersPostStartHook func(context.Context),
|
||||||
|
) (*apiserver.Config, error) {
|
||||||
provider, err := createStaticCertKeyProvider(cert)
|
provider, err := createStaticCertKeyProvider(cert)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("could not create static cert key provider: %w", err)
|
return nil, fmt.Errorf("could not create static cert key provider: %w", err)
|
||||||
@ -252,6 +287,7 @@ func (a *App) ConfigServer(cert *tls.Certificate, webhookTokenAuthenticator *web
|
|||||||
ExtraConfig: apiserver.ExtraConfig{
|
ExtraConfig: apiserver.ExtraConfig{
|
||||||
Webhook: webhookTokenAuthenticator,
|
Webhook: webhookTokenAuthenticator,
|
||||||
Issuer: ca,
|
Issuer: ca,
|
||||||
|
StartControllersPostStartHook: startControllersPostStartHook,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
return apiServerConfig, nil
|
return apiServerConfig, nil
|
||||||
@ -290,3 +326,41 @@ func createStaticCertKeyProvider(cert *tls.Certificate) (dynamiccertificates.Cer
|
|||||||
|
|
||||||
return dynamiccertificates.NewStaticCertKeyContent("some-name???", certChainPEM, privateKeyPEM)
|
return dynamiccertificates.NewStaticCertKeyContent("some-name???", certChainPEM, privateKeyPEM)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func wireControllerManagerRunFunc(
|
||||||
|
serverInstallationNamespace string,
|
||||||
|
discoveryURLOverride *string,
|
||||||
|
k8s kubernetes.Interface,
|
||||||
|
placeholder placeholderclientset.Interface,
|
||||||
|
) func(ctx context.Context) {
|
||||||
|
k8sInformers := k8sinformers.NewSharedInformerFactoryWithOptions(
|
||||||
|
k8s,
|
||||||
|
defaultResyncInterval,
|
||||||
|
k8sinformers.WithNamespace(
|
||||||
|
logindiscovery.ClusterInfoNamespace,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
placeholderInformers := placeholderinformers.NewSharedInformerFactoryWithOptions(
|
||||||
|
placeholder,
|
||||||
|
defaultResyncInterval,
|
||||||
|
placeholderinformers.WithNamespace(serverInstallationNamespace),
|
||||||
|
)
|
||||||
|
cm := controller.
|
||||||
|
NewManager().
|
||||||
|
WithController(
|
||||||
|
logindiscovery.NewPublisherController(
|
||||||
|
serverInstallationNamespace,
|
||||||
|
discoveryURLOverride,
|
||||||
|
placeholder,
|
||||||
|
k8sInformers.Core().V1().ConfigMaps(),
|
||||||
|
placeholderInformers.Crds().V1alpha1().LoginDiscoveryConfigs(),
|
||||||
|
controller.WithInformer,
|
||||||
|
),
|
||||||
|
singletonWorker,
|
||||||
|
)
|
||||||
|
return func(ctx context.Context) {
|
||||||
|
k8sInformers.Start(ctx.Done())
|
||||||
|
placeholderInformers.Start(ctx.Done())
|
||||||
|
go cm.Start(ctx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -8,6 +8,7 @@ package api
|
|||||||
// Config contains knobs to setup an instance of placeholder-name.
|
// Config contains knobs to setup an instance of placeholder-name.
|
||||||
type Config struct {
|
type Config struct {
|
||||||
WebhookConfig WebhookConfigSpec `json:"webhook"`
|
WebhookConfig WebhookConfigSpec `json:"webhook"`
|
||||||
|
DiscoveryConfig DiscoveryConfigSpec `json:"discovery"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// WebhookConfig contains configuration knobs specific to placeholder-name's use
|
// WebhookConfig contains configuration knobs specific to placeholder-name's use
|
||||||
@ -21,3 +22,12 @@ type WebhookConfigSpec struct {
|
|||||||
// to validate TLS connections to the WebhookURL.
|
// to validate TLS connections to the WebhookURL.
|
||||||
CABundle []byte `json:"caBundle"`
|
CABundle []byte `json:"caBundle"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DiscoveryConfigSpec contains configuration knobs specific to
|
||||||
|
// placeholder-name's publishing of discovery information. These values can be
|
||||||
|
// viewed as overrides, i.e., if these are set, then placeholder-name will
|
||||||
|
// publish these values in its discovery document instead of the ones it finds.
|
||||||
|
type DiscoveryConfigSpec struct {
|
||||||
|
// URL contains the URL at which placeholder-name can be contacted.
|
||||||
|
URL *string `json:"url,omitempty"`
|
||||||
|
}
|
||||||
|
@ -14,14 +14,50 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestFromPath(t *testing.T) {
|
func TestFromPath(t *testing.T) {
|
||||||
expect := require.New(t)
|
tests := []struct {
|
||||||
|
name string
|
||||||
config, err := FromPath("testdata/happy.yaml")
|
path string
|
||||||
expect.NoError(err)
|
wantConfig *api.Config
|
||||||
expect.Equal(config, &api.Config{
|
}{
|
||||||
|
{
|
||||||
|
name: "Happy",
|
||||||
|
path: "testdata/happy.yaml",
|
||||||
|
wantConfig: &api.Config{
|
||||||
|
DiscoveryConfig: api.DiscoveryConfigSpec{
|
||||||
|
URL: stringPtr("https://some.discovery/url"),
|
||||||
|
},
|
||||||
WebhookConfig: api.WebhookConfigSpec{
|
WebhookConfig: api.WebhookConfigSpec{
|
||||||
URL: "https://tuna.com/fish?marlin",
|
URL: "https://tuna.com/fish?marlin",
|
||||||
CABundle: []byte("-----BEGIN CERTIFICATE-----..."),
|
CABundle: []byte("-----BEGIN CERTIFICATE-----..."),
|
||||||
},
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "NoDiscovery",
|
||||||
|
path: "testdata/no-discovery.yaml",
|
||||||
|
wantConfig: &api.Config{
|
||||||
|
DiscoveryConfig: api.DiscoveryConfigSpec{
|
||||||
|
URL: nil,
|
||||||
|
},
|
||||||
|
WebhookConfig: api.WebhookConfigSpec{
|
||||||
|
URL: "https://tuna.com/fish?marlin",
|
||||||
|
CABundle: []byte("-----BEGIN CERTIFICATE-----..."),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, test := range tests {
|
||||||
|
test := test
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
config, err := FromPath(test.path)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, test.wantConfig, config)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func stringPtr(s string) *string {
|
||||||
|
sPtr := new(string)
|
||||||
|
*sPtr = s
|
||||||
|
return sPtr
|
||||||
|
}
|
||||||
|
2
pkg/config/testdata/happy.yaml
vendored
2
pkg/config/testdata/happy.yaml
vendored
@ -1,4 +1,6 @@
|
|||||||
---
|
---
|
||||||
|
discovery:
|
||||||
|
url: https://some.discovery/url
|
||||||
webhook:
|
webhook:
|
||||||
url: https://tuna.com/fish?marlin
|
url: https://tuna.com/fish?marlin
|
||||||
caBundle: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tLi4u
|
caBundle: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tLi4u
|
||||||
|
5
pkg/config/testdata/no-discovery.yaml
vendored
Normal file
5
pkg/config/testdata/no-discovery.yaml
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
webhook:
|
||||||
|
url: https://tuna.com/fish?marlin
|
||||||
|
caBundle: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tLi4u
|
||||||
|
|
100
test/integration/api_discovery_test.go
Normal file
100
test/integration/api_discovery_test.go
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2020 VMware, Inc.
|
||||||
|
SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package integration
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
|
||||||
|
"github.com/suzerain-io/placeholder-name/test/library"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGetAPIResourceList(t *testing.T) {
|
||||||
|
client := library.NewPlaceholderNameClientset(t)
|
||||||
|
|
||||||
|
groups, resources, err := client.Discovery().ServerGroupsAndResources()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
groupName := "placeholder.suzerain-io.github.io"
|
||||||
|
actualGroup := findGroup(groupName, groups)
|
||||||
|
require.NotNil(t, actualGroup)
|
||||||
|
|
||||||
|
expectedGroup := &metav1.APIGroup{
|
||||||
|
Name: "placeholder.suzerain-io.github.io",
|
||||||
|
Versions: []metav1.GroupVersionForDiscovery{
|
||||||
|
{
|
||||||
|
GroupVersion: "placeholder.suzerain-io.github.io/v1alpha1",
|
||||||
|
Version: "v1alpha1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
PreferredVersion: metav1.GroupVersionForDiscovery{
|
||||||
|
GroupVersion: "placeholder.suzerain-io.github.io/v1alpha1",
|
||||||
|
Version: "v1alpha1",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
require.Equal(t, expectedGroup, actualGroup)
|
||||||
|
|
||||||
|
actualPlaceHolderResources := findResources("placeholder.suzerain-io.github.io/v1alpha1", resources)
|
||||||
|
require.NotNil(t, actualPlaceHolderResources)
|
||||||
|
actualCrdsPlaceHolderResources := findResources("crds.placeholder.suzerain-io.github.io/v1alpha1", resources)
|
||||||
|
require.NotNil(t, actualPlaceHolderResources)
|
||||||
|
|
||||||
|
expectedLoginRequestAPIResource := metav1.APIResource{
|
||||||
|
Name: "loginrequests",
|
||||||
|
Kind: "LoginRequest",
|
||||||
|
Verbs: metav1.Verbs([]string{
|
||||||
|
"create",
|
||||||
|
}),
|
||||||
|
Namespaced: false,
|
||||||
|
|
||||||
|
// This is currently an empty string in the response; maybe it should not be
|
||||||
|
// empty? Seems like no harm in keeping it like this for now, but feel free
|
||||||
|
// to update in the future if there is a compelling reason to do so.
|
||||||
|
SingularName: "",
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedLDCAPIResource := metav1.APIResource{
|
||||||
|
Name: "logindiscoveryconfigs",
|
||||||
|
SingularName: "logindiscoveryconfig",
|
||||||
|
Namespaced: true,
|
||||||
|
Kind: "LoginDiscoveryConfig",
|
||||||
|
Verbs: metav1.Verbs([]string{
|
||||||
|
"delete", "deletecollection", "get", "list", "patch", "create", "update", "watch",
|
||||||
|
}),
|
||||||
|
ShortNames: []string{"ldc"},
|
||||||
|
StorageVersionHash: "unknown: to be filled in automatically below",
|
||||||
|
}
|
||||||
|
|
||||||
|
require.Len(t, actualPlaceHolderResources.APIResources, 1)
|
||||||
|
require.Equal(t, expectedLoginRequestAPIResource, actualPlaceHolderResources.APIResources[0])
|
||||||
|
|
||||||
|
require.Len(t, actualCrdsPlaceHolderResources.APIResources, 1)
|
||||||
|
actualAPIResource := actualCrdsPlaceHolderResources.APIResources[0]
|
||||||
|
// workaround because its hard to predict the storage version hash (e.g. "t/+v41y+3e4=")
|
||||||
|
// so just don't worry about comparing that field
|
||||||
|
expectedLDCAPIResource.StorageVersionHash = actualAPIResource.StorageVersionHash
|
||||||
|
require.Equal(t, expectedLDCAPIResource, actualAPIResource)
|
||||||
|
}
|
||||||
|
|
||||||
|
func findGroup(name string, groups []*metav1.APIGroup) *metav1.APIGroup {
|
||||||
|
for _, group := range groups {
|
||||||
|
if group.Name == name {
|
||||||
|
return group
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func findResources(groupVersion string, resources []*metav1.APIResourceList) *metav1.APIResourceList {
|
||||||
|
for _, resource := range resources {
|
||||||
|
if resource.GroupVersion == groupVersion {
|
||||||
|
return resource
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
@ -7,7 +7,6 @@ package integration
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"os"
|
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
@ -52,8 +51,7 @@ O2D8LtWhMbrYy755Fgq4H9s3vCgfvHY1AQ==
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestClient(t *testing.T) {
|
func TestClient(t *testing.T) {
|
||||||
tmcClusterToken := os.Getenv("PLACEHOLDER_NAME_TMC_CLUSTER_TOKEN")
|
tmcClusterToken := library.Getenv(t, "PLACEHOLDER_NAME_TMC_CLUSTER_TOKEN")
|
||||||
require.NotEmptyf(t, tmcClusterToken, "must specify PLACEHOLDER_NAME_TMC_CLUSTER_TOKEN env var for integration tests")
|
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
@ -7,7 +7,6 @@ package integration
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"os"
|
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -20,11 +19,8 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestGetDeployment(t *testing.T) {
|
func TestGetDeployment(t *testing.T) {
|
||||||
namespaceName := os.Getenv("PLACEHOLDER_NAME_NAMESPACE")
|
namespaceName := library.Getenv(t, "PLACEHOLDER_NAME_NAMESPACE")
|
||||||
require.NotEmptyf(t, namespaceName, "must specify PLACEHOLDER_NAME_NAMESPACE env var for integration tests")
|
deploymentName := library.Getenv(t, "PLACEHOLDER_NAME_DEPLOYMENT")
|
||||||
|
|
||||||
deploymentName := os.Getenv("PLACEHOLDER_NAME_DEPLOYMENT")
|
|
||||||
require.NotEmptyf(t, deploymentName, "must specify PLACEHOLDER_NAME_DEPLOYMENT env var for integration tests")
|
|
||||||
|
|
||||||
client := library.NewClientset(t)
|
client := library.NewClientset(t)
|
||||||
|
|
||||||
|
78
test/integration/logindiscoveryconfig_test.go
Normal file
78
test/integration/logindiscoveryconfig_test.go
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2020 VMware, Inc.
|
||||||
|
SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package integration
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/base64"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/client-go/rest"
|
||||||
|
|
||||||
|
crdsplaceholderv1alpha1 "github.com/suzerain-io/placeholder-name-api/pkg/apis/crdsplaceholder/v1alpha1"
|
||||||
|
"github.com/suzerain-io/placeholder-name/test/library"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSuccessfulLoginDiscoveryConfig(t *testing.T) {
|
||||||
|
namespaceName := library.Getenv(t, "PLACEHOLDER_NAME_NAMESPACE")
|
||||||
|
|
||||||
|
client := library.NewPlaceholderNameClientset(t)
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
config := library.NewClientConfig(t)
|
||||||
|
expectedLDCSpec := expectedLDCSpec(config)
|
||||||
|
configList, err := client.
|
||||||
|
CrdsV1alpha1().
|
||||||
|
LoginDiscoveryConfigs(namespaceName).
|
||||||
|
List(ctx, metav1.ListOptions{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, configList.Items, 1)
|
||||||
|
require.Equal(t, expectedLDCSpec, &configList.Items[0].Spec)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReconcilingLoginDiscoveryConfig(t *testing.T) {
|
||||||
|
namespaceName := library.Getenv(t, "PLACEHOLDER_NAME_NAMESPACE")
|
||||||
|
|
||||||
|
client := library.NewPlaceholderNameClientset(t)
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
err := client.
|
||||||
|
CrdsV1alpha1().
|
||||||
|
LoginDiscoveryConfigs(namespaceName).
|
||||||
|
Delete(ctx, "placeholder-name-config", metav1.DeleteOptions{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
config := library.NewClientConfig(t)
|
||||||
|
expectedLDCSpec := expectedLDCSpec(config)
|
||||||
|
|
||||||
|
var actualLDC *crdsplaceholderv1alpha1.LoginDiscoveryConfig
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
actualLDC, err = client.
|
||||||
|
CrdsV1alpha1().
|
||||||
|
LoginDiscoveryConfigs(namespaceName).
|
||||||
|
Get(ctx, "placeholder-name-config", metav1.GetOptions{})
|
||||||
|
if err == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
time.Sleep(time.Millisecond * 750)
|
||||||
|
}
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, expectedLDCSpec, &actualLDC.Spec)
|
||||||
|
}
|
||||||
|
|
||||||
|
func expectedLDCSpec(config *rest.Config) *crdsplaceholderv1alpha1.LoginDiscoveryConfigSpec {
|
||||||
|
return &crdsplaceholderv1alpha1.LoginDiscoveryConfigSpec{
|
||||||
|
Server: config.Host,
|
||||||
|
CertificateAuthorityData: base64.StdEncoding.EncodeToString(config.TLSClientConfig.CAData),
|
||||||
|
}
|
||||||
|
}
|
@ -8,7 +8,6 @@ package integration
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -58,8 +57,7 @@ func addTestClusterRoleBinding(ctx context.Context, t *testing.T, adminClient ku
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestSuccessfulLoginRequest(t *testing.T) {
|
func TestSuccessfulLoginRequest(t *testing.T) {
|
||||||
tmcClusterToken := os.Getenv("PLACEHOLDER_NAME_TMC_CLUSTER_TOKEN")
|
tmcClusterToken := library.Getenv(t, "PLACEHOLDER_NAME_TMC_CLUSTER_TOKEN")
|
||||||
require.NotEmptyf(t, tmcClusterToken, "must specify PLACEHOLDER_NAME_TMC_CLUSTER_TOKEN env var for integration tests")
|
|
||||||
|
|
||||||
response, err := makeRequest(t, v1alpha1.LoginRequestSpec{
|
response, err := makeRequest(t, v1alpha1.LoginRequestSpec{
|
||||||
Type: v1alpha1.TokenLoginCredentialType,
|
Type: v1alpha1.TokenLoginCredentialType,
|
||||||
@ -180,78 +178,3 @@ func TestLoginRequest_ShouldFailWhenRequestDoesNotIncludeToken(t *testing.T) {
|
|||||||
require.Empty(t, response.Spec)
|
require.Empty(t, response.Spec)
|
||||||
require.Nil(t, response.Status.Credential)
|
require.Nil(t, response.Status.Credential)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetAPIResourceList(t *testing.T) {
|
|
||||||
client := library.NewPlaceholderNameClientset(t)
|
|
||||||
|
|
||||||
groups, resources, err := client.Discovery().ServerGroupsAndResources()
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
groupName := "placeholder.suzerain-io.github.io"
|
|
||||||
actualGroup := findGroup(groupName, groups)
|
|
||||||
require.NotNil(t, actualGroup)
|
|
||||||
|
|
||||||
expectedGroup := &metav1.APIGroup{
|
|
||||||
Name: "placeholder.suzerain-io.github.io",
|
|
||||||
Versions: []metav1.GroupVersionForDiscovery{
|
|
||||||
{
|
|
||||||
GroupVersion: "placeholder.suzerain-io.github.io/v1alpha1",
|
|
||||||
Version: "v1alpha1",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
PreferredVersion: metav1.GroupVersionForDiscovery{
|
|
||||||
GroupVersion: "placeholder.suzerain-io.github.io/v1alpha1",
|
|
||||||
Version: "v1alpha1",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
require.Equal(t, expectedGroup, actualGroup)
|
|
||||||
|
|
||||||
resourceGroupVersion := "placeholder.suzerain-io.github.io/v1alpha1"
|
|
||||||
actualResources := findResources(resourceGroupVersion, resources)
|
|
||||||
require.NotNil(t, actualResources)
|
|
||||||
|
|
||||||
expectedResources := &metav1.APIResourceList{
|
|
||||||
TypeMeta: metav1.TypeMeta{
|
|
||||||
Kind: "APIResourceList",
|
|
||||||
APIVersion: "v1",
|
|
||||||
},
|
|
||||||
GroupVersion: "placeholder.suzerain-io.github.io/v1alpha1",
|
|
||||||
APIResources: []metav1.APIResource{
|
|
||||||
{
|
|
||||||
Name: "loginrequests",
|
|
||||||
Kind: "LoginRequest",
|
|
||||||
SingularName: "", // TODO(akeesler): what should this be?
|
|
||||||
Verbs: metav1.Verbs([]string{
|
|
||||||
"create",
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
require.Equal(t, expectedResources, actualResources)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetAPIVersion(t *testing.T) {
|
|
||||||
client := library.NewPlaceholderNameClientset(t)
|
|
||||||
|
|
||||||
version, err := client.Discovery().ServerVersion()
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.NotNil(t, version) // TODO(akeesler): what can we assert here?
|
|
||||||
}
|
|
||||||
|
|
||||||
func findGroup(name string, groups []*metav1.APIGroup) *metav1.APIGroup {
|
|
||||||
for _, group := range groups {
|
|
||||||
if group.Name == name {
|
|
||||||
return group
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func findResources(groupVersion string, resources []*metav1.APIResourceList) *metav1.APIResourceList {
|
|
||||||
for _, resource := range resources {
|
|
||||||
if resource.GroupVersion == groupVersion {
|
|
||||||
return resource
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
22
test/library/env.go
Normal file
22
test/library/env.go
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2020 VMware, Inc.
|
||||||
|
SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package library
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Getenv gets the environment variable with key and asserts that it is not
|
||||||
|
// empty. It returns the value of the environment variable.
|
||||||
|
func Getenv(t *testing.T, key string) string {
|
||||||
|
t.Helper()
|
||||||
|
value := os.Getenv(key)
|
||||||
|
require.NotEmptyf(t, value, "must specify %s env var for integration tests", key)
|
||||||
|
return value
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user