Merge remote-tracking branch 'origin/master' into containers
This commit is contained in:
commit
dcd52deb2c
1
Makefile
1
Makefile
|
|
@ -54,4 +54,3 @@ include build/makelib/image.mk
|
|||
# Generate manifests e.g. CRD, RBAC etc.
|
||||
manifests:
|
||||
go run vendor/sigs.k8s.io/controller-tools/cmd/controller-gen/main.go crd
|
||||
|
||||
|
|
|
|||
39
ROADMAP.md
39
ROADMAP.md
|
|
@ -8,5 +8,42 @@ We hope that the items listed below will inspire further engagement from the com
|
|||
Any dates listed below and the specific issues that will ship in a given milestone are subject to change but should give a general idea of what we are planning.
|
||||
We use the [milestone](https://github.com/upbound/conductor/milestones) feature in Github so look there for the most up-to-date and issue plan.
|
||||
|
||||
## Conductor 0.1
|
||||
## v0.1
|
||||
|
||||
* MySQL support for AWS, GCP, and Azure
|
||||
* Provider CRDs, credentials management, API/SDK consumption
|
||||
* Provider specific MySQL CRDs (Amazon RDS, Google Cloud SQL, Microsoft Azure Database for MySQL)
|
||||
* All 3 big cloud providers will be supported for all resources going forward
|
||||
* PostgreSQL support for AWS, GCP, and Azure
|
||||
* same work items as MySQL support
|
||||
* Controller depth and reliability
|
||||
* Full CRUD support for all resources (robust lifecycle management)
|
||||
* CRD status Conditions for status of resources
|
||||
* Event recording
|
||||
* Normalized logging using single logging solution (with configurable levels)
|
||||
* Retry/recovery from failure, idempotence, dealing with partial state
|
||||
* CI builds/tests/releases
|
||||
* New isolated jenkins instance (similar to Rook's jenkins)
|
||||
* Developer unit testing with high code coverage
|
||||
* Integration testing pipeline
|
||||
* Artifact publishing (container images, conductor helm chart, etc.)
|
||||
* Documentation
|
||||
* User guides, quick-starts, walkthroughs
|
||||
* Godocs developer docs for source code/packages/libraries
|
||||
* Open source project management
|
||||
* [CII best practices checklist](https://bestpractices.coreinfrastructure.org/en/projects/1599#)
|
||||
* Governance
|
||||
* Contributor License Agreement (CLA) or Developer Certificate of Origin (DCO)
|
||||
|
||||
## v0.2
|
||||
|
||||
* Support for other SockShop resources
|
||||
* MongoDB
|
||||
* Redis
|
||||
* RabbitMQ
|
||||
* Support for Clusters, deploy and manage clusters lifecycle via CRDs
|
||||
* Federation, deploy resources to target clusters (possibly a new project)
|
||||
* Performance and Efficiency
|
||||
* 2-way reconciliation with external resources
|
||||
* Events/notifications from cloud provider on changes to external resources to trigger reconciliation
|
||||
* Parallel processing of CRD instances
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ limitations under the License.
|
|||
package v1alpha1
|
||||
|
||||
import (
|
||||
"github.com/upbound/conductor/pkg/apis/core/v1alpha1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
|
@ -25,24 +26,6 @@ func init() {
|
|||
SchemeBuilder.Register(&RDSInstance{}, &RDSInstanceList{})
|
||||
}
|
||||
|
||||
// RDSInstanceConditionType type for possible conditions the provider could be in.
|
||||
type RDSInstanceConditionType string
|
||||
|
||||
const (
|
||||
// Pending means that the instance create request has been received and waiting to be fulfilled
|
||||
Pending RDSInstanceConditionType = "Pending"
|
||||
// Creating means that the DB instance create request has been processed and DB Instance is being created
|
||||
Creating RDSInstanceConditionType = "Creating"
|
||||
// Deleting means that the instance is being deleted.
|
||||
Deleting RDSInstanceConditionType = "Deleting"
|
||||
// Failed means that the instance creation has failed.
|
||||
Failed RDSInstanceConditionType = "Failed"
|
||||
// Running means that the instance creation has been successful.
|
||||
Running RDSInstanceConditionType = "Running"
|
||||
)
|
||||
|
||||
type RDSDBInstanceStatus string
|
||||
|
||||
// RDSInstanceSpec defines the desired state of RDSInstance
|
||||
type RDSInstanceSpec struct {
|
||||
MasterUsername string `json:"masterUsername"`
|
||||
|
|
@ -57,57 +40,11 @@ type RDSInstanceSpec struct {
|
|||
|
||||
// RDSInstanceStatus defines the observed state of RDSInstance
|
||||
type RDSInstanceStatus struct {
|
||||
v1alpha1.ConditionedStatus
|
||||
State string `json:"state,omitempty"`
|
||||
Message string `json:"message,omitempty"`
|
||||
ProviderID string `json:"providerID,omitempty"` // the external ID to identify this resource in the cloud provider
|
||||
InstanceName string `json:"instanceName,omitempty"` // the generated DB Instance name
|
||||
|
||||
// Conditions indicate state for particular aspects of a CustomResourceDefinition
|
||||
Conditions []RDSInstanceCondition
|
||||
}
|
||||
|
||||
// GetCondition returns a provider condition with the provided type if it exists.
|
||||
func (s *RDSInstanceStatus) GetCondition(conditionType RDSInstanceConditionType) *RDSInstanceCondition {
|
||||
for _, c := range s.Conditions {
|
||||
if c.Type == conditionType {
|
||||
return &c
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetCondition adds/replaces the given condition in the credentials controller status.
|
||||
func (s *RDSInstanceStatus) SetCondition(condition RDSInstanceCondition) {
|
||||
current := s.GetCondition(condition.Type)
|
||||
if current != nil && current.Status == condition.Status && current.Reason == condition.Reason {
|
||||
return
|
||||
}
|
||||
newConditions := FilterOutCondition(s.Conditions, condition.Type)
|
||||
s.Conditions = append(newConditions, condition)
|
||||
}
|
||||
|
||||
// UnsetCondition set condition status to false with the given type - if found.
|
||||
func (s *RDSInstanceStatus) UnsetCondition(conditionType RDSInstanceConditionType) {
|
||||
current := s.GetCondition(conditionType)
|
||||
if current != nil && current.Status == corev1.ConditionTrue {
|
||||
current.Status = corev1.ConditionFalse
|
||||
s.SetCondition(*current)
|
||||
}
|
||||
}
|
||||
|
||||
// UnsetAllConditions set conditions status to false on all conditions
|
||||
func (s *RDSInstanceStatus) UnsetAllConditions() {
|
||||
var newConditions []RDSInstanceCondition
|
||||
for _, c := range s.Conditions {
|
||||
c.Status = corev1.ConditionFalse
|
||||
newConditions = append(newConditions, c)
|
||||
}
|
||||
s.Conditions = newConditions
|
||||
}
|
||||
|
||||
// RemoveCondition removes the condition with the provided type from the credentials controller status.
|
||||
func (s *RDSInstanceStatus) RemoveCondition(condType RDSInstanceConditionType) {
|
||||
s.Conditions = FilterOutCondition(s.Conditions, condType)
|
||||
}
|
||||
|
||||
// +genclient
|
||||
|
|
@ -132,41 +69,3 @@ type RDSInstanceList struct {
|
|||
metav1.ListMeta `json:"metadata,omitempty"`
|
||||
Items []RDSInstance `json:"items"`
|
||||
}
|
||||
|
||||
// RDSInstanceCondition contains details for the current condition of this pod.
|
||||
type RDSInstanceCondition struct {
|
||||
Type RDSInstanceConditionType
|
||||
Status corev1.ConditionStatus
|
||||
LastTransitionTime metav1.Time
|
||||
Reason string
|
||||
Message string
|
||||
}
|
||||
|
||||
// ProviderStatus defines the observed state of Provider
|
||||
type ProviderStatus struct {
|
||||
// Conditions indicate state for particular aspects of a CustomResourceDefinition
|
||||
Conditions []RDSInstanceCondition
|
||||
}
|
||||
|
||||
// NewCondition creates a new RDS instance condition.
|
||||
func NewCondition(condType RDSInstanceConditionType, reason, msg string) *RDSInstanceCondition {
|
||||
return &RDSInstanceCondition{
|
||||
Type: condType,
|
||||
Status: corev1.ConditionTrue,
|
||||
LastTransitionTime: metav1.Now(),
|
||||
Reason: reason,
|
||||
Message: msg,
|
||||
}
|
||||
}
|
||||
|
||||
// FilterOutCondition returns a new slice of credentials controller conditions without conditions with the provided type.
|
||||
func FilterOutCondition(conditions []RDSInstanceCondition, condType RDSInstanceConditionType) []RDSInstanceCondition {
|
||||
var newConditions []RDSInstanceCondition
|
||||
for _, c := range conditions {
|
||||
if c.Type == condType {
|
||||
continue
|
||||
}
|
||||
newConditions = append(newConditions, c)
|
||||
}
|
||||
return newConditions
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,32 +20,32 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/onsi/gomega"
|
||||
"golang.org/x/net/context"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
)
|
||||
|
||||
func TestStorageRDSInstance(t *testing.T) {
|
||||
key := types.NamespacedName{Name: "foo", Namespace: "default"}
|
||||
created := &RDSInstance{ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "default"}}
|
||||
g := gomega.NewGomegaWithT(t)
|
||||
|
||||
key := types.NamespacedName{Name: name, Namespace: namespace}
|
||||
created := &RDSInstance{ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: namespace}}
|
||||
|
||||
// Test Create
|
||||
fetched := &RDSInstance{}
|
||||
g.Expect(c.Create(context.TODO(), created)).NotTo(gomega.HaveOccurred())
|
||||
g.Expect(c.Create(ctx, created)).NotTo(gomega.HaveOccurred())
|
||||
|
||||
g.Expect(c.Get(context.TODO(), key, fetched)).NotTo(gomega.HaveOccurred())
|
||||
g.Expect(c.Get(ctx, key, fetched)).NotTo(gomega.HaveOccurred())
|
||||
g.Expect(fetched).To(gomega.Equal(created))
|
||||
|
||||
// Test Updating the Labels
|
||||
updated := fetched.DeepCopy()
|
||||
updated.Labels = map[string]string{"hello": "world"}
|
||||
g.Expect(c.Update(context.TODO(), updated)).NotTo(gomega.HaveOccurred())
|
||||
g.Expect(c.Update(ctx, updated)).NotTo(gomega.HaveOccurred())
|
||||
|
||||
g.Expect(c.Get(context.TODO(), key, fetched)).NotTo(gomega.HaveOccurred())
|
||||
g.Expect(c.Get(ctx, key, fetched)).NotTo(gomega.HaveOccurred())
|
||||
g.Expect(fetched).To(gomega.Equal(updated))
|
||||
|
||||
// Test Delete
|
||||
g.Expect(c.Delete(context.TODO(), fetched)).NotTo(gomega.HaveOccurred())
|
||||
g.Expect(c.Get(context.TODO(), key, fetched)).To(gomega.HaveOccurred())
|
||||
g.Expect(c.Delete(ctx, fetched)).NotTo(gomega.HaveOccurred())
|
||||
g.Expect(c.Get(ctx, key, fetched)).To(gomega.HaveOccurred())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,39 +18,40 @@ package v1alpha1
|
|||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/upbound/conductor/pkg/test"
|
||||
"golang.org/x/net/context"
|
||||
"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 cfg *rest.Config
|
||||
var c client.Client
|
||||
const (
|
||||
namespace = "default"
|
||||
name = "test-instance"
|
||||
)
|
||||
|
||||
var (
|
||||
crds = []string{filepath.Join("..", "..", "..", "..", "..", "cluster", "charts", "conductor", "crds", "aws", "database", "v1alpha1")}
|
||||
ctx = context.TODO()
|
||||
cfg *rest.Config
|
||||
c client.Client
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
t := &envtest.Environment{
|
||||
CRDDirectoryPaths: []string{filepath.Join("..", "..", "..", "..", "..",
|
||||
"cluster", "charts", "conductor", "crds", "aws", "database", "v1alpha1")},
|
||||
}
|
||||
|
||||
err := SchemeBuilder.AddToScheme(scheme.Scheme)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if cfg, err = t.Start(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
t := test.NewTestEnv(crds, namespace)
|
||||
cfg = t.Start()
|
||||
|
||||
if c, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
code := m.Run()
|
||||
t.Stop()
|
||||
os.Exit(code)
|
||||
t.StopAndExit(m.Run())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,29 +23,6 @@ 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 *ProviderStatus) DeepCopyInto(out *ProviderStatus) {
|
||||
*out = *in
|
||||
if in.Conditions != nil {
|
||||
in, out := &in.Conditions, &out.Conditions
|
||||
*out = make([]RDSInstanceCondition, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ProviderStatus.
|
||||
func (in *ProviderStatus) DeepCopy() *ProviderStatus {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ProviderStatus)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *RDSInstance) DeepCopyInto(out *RDSInstance) {
|
||||
*out = *in
|
||||
|
|
@ -74,23 +51,6 @@ func (in *RDSInstance) DeepCopyObject() runtime.Object {
|
|||
return nil
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *RDSInstanceCondition) DeepCopyInto(out *RDSInstanceCondition) {
|
||||
*out = *in
|
||||
in.LastTransitionTime.DeepCopyInto(&out.LastTransitionTime)
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RDSInstanceCondition.
|
||||
func (in *RDSInstanceCondition) DeepCopy() *RDSInstanceCondition {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(RDSInstanceCondition)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *RDSInstanceList) DeepCopyInto(out *RDSInstanceList) {
|
||||
*out = *in
|
||||
|
|
@ -150,13 +110,7 @@ func (in *RDSInstanceSpec) DeepCopy() *RDSInstanceSpec {
|
|||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *RDSInstanceStatus) DeepCopyInto(out *RDSInstanceStatus) {
|
||||
*out = *in
|
||||
if in.Conditions != nil {
|
||||
in, out := &in.Conditions, &out.Conditions
|
||||
*out = make([]RDSInstanceCondition, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
in.ConditionedStatus.DeepCopyInto(&out.ConditionedStatus)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -20,18 +20,17 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/onsi/gomega"
|
||||
"golang.org/x/net/context"
|
||||
"k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
)
|
||||
|
||||
func TestStorageProvider(t *testing.T) {
|
||||
key := types.NamespacedName{Name: "foo", Namespace: "default"}
|
||||
key := types.NamespacedName{Name: name, Namespace: namespace}
|
||||
created := &Provider{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "foo",
|
||||
Namespace: "default",
|
||||
Name: name,
|
||||
Namespace: namespace,
|
||||
},
|
||||
Spec: ProviderSpec{
|
||||
Secret: v1.SecretKeySelector{
|
||||
|
|
@ -44,21 +43,21 @@ func TestStorageProvider(t *testing.T) {
|
|||
g := gomega.NewGomegaWithT(t)
|
||||
|
||||
// Test Create
|
||||
g.Expect(c.Create(context.TODO(), created)).NotTo(gomega.HaveOccurred())
|
||||
g.Expect(c.Create(ctx, created)).NotTo(gomega.HaveOccurred())
|
||||
|
||||
fetched := &Provider{}
|
||||
g.Expect(c.Get(context.TODO(), key, fetched)).NotTo(gomega.HaveOccurred())
|
||||
g.Expect(c.Get(ctx, key, fetched)).NotTo(gomega.HaveOccurred())
|
||||
g.Expect(fetched).To(gomega.Equal(created))
|
||||
|
||||
// Test Updating the Labels
|
||||
updated := fetched.DeepCopy()
|
||||
updated.Labels = map[string]string{"hello": "world"}
|
||||
g.Expect(c.Update(context.TODO(), updated)).NotTo(gomega.HaveOccurred())
|
||||
g.Expect(c.Update(ctx, updated)).NotTo(gomega.HaveOccurred())
|
||||
|
||||
g.Expect(c.Get(context.TODO(), key, fetched)).NotTo(gomega.HaveOccurred())
|
||||
g.Expect(c.Get(ctx, key, fetched)).NotTo(gomega.HaveOccurred())
|
||||
g.Expect(fetched).To(gomega.Equal(updated))
|
||||
|
||||
// Test Delete
|
||||
g.Expect(c.Delete(context.TODO(), fetched)).NotTo(gomega.HaveOccurred())
|
||||
g.Expect(c.Get(context.TODO(), key, fetched)).To(gomega.HaveOccurred())
|
||||
g.Expect(c.Delete(ctx, fetched)).NotTo(gomega.HaveOccurred())
|
||||
g.Expect(c.Get(ctx, key, fetched)).To(gomega.HaveOccurred())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,39 +18,40 @@ package v1alpha1
|
|||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/upbound/conductor/pkg/test"
|
||||
"golang.org/x/net/context"
|
||||
"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 cfg *rest.Config
|
||||
var c client.Client
|
||||
const (
|
||||
namespace = "default"
|
||||
name = "test-provider"
|
||||
)
|
||||
|
||||
var (
|
||||
crds = []string{filepath.Join("..", "..", "..", "..", "cluster", "charts", "conductor", "crds", "aws", "v1alpha1")}
|
||||
ctx = context.TODO()
|
||||
cfg *rest.Config
|
||||
c client.Client
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
t := &envtest.Environment{
|
||||
CRDDirectoryPaths: []string{filepath.Join("..", "..", "..", "..",
|
||||
"cluster", "charts", "conductor", "crds", "aws", "v1alpha1")},
|
||||
}
|
||||
|
||||
err := SchemeBuilder.AddToScheme(scheme.Scheme)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if cfg, err = t.Start(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
t := test.NewTestEnv(crds, namespace)
|
||||
cfg = t.Start()
|
||||
|
||||
if c, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
code := m.Run()
|
||||
t.Stop()
|
||||
os.Exit(code)
|
||||
t.StopAndExit(m.Run())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,120 @@
|
|||
/*
|
||||
Copyright 2018 The Conductor Authors.
|
||||
|
||||
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 (
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// ConditionType type for possible conditions the resource could be in.
|
||||
type ConditionType string
|
||||
|
||||
const (
|
||||
// Pending means that the resource create request has been received and waiting to be fulfilled
|
||||
Pending ConditionType = "Pending"
|
||||
// Creating means that the DB resource create request has been processed and managed resource is being created
|
||||
Creating ConditionType = "Creating"
|
||||
// Deleting means that the resource is being deleted.
|
||||
Deleting ConditionType = "Deleting"
|
||||
// Failed means that the resource creation has failed.
|
||||
Failed ConditionType = "Failed"
|
||||
// Running means that the resource creation has been successful.
|
||||
Running ConditionType = "Running"
|
||||
)
|
||||
|
||||
// Condition contains details for the current condition of this pod.
|
||||
type Condition struct {
|
||||
Type ConditionType
|
||||
Status corev1.ConditionStatus
|
||||
LastTransitionTime metav1.Time
|
||||
Reason string
|
||||
Message string
|
||||
}
|
||||
|
||||
// ConditionedStatus defines the observed state of RDSresource
|
||||
type ConditionedStatus struct {
|
||||
// Conditions indicate state for particular aspects of a CustomResourceDefinition
|
||||
Conditions []Condition
|
||||
}
|
||||
|
||||
// GetCondition returns a provider condition with the provided type if it exists.
|
||||
func (in *ConditionedStatus) GetCondition(conditionType ConditionType) *Condition {
|
||||
for _, c := range in.Conditions {
|
||||
if c.Type == conditionType {
|
||||
return &c
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetCondition adds/replaces the given condition in the credentials controller status.
|
||||
func (in *ConditionedStatus) SetCondition(condition Condition) {
|
||||
current := in.GetCondition(condition.Type)
|
||||
if current != nil && current.Status == condition.Status && current.Reason == condition.Reason {
|
||||
return
|
||||
}
|
||||
newConditions := FilterOutCondition(in.Conditions, condition.Type)
|
||||
in.Conditions = append(newConditions, condition)
|
||||
}
|
||||
|
||||
// UnsetCondition set condition status to false with the given type - if found.
|
||||
func (in *ConditionedStatus) UnsetCondition(conditionType ConditionType) {
|
||||
current := in.GetCondition(conditionType)
|
||||
if current != nil && current.Status == corev1.ConditionTrue {
|
||||
current.Status = corev1.ConditionFalse
|
||||
in.SetCondition(*current)
|
||||
}
|
||||
}
|
||||
|
||||
// UnsetAllConditions set conditions status to false on all conditions
|
||||
func (in *ConditionedStatus) UnsetAllConditions() {
|
||||
var newConditions []Condition
|
||||
for _, c := range in.Conditions {
|
||||
c.Status = corev1.ConditionFalse
|
||||
newConditions = append(newConditions, c)
|
||||
}
|
||||
in.Conditions = newConditions
|
||||
}
|
||||
|
||||
// RemoveCondition removes the condition with the provided type from the credentials controller status.
|
||||
func (in *ConditionedStatus) RemoveCondition(condType ConditionType) {
|
||||
in.Conditions = FilterOutCondition(in.Conditions, condType)
|
||||
}
|
||||
|
||||
// NewCondition creates a new RDS resource condition.
|
||||
func NewCondition(condType ConditionType, reason, msg string) *Condition {
|
||||
return &Condition{
|
||||
Type: condType,
|
||||
Status: corev1.ConditionTrue,
|
||||
LastTransitionTime: metav1.Now(),
|
||||
Reason: reason,
|
||||
Message: msg,
|
||||
}
|
||||
}
|
||||
|
||||
// FilterOutProviderCondition returns a new slice of credentials controller conditions without conditions with the provided type.
|
||||
func FilterOutCondition(conditions []Condition, condType ConditionType) []Condition {
|
||||
var newConditions []Condition
|
||||
for _, c := range conditions {
|
||||
if c.Type == condType {
|
||||
continue
|
||||
}
|
||||
newConditions = append(newConditions, c)
|
||||
}
|
||||
return newConditions
|
||||
}
|
||||
|
|
@ -45,3 +45,69 @@ type ProviderStatus struct {
|
|||
// Conditions indicate state for particular aspects of a CustomResourceDefinition
|
||||
Conditions []ProviderCondition
|
||||
}
|
||||
|
||||
// NewCondition creates a provider condition.
|
||||
func NewProviderCondition(condType ProviderConditionType, status corev1.ConditionStatus, reason, msg string) *ProviderCondition {
|
||||
return &ProviderCondition{
|
||||
Type: condType,
|
||||
Status: status,
|
||||
LastTransitionTime: metav1.Now(),
|
||||
Reason: reason,
|
||||
Message: msg,
|
||||
}
|
||||
}
|
||||
|
||||
// GetCondition returns a provider condition with the provided type if it exists.
|
||||
func (in *ProviderStatus) GetCondition(conditionType ProviderConditionType) *ProviderCondition {
|
||||
for _, c := range in.Conditions {
|
||||
if c.Type == conditionType {
|
||||
return &c
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetCondition adds/replaces the given condition in the credentials controller status.
|
||||
func (in *ProviderStatus) SetCondition(condition ProviderCondition) {
|
||||
current := in.GetCondition(condition.Type)
|
||||
if current != nil && current.Status == condition.Status && current.Reason == condition.Reason {
|
||||
return
|
||||
}
|
||||
newConditions := FilterOutProviderCondition(in.Conditions, condition.Type)
|
||||
in.Conditions = append(newConditions, condition)
|
||||
}
|
||||
|
||||
// SetInvalid condition and unset valid condition
|
||||
func (in *ProviderStatus) SetInvalid(reason, msg string) {
|
||||
in.SetCondition(*NewProviderCondition(Invalid, corev1.ConditionTrue, reason, msg))
|
||||
|
||||
if valid := in.GetCondition(Valid); valid != nil {
|
||||
in.SetCondition(*NewProviderCondition(Valid, corev1.ConditionFalse, "", valid.Message))
|
||||
}
|
||||
}
|
||||
|
||||
// SetValid condition and unset invalid condition
|
||||
func (in *ProviderStatus) SetValid(msg string) {
|
||||
in.SetCondition(*NewProviderCondition(Valid, corev1.ConditionTrue, "", msg))
|
||||
|
||||
if invalid := in.GetCondition(Invalid); invalid != nil {
|
||||
in.SetCondition(*NewProviderCondition(Invalid, corev1.ConditionFalse, invalid.Reason, invalid.Message))
|
||||
}
|
||||
}
|
||||
|
||||
// RemoveCondition removes the condition with the provided type from the credentials controller status.
|
||||
func (in *ProviderStatus) RemoveCondition(condType ProviderConditionType) {
|
||||
in.Conditions = FilterOutProviderCondition(in.Conditions, condType)
|
||||
}
|
||||
|
||||
// FilterOutProviderCondition returns a new slice of credentials controller conditions without conditions with the provided type.
|
||||
func FilterOutProviderCondition(conditions []ProviderCondition, condType ProviderConditionType) []ProviderCondition {
|
||||
var newConditions []ProviderCondition
|
||||
for _, c := range conditions {
|
||||
if c.Type == condType {
|
||||
continue
|
||||
}
|
||||
newConditions = append(newConditions, c)
|
||||
}
|
||||
return newConditions
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,198 @@
|
|||
/*
|
||||
Copyright 2018 The Conductor Authors.
|
||||
|
||||
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 (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
. "github.com/onsi/gomega"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
)
|
||||
|
||||
func TestFilterOutCondition(t *testing.T) {
|
||||
g := NewGomegaWithT(t)
|
||||
|
||||
var empty []ProviderCondition
|
||||
validOnly := append(empty, *NewProviderCondition(Valid, corev1.ConditionTrue, "", ""))
|
||||
invalidOnly := append(empty, *NewProviderCondition(Invalid, corev1.ConditionTrue, "", ""))
|
||||
mixed := append(validOnly, invalidOnly...)
|
||||
mixedWithDuplicates := append(mixed, mixed...)
|
||||
|
||||
// empty - any
|
||||
g.Expect(FilterOutProviderCondition(empty, Valid)).To(BeNil())
|
||||
|
||||
// {valid} - invalid = {valid}
|
||||
g.Expect(FilterOutProviderCondition(validOnly, Invalid)).To(Equal(validOnly))
|
||||
// {valid} - valid = nil}
|
||||
g.Expect(FilterOutProviderCondition(validOnly, Valid)).To(BeNil())
|
||||
// {valid, invalid} - invalid = {valid}
|
||||
g.Expect(FilterOutProviderCondition(mixed, Invalid)).To(Equal(validOnly))
|
||||
// {valid, invalid} - valid = {invalid}
|
||||
g.Expect(FilterOutProviderCondition(mixed, Valid)).To(Equal(invalidOnly))
|
||||
|
||||
// {valid,invalid,valid,invalid} - invalid = {valid,valid}
|
||||
c := FilterOutProviderCondition(mixedWithDuplicates, Invalid)
|
||||
g.Expect(c).To(Equal(append(validOnly, validOnly...)))
|
||||
// {valid,valid} - invalid = {valid, valid} (no change)
|
||||
c = FilterOutProviderCondition(c, Invalid)
|
||||
g.Expect(c).To(Equal(append(validOnly, validOnly...)))
|
||||
// {valid,valid} - valid = {nil}
|
||||
g.Expect(FilterOutProviderCondition(c, Valid)).To(BeNil())
|
||||
}
|
||||
|
||||
func TestRemoveCondition(t *testing.T) {
|
||||
g := NewGomegaWithT(t)
|
||||
status := &ProviderStatus{}
|
||||
g.Expect(status.Conditions).To(BeNil())
|
||||
|
||||
status.RemoveCondition(Valid)
|
||||
g.Expect(status.Conditions).To(BeNil())
|
||||
|
||||
conditions := []ProviderCondition{*NewProviderCondition(Valid, corev1.ConditionTrue, "", "")}
|
||||
status.SetCondition(conditions[0])
|
||||
g.Expect(status.Conditions).To(Equal(conditions))
|
||||
status.RemoveCondition(Invalid)
|
||||
g.Expect(status.Conditions).To(Equal(conditions))
|
||||
status.RemoveCondition(Valid)
|
||||
g.Expect(status.Conditions).To(BeNil())
|
||||
}
|
||||
|
||||
func TestGetConditions(t *testing.T) {
|
||||
g := NewGomegaWithT(t)
|
||||
status := &ProviderStatus{}
|
||||
g.Expect(status.Conditions).To(BeNil())
|
||||
|
||||
c := status.GetCondition(Invalid)
|
||||
g.Expect(c).To(BeNil())
|
||||
|
||||
st := time.Now()
|
||||
status.SetCondition(*NewProviderCondition(Valid, corev1.ConditionTrue, "", ""))
|
||||
|
||||
g.Expect(status.Conditions).To(Not(BeNil()))
|
||||
|
||||
c = status.GetCondition(Invalid)
|
||||
g.Expect(c).To(BeNil())
|
||||
|
||||
c = status.GetCondition(Valid)
|
||||
g.Expect(c.Type).To(Equal(Valid))
|
||||
g.Expect(c.Status).To(Equal(corev1.ConditionTrue))
|
||||
g.Expect(c.LastTransitionTime.After(st)).To(BeTrue())
|
||||
}
|
||||
|
||||
func TestSetConditions(t *testing.T) {
|
||||
g := NewGomegaWithT(t)
|
||||
status := &ProviderStatus{}
|
||||
g.Expect(status.Conditions).To(BeNil())
|
||||
|
||||
valid := *NewProviderCondition(Valid, corev1.ConditionTrue, "", "")
|
||||
status.SetCondition(valid)
|
||||
g.Expect(status.Conditions).To(Equal([]ProviderCondition{valid}))
|
||||
|
||||
invalid := *NewProviderCondition(Invalid, corev1.ConditionFalse, "Invalid reason", "")
|
||||
status.SetCondition(invalid)
|
||||
g.Expect(status.Conditions).To(Equal([]ProviderCondition{valid, invalid}))
|
||||
|
||||
// new valid - diff message only - no change
|
||||
newValid := *NewProviderCondition(Valid, corev1.ConditionTrue, "", "bar")
|
||||
status.SetCondition(newValid)
|
||||
g.Expect(status.Conditions).To(Equal([]ProviderCondition{valid, invalid}))
|
||||
|
||||
// new valid - diff reason and message - change
|
||||
newValid.Reason = "foo"
|
||||
valid = newValid
|
||||
status.SetCondition(newValid)
|
||||
g.Expect(status.Conditions).To(Equal([]ProviderCondition{invalid, valid}))
|
||||
|
||||
// new valid - diff Status - change
|
||||
newValid.Status = corev1.ConditionUnknown
|
||||
valid = newValid
|
||||
status.SetCondition(newValid)
|
||||
g.Expect(status.Conditions).To(Equal([]ProviderCondition{invalid, valid}))
|
||||
}
|
||||
|
||||
func TestSetInvalid(t *testing.T) {
|
||||
g := NewGomegaWithT(t)
|
||||
status := &ProviderStatus{}
|
||||
g.Expect(status.Conditions).To(BeNil())
|
||||
|
||||
ts := time.Now()
|
||||
status.SetInvalid("fail", "bye")
|
||||
i := status.GetCondition(Invalid)
|
||||
g.Expect(i).To(Not(BeNil()))
|
||||
g.Expect(i.Status).To(Equal(corev1.ConditionTrue))
|
||||
g.Expect(i.Reason).To(Equal("fail"))
|
||||
g.Expect(i.Message).To(Equal("bye"))
|
||||
g.Expect(i.LastTransitionTime.After(ts)).To(BeTrue())
|
||||
v := status.GetCondition(Valid)
|
||||
g.Expect(v).To(BeNil())
|
||||
|
||||
status.RemoveCondition(Invalid)
|
||||
g.Expect(status.Conditions).To(BeNil())
|
||||
|
||||
valid := *NewProviderCondition(Valid, corev1.ConditionTrue, "", "")
|
||||
status.SetCondition(valid)
|
||||
|
||||
ts = time.Now()
|
||||
status.SetInvalid("fail", "bye")
|
||||
i = status.GetCondition(Invalid)
|
||||
g.Expect(i).To(Not(BeNil()))
|
||||
g.Expect(i.Status).To(Equal(corev1.ConditionTrue))
|
||||
g.Expect(i.Reason).To(Equal("fail"))
|
||||
g.Expect(i.Message).To(Equal("bye"))
|
||||
g.Expect(i.LastTransitionTime.After(ts)).To(BeTrue())
|
||||
v = status.GetCondition(Valid)
|
||||
g.Expect(v).To(Not(BeNil()))
|
||||
g.Expect(v.Status).To(Equal(corev1.ConditionFalse))
|
||||
g.Expect(v.LastTransitionTime.After(ts)).To(BeTrue())
|
||||
}
|
||||
|
||||
func TestSetValid(t *testing.T) {
|
||||
g := NewGomegaWithT(t)
|
||||
status := &ProviderStatus{}
|
||||
g.Expect(status.Conditions).To(BeNil())
|
||||
|
||||
ts := time.Now()
|
||||
|
||||
status.SetValid("hello")
|
||||
g.Expect(len(status.Conditions)).To(Equal(1))
|
||||
v := status.GetCondition(Valid)
|
||||
g.Expect(v).To(Not(BeNil()))
|
||||
g.Expect(v.Status).To(Equal(corev1.ConditionTrue))
|
||||
g.Expect(v.Reason).To(Equal(""))
|
||||
g.Expect(v.Message).To(Equal("hello"))
|
||||
g.Expect(v.LastTransitionTime.After(ts)).To(BeTrue())
|
||||
i := status.GetCondition(Invalid)
|
||||
g.Expect(i).To(BeNil())
|
||||
|
||||
status.RemoveCondition(Valid)
|
||||
g.Expect(status.Conditions).To(BeNil())
|
||||
|
||||
invalid := *NewProviderCondition(Invalid, corev1.ConditionTrue, "fail", "")
|
||||
status.SetCondition(invalid)
|
||||
|
||||
ts = time.Now()
|
||||
status.SetValid("hello")
|
||||
v = status.GetCondition(Valid)
|
||||
g.Expect(v).To(Not(BeNil()))
|
||||
g.Expect(v.Status).To(Equal(corev1.ConditionTrue))
|
||||
g.Expect(v.Reason).To(Equal(""))
|
||||
g.Expect(v.Message).To(Equal("hello"))
|
||||
g.Expect(v.LastTransitionTime.After(ts)).To(BeTrue())
|
||||
i = status.GetCondition(Invalid)
|
||||
g.Expect(i.Status).To(Equal(corev1.ConditionFalse))
|
||||
}
|
||||
|
|
@ -19,6 +19,46 @@ limitations under the License.
|
|||
|
||||
package v1alpha1
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *Condition) DeepCopyInto(out *Condition) {
|
||||
*out = *in
|
||||
in.LastTransitionTime.DeepCopyInto(&out.LastTransitionTime)
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Condition.
|
||||
func (in *Condition) DeepCopy() *Condition {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(Condition)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ConditionedStatus) DeepCopyInto(out *ConditionedStatus) {
|
||||
*out = *in
|
||||
if in.Conditions != nil {
|
||||
in, out := &in.Conditions, &out.Conditions
|
||||
*out = make([]Condition, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConditionedStatus.
|
||||
func (in *ConditionedStatus) DeepCopy() *ConditionedStatus {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ConditionedStatus)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ProviderCondition) DeepCopyInto(out *ProviderCondition) {
|
||||
*out = *in
|
||||
|
|
|
|||
|
|
@ -20,32 +20,31 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/onsi/gomega"
|
||||
"golang.org/x/net/context"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
)
|
||||
|
||||
func TestStorageCloudsqlInstance(t *testing.T) {
|
||||
key := types.NamespacedName{Name: "foo", Namespace: "default"}
|
||||
created := &CloudsqlInstance{ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "default"}}
|
||||
key := types.NamespacedName{Name: name, Namespace: namespace}
|
||||
created := &CloudsqlInstance{ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: namespace}}
|
||||
g := gomega.NewGomegaWithT(t)
|
||||
|
||||
// Test Create
|
||||
fetched := &CloudsqlInstance{}
|
||||
g.Expect(c.Create(context.TODO(), created)).NotTo(gomega.HaveOccurred())
|
||||
g.Expect(c.Create(ctx, created)).NotTo(gomega.HaveOccurred())
|
||||
|
||||
g.Expect(c.Get(context.TODO(), key, fetched)).NotTo(gomega.HaveOccurred())
|
||||
g.Expect(c.Get(ctx, key, fetched)).NotTo(gomega.HaveOccurred())
|
||||
g.Expect(fetched).To(gomega.Equal(created))
|
||||
|
||||
// Test Updating the Labels
|
||||
updated := fetched.DeepCopy()
|
||||
updated.Labels = map[string]string{"hello": "world"}
|
||||
g.Expect(c.Update(context.TODO(), updated)).NotTo(gomega.HaveOccurred())
|
||||
g.Expect(c.Update(ctx, updated)).NotTo(gomega.HaveOccurred())
|
||||
|
||||
g.Expect(c.Get(context.TODO(), key, fetched)).NotTo(gomega.HaveOccurred())
|
||||
g.Expect(c.Get(ctx, key, fetched)).NotTo(gomega.HaveOccurred())
|
||||
g.Expect(fetched).To(gomega.Equal(updated))
|
||||
|
||||
// Test Delete
|
||||
g.Expect(c.Delete(context.TODO(), fetched)).NotTo(gomega.HaveOccurred())
|
||||
g.Expect(c.Get(context.TODO(), key, fetched)).To(gomega.HaveOccurred())
|
||||
g.Expect(c.Delete(ctx, fetched)).NotTo(gomega.HaveOccurred())
|
||||
g.Expect(c.Get(ctx, key, fetched)).To(gomega.HaveOccurred())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,40 +17,41 @@ limitations under the License.
|
|||
package v1alpha1
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/upbound/conductor/pkg/test"
|
||||
"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 cfg *rest.Config
|
||||
var c client.Client
|
||||
const (
|
||||
namespace = "default"
|
||||
name = "test-instance"
|
||||
)
|
||||
|
||||
var (
|
||||
cfg *rest.Config
|
||||
c client.Client
|
||||
crds = []string{filepath.Join("..", "..", "..", "..", "..", "cluster", "charts", "conductor", "crds", "gcp", "database", "v1alpha1")}
|
||||
ctx = context.TODO()
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
t := &envtest.Environment{
|
||||
CRDDirectoryPaths: []string{filepath.Join("..", "..", "..", "..", "..",
|
||||
"cluster", "charts", "conductor", "crds", "gcp", "database", "v1alpha1")},
|
||||
}
|
||||
|
||||
err := SchemeBuilder.AddToScheme(scheme.Scheme)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if cfg, err = t.Start(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
t := test.NewTestEnv(crds, namespace)
|
||||
cfg = t.Start()
|
||||
|
||||
if c, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
code := m.Run()
|
||||
t.Stop()
|
||||
os.Exit(code)
|
||||
t.StopAndExit(m.Run())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,23 +20,22 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/onsi/gomega"
|
||||
"golang.org/x/net/context"
|
||||
"k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
)
|
||||
|
||||
func TestStorageProvider(t *testing.T) {
|
||||
key := types.NamespacedName{Name: "foo", Namespace: "default"}
|
||||
key := types.NamespacedName{Name: name, Namespace: namespace}
|
||||
created := &Provider{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "foo",
|
||||
Namespace: "default",
|
||||
Name: name,
|
||||
Namespace: namespace,
|
||||
},
|
||||
Spec: ProviderSpec{
|
||||
SecretKey: v1.SecretKeySelector{
|
||||
LocalObjectReference: v1.LocalObjectReference{Name: "u-235"},
|
||||
Key: "credentials.json",
|
||||
Key: secretDataKey,
|
||||
},
|
||||
ProjectID: "manhattan",
|
||||
RequiredPermissions: []string{"crate", "update", "delete"},
|
||||
|
|
@ -45,21 +44,21 @@ func TestStorageProvider(t *testing.T) {
|
|||
g := gomega.NewGomegaWithT(t)
|
||||
|
||||
// Test Create
|
||||
g.Expect(c.Create(context.TODO(), created)).NotTo(gomega.HaveOccurred())
|
||||
g.Expect(c.Create(ctx, created)).NotTo(gomega.HaveOccurred())
|
||||
|
||||
fetched := &Provider{}
|
||||
g.Expect(c.Get(context.TODO(), key, fetched)).NotTo(gomega.HaveOccurred())
|
||||
g.Expect(c.Get(ctx, key, fetched)).NotTo(gomega.HaveOccurred())
|
||||
g.Expect(fetched).To(gomega.Equal(created))
|
||||
|
||||
// Test Updating the Labels
|
||||
updated := fetched.DeepCopy()
|
||||
updated.Labels = map[string]string{"hello": "world"}
|
||||
g.Expect(c.Update(context.TODO(), updated)).NotTo(gomega.HaveOccurred())
|
||||
g.Expect(c.Update(ctx, updated)).NotTo(gomega.HaveOccurred())
|
||||
|
||||
g.Expect(c.Get(context.TODO(), key, fetched)).NotTo(gomega.HaveOccurred())
|
||||
g.Expect(c.Get(ctx, key, fetched)).NotTo(gomega.HaveOccurred())
|
||||
g.Expect(fetched).To(gomega.Equal(updated))
|
||||
|
||||
// Test Delete
|
||||
g.Expect(c.Delete(context.TODO(), fetched)).NotTo(gomega.HaveOccurred())
|
||||
g.Expect(c.Get(context.TODO(), key, fetched)).To(gomega.HaveOccurred())
|
||||
g.Expect(c.Delete(ctx, fetched)).NotTo(gomega.HaveOccurred())
|
||||
g.Expect(c.Get(ctx, key, fetched)).To(gomega.HaveOccurred())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,40 +17,42 @@ limitations under the License.
|
|||
package v1alpha1
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/upbound/conductor/pkg/test"
|
||||
"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 cfg *rest.Config
|
||||
var c client.Client
|
||||
const (
|
||||
namespace = "default"
|
||||
name = "test-provider"
|
||||
secretDataKey = "credentials.json"
|
||||
)
|
||||
|
||||
var (
|
||||
cfg *rest.Config
|
||||
c client.Client
|
||||
crds = []string{filepath.Join("..", "..", "..", "..", "cluster", "charts", "conductor", "crds", "gcp", "v1alpha1")}
|
||||
ctx = context.TODO()
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
t := &envtest.Environment{
|
||||
CRDDirectoryPaths: []string{filepath.Join("..", "..", "..", "..",
|
||||
"cluster", "charts", "conductor", "crds", "gcp", "v1alpha1")},
|
||||
}
|
||||
|
||||
err := SchemeBuilder.AddToScheme(scheme.Scheme)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if cfg, err = t.Start(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
t := test.NewTestEnv(crds, namespace)
|
||||
cfg = t.Start()
|
||||
|
||||
if c, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
code := m.Run()
|
||||
t.Stop()
|
||||
os.Exit(code)
|
||||
t.StopAndExit(m.Run())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,108 @@
|
|||
package test
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
"strconv"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/rest"
|
||||
"sigs.k8s.io/controller-runtime/pkg/envtest"
|
||||
)
|
||||
|
||||
const (
|
||||
DEFAULT_NAMESPACE = "default"
|
||||
TEST_ASSET_USE_EXISTING_CLUSTER = "USE_EXISTING_CLUSTER"
|
||||
)
|
||||
|
||||
// TestEnv - wrapper for controller-runtime envtest with additional functionality
|
||||
type TestEnv struct {
|
||||
envtest.Environment
|
||||
|
||||
namespace string
|
||||
cfg *rest.Config
|
||||
}
|
||||
|
||||
// NewTestEnv - create new test environment instance
|
||||
func NewTestEnv(crds []string, namespace string) *TestEnv {
|
||||
t := envtest.Environment{
|
||||
UseExistingCluster: UseExistingCluster(),
|
||||
}
|
||||
if !t.UseExistingCluster {
|
||||
if err := CheckCRDFiles(crds); err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
t.CRDDirectoryPaths = crds
|
||||
}
|
||||
if len(namespace) == 0 {
|
||||
namespace = "default"
|
||||
}
|
||||
return &TestEnv{
|
||||
Environment: t,
|
||||
namespace: namespace,
|
||||
}
|
||||
}
|
||||
|
||||
// Start - starts and bootstraps test environment ang returns Kubernetes config instance
|
||||
func (te *TestEnv) Start() *rest.Config {
|
||||
cfg, err := te.Environment.Start()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
te.cfg = cfg
|
||||
|
||||
// Crate testing namespace for this package if it is not "default"
|
||||
if te.namespace != "default" {
|
||||
k := kubernetes.NewForConfigOrDie(cfg)
|
||||
_, err := k.CoreV1().Namespaces().Create(&corev1.Namespace{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: te.namespace,
|
||||
},
|
||||
})
|
||||
if err != nil && !errors.IsAlreadyExists(err) {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
return cfg
|
||||
}
|
||||
|
||||
// Stop - stops test environment performing additional cleanup (if needed)
|
||||
func (te *TestEnv) Stop() {
|
||||
defer te.Environment.Stop()
|
||||
if te.namespace != DEFAULT_NAMESPACE {
|
||||
k := kubernetes.NewForConfigOrDie(te.cfg)
|
||||
dp := metav1.DeletePropagationForeground
|
||||
err := k.CoreV1().Namespaces().Delete(te.namespace, &metav1.DeleteOptions{PropagationPolicy: &dp})
|
||||
if err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// StopAndExit - stops and exists, typically used as a last call in TestMain
|
||||
func (te *TestEnv) StopAndExit(code int) {
|
||||
te.Stop()
|
||||
os.Exit(code)
|
||||
}
|
||||
|
||||
// UseExistingCluster - checks if USE_EXISTING_CLUSTER environment variable is set
|
||||
func UseExistingCluster() bool {
|
||||
env, err := strconv.ParseBool(os.Getenv(TEST_ASSET_USE_EXISTING_CLUSTER))
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return env
|
||||
}
|
||||
|
||||
// CheckCRDFiles - validates that all crds files are found.
|
||||
func CheckCRDFiles(crds []string) error {
|
||||
for _, path := range crds {
|
||||
if _, err := os.Stat(path); os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Loading…
Reference in New Issue