Merge pull request #12 from jakkab/scaffold-controller
First controller version
This commit is contained in:
commit
0cecfbbe41
@ -1,9 +1,5 @@
|
|||||||
version: 2
|
version: 2
|
||||||
jobs:
|
jobs:
|
||||||
aloha:
|
|
||||||
machine: true
|
|
||||||
steps:
|
|
||||||
- run: echo "Aloha! ;-)"
|
|
||||||
build:
|
build:
|
||||||
docker:
|
docker:
|
||||||
- image: circleci/golang:1.12
|
- image: circleci/golang:1.12
|
||||||
@ -145,36 +141,31 @@ workflows:
|
|||||||
version: 2
|
version: 2
|
||||||
"test, build and release":
|
"test, build and release":
|
||||||
jobs:
|
jobs:
|
||||||
- aloha:
|
- build:
|
||||||
filters:
|
filters:
|
||||||
tags:
|
tags:
|
||||||
only: /.*/
|
only: /.*/
|
||||||
# ENABLE IT once controller with make target will be created
|
- test-integration:
|
||||||
# - build:
|
filters:
|
||||||
# filters:
|
tags:
|
||||||
# tags:
|
only: /.*/
|
||||||
# only: /.*/
|
- test:
|
||||||
# - test-integration:
|
filters:
|
||||||
# filters:
|
tags:
|
||||||
# tags:
|
only: /.*/
|
||||||
# only: /.*/
|
- release:
|
||||||
# - test:
|
requires:
|
||||||
# filters:
|
- test
|
||||||
# tags:
|
filters:
|
||||||
# only: /.*/
|
tags:
|
||||||
# - release:
|
only: /.*/
|
||||||
# requires:
|
branches:
|
||||||
# - test
|
ignore: /.*/
|
||||||
# filters:
|
- release-changelog:
|
||||||
# tags:
|
requires:
|
||||||
# only: /.*/
|
- release
|
||||||
# branches:
|
filters:
|
||||||
# ignore: /.*/
|
tags:
|
||||||
# - release-changelog:
|
only: /.*/
|
||||||
# requires:
|
branches:
|
||||||
# - release
|
ignore: /.*/
|
||||||
# filters:
|
|
||||||
# tags:
|
|
||||||
# only: /.*/
|
|
||||||
# branches:
|
|
||||||
# ignore: /.*/
|
|
||||||
|
26
.gitignore
vendored
Normal file
26
.gitignore
vendored
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
|
||||||
|
# Binaries for programs and plugins
|
||||||
|
*.exe
|
||||||
|
*.exe~
|
||||||
|
*.dll
|
||||||
|
*.so
|
||||||
|
*.dylib
|
||||||
|
bin
|
||||||
|
|
||||||
|
# Test binary, build with `go test -c`
|
||||||
|
*.test
|
||||||
|
|
||||||
|
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||||
|
*.out
|
||||||
|
|
||||||
|
# Kubernetes Generated files - skip generated files, except for vendored files
|
||||||
|
|
||||||
|
!vendor/**/zz_generated.*
|
||||||
|
|
||||||
|
# editor and IDE paraphernalia
|
||||||
|
.idea
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
*~
|
||||||
|
|
||||||
|
config/default/manager_image_patch.yaml-e
|
26
Dockerfile
Normal file
26
Dockerfile
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
# Build the manager binary
|
||||||
|
FROM golang:1.12.5 as builder
|
||||||
|
|
||||||
|
WORKDIR /workspace
|
||||||
|
# Copy the Go Modules manifests
|
||||||
|
COPY go.mod go.mod
|
||||||
|
COPY go.sum go.sum
|
||||||
|
# cache deps before building and copying source so that we don't need to re-download as much
|
||||||
|
# and so that source changes don't invalidate our downloaded layer
|
||||||
|
RUN go mod download
|
||||||
|
|
||||||
|
# Copy the go source
|
||||||
|
COPY main.go main.go
|
||||||
|
COPY api/ api/
|
||||||
|
COPY controllers/ controllers/
|
||||||
|
COPY hydra/ hydra/
|
||||||
|
|
||||||
|
# Build
|
||||||
|
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GO111MODULE=on go build -a -o manager main.go
|
||||||
|
|
||||||
|
# Use distroless as minimal base image to package the manager binary
|
||||||
|
# Refer to https://github.com/GoogleContainerTools/distroless for more details
|
||||||
|
FROM gcr.io/distroless/static:latest
|
||||||
|
WORKDIR /
|
||||||
|
COPY --from=builder /workspace/manager .
|
||||||
|
ENTRYPOINT ["/manager"]
|
69
Makefile
Normal file
69
Makefile
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
|
||||||
|
# Image URL to use all building/pushing image targets
|
||||||
|
IMG ?= controller:latest
|
||||||
|
# Produce CRDs that work back to Kubernetes 1.11 (no version conversion)
|
||||||
|
CRD_OPTIONS ?= "crd:trivialVersions=true"
|
||||||
|
|
||||||
|
all: manager
|
||||||
|
|
||||||
|
# Run tests
|
||||||
|
test: generate fmt vet manifests
|
||||||
|
go test ./api/... ./controllers/... ./hydra... -coverprofile cover.out
|
||||||
|
|
||||||
|
# Run integration tests on local KIND cluster
|
||||||
|
# TODO: modify once integration tests have been implemented
|
||||||
|
test-integration:
|
||||||
|
ginkgo -v ./controllers/...
|
||||||
|
|
||||||
|
# Build manager binary
|
||||||
|
manager: generate fmt vet
|
||||||
|
go build -o bin/manager main.go
|
||||||
|
|
||||||
|
# Run against the configured Kubernetes cluster in ~/.kube/config
|
||||||
|
run: generate fmt vet
|
||||||
|
go run ./main.go --hydra-url ${HYDRA_URL}
|
||||||
|
|
||||||
|
# Install CRDs into a cluster
|
||||||
|
install: manifests
|
||||||
|
kubectl apply -f config/crd/bases
|
||||||
|
|
||||||
|
# Deploy controller in the configured Kubernetes cluster in ~/.kube/config
|
||||||
|
deploy: manifests
|
||||||
|
kubectl apply -f config/crd/bases
|
||||||
|
kustomize build config/default | kubectl apply -f -
|
||||||
|
|
||||||
|
# Generate manifests e.g. CRD, RBAC etc.
|
||||||
|
manifests: controller-gen
|
||||||
|
$(CONTROLLER_GEN) $(CRD_OPTIONS) rbac:roleName=manager-role webhook paths="./..." output:crd:artifacts:config=config/crd/bases
|
||||||
|
|
||||||
|
# Run go fmt against code
|
||||||
|
fmt:
|
||||||
|
go fmt ./...
|
||||||
|
|
||||||
|
# Run go vet against code
|
||||||
|
vet:
|
||||||
|
go vet ./...
|
||||||
|
|
||||||
|
# Generate code
|
||||||
|
generate: controller-gen
|
||||||
|
$(CONTROLLER_GEN) object:headerFile=./hack/boilerplate.go.txt paths=./api/...
|
||||||
|
|
||||||
|
# Build the docker image
|
||||||
|
docker-build: test
|
||||||
|
docker build . -t ${IMG}
|
||||||
|
@echo "updating kustomize image patch file for manager resource"
|
||||||
|
sed -i'' -e 's@image: .*@image: '"${IMG}"'@' ./config/default/manager_image_patch.yaml
|
||||||
|
|
||||||
|
# Push the docker image
|
||||||
|
docker-push:
|
||||||
|
docker push ${IMG}
|
||||||
|
|
||||||
|
# find or download controller-gen
|
||||||
|
# download controller-gen if necessary
|
||||||
|
controller-gen:
|
||||||
|
ifeq (, $(shell which controller-gen))
|
||||||
|
go get sigs.k8s.io/controller-tools/cmd/controller-gen@v0.2.0-beta.2
|
||||||
|
CONTROLLER_GEN=$(shell go env GOPATH)/bin/controller-gen
|
||||||
|
else
|
||||||
|
CONTROLLER_GEN=$(shell which controller-gen)
|
||||||
|
endif
|
7
PROJECT
Normal file
7
PROJECT
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
version: "2"
|
||||||
|
domain: ory.sh
|
||||||
|
repo: github.com/ory/hydra-maester
|
||||||
|
resources:
|
||||||
|
- group: hydra
|
||||||
|
version: v1alpha1
|
||||||
|
kind: OAuth2Client
|
29
README.md
29
README.md
@ -1,10 +1,31 @@
|
|||||||
# hydra-maester
|
# Hydra-maester
|
||||||
|
|
||||||
|
|
||||||
This project contains a Kubernetes controller that uses Custom Resources to manage Hydra Oauth2 clients.
|
This project contains a Kubernetes controller that uses Custom Resources to manage Hydra Oauth2 clients.
|
||||||
The project is based on [Kubebuilder](https://github.com/kubernetes-sigs/kubebuilder)
|
ORY Hydra Maester watches for instances of `oauth2clients.oathkeeper.ory.sh/v1alpha1` and creates, updates, or deletes corresponding OAuth2 clients by communicating with ORY Hydra API.
|
||||||
|
|
||||||
|
The project is based on [Kubebuilder](https://github.com/kubernetes-sigs/kubebuilder).
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
- recent version of Go language with support for modules (e.g: 1.12.6)
|
||||||
|
- make
|
||||||
|
- kubectl
|
||||||
|
- kustomize
|
||||||
|
- [ginkgo](https://onsi.github.io/ginkgo/) for local integration testing
|
||||||
|
- access to K8s environment: minikube or a remote K8s cluster
|
||||||
|
|
||||||
|
|
||||||
# Design
|
|
||||||
|
|
||||||
Take a look at [Design Readme](./docs/README.md)
|
## Design
|
||||||
|
|
||||||
|
Take a look at [Design Readme](./docs/README.md).
|
||||||
|
|
||||||
|
## How to use it
|
||||||
|
|
||||||
|
- `make test` to run tests
|
||||||
|
- `make test-integration` to run integration tests
|
||||||
|
- `make install` to generate CRD file from go sources and install it on the cluster
|
||||||
|
- `export HYDRA_URL={HYDRA_SERVICE_URL} && make run` to run the controller
|
||||||
|
|
||||||
|
To deploy the controller, edit the value of the ```--hydra-url``` argument in the [manager.yaml](config/manager/manager.yaml) file and run ```make deploy```.
|
35
api/v1alpha1/groupversion_info.go
Normal file
35
api/v1alpha1/groupversion_info.go
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
/*
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Package v1alpha1 contains API Schema definitions for the hydra v1alpha1 API group
|
||||||
|
// +kubebuilder:object:generate=true
|
||||||
|
// +groupName=hydra.ory.sh
|
||||||
|
package v1alpha1
|
||||||
|
|
||||||
|
import (
|
||||||
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
"sigs.k8s.io/controller-runtime/pkg/scheme"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// GroupVersion is group version used to register these objects
|
||||||
|
GroupVersion = schema.GroupVersion{Group: "hydra.ory.sh", Version: "v1alpha1"}
|
||||||
|
|
||||||
|
// SchemeBuilder is used to add go types to the GroupVersionKind scheme
|
||||||
|
SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion}
|
||||||
|
|
||||||
|
// AddToScheme adds the types in this group-version to the given scheme.
|
||||||
|
AddToScheme = SchemeBuilder.AddToScheme
|
||||||
|
)
|
114
api/v1alpha1/oauth2client_types.go
Normal file
114
api/v1alpha1/oauth2client_types.go
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
/*
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package v1alpha1
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/ory/hydra-maester/hydra"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
// OAuth2ClientSpec defines the desired state of OAuth2Client
|
||||||
|
type OAuth2ClientSpec struct {
|
||||||
|
// +kubebuilder:validation:MaxItems=4
|
||||||
|
// +kubebuilder:validation:MinItems=1
|
||||||
|
//
|
||||||
|
// GrantTypes is an array of grant types the client is allowed to use.
|
||||||
|
GrantTypes []GrantType `json:"grantTypes"`
|
||||||
|
|
||||||
|
// +kubebuilder:validation:MaxItems=3
|
||||||
|
// +kubebuilder:validation:MinItems=1
|
||||||
|
//
|
||||||
|
// ResponseTypes is an array of the OAuth 2.0 response type strings that the client can
|
||||||
|
// use at the authorization endpoint.
|
||||||
|
ResponseTypes []ResponseType `json:"responseTypes,omitempty"`
|
||||||
|
|
||||||
|
// +kubebuilder:validation:Pattern=([a-zA-Z0-9\.\*]+\s?)+
|
||||||
|
//
|
||||||
|
// Scope is a string containing a space-separated list of scope values (as
|
||||||
|
// described in Section 3.3 of OAuth 2.0 [RFC6749]) that the client
|
||||||
|
// can use when requesting access tokens.
|
||||||
|
Scope string `json:"scope"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// +kubebuilder:validation:Enum=client_credentials;authorization_code;implicit;refresh_token
|
||||||
|
// GrantType represents an OAuth 2.0 grant type
|
||||||
|
type GrantType string
|
||||||
|
|
||||||
|
// +kubebuilder:validation:Enum=id_token;code;token
|
||||||
|
// ResponseType represents an OAuth 2.0 response type strings
|
||||||
|
type ResponseType string
|
||||||
|
|
||||||
|
// OAuth2ClientStatus defines the observed state of OAuth2Client
|
||||||
|
type OAuth2ClientStatus struct {
|
||||||
|
// Secret points to the K8s secret that contains this client's id and password
|
||||||
|
Secret *string `json:"secret,omitempty"`
|
||||||
|
// ClientID is the id for this client.
|
||||||
|
ClientID *string `json:"clientID,omitempty"`
|
||||||
|
// ObservedGeneration represents the most recent generation observed by the daemon set controller.
|
||||||
|
ObservedGeneration int64 `json:"observedGeneration,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// +kubebuilder:object:root=true
|
||||||
|
// +kubebuilder:subresource:status
|
||||||
|
|
||||||
|
// OAuth2Client is the Schema for the oauth2clients API
|
||||||
|
type OAuth2Client struct {
|
||||||
|
metav1.TypeMeta `json:",inline"`
|
||||||
|
metav1.ObjectMeta `json:"metadata,omitempty"`
|
||||||
|
|
||||||
|
Spec OAuth2ClientSpec `json:"spec,omitempty"`
|
||||||
|
Status OAuth2ClientStatus `json:"status,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// +kubebuilder:object:root=true
|
||||||
|
|
||||||
|
// OAuth2ClientList contains a list of OAuth2Client
|
||||||
|
type OAuth2ClientList struct {
|
||||||
|
metav1.TypeMeta `json:",inline"`
|
||||||
|
metav1.ListMeta `json:"metadata,omitempty"`
|
||||||
|
Items []OAuth2Client `json:"items"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
SchemeBuilder.Register(&OAuth2Client{}, &OAuth2ClientList{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToOAuth2ClientJSON converts an OAuth2Client into a OAuth2ClientJSON object that represents an OAuth2 client digestible by ORY Hydra
|
||||||
|
func (c *OAuth2Client) ToOAuth2ClientJSON() *hydra.OAuth2ClientJSON {
|
||||||
|
return &hydra.OAuth2ClientJSON{
|
||||||
|
Name: c.Name,
|
||||||
|
ClientID: c.Status.ClientID,
|
||||||
|
GrantTypes: grantToStringSlice(c.Spec.GrantTypes),
|
||||||
|
ResponseTypes: responseToStringSlice(c.Spec.ResponseTypes),
|
||||||
|
Scope: c.Spec.Scope,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func responseToStringSlice(rt []ResponseType) []string {
|
||||||
|
var output = make([]string, len(rt))
|
||||||
|
for i, elem := range rt {
|
||||||
|
output[i] = string(elem)
|
||||||
|
}
|
||||||
|
return output
|
||||||
|
}
|
||||||
|
|
||||||
|
func grantToStringSlice(gt []GrantType) []string {
|
||||||
|
var output = make([]string, len(gt))
|
||||||
|
for i, elem := range gt {
|
||||||
|
output[i] = string(elem)
|
||||||
|
}
|
||||||
|
return output
|
||||||
|
}
|
129
api/v1alpha1/oauth2client_types_test.go
Normal file
129
api/v1alpha1/oauth2client_types_test.go
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
/*
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package v1alpha1
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/types"
|
||||||
|
"k8s.io/client-go/kubernetes/scheme"
|
||||||
|
"k8s.io/client-go/rest"
|
||||||
|
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||||
|
"sigs.k8s.io/controller-runtime/pkg/envtest"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
k8sClient client.Client
|
||||||
|
cfg *rest.Config
|
||||||
|
testEnv *envtest.Environment
|
||||||
|
key types.NamespacedName
|
||||||
|
created, fetched *OAuth2Client
|
||||||
|
createErr, getErr, deleteErr error
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCreateAPI(t *testing.T) {
|
||||||
|
|
||||||
|
runEnv(t)
|
||||||
|
defer stopEnv(t)
|
||||||
|
|
||||||
|
t.Run("should handle an object properly", func(t *testing.T) {
|
||||||
|
|
||||||
|
key = types.NamespacedName{
|
||||||
|
Name: "foo",
|
||||||
|
Namespace: "default",
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("by creating an API object if it meets CRD requirements", func(t *testing.T) {
|
||||||
|
|
||||||
|
resetTestClient()
|
||||||
|
|
||||||
|
createErr = k8sClient.Create(context.TODO(), created)
|
||||||
|
require.NoError(t, createErr)
|
||||||
|
|
||||||
|
fetched = &OAuth2Client{}
|
||||||
|
getErr = k8sClient.Get(context.TODO(), key, fetched)
|
||||||
|
require.NoError(t, getErr)
|
||||||
|
assert.Equal(t, created, fetched)
|
||||||
|
|
||||||
|
deleteErr = k8sClient.Delete(context.TODO(), created)
|
||||||
|
require.NoError(t, deleteErr)
|
||||||
|
|
||||||
|
getErr = k8sClient.Get(context.TODO(), key, created)
|
||||||
|
require.Error(t, getErr)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("by failing if the requested object doesn't meet CRD requirements", func(t *testing.T) {
|
||||||
|
|
||||||
|
for desc, modifyClient := range map[string]func(){
|
||||||
|
"invalid grant type": func() { created.Spec.GrantTypes = []GrantType{"invalid"} },
|
||||||
|
"invalid response type": func() { created.Spec.ResponseTypes = []ResponseType{"invalid"} },
|
||||||
|
"invalid scope": func() { created.Spec.Scope = "" },
|
||||||
|
} {
|
||||||
|
t.Run(fmt.Sprintf("case=%s", desc), func(t *testing.T) {
|
||||||
|
|
||||||
|
resetTestClient()
|
||||||
|
modifyClient()
|
||||||
|
createErr = k8sClient.Create(context.TODO(), created)
|
||||||
|
require.Error(t, createErr)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func runEnv(t *testing.T) {
|
||||||
|
|
||||||
|
testEnv = &envtest.Environment{
|
||||||
|
CRDDirectoryPaths: []string{filepath.Join("..", "..", "config", "crd", "bases")},
|
||||||
|
}
|
||||||
|
|
||||||
|
err := SchemeBuilder.AddToScheme(scheme.Scheme)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
cfg, err = testEnv.Start()
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, cfg)
|
||||||
|
|
||||||
|
k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme})
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, k8sClient)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func stopEnv(t *testing.T) {
|
||||||
|
err := testEnv.Stop()
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func resetTestClient() {
|
||||||
|
created = &OAuth2Client{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "foo",
|
||||||
|
Namespace: "default",
|
||||||
|
},
|
||||||
|
Spec: OAuth2ClientSpec{
|
||||||
|
GrantTypes: []GrantType{"implicit", "client_credentials", "authorization_code", "refresh_token"},
|
||||||
|
ResponseTypes: []ResponseType{"id_token", "code", "token"},
|
||||||
|
Scope: "read,write",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
133
api/v1alpha1/zz_generated.deepcopy.go
Normal file
133
api/v1alpha1/zz_generated.deepcopy.go
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
// +build !ignore_autogenerated
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// autogenerated by controller-gen object, do not modify manually
|
||||||
|
|
||||||
|
package v1alpha1
|
||||||
|
|
||||||
|
import (
|
||||||
|
runtime "k8s.io/apimachinery/pkg/runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *OAuth2Client) DeepCopyInto(out *OAuth2Client) {
|
||||||
|
*out = *in
|
||||||
|
out.TypeMeta = in.TypeMeta
|
||||||
|
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
|
||||||
|
in.Spec.DeepCopyInto(&out.Spec)
|
||||||
|
in.Status.DeepCopyInto(&out.Status)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OAuth2Client.
|
||||||
|
func (in *OAuth2Client) DeepCopy() *OAuth2Client {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(OAuth2Client)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||||
|
func (in *OAuth2Client) DeepCopyObject() runtime.Object {
|
||||||
|
if c := in.DeepCopy(); c != nil {
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *OAuth2ClientList) DeepCopyInto(out *OAuth2ClientList) {
|
||||||
|
*out = *in
|
||||||
|
out.TypeMeta = in.TypeMeta
|
||||||
|
out.ListMeta = in.ListMeta
|
||||||
|
if in.Items != nil {
|
||||||
|
in, out := &in.Items, &out.Items
|
||||||
|
*out = make([]OAuth2Client, len(*in))
|
||||||
|
for i := range *in {
|
||||||
|
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OAuth2ClientList.
|
||||||
|
func (in *OAuth2ClientList) DeepCopy() *OAuth2ClientList {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(OAuth2ClientList)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||||
|
func (in *OAuth2ClientList) DeepCopyObject() runtime.Object {
|
||||||
|
if c := in.DeepCopy(); c != nil {
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *OAuth2ClientSpec) DeepCopyInto(out *OAuth2ClientSpec) {
|
||||||
|
*out = *in
|
||||||
|
if in.GrantTypes != nil {
|
||||||
|
in, out := &in.GrantTypes, &out.GrantTypes
|
||||||
|
*out = make([]GrantType, len(*in))
|
||||||
|
copy(*out, *in)
|
||||||
|
}
|
||||||
|
if in.ResponseTypes != nil {
|
||||||
|
in, out := &in.ResponseTypes, &out.ResponseTypes
|
||||||
|
*out = make([]ResponseType, len(*in))
|
||||||
|
copy(*out, *in)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OAuth2ClientSpec.
|
||||||
|
func (in *OAuth2ClientSpec) DeepCopy() *OAuth2ClientSpec {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(OAuth2ClientSpec)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *OAuth2ClientStatus) DeepCopyInto(out *OAuth2ClientStatus) {
|
||||||
|
*out = *in
|
||||||
|
if in.Secret != nil {
|
||||||
|
in, out := &in.Secret, &out.Secret
|
||||||
|
*out = new(string)
|
||||||
|
**out = **in
|
||||||
|
}
|
||||||
|
if in.ClientID != nil {
|
||||||
|
in, out := &in.ClientID, &out.ClientID
|
||||||
|
*out = new(string)
|
||||||
|
**out = **in
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OAuth2ClientStatus.
|
||||||
|
func (in *OAuth2ClientStatus) DeepCopy() *OAuth2ClientStatus {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(OAuth2ClientStatus)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
24
config/certmanager/certificate.yaml
Normal file
24
config/certmanager/certificate.yaml
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
# The following manifests contain a self-signed issuer CR and a certificate CR.
|
||||||
|
# More document can be found at https://docs.cert-manager.io
|
||||||
|
apiVersion: certmanager.k8s.io/v1alpha1
|
||||||
|
kind: Issuer
|
||||||
|
metadata:
|
||||||
|
name: selfsigned-issuer
|
||||||
|
namespace: system
|
||||||
|
spec:
|
||||||
|
selfSigned: {}
|
||||||
|
---
|
||||||
|
apiVersion: certmanager.k8s.io/v1alpha1
|
||||||
|
kind: Certificate
|
||||||
|
metadata:
|
||||||
|
name: serving-cert # this name should match the one appeared in kustomizeconfig.yaml
|
||||||
|
namespace: system
|
||||||
|
spec:
|
||||||
|
# $(SERVICENAME) and $(NAMESPACE) will be substituted by kustomize
|
||||||
|
commonName: $(SERVICENAME).$(NAMESPACE).svc
|
||||||
|
dnsNames:
|
||||||
|
- $(SERVICENAME).$(NAMESPACE).svc.cluster.local
|
||||||
|
issuerRef:
|
||||||
|
kind: Issuer
|
||||||
|
name: selfsigned-issuer
|
||||||
|
secretName: webhook-server-cert # this secret will not be prefixed, since it's not managed by kustomize
|
26
config/certmanager/kustomization.yaml
Normal file
26
config/certmanager/kustomization.yaml
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
resources:
|
||||||
|
- certificate.yaml
|
||||||
|
|
||||||
|
# the following config is for teaching kustomize how to do var substitution
|
||||||
|
vars:
|
||||||
|
- name: NAMESPACE # namespace of the service and the certificate CR
|
||||||
|
objref:
|
||||||
|
kind: Service
|
||||||
|
version: v1
|
||||||
|
name: webhook-service
|
||||||
|
fieldref:
|
||||||
|
fieldpath: metadata.namespace
|
||||||
|
- name: CERTIFICATENAME
|
||||||
|
objref:
|
||||||
|
kind: Certificate
|
||||||
|
group: certmanager.k8s.io
|
||||||
|
version: v1alpha1
|
||||||
|
name: serving-cert # this name should match the one in certificate.yaml
|
||||||
|
- name: SERVICENAME
|
||||||
|
objref:
|
||||||
|
kind: Service
|
||||||
|
version: v1
|
||||||
|
name: webhook-service
|
||||||
|
|
||||||
|
configurations:
|
||||||
|
- kustomizeconfig.yaml
|
16
config/certmanager/kustomizeconfig.yaml
Normal file
16
config/certmanager/kustomizeconfig.yaml
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
# This configuration is for teaching kustomize how to update name ref and var substitution
|
||||||
|
nameReference:
|
||||||
|
- kind: Issuer
|
||||||
|
group: certmanager.k8s.io
|
||||||
|
fieldSpecs:
|
||||||
|
- kind: Certificate
|
||||||
|
group: certmanager.k8s.io
|
||||||
|
path: spec/issuerRef/name
|
||||||
|
|
||||||
|
varReference:
|
||||||
|
- kind: Certificate
|
||||||
|
group: certmanager.k8s.io
|
||||||
|
path: spec/commonName
|
||||||
|
- kind: Certificate
|
||||||
|
group: certmanager.k8s.io
|
||||||
|
path: spec/dnsNames
|
450
config/crd/bases/hydra.ory.sh_oauth2clients.yaml
Normal file
450
config/crd/bases/hydra.ory.sh_oauth2clients.yaml
Normal file
@ -0,0 +1,450 @@
|
|||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: apiextensions.k8s.io/v1beta1
|
||||||
|
kind: CustomResourceDefinition
|
||||||
|
metadata:
|
||||||
|
creationTimestamp: null
|
||||||
|
name: oauth2clients.hydra.ory.sh
|
||||||
|
spec:
|
||||||
|
group: hydra.ory.sh
|
||||||
|
names:
|
||||||
|
kind: OAuth2Client
|
||||||
|
plural: oauth2clients
|
||||||
|
scope: ""
|
||||||
|
subresources:
|
||||||
|
status: {}
|
||||||
|
validation:
|
||||||
|
openAPIV3Schema:
|
||||||
|
description: OAuth2Client is the Schema for the oauth2clients API
|
||||||
|
properties:
|
||||||
|
apiVersion:
|
||||||
|
description: 'APIVersion defines the versioned schema of this representation
|
||||||
|
of an object. Servers should convert recognized schemas to the latest
|
||||||
|
internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#resources'
|
||||||
|
type: string
|
||||||
|
kind:
|
||||||
|
description: 'Kind is a string value representing the REST resource this
|
||||||
|
object represents. Servers may infer this from the endpoint the client
|
||||||
|
submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds'
|
||||||
|
type: string
|
||||||
|
metadata:
|
||||||
|
properties:
|
||||||
|
annotations:
|
||||||
|
additionalProperties:
|
||||||
|
type: string
|
||||||
|
description: 'Annotations is an unstructured key value map stored with
|
||||||
|
a resource that may be set by external tools to store and retrieve
|
||||||
|
arbitrary metadata. They are not queryable and should be preserved
|
||||||
|
when modifying objects. More info: http://kubernetes.io/docs/user-guide/annotations'
|
||||||
|
type: object
|
||||||
|
clusterName:
|
||||||
|
description: The name of the cluster which the object belongs to. This
|
||||||
|
is used to distinguish resources with same name and namespace in different
|
||||||
|
clusters. This field is not set anywhere right now and apiserver is
|
||||||
|
going to ignore it if set in create or update request.
|
||||||
|
type: string
|
||||||
|
creationTimestamp:
|
||||||
|
description: "CreationTimestamp is a timestamp representing the server
|
||||||
|
time when this object was created. It is not guaranteed to be set
|
||||||
|
in happens-before order across separate operations. Clients may not
|
||||||
|
set this value. It is represented in RFC3339 form and is in UTC. \n
|
||||||
|
Populated by the system. Read-only. Null for lists. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata"
|
||||||
|
format: date-time
|
||||||
|
type: string
|
||||||
|
deletionGracePeriodSeconds:
|
||||||
|
description: Number of seconds allowed for this object to gracefully
|
||||||
|
terminate before it will be removed from the system. Only set when
|
||||||
|
deletionTimestamp is also set. May only be shortened. Read-only.
|
||||||
|
format: int64
|
||||||
|
type: integer
|
||||||
|
deletionTimestamp:
|
||||||
|
description: "DeletionTimestamp is RFC 3339 date and time at which this
|
||||||
|
resource will be deleted. This field is set by the server when a graceful
|
||||||
|
deletion is requested by the user, and is not directly settable by
|
||||||
|
a client. The resource is expected to be deleted (no longer visible
|
||||||
|
from resource lists, and not reachable by name) after the time in
|
||||||
|
this field, once the finalizers list is empty. As long as the finalizers
|
||||||
|
list contains items, deletion is blocked. Once the deletionTimestamp
|
||||||
|
is set, this value may not be unset or be set further into the future,
|
||||||
|
although it may be shortened or the resource may be deleted prior
|
||||||
|
to this time. For example, a user may request that a pod is deleted
|
||||||
|
in 30 seconds. The Kubelet will react by sending a graceful termination
|
||||||
|
signal to the containers in the pod. After that 30 seconds, the Kubelet
|
||||||
|
will send a hard termination signal (SIGKILL) to the container and
|
||||||
|
after cleanup, remove the pod from the API. In the presence of network
|
||||||
|
partitions, this object may still exist after this timestamp, until
|
||||||
|
an administrator or automated process can determine the resource is
|
||||||
|
fully terminated. If not set, graceful deletion of the object has
|
||||||
|
not been requested. \n Populated by the system when a graceful deletion
|
||||||
|
is requested. Read-only. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata"
|
||||||
|
format: date-time
|
||||||
|
type: string
|
||||||
|
finalizers:
|
||||||
|
description: Must be empty before the object is deleted from the registry.
|
||||||
|
Each entry is an identifier for the responsible component that will
|
||||||
|
remove the entry from the list. If the deletionTimestamp of the object
|
||||||
|
is non-nil, entries in this list can only be removed.
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
type: array
|
||||||
|
generateName:
|
||||||
|
description: "GenerateName is an optional prefix, used by the server,
|
||||||
|
to generate a unique name ONLY IF the Name field has not been provided.
|
||||||
|
If this field is used, the name returned to the client will be different
|
||||||
|
than the name passed. This value will also be combined with a unique
|
||||||
|
suffix. The provided value has the same validation rules as the Name
|
||||||
|
field, and may be truncated by the length of the suffix required to
|
||||||
|
make the value unique on the server. \n If this field is specified
|
||||||
|
and the generated name exists, the server will NOT return a 409 -
|
||||||
|
instead, it will either return 201 Created or 500 with Reason ServerTimeout
|
||||||
|
indicating a unique name could not be found in the time allotted,
|
||||||
|
and the client should retry (optionally after the time indicated in
|
||||||
|
the Retry-After header). \n Applied only if Name is not specified.
|
||||||
|
More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#idempotency"
|
||||||
|
type: string
|
||||||
|
generation:
|
||||||
|
description: A sequence number representing a specific generation of
|
||||||
|
the desired state. Populated by the system. Read-only.
|
||||||
|
format: int64
|
||||||
|
type: integer
|
||||||
|
initializers:
|
||||||
|
description: "An initializer is a controller which enforces some system
|
||||||
|
invariant at object creation time. This field is a list of initializers
|
||||||
|
that have not yet acted on this object. If nil or empty, this object
|
||||||
|
has been completely initialized. Otherwise, the object is considered
|
||||||
|
uninitialized and is hidden (in list/watch and get calls) from clients
|
||||||
|
that haven't explicitly asked to observe uninitialized objects. \n
|
||||||
|
When an object is created, the system will populate this list with
|
||||||
|
the current set of initializers. Only privileged users may set or
|
||||||
|
modify this list. Once it is empty, it may not be modified further
|
||||||
|
by any user. \n DEPRECATED - initializers are an alpha field and will
|
||||||
|
be removed in v1.15."
|
||||||
|
properties:
|
||||||
|
pending:
|
||||||
|
description: Pending is a list of initializers that must execute
|
||||||
|
in order before this object is visible. When the last pending
|
||||||
|
initializer is removed, and no failing result is set, the initializers
|
||||||
|
struct will be set to nil and the object is considered as initialized
|
||||||
|
and visible to all clients.
|
||||||
|
items:
|
||||||
|
properties:
|
||||||
|
name:
|
||||||
|
description: name of the process that is responsible for initializing
|
||||||
|
this object.
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- name
|
||||||
|
type: object
|
||||||
|
type: array
|
||||||
|
result:
|
||||||
|
description: If result is set with the Failure field, the object
|
||||||
|
will be persisted to storage and then deleted, ensuring that other
|
||||||
|
clients can observe the deletion.
|
||||||
|
properties:
|
||||||
|
apiVersion:
|
||||||
|
description: 'APIVersion defines the versioned schema of this
|
||||||
|
representation of an object. Servers should convert recognized
|
||||||
|
schemas to the latest internal value, and may reject unrecognized
|
||||||
|
values. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#resources'
|
||||||
|
type: string
|
||||||
|
code:
|
||||||
|
description: Suggested HTTP return code for this status, 0 if
|
||||||
|
not set.
|
||||||
|
format: int32
|
||||||
|
type: integer
|
||||||
|
details:
|
||||||
|
description: Extended data associated with the reason. Each
|
||||||
|
reason may define its own extended details. This field is
|
||||||
|
optional and the data returned is not guaranteed to conform
|
||||||
|
to any schema except that defined by the reason type.
|
||||||
|
properties:
|
||||||
|
causes:
|
||||||
|
description: The Causes array includes more details associated
|
||||||
|
with the StatusReason failure. Not all StatusReasons may
|
||||||
|
provide detailed causes.
|
||||||
|
items:
|
||||||
|
properties:
|
||||||
|
field:
|
||||||
|
description: "The field of the resource that has caused
|
||||||
|
this error, as named by its JSON serialization.
|
||||||
|
May include dot and postfix notation for nested
|
||||||
|
attributes. Arrays are zero-indexed. Fields may
|
||||||
|
appear more than once in an array of causes due
|
||||||
|
to fields having multiple errors. Optional. \n Examples:
|
||||||
|
\ \"name\" - the field \"name\" on the current
|
||||||
|
resource \"items[0].name\" - the field \"name\"
|
||||||
|
on the first array entry in \"items\""
|
||||||
|
type: string
|
||||||
|
message:
|
||||||
|
description: A human-readable description of the cause
|
||||||
|
of the error. This field may be presented as-is
|
||||||
|
to a reader.
|
||||||
|
type: string
|
||||||
|
reason:
|
||||||
|
description: A machine-readable description of the
|
||||||
|
cause of the error. If this value is empty there
|
||||||
|
is no information available.
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
type: array
|
||||||
|
group:
|
||||||
|
description: The group attribute of the resource associated
|
||||||
|
with the status StatusReason.
|
||||||
|
type: string
|
||||||
|
kind:
|
||||||
|
description: 'The kind attribute of the resource associated
|
||||||
|
with the status StatusReason. On some operations may differ
|
||||||
|
from the requested resource Kind. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds'
|
||||||
|
type: string
|
||||||
|
name:
|
||||||
|
description: The name attribute of the resource associated
|
||||||
|
with the status StatusReason (when there is a single name
|
||||||
|
which can be described).
|
||||||
|
type: string
|
||||||
|
retryAfterSeconds:
|
||||||
|
description: If specified, the time in seconds before the
|
||||||
|
operation should be retried. Some errors may indicate
|
||||||
|
the client must take an alternate action - for those errors
|
||||||
|
this field may indicate how long to wait before taking
|
||||||
|
the alternate action.
|
||||||
|
format: int32
|
||||||
|
type: integer
|
||||||
|
uid:
|
||||||
|
description: 'UID of the resource. (when there is a single
|
||||||
|
resource which can be described). More info: http://kubernetes.io/docs/user-guide/identifiers#uids'
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
kind:
|
||||||
|
description: 'Kind is a string value representing the REST resource
|
||||||
|
this object represents. Servers may infer this from the endpoint
|
||||||
|
the client submits requests to. Cannot be updated. In CamelCase.
|
||||||
|
More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds'
|
||||||
|
type: string
|
||||||
|
message:
|
||||||
|
description: A human-readable description of the status of this
|
||||||
|
operation.
|
||||||
|
type: string
|
||||||
|
metadata:
|
||||||
|
description: 'Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds'
|
||||||
|
properties:
|
||||||
|
continue:
|
||||||
|
description: continue may be set if the user set a limit
|
||||||
|
on the number of items returned, and indicates that the
|
||||||
|
server has more data available. The value is opaque and
|
||||||
|
may be used to issue another request to the endpoint that
|
||||||
|
served this list to retrieve the next set of available
|
||||||
|
objects. Continuing a consistent list may not be possible
|
||||||
|
if the server configuration has changed or more than a
|
||||||
|
few minutes have passed. The resourceVersion field returned
|
||||||
|
when using this continue value will be identical to the
|
||||||
|
value in the first response, unless you have received
|
||||||
|
this token from an error message.
|
||||||
|
type: string
|
||||||
|
resourceVersion:
|
||||||
|
description: 'String that identifies the server''s internal
|
||||||
|
version of this object that can be used by clients to
|
||||||
|
determine when objects have changed. Value must be treated
|
||||||
|
as opaque by clients and passed unmodified back to the
|
||||||
|
server. Populated by the system. Read-only. More info:
|
||||||
|
https://git.k8s.io/community/contributors/devel/api-conventions.md#concurrency-control-and-consistency'
|
||||||
|
type: string
|
||||||
|
selfLink:
|
||||||
|
description: selfLink is a URL representing this object.
|
||||||
|
Populated by the system. Read-only.
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
reason:
|
||||||
|
description: A machine-readable description of why this operation
|
||||||
|
is in the "Failure" status. If this value is empty there is
|
||||||
|
no information available. A Reason clarifies an HTTP status
|
||||||
|
code but does not override it.
|
||||||
|
type: string
|
||||||
|
status:
|
||||||
|
description: 'Status of the operation. One of: "Success" or
|
||||||
|
"Failure". More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status'
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- pending
|
||||||
|
type: object
|
||||||
|
labels:
|
||||||
|
additionalProperties:
|
||||||
|
type: string
|
||||||
|
description: 'Map of string keys and values that can be used to organize
|
||||||
|
and categorize (scope and select) objects. May match selectors of
|
||||||
|
replication controllers and services. More info: http://kubernetes.io/docs/user-guide/labels'
|
||||||
|
type: object
|
||||||
|
managedFields:
|
||||||
|
description: "ManagedFields maps workflow-id and version to the set
|
||||||
|
of fields that are managed by that workflow. This is mostly for internal
|
||||||
|
housekeeping, and users typically shouldn't need to set or understand
|
||||||
|
this field. A workflow can be the user's name, a controller's name,
|
||||||
|
or the name of a specific apply path like \"ci-cd\". The set of fields
|
||||||
|
is always in the version that the workflow used when modifying the
|
||||||
|
object. \n This field is alpha and can be changed or removed without
|
||||||
|
notice."
|
||||||
|
items:
|
||||||
|
properties:
|
||||||
|
apiVersion:
|
||||||
|
description: APIVersion defines the version of this resource that
|
||||||
|
this field set applies to. The format is "group/version" just
|
||||||
|
like the top-level APIVersion field. It is necessary to track
|
||||||
|
the version of a field set because it cannot be automatically
|
||||||
|
converted.
|
||||||
|
type: string
|
||||||
|
fields:
|
||||||
|
additionalProperties: true
|
||||||
|
description: Fields identifies a set of fields.
|
||||||
|
type: object
|
||||||
|
manager:
|
||||||
|
description: Manager is an identifier of the workflow managing
|
||||||
|
these fields.
|
||||||
|
type: string
|
||||||
|
operation:
|
||||||
|
description: Operation is the type of operation which lead to
|
||||||
|
this ManagedFieldsEntry being created. The only valid values
|
||||||
|
for this field are 'Apply' and 'Update'.
|
||||||
|
type: string
|
||||||
|
time:
|
||||||
|
description: Time is timestamp of when these fields were set.
|
||||||
|
It should always be empty if Operation is 'Apply'
|
||||||
|
format: date-time
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
type: array
|
||||||
|
name:
|
||||||
|
description: 'Name must be unique within a namespace. Is required when
|
||||||
|
creating resources, although some resources may allow a client to
|
||||||
|
request the generation of an appropriate name automatically. Name
|
||||||
|
is primarily intended for creation idempotence and configuration definition.
|
||||||
|
Cannot be updated. More info: http://kubernetes.io/docs/user-guide/identifiers#names'
|
||||||
|
type: string
|
||||||
|
namespace:
|
||||||
|
description: "Namespace defines the space within each name must be unique.
|
||||||
|
An empty namespace is equivalent to the \"default\" namespace, but
|
||||||
|
\"default\" is the canonical representation. Not all objects are required
|
||||||
|
to be scoped to a namespace - the value of this field for those objects
|
||||||
|
will be empty. \n Must be a DNS_LABEL. Cannot be updated. More info:
|
||||||
|
http://kubernetes.io/docs/user-guide/namespaces"
|
||||||
|
type: string
|
||||||
|
ownerReferences:
|
||||||
|
description: List of objects depended by this object. If ALL objects
|
||||||
|
in the list have been deleted, this object will be garbage collected.
|
||||||
|
If this object is managed by a controller, then an entry in this list
|
||||||
|
will point to this controller, with the controller field set to true.
|
||||||
|
There cannot be more than one managing controller.
|
||||||
|
items:
|
||||||
|
properties:
|
||||||
|
apiVersion:
|
||||||
|
description: API version of the referent.
|
||||||
|
type: string
|
||||||
|
blockOwnerDeletion:
|
||||||
|
description: If true, AND if the owner has the "foregroundDeletion"
|
||||||
|
finalizer, then the owner cannot be deleted from the key-value
|
||||||
|
store until this reference is removed. Defaults to false. To
|
||||||
|
set this field, a user needs "delete" permission of the owner,
|
||||||
|
otherwise 422 (Unprocessable Entity) will be returned.
|
||||||
|
type: boolean
|
||||||
|
controller:
|
||||||
|
description: If true, this reference points to the managing controller.
|
||||||
|
type: boolean
|
||||||
|
kind:
|
||||||
|
description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds'
|
||||||
|
type: string
|
||||||
|
name:
|
||||||
|
description: 'Name of the referent. More info: http://kubernetes.io/docs/user-guide/identifiers#names'
|
||||||
|
type: string
|
||||||
|
uid:
|
||||||
|
description: 'UID of the referent. More info: http://kubernetes.io/docs/user-guide/identifiers#uids'
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- apiVersion
|
||||||
|
- kind
|
||||||
|
- name
|
||||||
|
- uid
|
||||||
|
type: object
|
||||||
|
type: array
|
||||||
|
resourceVersion:
|
||||||
|
description: "An opaque value that represents the internal version of
|
||||||
|
this object that can be used by clients to determine when objects
|
||||||
|
have changed. May be used for optimistic concurrency, change detection,
|
||||||
|
and the watch operation on a resource or set of resources. Clients
|
||||||
|
must treat these values as opaque and passed unmodified back to the
|
||||||
|
server. They may only be valid for a particular resource or set of
|
||||||
|
resources. \n Populated by the system. Read-only. Value must be treated
|
||||||
|
as opaque by clients and . More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#concurrency-control-and-consistency"
|
||||||
|
type: string
|
||||||
|
selfLink:
|
||||||
|
description: SelfLink is a URL representing this object. Populated by
|
||||||
|
the system. Read-only.
|
||||||
|
type: string
|
||||||
|
uid:
|
||||||
|
description: "UID is the unique in time and space value for this object.
|
||||||
|
It is typically generated by the server on successful creation of
|
||||||
|
a resource and is not allowed to change on PUT operations. \n Populated
|
||||||
|
by the system. Read-only. More info: http://kubernetes.io/docs/user-guide/identifiers#uids"
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
spec:
|
||||||
|
properties:
|
||||||
|
grantTypes:
|
||||||
|
description: GrantTypes is an array of grant types the client is allowed
|
||||||
|
to use.
|
||||||
|
items:
|
||||||
|
enum:
|
||||||
|
- client_credentials
|
||||||
|
- authorization_code
|
||||||
|
- implicit
|
||||||
|
- refresh_token
|
||||||
|
type: string
|
||||||
|
maxItems: 4
|
||||||
|
minItems: 1
|
||||||
|
type: array
|
||||||
|
responseTypes:
|
||||||
|
description: ResponseTypes is an array of the OAuth 2.0 response type
|
||||||
|
strings that the client can use at the authorization endpoint.
|
||||||
|
items:
|
||||||
|
enum:
|
||||||
|
- id_token
|
||||||
|
- code
|
||||||
|
- token
|
||||||
|
type: string
|
||||||
|
maxItems: 3
|
||||||
|
minItems: 1
|
||||||
|
type: array
|
||||||
|
scope:
|
||||||
|
description: Scope is a string containing a space-separated list of
|
||||||
|
scope values (as described in Section 3.3 of OAuth 2.0 [RFC6749])
|
||||||
|
that the client can use when requesting access tokens.
|
||||||
|
pattern: ([a-zA-Z0-9\.\*]+\s?)+
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- grantTypes
|
||||||
|
- scope
|
||||||
|
type: object
|
||||||
|
status:
|
||||||
|
properties:
|
||||||
|
clientID:
|
||||||
|
description: ClientID is the id for this client.
|
||||||
|
type: string
|
||||||
|
observedGeneration:
|
||||||
|
description: ObservedGeneration represents the most recent generation
|
||||||
|
observed by the daemon set controller.
|
||||||
|
format: int64
|
||||||
|
type: integer
|
||||||
|
secret:
|
||||||
|
description: Secret points to the K8s secret that contains this client's
|
||||||
|
id and password
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
type: object
|
||||||
|
versions:
|
||||||
|
- name: v1alpha1
|
||||||
|
served: true
|
||||||
|
storage: true
|
||||||
|
status:
|
||||||
|
acceptedNames:
|
||||||
|
kind: ""
|
||||||
|
plural: ""
|
||||||
|
conditions: []
|
||||||
|
storedVersions: []
|
19
config/crd/kustomization.yaml
Normal file
19
config/crd/kustomization.yaml
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
# This kustomization.yaml is not intended to be run by itself,
|
||||||
|
# since it depends on service name and namespace that are out of this kustomize package.
|
||||||
|
# It should be run by config/default
|
||||||
|
resources:
|
||||||
|
- bases/hydra.ory.sh_oauth2clients.yaml
|
||||||
|
# +kubebuilder:scaffold:crdkustomizeresource
|
||||||
|
|
||||||
|
patches:
|
||||||
|
# [WEBHOOK] patches here are for enabling the conversion webhook for each CRD
|
||||||
|
#- patches/webhook_in_oauth2clients.yaml
|
||||||
|
# +kubebuilder:scaffold:crdkustomizewebhookpatch
|
||||||
|
|
||||||
|
# [CAINJECTION] patches here are for enabling the CA injection for each CRD
|
||||||
|
#- patches/cainjection_in_oauth2clients.yaml
|
||||||
|
# +kubebuilder:scaffold:crdkustomizecainjectionpatch
|
||||||
|
|
||||||
|
# the following config is for teaching kustomize how to do kustomization for CRDs.
|
||||||
|
configurations:
|
||||||
|
- kustomizeconfig.yaml
|
17
config/crd/kustomizeconfig.yaml
Normal file
17
config/crd/kustomizeconfig.yaml
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
# This file is for teaching kustomize how to substitute name and namespace reference in CRD
|
||||||
|
nameReference:
|
||||||
|
- kind: Service
|
||||||
|
version: v1
|
||||||
|
fieldSpecs:
|
||||||
|
- kind: CustomResourceDefinition
|
||||||
|
group: apiextensions.k8s.io
|
||||||
|
path: spec/conversion/webhookClientConfig/service/name
|
||||||
|
|
||||||
|
namespace:
|
||||||
|
- kind: CustomResourceDefinition
|
||||||
|
group: apiextensions.k8s.io
|
||||||
|
path: spec/conversion/webhookClientConfig/service/namespace
|
||||||
|
create: false
|
||||||
|
|
||||||
|
varReference:
|
||||||
|
- path: metadata/annotations
|
8
config/crd/patches/cainjection_in_oauth2clients.yaml
Normal file
8
config/crd/patches/cainjection_in_oauth2clients.yaml
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
# The following patch adds a directive for certmanager to inject CA into the CRD
|
||||||
|
# CRD conversion requires k8s 1.13 or later.
|
||||||
|
apiVersion: apiextensions.k8s.io/v1beta1
|
||||||
|
kind: CustomResourceDefinition
|
||||||
|
metadata:
|
||||||
|
annotations:
|
||||||
|
certmanager.k8s.io/inject-ca-from: $(NAMESPACE)/$(CERTIFICATENAME)
|
||||||
|
name: oauth2clients.hydra.ory.sh
|
17
config/crd/patches/webhook_in_oauth2clients.yaml
Normal file
17
config/crd/patches/webhook_in_oauth2clients.yaml
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
# The following patch enables conversion webhook for CRD
|
||||||
|
# CRD conversion requires k8s 1.13 or later.
|
||||||
|
apiVersion: apiextensions.k8s.io/v1beta1
|
||||||
|
kind: CustomResourceDefinition
|
||||||
|
metadata:
|
||||||
|
name: oauth2clients.hydra.ory.sh
|
||||||
|
spec:
|
||||||
|
conversion:
|
||||||
|
strategy: Webhook
|
||||||
|
webhookClientConfig:
|
||||||
|
# this is "\n" used as a placeholder, otherwise it will be rejected by the apiserver for being blank,
|
||||||
|
# but we're going to set it later using the cert-manager (or potentially a patch if not using cert-manager)
|
||||||
|
caBundle: Cg==
|
||||||
|
service:
|
||||||
|
namespace: system
|
||||||
|
name: webhook-service
|
||||||
|
path: /convert
|
43
config/default/kustomization.yaml
Normal file
43
config/default/kustomization.yaml
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
# Adds namespace to all resources.
|
||||||
|
namespace: hydra-maester-system
|
||||||
|
|
||||||
|
# Value of this field is prepended to the
|
||||||
|
# names of all resources, e.g. a deployment named
|
||||||
|
# "wordpress" becomes "alices-wordpress".
|
||||||
|
# Note that it should also match with the prefix (text before '-') of the namespace
|
||||||
|
# field above.
|
||||||
|
namePrefix: hydra-maester-
|
||||||
|
|
||||||
|
# Labels to add to all resources and selectors.
|
||||||
|
#commonLabels:
|
||||||
|
# someName: someValue
|
||||||
|
|
||||||
|
bases:
|
||||||
|
- ../crd
|
||||||
|
- ../rbac
|
||||||
|
- ../manager
|
||||||
|
# [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in crd/kustomization.yaml
|
||||||
|
#- ../webhook
|
||||||
|
# [CERTMANAGER] To enable cert-manager, uncomment next line. 'WEBHOOK' components are required.
|
||||||
|
#- ../certmanager
|
||||||
|
|
||||||
|
patches:
|
||||||
|
- manager_image_patch.yaml
|
||||||
|
# Protect the /metrics endpoint by putting it behind auth.
|
||||||
|
# Only one of manager_auth_proxy_patch.yaml and
|
||||||
|
# manager_prometheus_metrics_patch.yaml should be enabled.
|
||||||
|
- manager_auth_proxy_patch.yaml
|
||||||
|
# If you want your controller-manager to expose the /metrics
|
||||||
|
# endpoint w/o any authn/z, uncomment the following line and
|
||||||
|
# comment manager_auth_proxy_patch.yaml.
|
||||||
|
# Only one of manager_auth_proxy_patch.yaml and
|
||||||
|
# manager_prometheus_metrics_patch.yaml should be enabled.
|
||||||
|
#- manager_prometheus_metrics_patch.yaml
|
||||||
|
|
||||||
|
# [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in crd/kustomization.yaml
|
||||||
|
#- manager_webhook_patch.yaml
|
||||||
|
|
||||||
|
# [CAINJECTION] Uncomment next line to enable the CA injection in the admission webhooks.
|
||||||
|
# Uncomment 'CAINJECTION' in crd/kustomization.yaml to enable the CA injection in the admission webhooks.
|
||||||
|
# 'CERTMANAGER' needs to be enabled to use ca injection
|
||||||
|
#- webhookcainjection_patch.yaml
|
24
config/default/manager_auth_proxy_patch.yaml
Normal file
24
config/default/manager_auth_proxy_patch.yaml
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
# This patch inject a sidecar container which is a HTTP proxy for the controller manager,
|
||||||
|
# it performs RBAC authorization against the Kubernetes API using SubjectAccessReviews.
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: controller-manager
|
||||||
|
namespace: system
|
||||||
|
spec:
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: kube-rbac-proxy
|
||||||
|
image: gcr.io/kubebuilder/kube-rbac-proxy:v0.4.0
|
||||||
|
args:
|
||||||
|
- "--secure-listen-address=0.0.0.0:8443"
|
||||||
|
- "--upstream=http://127.0.0.1:8080/"
|
||||||
|
- "--logtostderr=true"
|
||||||
|
- "--v=10"
|
||||||
|
ports:
|
||||||
|
- containerPort: 8443
|
||||||
|
name: https
|
||||||
|
- name: manager
|
||||||
|
args:
|
||||||
|
- "--metrics-addr=127.0.0.1:8080"
|
12
config/default/manager_image_patch.yaml
Normal file
12
config/default/manager_image_patch.yaml
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: controller-manager
|
||||||
|
namespace: system
|
||||||
|
spec:
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
# Change the value of image field below to your controller image URL
|
||||||
|
- image: controller:latest
|
||||||
|
name: manager
|
19
config/default/manager_prometheus_metrics_patch.yaml
Normal file
19
config/default/manager_prometheus_metrics_patch.yaml
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
# This patch enables Prometheus scraping for the manager pod.
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: controller-manager
|
||||||
|
namespace: system
|
||||||
|
spec:
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
annotations:
|
||||||
|
prometheus.io/scrape: 'true'
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
# Expose the prometheus metrics on default port
|
||||||
|
- name: manager
|
||||||
|
ports:
|
||||||
|
- containerPort: 8080
|
||||||
|
name: metrics
|
||||||
|
protocol: TCP
|
23
config/default/manager_webhook_patch.yaml
Normal file
23
config/default/manager_webhook_patch.yaml
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: controller-manager
|
||||||
|
namespace: system
|
||||||
|
spec:
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: manager
|
||||||
|
ports:
|
||||||
|
- containerPort: 443
|
||||||
|
name: webhook-server
|
||||||
|
protocol: TCP
|
||||||
|
volumeMounts:
|
||||||
|
- mountPath: /tmp/k8s-webhook-server/serving-certs
|
||||||
|
name: cert
|
||||||
|
readOnly: true
|
||||||
|
volumes:
|
||||||
|
- name: cert
|
||||||
|
secret:
|
||||||
|
defaultMode: 420
|
||||||
|
secretName: webhook-server-cert
|
15
config/default/webhookcainjection_patch.yaml
Normal file
15
config/default/webhookcainjection_patch.yaml
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
# This patch add annotation to admission webhook config and
|
||||||
|
# the variables $(NAMESPACE) and $(CERTIFICATENAME) will be substituted by kustomize.
|
||||||
|
apiVersion: admissionregistration.k8s.io/v1beta1
|
||||||
|
kind: MutatingWebhookConfiguration
|
||||||
|
metadata:
|
||||||
|
name: mutating-webhook-configuration
|
||||||
|
annotations:
|
||||||
|
certmanager.k8s.io/inject-ca-from: $(NAMESPACE)/$(CERTIFICATENAME)
|
||||||
|
---
|
||||||
|
apiVersion: admissionregistration.k8s.io/v1beta1
|
||||||
|
kind: ValidatingWebhookConfiguration
|
||||||
|
metadata:
|
||||||
|
name: validating-webhook-configuration
|
||||||
|
annotations:
|
||||||
|
certmanager.k8s.io/inject-ca-from: $(NAMESPACE)/$(CERTIFICATENAME)
|
2
config/manager/kustomization.yaml
Normal file
2
config/manager/kustomization.yaml
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
resources:
|
||||||
|
- manager.yaml
|
40
config/manager/manager.yaml
Normal file
40
config/manager/manager.yaml
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Namespace
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
control-plane: controller-manager
|
||||||
|
name: system
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: controller-manager
|
||||||
|
namespace: system
|
||||||
|
labels:
|
||||||
|
control-plane: controller-manager
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
control-plane: controller-manager
|
||||||
|
replicas: 1
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
control-plane: controller-manager
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- command:
|
||||||
|
- /manager
|
||||||
|
args:
|
||||||
|
- --enable-leader-election
|
||||||
|
- --hydra-url=http://use.actual.hydra.fqdn #change it to your ORY Hydra address
|
||||||
|
image: controller:latest
|
||||||
|
name: manager
|
||||||
|
resources:
|
||||||
|
limits:
|
||||||
|
cpu: 100m
|
||||||
|
memory: 30Mi
|
||||||
|
requests:
|
||||||
|
cpu: 100m
|
||||||
|
memory: 20Mi
|
||||||
|
terminationGracePeriodSeconds: 10
|
13
config/rbac/auth_proxy_role.yaml
Normal file
13
config/rbac/auth_proxy_role.yaml
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: ClusterRole
|
||||||
|
metadata:
|
||||||
|
name: proxy-role
|
||||||
|
rules:
|
||||||
|
- apiGroups: ["authentication.k8s.io"]
|
||||||
|
resources:
|
||||||
|
- tokenreviews
|
||||||
|
verbs: ["create"]
|
||||||
|
- apiGroups: ["authorization.k8s.io"]
|
||||||
|
resources:
|
||||||
|
- subjectaccessreviews
|
||||||
|
verbs: ["create"]
|
12
config/rbac/auth_proxy_role_binding.yaml
Normal file
12
config/rbac/auth_proxy_role_binding.yaml
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: ClusterRoleBinding
|
||||||
|
metadata:
|
||||||
|
name: proxy-rolebinding
|
||||||
|
roleRef:
|
||||||
|
apiGroup: rbac.authorization.k8s.io
|
||||||
|
kind: ClusterRole
|
||||||
|
name: proxy-role
|
||||||
|
subjects:
|
||||||
|
- kind: ServiceAccount
|
||||||
|
name: default
|
||||||
|
namespace: system
|
18
config/rbac/auth_proxy_service.yaml
Normal file
18
config/rbac/auth_proxy_service.yaml
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
annotations:
|
||||||
|
prometheus.io/port: "8443"
|
||||||
|
prometheus.io/scheme: https
|
||||||
|
prometheus.io/scrape: "true"
|
||||||
|
labels:
|
||||||
|
control-plane: controller-manager
|
||||||
|
name: controller-manager-metrics-service
|
||||||
|
namespace: system
|
||||||
|
spec:
|
||||||
|
ports:
|
||||||
|
- name: https
|
||||||
|
port: 8443
|
||||||
|
targetPort: https
|
||||||
|
selector:
|
||||||
|
control-plane: controller-manager
|
11
config/rbac/kustomization.yaml
Normal file
11
config/rbac/kustomization.yaml
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
resources:
|
||||||
|
- role.yaml
|
||||||
|
- role_binding.yaml
|
||||||
|
- leader_election_role.yaml
|
||||||
|
- leader_election_role_binding.yaml
|
||||||
|
# Comment the following 3 lines if you want to disable
|
||||||
|
# the auth proxy (https://github.com/brancz/kube-rbac-proxy)
|
||||||
|
# which protects your /metrics endpoint.
|
||||||
|
- auth_proxy_service.yaml
|
||||||
|
- auth_proxy_role.yaml
|
||||||
|
- auth_proxy_role_binding.yaml
|
26
config/rbac/leader_election_role.yaml
Normal file
26
config/rbac/leader_election_role.yaml
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
# permissions to do leader election.
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: Role
|
||||||
|
metadata:
|
||||||
|
name: leader-election-role
|
||||||
|
rules:
|
||||||
|
- apiGroups:
|
||||||
|
- ""
|
||||||
|
resources:
|
||||||
|
- configmaps
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
- list
|
||||||
|
- watch
|
||||||
|
- create
|
||||||
|
- update
|
||||||
|
- patch
|
||||||
|
- delete
|
||||||
|
- apiGroups:
|
||||||
|
- ""
|
||||||
|
resources:
|
||||||
|
- configmaps/status
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
- update
|
||||||
|
- patch
|
12
config/rbac/leader_election_role_binding.yaml
Normal file
12
config/rbac/leader_election_role_binding.yaml
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: RoleBinding
|
||||||
|
metadata:
|
||||||
|
name: leader-election-rolebinding
|
||||||
|
roleRef:
|
||||||
|
apiGroup: rbac.authorization.k8s.io
|
||||||
|
kind: Role
|
||||||
|
name: leader-election-role
|
||||||
|
subjects:
|
||||||
|
- kind: ServiceAccount
|
||||||
|
name: default
|
||||||
|
namespace: system
|
40
config/rbac/role.yaml
Normal file
40
config/rbac/role.yaml
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: ClusterRole
|
||||||
|
metadata:
|
||||||
|
creationTimestamp: null
|
||||||
|
name: manager-role
|
||||||
|
rules:
|
||||||
|
- apiGroups:
|
||||||
|
- hydra.ory.sh
|
||||||
|
resources:
|
||||||
|
- oauth2clients
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
- list
|
||||||
|
- watch
|
||||||
|
- create
|
||||||
|
- update
|
||||||
|
- patch
|
||||||
|
- delete
|
||||||
|
- apiGroups:
|
||||||
|
- hydra.ory.sh
|
||||||
|
resources:
|
||||||
|
- oauth2clients/status
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
- update
|
||||||
|
- patch
|
||||||
|
- apiGroups:
|
||||||
|
- ""
|
||||||
|
resources:
|
||||||
|
- secrets
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
- list
|
||||||
|
- watch
|
||||||
|
- create
|
||||||
|
- update
|
||||||
|
- patch
|
||||||
|
- delete
|
12
config/rbac/role_binding.yaml
Normal file
12
config/rbac/role_binding.yaml
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: ClusterRoleBinding
|
||||||
|
metadata:
|
||||||
|
name: manager-rolebinding
|
||||||
|
roleRef:
|
||||||
|
apiGroup: rbac.authorization.k8s.io
|
||||||
|
kind: ClusterRole
|
||||||
|
name: manager-role
|
||||||
|
subjects:
|
||||||
|
- kind: ServiceAccount
|
||||||
|
name: default
|
||||||
|
namespace: system
|
7
config/samples/hydra_v1alpha1_oauth2client.yaml
Normal file
7
config/samples/hydra_v1alpha1_oauth2client.yaml
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
apiVersion: hydra.ory.sh/v1alpha1
|
||||||
|
kind: OAuth2Client
|
||||||
|
metadata:
|
||||||
|
name: oauth2client-sample
|
||||||
|
spec:
|
||||||
|
# Add fields here
|
||||||
|
foo: bar
|
6
config/webhook/kustomization.yaml
Normal file
6
config/webhook/kustomization.yaml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
resources:
|
||||||
|
- manifests.yaml
|
||||||
|
- service.yaml
|
||||||
|
|
||||||
|
configurations:
|
||||||
|
- kustomizeconfig.yaml
|
25
config/webhook/kustomizeconfig.yaml
Normal file
25
config/webhook/kustomizeconfig.yaml
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
# the following config is for teaching kustomize where to look at when substituting vars.
|
||||||
|
# It requires kustomize v2.1.0 or newer to work properly.
|
||||||
|
nameReference:
|
||||||
|
- kind: Service
|
||||||
|
version: v1
|
||||||
|
fieldSpecs:
|
||||||
|
- kind: MutatingWebhookConfiguration
|
||||||
|
group: admissionregistration.k8s.io
|
||||||
|
path: webhooks/clientConfig/service/name
|
||||||
|
- kind: ValidatingWebhookConfiguration
|
||||||
|
group: admissionregistration.k8s.io
|
||||||
|
path: webhooks/clientConfig/service/name
|
||||||
|
|
||||||
|
namespace:
|
||||||
|
- kind: MutatingWebhookConfiguration
|
||||||
|
group: admissionregistration.k8s.io
|
||||||
|
path: webhooks/clientConfig/service/namespace
|
||||||
|
create: true
|
||||||
|
- kind: ValidatingWebhookConfiguration
|
||||||
|
group: admissionregistration.k8s.io
|
||||||
|
path: webhooks/clientConfig/service/namespace
|
||||||
|
create: true
|
||||||
|
|
||||||
|
varReference:
|
||||||
|
- path: metadata/annotations
|
0
config/webhook/manifests.yaml
Normal file
0
config/webhook/manifests.yaml
Normal file
12
config/webhook/service.yaml
Normal file
12
config/webhook/service.yaml
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: webhook-service
|
||||||
|
namespace: system
|
||||||
|
spec:
|
||||||
|
ports:
|
||||||
|
- port: 443
|
||||||
|
targetPort: 443
|
||||||
|
selector:
|
||||||
|
control-plane: controller-manager
|
145
controllers/oauth2client_controller.go
Normal file
145
controllers/oauth2client_controller.go
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
/*
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package controllers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"k8s.io/apimachinery/pkg/types"
|
||||||
|
|
||||||
|
"github.com/go-logr/logr"
|
||||||
|
hydrav1alpha1 "github.com/ory/hydra-maester/api/v1alpha1"
|
||||||
|
"github.com/ory/hydra-maester/hydra"
|
||||||
|
apiv1 "k8s.io/api/core/v1"
|
||||||
|
apierrs "k8s.io/apimachinery/pkg/api/errors"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
ctrl "sigs.k8s.io/controller-runtime"
|
||||||
|
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
clientIDKey = "client_id"
|
||||||
|
clientSecretKey = "client_secret"
|
||||||
|
)
|
||||||
|
|
||||||
|
type HydraClientInterface interface {
|
||||||
|
GetOAuth2Client(id string) (*hydra.OAuth2ClientJSON, bool, error)
|
||||||
|
PostOAuth2Client(o *hydra.OAuth2ClientJSON) (*hydra.OAuth2ClientJSON, error)
|
||||||
|
PutOAuth2Client(o *hydra.OAuth2ClientJSON) (*hydra.OAuth2ClientJSON, error)
|
||||||
|
DeleteOAuth2Client(id string) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// OAuth2ClientReconciler reconciles a OAuth2Client object
|
||||||
|
type OAuth2ClientReconciler struct {
|
||||||
|
HydraClient HydraClientInterface
|
||||||
|
Log logr.Logger
|
||||||
|
client.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
// +kubebuilder:rbac:groups=hydra.ory.sh,resources=oauth2clients,verbs=get;list;watch;create;update;patch;delete
|
||||||
|
// +kubebuilder:rbac:groups=hydra.ory.sh,resources=oauth2clients/status,verbs=get;update;patch
|
||||||
|
// +kubebuilder:rbac:groups="",resources=secrets,verbs=get;list;watch;create;update;patch;delete
|
||||||
|
|
||||||
|
func (r *OAuth2ClientReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
|
||||||
|
ctx := context.Background()
|
||||||
|
_ = r.Log.WithValues("oauth2client", req.NamespacedName)
|
||||||
|
|
||||||
|
var client hydrav1alpha1.OAuth2Client
|
||||||
|
if err := r.Get(ctx, req.NamespacedName, &client); err != nil {
|
||||||
|
if apierrs.IsNotFound(err) {
|
||||||
|
if err := r.unregisterOAuth2Client(ctx, req.NamespacedName); err != nil {
|
||||||
|
return ctrl.Result{}, err
|
||||||
|
}
|
||||||
|
return ctrl.Result{}, nil
|
||||||
|
}
|
||||||
|
return ctrl.Result{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if client.Generation != client.Status.ObservedGeneration {
|
||||||
|
|
||||||
|
var registered = false
|
||||||
|
var err error
|
||||||
|
|
||||||
|
if client.Status.ClientID != nil {
|
||||||
|
|
||||||
|
_, registered, err = r.HydraClient.GetOAuth2Client(*client.Status.ClientID)
|
||||||
|
if err != nil {
|
||||||
|
return ctrl.Result{}, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !registered {
|
||||||
|
return ctrl.Result{}, r.registerOAuth2Client(ctx, &client)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ctrl.Result{}, r.updateRegisteredOAuth2Client(&client)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ctrl.Result{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *OAuth2ClientReconciler) SetupWithManager(mgr ctrl.Manager) error {
|
||||||
|
return ctrl.NewControllerManagedBy(mgr).
|
||||||
|
For(&hydrav1alpha1.OAuth2Client{}).
|
||||||
|
Complete(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *OAuth2ClientReconciler) registerOAuth2Client(ctx context.Context, client *hydrav1alpha1.OAuth2Client) error {
|
||||||
|
created, err := r.HydraClient.PostOAuth2Client(client.ToOAuth2ClientJSON())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
clientSecret := apiv1.Secret{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: client.Name,
|
||||||
|
Namespace: client.Namespace,
|
||||||
|
},
|
||||||
|
Data: map[string][]byte{
|
||||||
|
clientSecretKey: []byte(*created.Secret),
|
||||||
|
clientIDKey: []byte(*created.ClientID),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
err = r.Create(ctx, &clientSecret)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
client.Status.Secret = &clientSecret.Name
|
||||||
|
client.Status.ClientID = created.ClientID
|
||||||
|
client.Status.ObservedGeneration = client.Generation
|
||||||
|
return r.Status().Update(ctx, client)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *OAuth2ClientReconciler) unregisterOAuth2Client(ctx context.Context, namespacedName types.NamespacedName) error {
|
||||||
|
var sec apiv1.Secret
|
||||||
|
if err := r.Get(ctx, namespacedName, &sec); err != nil {
|
||||||
|
if apierrs.IsNotFound(err) {
|
||||||
|
r.Log.Info(fmt.Sprintf("unable to find secret corresponding with client %s/%s. Manual deletion recommended", namespacedName.Name, namespacedName.Namespace))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return r.HydraClient.DeleteOAuth2Client(string(sec.Data[clientIDKey]))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *OAuth2ClientReconciler) updateRegisteredOAuth2Client(client *hydrav1alpha1.OAuth2Client) error {
|
||||||
|
_, err := r.HydraClient.PutOAuth2Client(client.ToOAuth2ClientJSON())
|
||||||
|
return err
|
||||||
|
}
|
197
controllers/oauth2client_controller_integration_test.go
Normal file
197
controllers/oauth2client_controller_integration_test.go
Normal file
@ -0,0 +1,197 @@
|
|||||||
|
package controllers_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
. "github.com/onsi/ginkgo"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
hydrav1alpha1 "github.com/ory/hydra-maester/api/v1alpha1"
|
||||||
|
"github.com/ory/hydra-maester/controllers"
|
||||||
|
"github.com/ory/hydra-maester/hydra"
|
||||||
|
apiv1 "k8s.io/api/core/v1"
|
||||||
|
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/types"
|
||||||
|
"k8s.io/client-go/kubernetes/scheme"
|
||||||
|
ctrl "sigs.k8s.io/controller-runtime"
|
||||||
|
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||||
|
"sigs.k8s.io/controller-runtime/pkg/controller"
|
||||||
|
"sigs.k8s.io/controller-runtime/pkg/handler"
|
||||||
|
"sigs.k8s.io/controller-runtime/pkg/manager"
|
||||||
|
"sigs.k8s.io/controller-runtime/pkg/reconcile"
|
||||||
|
"sigs.k8s.io/controller-runtime/pkg/source"
|
||||||
|
)
|
||||||
|
|
||||||
|
const timeout = time.Second * 5
|
||||||
|
|
||||||
|
var _ = Describe("OAuth2Client Controller", func() {
|
||||||
|
Context("in a happy-path scenario", func() {
|
||||||
|
|
||||||
|
var tstName = "test"
|
||||||
|
var tstNamespace = "default"
|
||||||
|
var tstScopes = "a b c"
|
||||||
|
var tstClientID = "testClientID"
|
||||||
|
var tstSecret = "testSecret"
|
||||||
|
|
||||||
|
var expectedRequest = reconcile.Request{NamespacedName: types.NamespacedName{Name: tstName, Namespace: tstNamespace}}
|
||||||
|
It("should call create OAuth2 client in Hydra and a Secret", func() {
|
||||||
|
|
||||||
|
s := scheme.Scheme
|
||||||
|
err := hydrav1alpha1.AddToScheme(s)
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
// Setup the Manager and Controller. Wrap the Controller Reconcile function so it writes each request to a
|
||||||
|
// channel when it is finished.
|
||||||
|
mgr, err := manager.New(cfg, manager.Options{Scheme: s})
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
c := mgr.GetClient()
|
||||||
|
|
||||||
|
mch := (&mockHydraClient{}).
|
||||||
|
withSecret(tstSecret).
|
||||||
|
withClientID(tstClientID)
|
||||||
|
|
||||||
|
recFn, requests := SetupTestReconcile(getAPIReconciler(mgr, mch))
|
||||||
|
//_, requests := SetupTestReconcile(getApiReconciler(mgr))
|
||||||
|
|
||||||
|
Expect(add(mgr, recFn)).To(Succeed())
|
||||||
|
|
||||||
|
//Start the manager and the controller
|
||||||
|
stopMgr, mgrStopped := StartTestManager(mgr)
|
||||||
|
|
||||||
|
//Ensure manager is stopped properly
|
||||||
|
defer func() {
|
||||||
|
close(stopMgr)
|
||||||
|
mgrStopped.Wait()
|
||||||
|
}()
|
||||||
|
|
||||||
|
instance := testInstance(tstName, tstNamespace, tstScopes)
|
||||||
|
err = c.Create(context.TODO(), instance)
|
||||||
|
// The instance object may not be a valid object because it might be missing some required fields.
|
||||||
|
// Please modify the instance object by adding required fields and then remove the following if statement.
|
||||||
|
if apierrors.IsInvalid(err) {
|
||||||
|
Fail(fmt.Sprintf("failed to create object, got an invalid object error: %v", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
defer c.Delete(context.TODO(), instance)
|
||||||
|
Eventually(requests, timeout).Should(Receive(Equal(expectedRequest)))
|
||||||
|
|
||||||
|
//Verify the created CR instance status
|
||||||
|
var retrieved hydrav1alpha1.OAuth2Client
|
||||||
|
ok := client.ObjectKey{Name: tstName, Namespace: tstNamespace}
|
||||||
|
err = c.Get(context.TODO(), ok, &retrieved)
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
Expect(*retrieved.Status.ClientID).To(Equal(tstClientID))
|
||||||
|
Expect(*retrieved.Status.Secret).To(Equal(tstName)) //Secret contents is not visible in the CR instance!
|
||||||
|
|
||||||
|
//Verify the created Secret
|
||||||
|
var createdSecret = apiv1.Secret{}
|
||||||
|
k8sClient.Get(context.TODO(), ok, &createdSecret)
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
Expect(createdSecret.Data["client_secret"]).To(Equal([]byte(tstSecret)))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
// add adds a new Controller to mgr with r as the reconcile.Reconciler
|
||||||
|
func add(mgr manager.Manager, r reconcile.Reconciler) error {
|
||||||
|
// Create a new controller
|
||||||
|
c, err := controller.New("api-gateway-controller", mgr, controller.Options{Reconciler: r})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Watch for changes to Api
|
||||||
|
err = c.Watch(&source.Kind{Type: &hydrav1alpha1.OAuth2Client{}}, &handler.EnqueueRequestForObject{})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(user): Modify this to be the types you create
|
||||||
|
// Uncomment watch a Deployment created by Guestbook - change this for objects you create
|
||||||
|
//err = c.Watch(&source.Kind{Type: &appsv1.Deployment{}}, &handler.EnqueueRequestForOwner{
|
||||||
|
// IsController: true,
|
||||||
|
// OwnerType: &webappv1.Guestbook{},
|
||||||
|
//})
|
||||||
|
//if err != nil {
|
||||||
|
// return err
|
||||||
|
//}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getAPIReconciler(mgr ctrl.Manager, mock *mockHydraClient) reconcile.Reconciler {
|
||||||
|
return &controllers.OAuth2ClientReconciler{
|
||||||
|
Client: mgr.GetClient(),
|
||||||
|
Log: ctrl.Log.WithName("controllers").WithName("OAuth2Client"),
|
||||||
|
HydraClient: mock,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testInstance(name, namespace, scopes string) *hydrav1alpha1.OAuth2Client {
|
||||||
|
|
||||||
|
return &hydrav1alpha1.OAuth2Client{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: name,
|
||||||
|
Namespace: namespace,
|
||||||
|
},
|
||||||
|
Spec: hydrav1alpha1.OAuth2ClientSpec{
|
||||||
|
GrantTypes: []hydrav1alpha1.GrantType{"client_credentials"},
|
||||||
|
ResponseTypes: []hydrav1alpha1.ResponseType{"token"},
|
||||||
|
Scope: scopes,
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO: Replace with full-fledged mocking framework (mockery/go-mock)
|
||||||
|
type mockHydraClient struct {
|
||||||
|
resSecret string
|
||||||
|
resClientID string
|
||||||
|
postedData *hydra.OAuth2ClientJSON
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockHydraClient) withSecret(secret string) *mockHydraClient {
|
||||||
|
m.resSecret = secret
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockHydraClient) withClientID(clientID string) *mockHydraClient {
|
||||||
|
m.resClientID = clientID
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
//Returns the data previously "stored" by PostOAuth2Client
|
||||||
|
func (m *mockHydraClient) GetOAuth2Client(id string) (*hydra.OAuth2ClientJSON, bool, error) {
|
||||||
|
res := &hydra.OAuth2ClientJSON{
|
||||||
|
ClientID: &m.resClientID,
|
||||||
|
Secret: &m.resSecret,
|
||||||
|
Name: m.postedData.Name,
|
||||||
|
GrantTypes: m.postedData.GrantTypes,
|
||||||
|
ResponseTypes: m.postedData.ResponseTypes,
|
||||||
|
Scope: m.postedData.Scope,
|
||||||
|
}
|
||||||
|
return res, true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockHydraClient) PostOAuth2Client(o *hydra.OAuth2ClientJSON) (*hydra.OAuth2ClientJSON, error) {
|
||||||
|
m.postedData = o
|
||||||
|
res := &hydra.OAuth2ClientJSON{
|
||||||
|
ClientID: &m.resClientID,
|
||||||
|
Secret: &m.resSecret,
|
||||||
|
Name: o.Name,
|
||||||
|
GrantTypes: o.GrantTypes,
|
||||||
|
ResponseTypes: o.ResponseTypes,
|
||||||
|
Scope: o.Scope,
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockHydraClient) DeleteOAuth2Client(id string) error {
|
||||||
|
panic("Should not be invoked!")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockHydraClient) PutOAuth2Client(o *hydra.OAuth2ClientJSON) (*hydra.OAuth2ClientJSON, error) {
|
||||||
|
panic("Should not be invoked!")
|
||||||
|
}
|
102
controllers/suite_test.go
Normal file
102
controllers/suite_test.go
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
/*
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package controllers_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"path/filepath"
|
||||||
|
"sync"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
. "github.com/onsi/ginkgo"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
|
||||||
|
"k8s.io/client-go/kubernetes/scheme"
|
||||||
|
"k8s.io/client-go/rest"
|
||||||
|
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||||
|
"sigs.k8s.io/controller-runtime/pkg/envtest"
|
||||||
|
logf "sigs.k8s.io/controller-runtime/pkg/log"
|
||||||
|
"sigs.k8s.io/controller-runtime/pkg/log/zap"
|
||||||
|
"sigs.k8s.io/controller-runtime/pkg/manager"
|
||||||
|
"sigs.k8s.io/controller-runtime/pkg/reconcile"
|
||||||
|
// +kubebuilder:scaffold:imports
|
||||||
|
)
|
||||||
|
|
||||||
|
// These tests use Ginkgo (BDD-style Go testing framework). Refer to
|
||||||
|
// http://onsi.github.io/ginkgo/ to learn more about Ginkgo.
|
||||||
|
|
||||||
|
var cfg *rest.Config
|
||||||
|
var k8sClient client.Client
|
||||||
|
var testEnv *envtest.Environment
|
||||||
|
|
||||||
|
func TestAPIs(t *testing.T) {
|
||||||
|
RegisterFailHandler(Fail)
|
||||||
|
|
||||||
|
RunSpecsWithDefaultAndCustomReporters(t,
|
||||||
|
"Controller Suite",
|
||||||
|
[]Reporter{envtest.NewlineReporter{}})
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ = BeforeSuite(func(done Done) {
|
||||||
|
logf.SetLogger(zap.LoggerTo(GinkgoWriter, true))
|
||||||
|
|
||||||
|
By("bootstrapping test environment")
|
||||||
|
testEnv = &envtest.Environment{
|
||||||
|
CRDDirectoryPaths: []string{filepath.Join("..", "config", "crd", "bases")},
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
cfg, err = testEnv.Start()
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
Expect(cfg).ToNot(BeNil())
|
||||||
|
|
||||||
|
// +kubebuilder:scaffold:scheme
|
||||||
|
|
||||||
|
k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme})
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
Expect(k8sClient).ToNot(BeNil())
|
||||||
|
|
||||||
|
close(done)
|
||||||
|
}, 60)
|
||||||
|
|
||||||
|
var _ = AfterSuite(func() {
|
||||||
|
By("tearing down the test environment")
|
||||||
|
err := testEnv.Stop()
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
})
|
||||||
|
|
||||||
|
// SetupTestReconcile returns a reconcile.Reconcile implementation that delegates to inner and
|
||||||
|
// writes the request to requests after Reconcile is finished.
|
||||||
|
func SetupTestReconcile(inner reconcile.Reconciler) (reconcile.Reconciler, chan reconcile.Request) {
|
||||||
|
requests := make(chan reconcile.Request)
|
||||||
|
fn := reconcile.Func(func(req reconcile.Request) (reconcile.Result, error) {
|
||||||
|
result, err := inner.Reconcile(req)
|
||||||
|
requests <- req
|
||||||
|
return result, err
|
||||||
|
})
|
||||||
|
return fn, requests
|
||||||
|
}
|
||||||
|
|
||||||
|
// StartTestManager adds recFn
|
||||||
|
func StartTestManager(mgr manager.Manager) (chan struct{}, *sync.WaitGroup) {
|
||||||
|
stop := make(chan struct{})
|
||||||
|
wg := &sync.WaitGroup{}
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
Expect(mgr.Start(stop)).NotTo(HaveOccurred())
|
||||||
|
}()
|
||||||
|
return stop, wg
|
||||||
|
}
|
16
go.mod
Normal file
16
go.mod
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
module github.com/ory/hydra-maester
|
||||||
|
|
||||||
|
go 1.12
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/go-logr/logr v0.1.0
|
||||||
|
github.com/onsi/ginkgo v1.6.0
|
||||||
|
github.com/onsi/gomega v1.4.2
|
||||||
|
github.com/stretchr/testify v1.3.0
|
||||||
|
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd
|
||||||
|
k8s.io/api v0.0.0-20190409021203-6e4e0e4f393b
|
||||||
|
k8s.io/apimachinery v0.0.0-20190404173353-6a84e37a896d
|
||||||
|
k8s.io/client-go v11.0.1-0.20190409021438-1a26190bd76a+incompatible
|
||||||
|
k8s.io/utils v0.0.0-20190506122338-8fab8cb257d5
|
||||||
|
sigs.k8s.io/controller-runtime v0.2.0-beta.2
|
||||||
|
)
|
126
go.sum
Normal file
126
go.sum
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
cloud.google.com/go v0.26.0 h1:e0WKqKTd5BnrG8aKH3J3h+QvEIQtSUcf2n5UZ5ZgLtQ=
|
||||||
|
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||||
|
github.com/appscode/jsonpatch v0.0.0-20190108182946-7c0e3b262f30 h1:Kn3rqvbUFqSepE2OqVu0Pn1CbDw9IuMlONapol0zuwk=
|
||||||
|
github.com/appscode/jsonpatch v0.0.0-20190108182946-7c0e3b262f30/go.mod h1:4AJxUpXUhv4N+ziTvIcWWXgeorXpxPZOfk9HdEVr96M=
|
||||||
|
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLMYoU8P317H5OQ+Via4RmuPwCS0=
|
||||||
|
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/evanphx/json-patch v4.0.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
|
||||||
|
github.com/evanphx/json-patch v4.1.0+incompatible h1:K1MDoo4AZ4wU0GIU/fPmtZg7VpzLjCxu+UwBD1FvwOc=
|
||||||
|
github.com/evanphx/json-patch v4.1.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
|
||||||
|
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
|
||||||
|
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||||
|
github.com/go-logr/logr v0.1.0 h1:M1Tv3VzNlEHg6uyACnRdtrploV2P7wZqH8BoQMtz0cg=
|
||||||
|
github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas=
|
||||||
|
github.com/go-logr/zapr v0.1.0 h1:h+WVe9j6HAA01niTJPA/kKH0i7e0rLZBCwauQFcRE54=
|
||||||
|
github.com/go-logr/zapr v0.1.0/go.mod h1:tabnROwaDl0UNxkVeFRbY8bwB37GwRv0P8lg6aAiEnk=
|
||||||
|
github.com/gogo/protobuf v1.1.1 h1:72R+M5VuhED/KujmZVcIquuo8mBgX4oVda//DQb3PXo=
|
||||||
|
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||||
|
github.com/golang/groupcache v0.0.0-20180513044358-24b0969c4cb7 h1:u4bArs140e9+AfE52mFHOXVFnOSBJBRlzTHrOPLOIhE=
|
||||||
|
github.com/golang/groupcache v0.0.0-20180513044358-24b0969c4cb7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||||
|
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
|
||||||
|
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf h1:+RRA9JqSOZFfKrOeqr2z77+8R2RKyh8PG66dcu1V0ck=
|
||||||
|
github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI=
|
||||||
|
github.com/googleapis/gnostic v0.2.0 h1:l6N3VoaVzTncYYW+9yOz2LJJammFZGBO13sqgEhpy9g=
|
||||||
|
github.com/googleapis/gnostic v0.2.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
|
||||||
|
github.com/hashicorp/golang-lru v0.0.0-20180201235237-0fb14efe8c47 h1:UnszMmmmm5vLwWzDjTFVIkfhvWF1NdrmChl8L2NUDCw=
|
||||||
|
github.com/hashicorp/golang-lru v0.0.0-20180201235237-0fb14efe8c47/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||||
|
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
|
||||||
|
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||||
|
github.com/imdario/mergo v0.3.6 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28=
|
||||||
|
github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
|
||||||
|
github.com/json-iterator/go v1.1.5 h1:gL2yXlmiIo4+t+y32d4WGwOjKGYcGOuyrg46vadswDE=
|
||||||
|
github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||||
|
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||||
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
|
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||||
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
|
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
|
||||||
|
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
|
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg=
|
||||||
|
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||||
|
github.com/onsi/ginkgo v1.6.0 h1:Ix8l273rp3QzYgXSR+c8d1fTG7UPgYkOSELPhiY/YGw=
|
||||||
|
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||||
|
github.com/onsi/gomega v1.4.2 h1:3mYCb7aPxS/RU7TI1y4rkEn1oKmPRjNJLNEXgw7MH2I=
|
||||||
|
github.com/onsi/gomega v1.4.2/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||||
|
github.com/pborman/uuid v0.0.0-20170612153648-e790cca94e6c h1:MUyE44mTvnI5A0xrxIxaMqoWFzPfQvtE2IWUollMDMs=
|
||||||
|
github.com/pborman/uuid v0.0.0-20170612153648-e790cca94e6c/go.mod h1:VyrYX9gd7irzKovcSS6BIIEwPRkP2Wm2m9ufcdFSJ34=
|
||||||
|
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||||
|
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/prometheus/client_golang v0.9.0 h1:tXuTFVHC03mW0D+Ua1Q2d1EAVqLTuggX50V0VLICCzY=
|
||||||
|
github.com/prometheus/client_golang v0.9.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||||
|
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910 h1:idejC8f05m9MGOsuEi1ATq9shN03HrxNkD/luQvxCv8=
|
||||||
|
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||||
|
github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e h1:n/3MEhJQjQxrOUCzh1Y3Re6aJUUWRp2M9+Oc3eVn/54=
|
||||||
|
github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
||||||
|
github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273 h1:agujYaXJSxSo18YNX3jzl+4G6Bstwt+kqv47GS12uL0=
|
||||||
|
github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||||
|
github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
|
||||||
|
github.com/spf13/pflag v1.0.2 h1:Fy0orTDgHdbnzHcsOgfCN4LtHf0ec3wwtiwJqwvf3Gc=
|
||||||
|
github.com/spf13/pflag v1.0.2/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
|
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
|
||||||
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
|
go.uber.org/atomic v1.3.2 h1:2Oa65PReHzfn29GpvgsYwloV9AVFHPDk8tYxt2c2tr4=
|
||||||
|
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||||
|
go.uber.org/multierr v1.1.0 h1:HoEmRHQPVSqub6w2z2d2EOVs2fjyFRGyofhKuyDq0QI=
|
||||||
|
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
||||||
|
go.uber.org/zap v1.9.1 h1:XCJQEf3W6eZaVwhRBof6ImoYGJSITeKWsyeh3HFu/5o=
|
||||||
|
go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||||
|
golang.org/x/crypto v0.0.0-20180820150726-614d502a4dac h1:7d7lG9fHOLdL6jZPtnV4LpI41SbohIJ1Atq7U991dMg=
|
||||||
|
golang.org/x/crypto v0.0.0-20180820150726-614d502a4dac/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||||
|
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd h1:nTDtHvHSdCn1m6ITfMRqtOd/9+7a3s8RBNOZ3eYZzJA=
|
||||||
|
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be h1:vEDujvNQGv4jgYKudGeI/+DAX4Jffq6hpD55MmoEvKs=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
|
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
|
||||||
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e h1:o3PsSEY8E4eXWkXrIP9YJALUkVZqzHJT5DOasTyn8Vs=
|
||||||
|
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||||
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2 h1:+DCIGbF/swA92ohVg0//6X2IVY3KZs6p9mix0ziNYJM=
|
||||||
|
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
|
google.golang.org/appengine v1.1.0 h1:igQkv0AAhEIvTEpD5LIpAfav2eeVO9HBTjvKHVJPRSs=
|
||||||
|
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
|
||||||
|
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||||
|
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
|
||||||
|
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
||||||
|
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||||
|
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||||
|
gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
|
||||||
|
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
k8s.io/api v0.0.0-20190409021203-6e4e0e4f393b h1:aBGgKJUM9Hk/3AE8WaZIApnTxG35kbuQba2w+SXqezo=
|
||||||
|
k8s.io/api v0.0.0-20190409021203-6e4e0e4f393b/go.mod h1:iuAfoD4hCxJ8Onx9kaTIt30j7jUFS00AXQi6QMi99vA=
|
||||||
|
k8s.io/apiextensions-apiserver v0.0.0-20190409022649-727a075fdec8 h1:q1Qvjzs/iEdXF6A1a8H3AKVFDzJNcJn3nXMs6R6qFtA=
|
||||||
|
k8s.io/apiextensions-apiserver v0.0.0-20190409022649-727a075fdec8/go.mod h1:IxkesAMoaCRoLrPJdZNZUQp9NfZnzqaVzLhb2VEQzXE=
|
||||||
|
k8s.io/apimachinery v0.0.0-20190404173353-6a84e37a896d h1:Jmdtdt1ZnoGfWWIIik61Z7nKYgO3J+swQJtPYsP9wHA=
|
||||||
|
k8s.io/apimachinery v0.0.0-20190404173353-6a84e37a896d/go.mod h1:ccL7Eh7zubPUSh9A3USN90/OzHNSVN6zxzde07TDCL0=
|
||||||
|
k8s.io/client-go v11.0.1-0.20190409021438-1a26190bd76a+incompatible h1:U5Bt+dab9K8qaUmXINrkXO135kA11/i5Kg1RUydgaMQ=
|
||||||
|
k8s.io/client-go v11.0.1-0.20190409021438-1a26190bd76a+incompatible/go.mod h1:7vJpHMYJwNQCWgzmNV+VYUl1zCObLyodBc8nIyt8L5s=
|
||||||
|
k8s.io/klog v0.3.0 h1:0VPpR+sizsiivjIfIAQH/rl8tan6jvWkS7lU+0di3lE=
|
||||||
|
k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
|
||||||
|
k8s.io/kube-openapi v0.0.0-20180731170545-e3762e86a74c h1:3KSCztE7gPitlZmWbNwue/2U0YruD65DqX3INopDAQM=
|
||||||
|
k8s.io/kube-openapi v0.0.0-20180731170545-e3762e86a74c/go.mod h1:BXM9ceUBTj2QnfH2MK1odQs778ajze1RxcmP6S8RVVc=
|
||||||
|
k8s.io/utils v0.0.0-20190506122338-8fab8cb257d5 h1:VBM/0P5TWxwk+Nw6Z+lAw3DKgO76g90ETOiA6rfLV1Y=
|
||||||
|
k8s.io/utils v0.0.0-20190506122338-8fab8cb257d5/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew=
|
||||||
|
sigs.k8s.io/controller-runtime v0.2.0-beta.2 h1:hOWldx1qmGI9TsU+uUsq1xTgVmUV7AZo08VAYX0dwGI=
|
||||||
|
sigs.k8s.io/controller-runtime v0.2.0-beta.2/go.mod h1:TSH2R0nSz4WAlUUlNnOFcOR/VUhfwBLlmtq2X6AiQCA=
|
||||||
|
sigs.k8s.io/testing_frameworks v0.1.1 h1:cP2l8fkA3O9vekpy5Ks8mmA0NW/F7yBdXf8brkWhVrs=
|
||||||
|
sigs.k8s.io/testing_frameworks v0.1.1/go.mod h1:VVBKrHmJ6Ekkfz284YKhQePcdycOzNH9qL6ht1zEr/U=
|
||||||
|
sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs=
|
||||||
|
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
|
14
hack/boilerplate.go.txt
Normal file
14
hack/boilerplate.go.txt
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
/*
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
149
hydra/client.go
Normal file
149
hydra/client.go
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
package hydra
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"path"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Client struct {
|
||||||
|
HydraURL url.URL
|
||||||
|
HTTPClient *http.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) GetOAuth2Client(id string) (*OAuth2ClientJSON, bool, error) {
|
||||||
|
|
||||||
|
var jsonClient *OAuth2ClientJSON
|
||||||
|
|
||||||
|
req, err := c.newRequest(http.MethodGet, id, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := c.do(req, &jsonClient)
|
||||||
|
if err != nil {
|
||||||
|
return nil, false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch resp.StatusCode {
|
||||||
|
case http.StatusOK:
|
||||||
|
return jsonClient, true, nil
|
||||||
|
case http.StatusNotFound:
|
||||||
|
return nil, false, nil
|
||||||
|
default:
|
||||||
|
return nil, false, fmt.Errorf("%s %s http request returned unexpected status code %s", req.Method, req.URL.String(), resp.Status)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) PostOAuth2Client(o *OAuth2ClientJSON) (*OAuth2ClientJSON, error) {
|
||||||
|
|
||||||
|
var jsonClient *OAuth2ClientJSON
|
||||||
|
|
||||||
|
req, err := c.newRequest(http.MethodPost, "", o)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := c.do(req, &jsonClient)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch resp.StatusCode {
|
||||||
|
case http.StatusCreated:
|
||||||
|
return jsonClient, nil
|
||||||
|
case http.StatusConflict:
|
||||||
|
return nil, fmt.Errorf(" %s %s http request failed: requested ID already exists", req.Method, req.URL)
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("%s %s http request returned unexpected status code: %s", req.Method, req.URL, resp.Status)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) PutOAuth2Client(o *OAuth2ClientJSON) (*OAuth2ClientJSON, error) {
|
||||||
|
|
||||||
|
var jsonClient *OAuth2ClientJSON
|
||||||
|
|
||||||
|
req, err := c.newRequest(http.MethodPut, *o.ClientID, o)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := c.do(req, &jsonClient)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return nil, fmt.Errorf("%s %s http request returned unexpected status code: %s", req.Method, req.URL, resp.Status)
|
||||||
|
}
|
||||||
|
|
||||||
|
return jsonClient, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) DeleteOAuth2Client(id string) error {
|
||||||
|
|
||||||
|
req, err := c.newRequest(http.MethodDelete, id, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := c.do(req, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch resp.StatusCode {
|
||||||
|
case http.StatusNoContent:
|
||||||
|
return nil
|
||||||
|
case http.StatusNotFound:
|
||||||
|
fmt.Printf("client with id %s does not exist", id)
|
||||||
|
return nil
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("%s %s http request returned unexpected status code %s", req.Method, req.URL.String(), resp.Status)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) newRequest(method, relativePath string, body interface{}) (*http.Request, error) {
|
||||||
|
|
||||||
|
var buf io.ReadWriter
|
||||||
|
if body != nil {
|
||||||
|
buf = new(bytes.Buffer)
|
||||||
|
err := json.NewEncoder(buf).Encode(body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
u := c.HydraURL
|
||||||
|
u.Path = path.Join(u.Path, relativePath)
|
||||||
|
|
||||||
|
req, err := http.NewRequest(method, u.String(), buf)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if body != nil {
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
}
|
||||||
|
req.Header.Set("Accept", "application/json")
|
||||||
|
|
||||||
|
return req, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) do(req *http.Request, v interface{}) (*http.Response, error) {
|
||||||
|
resp, err := c.HTTPClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer resp.Body.Close()
|
||||||
|
if v != nil {
|
||||||
|
err = json.NewDecoder(resp.Body).Decode(v)
|
||||||
|
}
|
||||||
|
return resp, err
|
||||||
|
}
|
269
hydra/client_test.go
Normal file
269
hydra/client_test.go
Normal file
@ -0,0 +1,269 @@
|
|||||||
|
package hydra_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"net/url"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"k8s.io/utils/pointer"
|
||||||
|
|
||||||
|
"github.com/ory/hydra-maester/hydra"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
testID = "test-id"
|
||||||
|
schemeHTTP = "http"
|
||||||
|
testClient = `{"client_id":"test-id","client_name":"test-name","scope":"some,scopes","grant_types":["type1"]}`
|
||||||
|
testClientCreated = `{"client_id":"test-id-2", "client_secret": "TmGkvcY7k526","client_name":"test-name-2","scope":"some,other,scopes","grant_types":["type2"]}`
|
||||||
|
testClientUpdated = `{"client_id":"test-id-3", "client_secret": "xFoPPm654por","client_name":"test-name-3","scope":"yet,another,scope","grant_types":["type3"]}`
|
||||||
|
emptyBody = `{}`
|
||||||
|
clientsEndpoint = "/clients"
|
||||||
|
)
|
||||||
|
|
||||||
|
type server struct {
|
||||||
|
statusCode int
|
||||||
|
respBody string
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
var testOAuthJSONPost = &hydra.OAuth2ClientJSON{
|
||||||
|
Name: "test-name-2",
|
||||||
|
Scope: "some,other,scopes",
|
||||||
|
GrantTypes: []string{"type2"},
|
||||||
|
}
|
||||||
|
|
||||||
|
var testOAuthJSONPut = &hydra.OAuth2ClientJSON{
|
||||||
|
ClientID: pointer.StringPtr("test-id-3"),
|
||||||
|
Name: "test-name-3",
|
||||||
|
Scope: "yet,another,scope",
|
||||||
|
GrantTypes: []string{"type3"},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCRUD(t *testing.T) {
|
||||||
|
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
c := hydra.Client{
|
||||||
|
HTTPClient: &http.Client{},
|
||||||
|
HydraURL: url.URL{Scheme: schemeHTTP},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("method=get", func(t *testing.T) {
|
||||||
|
|
||||||
|
for d, tc := range map[string]server{
|
||||||
|
"getting registered client": {
|
||||||
|
http.StatusOK,
|
||||||
|
testClient,
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
"getting unregistered client": {
|
||||||
|
http.StatusNotFound,
|
||||||
|
emptyBody,
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
"internal server error when requesting": {
|
||||||
|
http.StatusInternalServerError,
|
||||||
|
emptyBody,
|
||||||
|
errors.New("http request returned unexpected status code"),
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
t.Run(fmt.Sprintf("case/%s", d), func(t *testing.T) {
|
||||||
|
|
||||||
|
//given
|
||||||
|
shouldFind := tc.statusCode == http.StatusOK
|
||||||
|
|
||||||
|
h := http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||||
|
assert.Equal(fmt.Sprintf("%s/%s", c.HydraURL.String(), testID), fmt.Sprintf("%s://%s%s", schemeHTTP, req.Host, req.URL.Path))
|
||||||
|
assert.Equal(http.MethodGet, req.Method)
|
||||||
|
w.WriteHeader(tc.statusCode)
|
||||||
|
w.Write([]byte(tc.respBody))
|
||||||
|
if shouldFind {
|
||||||
|
w.Header().Set("Content-type", "application/json")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
runServer(&c, h)
|
||||||
|
|
||||||
|
//when
|
||||||
|
o, found, err := c.GetOAuth2Client(testID)
|
||||||
|
|
||||||
|
//then
|
||||||
|
if tc.err == nil {
|
||||||
|
require.NoError(t, err)
|
||||||
|
} else {
|
||||||
|
require.Error(t, err)
|
||||||
|
assert.Contains(err.Error(), tc.err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(shouldFind, found)
|
||||||
|
if shouldFind {
|
||||||
|
require.NotNil(t, o)
|
||||||
|
var expected hydra.OAuth2ClientJSON
|
||||||
|
json.Unmarshal([]byte(testClient), &expected)
|
||||||
|
assert.Equal(&expected, o)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("method=post", func(t *testing.T) {
|
||||||
|
|
||||||
|
for d, tc := range map[string]server{
|
||||||
|
"with new client": {
|
||||||
|
http.StatusCreated,
|
||||||
|
testClientCreated,
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
"with existing client": {
|
||||||
|
http.StatusConflict,
|
||||||
|
emptyBody,
|
||||||
|
errors.New("requested ID already exists"),
|
||||||
|
},
|
||||||
|
"internal server error when requesting": {
|
||||||
|
http.StatusInternalServerError,
|
||||||
|
emptyBody,
|
||||||
|
errors.New("http request returned unexpected status code"),
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
t.Run(fmt.Sprintf("case/%s", d), func(t *testing.T) {
|
||||||
|
|
||||||
|
//given
|
||||||
|
new := tc.statusCode == http.StatusCreated
|
||||||
|
|
||||||
|
h := http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||||
|
assert.Equal(c.HydraURL.String(), fmt.Sprintf("%s://%s%s", schemeHTTP, req.Host, req.URL.Path))
|
||||||
|
assert.Equal(http.MethodPost, req.Method)
|
||||||
|
w.WriteHeader(tc.statusCode)
|
||||||
|
w.Write([]byte(tc.respBody))
|
||||||
|
if new {
|
||||||
|
w.Header().Set("Content-type", "application/json")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
runServer(&c, h)
|
||||||
|
|
||||||
|
//when
|
||||||
|
o, err := c.PostOAuth2Client(testOAuthJSONPost)
|
||||||
|
|
||||||
|
//then
|
||||||
|
if tc.err == nil {
|
||||||
|
require.NoError(t, err)
|
||||||
|
} else {
|
||||||
|
require.Error(t, err)
|
||||||
|
assert.Contains(err.Error(), tc.err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if new {
|
||||||
|
require.NotNil(t, o)
|
||||||
|
|
||||||
|
assert.Equal(testOAuthJSONPost.Name, o.Name)
|
||||||
|
assert.Equal(testOAuthJSONPost.Scope, o.Scope)
|
||||||
|
assert.Equal(testOAuthJSONPost.GrantTypes, o.GrantTypes)
|
||||||
|
assert.NotNil(o.Secret)
|
||||||
|
assert.NotNil(o.ClientID)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("method=put", func(t *testing.T) {
|
||||||
|
for d, tc := range map[string]server{
|
||||||
|
"with registered client": {
|
||||||
|
http.StatusOK,
|
||||||
|
testClientUpdated,
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
"internal server error when requesting": {
|
||||||
|
http.StatusInternalServerError,
|
||||||
|
emptyBody,
|
||||||
|
errors.New("http request returned unexpected status code"),
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
t.Run(fmt.Sprintf("case/%s", d), func(t *testing.T) {
|
||||||
|
|
||||||
|
ok := tc.statusCode == http.StatusOK
|
||||||
|
|
||||||
|
//given
|
||||||
|
h := http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||||
|
assert.Equal(fmt.Sprintf("%s/%s", c.HydraURL.String(), *testOAuthJSONPut.ClientID), fmt.Sprintf("%s://%s%s", schemeHTTP, req.Host, req.URL.Path))
|
||||||
|
assert.Equal(http.MethodPut, req.Method)
|
||||||
|
w.WriteHeader(tc.statusCode)
|
||||||
|
w.Write([]byte(tc.respBody))
|
||||||
|
if ok {
|
||||||
|
w.Header().Set("Content-type", "application/json")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
runServer(&c, h)
|
||||||
|
|
||||||
|
//when
|
||||||
|
o, err := c.PutOAuth2Client(testOAuthJSONPut)
|
||||||
|
|
||||||
|
//then
|
||||||
|
if tc.err == nil {
|
||||||
|
require.NoError(t, err)
|
||||||
|
} else {
|
||||||
|
require.Error(t, err)
|
||||||
|
assert.Contains(err.Error(), tc.err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if ok {
|
||||||
|
require.NotNil(t, o)
|
||||||
|
|
||||||
|
assert.Equal(testOAuthJSONPut.Name, o.Name)
|
||||||
|
assert.Equal(testOAuthJSONPut.Scope, o.Scope)
|
||||||
|
assert.Equal(testOAuthJSONPut.GrantTypes, o.GrantTypes)
|
||||||
|
assert.Equal(testOAuthJSONPut.ClientID, o.ClientID)
|
||||||
|
assert.NotNil(o.Secret)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("method=delete", func(t *testing.T) {
|
||||||
|
|
||||||
|
for d, tc := range map[string]server{
|
||||||
|
"with registered client": {
|
||||||
|
statusCode: http.StatusNoContent,
|
||||||
|
},
|
||||||
|
"with unregistered client": {
|
||||||
|
statusCode: http.StatusNotFound,
|
||||||
|
},
|
||||||
|
"internal server error when requesting": {
|
||||||
|
statusCode: http.StatusInternalServerError,
|
||||||
|
err: errors.New("http request returned unexpected status code"),
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
t.Run(fmt.Sprintf("case/%s", d), func(t *testing.T) {
|
||||||
|
|
||||||
|
//given
|
||||||
|
h := http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||||
|
assert.Equal(fmt.Sprintf("%s/%s", c.HydraURL.String(), testID), fmt.Sprintf("%s://%s%s", schemeHTTP, req.Host, req.URL.Path))
|
||||||
|
assert.Equal(http.MethodDelete, req.Method)
|
||||||
|
w.WriteHeader(tc.statusCode)
|
||||||
|
})
|
||||||
|
runServer(&c, h)
|
||||||
|
|
||||||
|
//when
|
||||||
|
err := c.DeleteOAuth2Client(testID)
|
||||||
|
|
||||||
|
//then
|
||||||
|
if tc.err == nil {
|
||||||
|
require.NoError(t, err)
|
||||||
|
} else {
|
||||||
|
require.Error(t, err)
|
||||||
|
assert.Contains(err.Error(), tc.err.Error())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func runServer(c *hydra.Client, h http.HandlerFunc) {
|
||||||
|
s := httptest.NewServer(h)
|
||||||
|
serverUrl, _ := url.Parse(s.URL)
|
||||||
|
c.HydraURL = *serverUrl.ResolveReference(&url.URL{Path: clientsEndpoint})
|
||||||
|
}
|
11
hydra/types.go
Normal file
11
hydra/types.go
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
package hydra
|
||||||
|
|
||||||
|
// OAuth2ClientJSON represents an OAuth2 client digestible by ORY Hydra
|
||||||
|
type OAuth2ClientJSON struct {
|
||||||
|
ClientID *string `json:"client_id,omitempty"`
|
||||||
|
Name string `json:"client_name"`
|
||||||
|
GrantTypes []string `json:"grant_types"`
|
||||||
|
ResponseTypes []string `json:"response_types,omitempty"`
|
||||||
|
Scope string `json:"scope"`
|
||||||
|
Secret *string `json:"client_secret,omitempty"`
|
||||||
|
}
|
106
main.go
Normal file
106
main.go
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
/*
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/ory/hydra-maester/hydra"
|
||||||
|
|
||||||
|
hydrav1alpha1 "github.com/ory/hydra-maester/api/v1alpha1"
|
||||||
|
"github.com/ory/hydra-maester/controllers"
|
||||||
|
apiv1 "k8s.io/api/core/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
_ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
|
||||||
|
ctrl "sigs.k8s.io/controller-runtime"
|
||||||
|
"sigs.k8s.io/controller-runtime/pkg/log/zap"
|
||||||
|
// +kubebuilder:scaffold:imports
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
scheme = runtime.NewScheme()
|
||||||
|
setupLog = ctrl.Log.WithName("setup")
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
|
||||||
|
apiv1.AddToScheme(scheme)
|
||||||
|
hydrav1alpha1.AddToScheme(scheme)
|
||||||
|
// +kubebuilder:scaffold:scheme
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
var metricsAddr string
|
||||||
|
var hydraURL string
|
||||||
|
var port int
|
||||||
|
var endpoint string
|
||||||
|
var enableLeaderElection bool
|
||||||
|
|
||||||
|
flag.StringVar(&metricsAddr, "metrics-addr", ":8080", "The address the metric endpoint binds to.")
|
||||||
|
flag.StringVar(&hydraURL, "hydra-url", "", "The address of ORY Hydra")
|
||||||
|
flag.IntVar(&port, "port", 4445, "Port ORY Hydra is listening on")
|
||||||
|
flag.StringVar(&endpoint, "endpoint", "/clients", "ORY Hydra's client endpoint")
|
||||||
|
flag.BoolVar(&enableLeaderElection, "enable-leader-election", false,
|
||||||
|
"Enable leader election for controller manager. Enabling this will ensure there is only one active controller manager.")
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
ctrl.SetLogger(zap.Logger(true))
|
||||||
|
|
||||||
|
mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{
|
||||||
|
Scheme: scheme,
|
||||||
|
MetricsBindAddress: metricsAddr,
|
||||||
|
LeaderElection: enableLeaderElection,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
setupLog.Error(err, "unable to start manager")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if hydraURL == "" {
|
||||||
|
setupLog.Error(fmt.Errorf("hydra service address can't be empty"), "unable to create controller", "controller", "OAuth2Client")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
u, err := url.Parse(fmt.Sprintf("%s:%d", hydraURL, port))
|
||||||
|
if err != nil {
|
||||||
|
setupLog.Error(err, "unable to parse ORY Hydra's URL", "controller", "OAuth2Client")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = (&controllers.OAuth2ClientReconciler{
|
||||||
|
Client: mgr.GetClient(),
|
||||||
|
Log: ctrl.Log.WithName("controllers").WithName("OAuth2Client"),
|
||||||
|
HydraClient: &hydra.Client{
|
||||||
|
HydraURL: *u.ResolveReference(&url.URL{Path: endpoint}),
|
||||||
|
HTTPClient: &http.Client{},
|
||||||
|
},
|
||||||
|
}).SetupWithManager(mgr)
|
||||||
|
if err != nil {
|
||||||
|
setupLog.Error(err, "unable to create controller", "controller", "OAuth2Client")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
// +kubebuilder:scaffold:builder
|
||||||
|
|
||||||
|
setupLog.Info("starting manager")
|
||||||
|
if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil {
|
||||||
|
setupLog.Error(err, "problem running manager")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user