api + basic logic + hydra client crud + validation + happy path integration test

This commit is contained in:
Jakub Kabza
2019-08-21 12:10:25 +02:00
parent dfb5974746
commit cd78361bf7
20 changed files with 1484 additions and 169 deletions

View File

@ -16,25 +16,53 @@ limitations under the License.
package v1alpha1
import (
"github.com/ory/hydra-maester/hydra"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN!
// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized.
// OAuth2ClientSpec defines the desired state of OAuth2Client
type OAuth2ClientSpec struct {
// INSERT ADDITIONAL SPEC FIELDS - desired state of cluster
// Important: Run "make" to regenerate code after modifying this file
// +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 {
// INSERT ADDITIONAL STATUS FIELD - define observed state of cluster
// Important: Run "make" to regenerate code after modifying this file
// 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 {
@ -57,3 +85,30 @@ type OAuth2ClientList struct {
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
}

View File

@ -16,61 +16,114 @@ limitations under the License.
package v1alpha1
import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"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"
)
// These tests are written in BDD-style using Ginkgo framework. Refer to
// http://onsi.github.io/ginkgo to learn more.
var (
k8sClient client.Client
cfg *rest.Config
testEnv *envtest.Environment
key types.NamespacedName
created, fetched *OAuth2Client
createErr, getErr, deleteErr error
)
var _ = Describe("OAuth2Client", func() {
var (
key types.NamespacedName
created, fetched *OAuth2Client
)
func TestCreateAPI(t *testing.T) {
BeforeEach(func() {
// Add any setup steps that needs to be executed before each test
})
runEnv(t)
defer stopEnv(t)
AfterEach(func() {
// Add any teardown steps that needs to be executed after each test
})
t.Run("should handle an object properly", func(t *testing.T) {
// Add Tests for OpenAPI validation (or additonal CRD features) specified in
// your API definition.
// Avoid adding tests for vanilla CRUD operations because they would
// test Kubernetes API server, which isn't the goal here.
Context("Create API", func() {
key = types.NamespacedName{
Name: "foo",
Namespace: "default",
}
It("should create an object successfully", func() {
t.Run("by creating an API object if it meets CRD requirements", func(t *testing.T) {
key = types.NamespacedName{
Name: "foo",
Namespace: "default",
}
created = &OAuth2Client{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
Namespace: "default",
}}
resetTestClient()
By("creating an API obj")
Expect(k8sClient.Create(context.TODO(), created)).To(Succeed())
createErr = k8sClient.Create(context.TODO(), created)
require.NoError(t, createErr)
fetched = &OAuth2Client{}
Expect(k8sClient.Get(context.TODO(), key, fetched)).To(Succeed())
Expect(fetched).To(Equal(created))
getErr = k8sClient.Get(context.TODO(), key, fetched)
require.NoError(t, getErr)
assert.Equal(t, created, fetched)
By("deleting the created object")
Expect(k8sClient.Delete(context.TODO(), created)).To(Succeed())
Expect(k8sClient.Get(context.TODO(), key, created)).ToNot(Succeed())
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",
},
}
}

View File

@ -1,74 +0,0 @@
/*
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 (
"path/filepath"
"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"
)
// 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,
"v1alpha1 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")},
}
err := SchemeBuilder.AddToScheme(scheme.Scheme)
Expect(err).NotTo(HaveOccurred())
cfg, err = testEnv.Start()
Expect(err).ToNot(HaveOccurred())
Expect(cfg).ToNot(BeNil())
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())
})

View File

@ -28,8 +28,8 @@ func (in *OAuth2Client) DeepCopyInto(out *OAuth2Client) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
out.Spec = in.Spec
out.Status = in.Status
in.Spec.DeepCopyInto(&out.Spec)
in.Status.DeepCopyInto(&out.Status)
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OAuth2Client.
@ -85,6 +85,16 @@ func (in *OAuth2ClientList) DeepCopyObject() runtime.Object {
// 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.
@ -100,6 +110,16 @@ func (in *OAuth2ClientSpec) DeepCopy() *OAuth2ClientSpec {
// 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.