Merge pull request #12 from jakkab/scaffold-controller
First controller version
This commit is contained in:
commit
0cecfbbe41
@ -1,9 +1,5 @@
|
||||
version: 2
|
||||
jobs:
|
||||
aloha:
|
||||
machine: true
|
||||
steps:
|
||||
- run: echo "Aloha! ;-)"
|
||||
build:
|
||||
docker:
|
||||
- image: circleci/golang:1.12
|
||||
@ -145,36 +141,31 @@ workflows:
|
||||
version: 2
|
||||
"test, build and release":
|
||||
jobs:
|
||||
- aloha:
|
||||
- build:
|
||||
filters:
|
||||
tags:
|
||||
only: /.*/
|
||||
# ENABLE IT once controller with make target will be created
|
||||
# - build:
|
||||
# filters:
|
||||
# tags:
|
||||
# only: /.*/
|
||||
# - test-integration:
|
||||
# filters:
|
||||
# tags:
|
||||
# only: /.*/
|
||||
# - test:
|
||||
# filters:
|
||||
# tags:
|
||||
# only: /.*/
|
||||
# - release:
|
||||
# requires:
|
||||
# - test
|
||||
# filters:
|
||||
# tags:
|
||||
# only: /.*/
|
||||
# branches:
|
||||
# ignore: /.*/
|
||||
# - release-changelog:
|
||||
# requires:
|
||||
# - release
|
||||
# filters:
|
||||
# tags:
|
||||
# only: /.*/
|
||||
# branches:
|
||||
# ignore: /.*/
|
||||
- test-integration:
|
||||
filters:
|
||||
tags:
|
||||
only: /.*/
|
||||
- test:
|
||||
filters:
|
||||
tags:
|
||||
only: /.*/
|
||||
- release:
|
||||
requires:
|
||||
- test
|
||||
filters:
|
||||
tags:
|
||||
only: /.*/
|
||||
branches:
|
||||
ignore: /.*/
|
||||
- release-changelog:
|
||||
requires:
|
||||
- release
|
||||
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.
|
||||
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