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)
|
||||
r.EqualError(err, "failed to login: environment variable not set: PLACEHOLDER_NAME_K8S_API_ENDPOINT")
|
||||
})
|
||||
}, spec.Parallel())
|
||||
})
|
||||
|
||||
when("the token exchange fails", func() {
|
||||
it.Before(func() {
|
||||
@ -76,7 +76,7 @@ func TestRun(t *testing.T) {
|
||||
err := run(envGetter, tokenExchanger, buffer, 30*time.Second)
|
||||
r.EqualError(err, "failed to login: some error")
|
||||
})
|
||||
}, spec.Parallel())
|
||||
})
|
||||
|
||||
when("the JSON encoder fails", 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)
|
||||
r.EqualError(err, "failed to marshal response to stdout: some IO error")
|
||||
})
|
||||
}, spec.Parallel())
|
||||
})
|
||||
|
||||
when("the token exchange times out", func() {
|
||||
it.Before(func() {
|
||||
@ -107,7 +107,7 @@ func TestRun(t *testing.T) {
|
||||
err := run(envGetter, tokenExchanger, buffer, 1*time.Millisecond)
|
||||
r.EqualError(err, "failed to login: context deadline exceeded")
|
||||
})
|
||||
}, spec.Parallel())
|
||||
})
|
||||
|
||||
when("the token exchange succeeds", func() {
|
||||
var actualToken, actualCaBundle, actualAPIEndpoint string
|
||||
@ -144,6 +144,6 @@ func TestRun(t *testing.T) {
|
||||
}`
|
||||
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:
|
||||
#@yaml/text-templated-strings
|
||||
placeholder-name.yaml: |
|
||||
discovery:
|
||||
url: (@= data.values.discovery_url or "null" @)
|
||||
webhook:
|
||||
url: (@= data.values.webhook_url @)
|
||||
caBundle: (@= data.values.webhook_ca_bundle @)
|
||||
|
@ -38,6 +38,9 @@ rules:
|
||||
- apiGroups: [""]
|
||||
resources: [services]
|
||||
verbs: [create, get, list, patch, update, watch]
|
||||
- apiGroups: [crds.placeholder.suzerain-io.github.io]
|
||||
resources: [logindiscoveryconfigs]
|
||||
verbs: [create, get, list, update, watch]
|
||||
---
|
||||
kind: RoleBinding
|
||||
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
|
||||
name: extension-apiserver-authentication-reader
|
||||
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/spf13/cobra v1.0.0
|
||||
github.com/stretchr/testify v1.6.1
|
||||
github.com/suzerain-io/placeholder-name-api v0.0.0-20200731022217-d7e4c306f7fd
|
||||
github.com/suzerain-io/placeholder-name-client-go v0.0.0-20200731022627-a9c34c8413ac
|
||||
github.com/suzerain-io/controller-go v0.0.0-20200730212956-7f99b569ca9f
|
||||
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
|
||||
k8s.io/api 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/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-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM=
|
||||
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-20190717042225-c3de453c63f4 h1:Hs82Z41s6SdL1CELW+XaDYmOH4hkBN4/N9og/AsOv7E=
|
||||
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/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/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
|
||||
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/placeholder-name-api v0.0.0-20200731022217-d7e4c306f7fd/go.mod h1:OuYBJDpMMnvMUoBn+XeMWtHghuYk0cq9bNkNa3T8j/g=
|
||||
github.com/suzerain-io/placeholder-name-client-go v0.0.0-20200731022627-a9c34c8413ac h1:3QfNymuRno/01062Ns/xv70m8TxQjiJQKTYCjIztGJw=
|
||||
github.com/suzerain-io/placeholder-name-client-go v0.0.0-20200731022627-a9c34c8413ac/go.mod h1:EFFPGm0xrUidCxQkF1g2pGdKifwo4U0Dwi70TTluNGM=
|
||||
github.com/suzerain-io/controller-go v0.0.0-20200730212956-7f99b569ca9f h1:gZ6rAdl+VE9DT0yE52xY/kJZ/hOJYxwtsgGoPr5vItI=
|
||||
github.com/suzerain-io/controller-go v0.0.0-20200730212956-7f99b569ca9f/go.mod h1:+v9upryFWBJac6KXKlheGHr7e3kqpk1ldH1iIMFopMs=
|
||||
github.com/suzerain-io/placeholder-name-api v0.0.0-20200731224558-ff85679d3364 h1:5NaQExCSh8+6YP3QNhWsWyMrEd+7zsnrznRlisydlZo=
|
||||
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/go.mod h1:yHp0ai0Z9gUljN3o0xMhYJnH/IcvkdTBOX2fmJ93JEM=
|
||||
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-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-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/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=
|
||||
@ -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.24.0 h1:UhZDfRO8JRQru4/+LlLE0BRKGF8L+PICnvYZmx/fEGA=
|
||||
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/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=
|
||||
|
@ -6,6 +6,7 @@ SPDX-License-Identifier: Apache-2.0
|
||||
package apiserver
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
@ -58,6 +59,7 @@ type Config struct {
|
||||
type ExtraConfig struct {
|
||||
Webhook authenticator.Token
|
||||
Issuer loginrequest.CertIssuer
|
||||
StartControllersPostStartHook func(ctx context.Context)
|
||||
}
|
||||
|
||||
type PlaceHolderServer struct {
|
||||
@ -122,9 +124,17 @@ func (c completedConfig) New() (*PlaceHolderServer, error) {
|
||||
return nil, fmt.Errorf("install API group error: %w", err)
|
||||
}
|
||||
|
||||
s.GenericAPIServer.AddPostStartHookOrDie("place-holder-post-start-hook",
|
||||
func(context genericapiserver.PostStartHookContext) error {
|
||||
klog.InfoS("post start hook", "foo", "bar")
|
||||
s.GenericAPIServer.AddPostStartHookOrDie("start-controllers",
|
||||
func(postStartContext genericapiserver.PostStartHookContext) error {
|
||||
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
|
||||
},
|
||||
)
|
||||
|
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"
|
||||
genericoptions "k8s.io/apiserver/pkg/server/options"
|
||||
"k8s.io/apiserver/plugin/pkg/authenticator/token/webhook"
|
||||
k8sinformers "k8s.io/client-go/informers"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
corev1client "k8s.io/client-go/kubernetes/typed/core/v1"
|
||||
restclient "k8s.io/client-go/rest"
|
||||
apiregistrationv1 "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1"
|
||||
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"
|
||||
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/autoregistration"
|
||||
"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/pkg/config"
|
||||
)
|
||||
|
||||
const (
|
||||
singletonWorker = 1
|
||||
defaultResyncInterval = 3 * time.Minute
|
||||
)
|
||||
|
||||
// App is an object that represents the placeholder-name-server application.
|
||||
type App struct {
|
||||
cmd *cobra.Command
|
||||
@ -83,18 +92,26 @@ authenticating to the Kubernetes API.`,
|
||||
protoKubeConfig := createProtoKubeConfig(kubeConfig)
|
||||
|
||||
// Connect to the core Kubernetes API.
|
||||
k8s, err := kubernetes.NewForConfig(protoKubeConfig)
|
||||
k8sClient, err := kubernetes.NewForConfig(protoKubeConfig)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not initialize Kubernetes client: %w", err)
|
||||
}
|
||||
|
||||
// Connect to the Kubernetes aggregation API.
|
||||
aggregation, err := aggregationv1client.NewForConfig(protoKubeConfig)
|
||||
aggregationClient, err := aggregationv1client.NewForConfig(protoKubeConfig)
|
||||
if err != nil {
|
||||
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,
|
||||
}
|
||||
@ -143,8 +160,9 @@ func (a *App) Run() error {
|
||||
|
||||
func (a *App) run(
|
||||
ctx context.Context,
|
||||
k8s corev1client.CoreV1Interface,
|
||||
aggregation aggregationv1client.Interface,
|
||||
k8sClient kubernetes.Interface,
|
||||
aggregationClient aggregationv1client.Interface,
|
||||
placeholderClient placeholderclientset.Interface,
|
||||
) error {
|
||||
cfg, err := config.FromPath(a.configPath)
|
||||
if err != nil {
|
||||
@ -166,6 +184,7 @@ func (a *App) run(
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not read pod metadata: %w", err)
|
||||
}
|
||||
serverInstallationNamespace := podinfo.Namespace
|
||||
|
||||
// TODO use the postStart hook to generate certs?
|
||||
|
||||
@ -177,7 +196,7 @@ func (a *App) run(
|
||||
const serviceName = "placeholder-name-api"
|
||||
|
||||
cert, err := apiCA.Issue(
|
||||
pkix.Name{CommonName: serviceName + "." + podinfo.Namespace + ".svc"},
|
||||
pkix.Name{CommonName: serviceName + "." + serverInstallationNamespace + ".svc"},
|
||||
[]string{},
|
||||
24*365*time.Hour,
|
||||
)
|
||||
@ -213,16 +232,27 @@ func (a *App) run(
|
||||
},
|
||||
}
|
||||
if err := autoregistration.Setup(ctx, autoregistration.SetupOptions{
|
||||
CoreV1: k8s,
|
||||
AggregationV1: aggregation,
|
||||
Namespace: podinfo.Namespace,
|
||||
CoreV1: k8sClient.CoreV1(),
|
||||
AggregationV1: aggregationClient,
|
||||
Namespace: serverInstallationNamespace,
|
||||
ServiceTemplate: service,
|
||||
APIServiceTemplate: apiService,
|
||||
}); err != nil {
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
@ -235,7 +265,12 @@ func (a *App) run(
|
||||
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)
|
||||
if err != nil {
|
||||
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{
|
||||
Webhook: webhookTokenAuthenticator,
|
||||
Issuer: ca,
|
||||
StartControllersPostStartHook: startControllersPostStartHook,
|
||||
},
|
||||
}
|
||||
return apiServerConfig, nil
|
||||
@ -290,3 +326,41 @@ func createStaticCertKeyProvider(cert *tls.Certificate) (dynamiccertificates.Cer
|
||||
|
||||
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.
|
||||
type Config struct {
|
||||
WebhookConfig WebhookConfigSpec `json:"webhook"`
|
||||
DiscoveryConfig DiscoveryConfigSpec `json:"discovery"`
|
||||
}
|
||||
|
||||
// WebhookConfig contains configuration knobs specific to placeholder-name's use
|
||||
@ -21,3 +22,12 @@ type WebhookConfigSpec struct {
|
||||
// to validate TLS connections to the WebhookURL.
|
||||
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) {
|
||||
expect := require.New(t)
|
||||
|
||||
config, err := FromPath("testdata/happy.yaml")
|
||||
expect.NoError(err)
|
||||
expect.Equal(config, &api.Config{
|
||||
tests := []struct {
|
||||
name string
|
||||
path string
|
||||
wantConfig *api.Config
|
||||
}{
|
||||
{
|
||||
name: "Happy",
|
||||
path: "testdata/happy.yaml",
|
||||
wantConfig: &api.Config{
|
||||
DiscoveryConfig: api.DiscoveryConfigSpec{
|
||||
URL: stringPtr("https://some.discovery/url"),
|
||||
},
|
||||
WebhookConfig: api.WebhookConfigSpec{
|
||||
URL: "https://tuna.com/fish?marlin",
|
||||
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:
|
||||
url: https://tuna.com/fish?marlin
|
||||
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 (
|
||||
"context"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
@ -52,8 +51,7 @@ O2D8LtWhMbrYy755Fgq4H9s3vCgfvHY1AQ==
|
||||
)
|
||||
|
||||
func TestClient(t *testing.T) {
|
||||
tmcClusterToken := os.Getenv("PLACEHOLDER_NAME_TMC_CLUSTER_TOKEN")
|
||||
require.NotEmptyf(t, tmcClusterToken, "must specify PLACEHOLDER_NAME_TMC_CLUSTER_TOKEN env var for integration tests")
|
||||
tmcClusterToken := library.Getenv(t, "PLACEHOLDER_NAME_TMC_CLUSTER_TOKEN")
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
@ -7,7 +7,6 @@ package integration
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@ -20,11 +19,8 @@ import (
|
||||
)
|
||||
|
||||
func TestGetDeployment(t *testing.T) {
|
||||
namespaceName := os.Getenv("PLACEHOLDER_NAME_NAMESPACE")
|
||||
require.NotEmptyf(t, namespaceName, "must specify PLACEHOLDER_NAME_NAMESPACE env var for integration tests")
|
||||
|
||||
deploymentName := os.Getenv("PLACEHOLDER_NAME_DEPLOYMENT")
|
||||
require.NotEmptyf(t, deploymentName, "must specify PLACEHOLDER_NAME_DEPLOYMENT env var for integration tests")
|
||||
namespaceName := library.Getenv(t, "PLACEHOLDER_NAME_NAMESPACE")
|
||||
deploymentName := library.Getenv(t, "PLACEHOLDER_NAME_DEPLOYMENT")
|
||||
|
||||
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 (
|
||||
"context"
|
||||
"net/http"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@ -58,8 +57,7 @@ func addTestClusterRoleBinding(ctx context.Context, t *testing.T, adminClient ku
|
||||
}
|
||||
|
||||
func TestSuccessfulLoginRequest(t *testing.T) {
|
||||
tmcClusterToken := os.Getenv("PLACEHOLDER_NAME_TMC_CLUSTER_TOKEN")
|
||||
require.NotEmptyf(t, tmcClusterToken, "must specify PLACEHOLDER_NAME_TMC_CLUSTER_TOKEN env var for integration tests")
|
||||
tmcClusterToken := library.Getenv(t, "PLACEHOLDER_NAME_TMC_CLUSTER_TOKEN")
|
||||
|
||||
response, err := makeRequest(t, v1alpha1.LoginRequestSpec{
|
||||
Type: v1alpha1.TokenLoginCredentialType,
|
||||
@ -180,78 +178,3 @@ func TestLoginRequest_ShouldFailWhenRequestDoesNotIncludeToken(t *testing.T) {
|
||||
require.Empty(t, response.Spec)
|
||||
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