-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #35 from vshn/postgresql
Define API scheme for managed PostgreSQL DBaaS
- Loading branch information
Showing
11 changed files
with
1,168 additions
and
28 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,140 @@ | ||
package v1 | ||
|
||
import ( | ||
"fmt" | ||
"regexp" | ||
"strconv" | ||
|
||
xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1" | ||
exoscaleoapi "github.com/exoscale/egoscale/v2/oapi" | ||
corev1 "k8s.io/api/core/v1" | ||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||
"k8s.io/apimachinery/pkg/runtime" | ||
) | ||
|
||
type DBaaSParameters struct { | ||
// TerminationProtection protects against termination and powering off. | ||
TerminationProtection bool `json:"terminationProtection,omitempty"` | ||
// Version is the (major) version identifier for the instance. | ||
Version string `json:"version,omitempty"` | ||
// Size contains the service capacity settings. | ||
Size SizeSpec `json:"size,omitempty"` | ||
|
||
IPFilter IPFilter `json:"ipFilter,omitempty"` | ||
} | ||
|
||
// NodeState describes the state of a service node. | ||
type NodeState struct { | ||
// Name of the service node | ||
Name string `json:"name,omitempty"` | ||
// Role of this node. | ||
Role exoscaleoapi.DbaasNodeStateRole `json:"role,omitempty"` | ||
// State of the service node. | ||
State exoscaleoapi.DbaasNodeStateState `json:"state,omitempty"` | ||
} | ||
|
||
// Notification contains a service message. | ||
type Notification struct { | ||
// Level of the notification. | ||
Level exoscaleoapi.DbaasServiceNotificationLevel `json:"level,omitempty"` | ||
// Message contains the notification. | ||
Message string `json:"message,omitempty"` | ||
// Type of the notification. | ||
Type exoscaleoapi.DbaasServiceNotificationType `json:"type,omitempty"` | ||
// Metadata contains additional data. | ||
Metadata runtime.RawExtension `json:"metadata,omitempty"` | ||
} | ||
|
||
// BackupSpec contains settings to control the backups of an instance. | ||
type BackupSpec struct { | ||
// TimeOfDay for doing daily backups, in UTC. | ||
// Format: "hh:mm:ss". | ||
TimeOfDay TimeOfDay `json:"timeOfDay,omitempty"` | ||
} | ||
|
||
// MaintenanceSpec contains settings to control the maintenance of an instance. | ||
type MaintenanceSpec struct { | ||
// +kubebuilder:validation:Enum=monday;tuesday;wednesday;thursday;friday;saturday;sunday;never | ||
|
||
// DayOfWeek specifies at which weekday the maintenance is held place. | ||
// Allowed values are [monday, tuesday, wednesday, thursday, friday, saturday, sunday, never] | ||
DayOfWeek exoscaleoapi.DbaasServiceMaintenanceDow `json:"dayOfWeek,omitempty"` | ||
|
||
// TimeOfDay for installing updates in UTC. | ||
// Format: "hh:mm:ss". | ||
TimeOfDay TimeOfDay `json:"timeOfDay,omitempty"` | ||
} | ||
|
||
var timeOfDayRegex = regexp.MustCompile("^([0-1]?[0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9])$") | ||
|
||
// +kubebuilder:validation:Pattern="^([0-1]?[0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9])$" | ||
|
||
// TimeOfDay contains a time in the 24hr clock. | ||
// Format: "hh:mm:ss". | ||
type TimeOfDay string | ||
|
||
// String implements fmt.Stringer. | ||
func (t TimeOfDay) String() string { | ||
return string(t) | ||
} | ||
|
||
// Parse returns the hour and minute of the string representation. | ||
// Returns errors if the format is invalid. | ||
func (t TimeOfDay) Parse() (hour, minute, second int64, err error) { | ||
if t.String() == "" { | ||
return -1, -1, -1, fmt.Errorf("time cannot be empty") | ||
} | ||
arr := timeOfDayRegex.FindStringSubmatch(t.String()) | ||
if len(arr) < 3 { | ||
return -1, -1, -1, fmt.Errorf("invalid format for time of day (hh:mm:ss): %s", t) | ||
} | ||
parts := []int64{0, 0, 0} | ||
for i, part := range arr[1:] { | ||
parsed, err := strconv.ParseInt(part, 10, 64) | ||
if err != nil && part != "" { | ||
return -1, -1, -1, fmt.Errorf("invalid time given for time of day: %w", err) | ||
} | ||
parts[i] = parsed | ||
} | ||
return parts[0], parts[1], parts[2], nil | ||
} | ||
|
||
// Rebuilding returns a Ready condition where the service is rebuilding. | ||
func Rebuilding() xpv1.Condition { | ||
return xpv1.Condition{ | ||
Type: xpv1.TypeReady, | ||
Status: corev1.ConditionFalse, | ||
Reason: "Rebuilding", | ||
Message: "The service is being provisioned", | ||
LastTransitionTime: metav1.Now(), | ||
} | ||
} | ||
|
||
// PoweredOff returns a Ready condition where the service is powered off. | ||
func PoweredOff() xpv1.Condition { | ||
return xpv1.Condition{ | ||
Type: xpv1.TypeReady, | ||
Status: corev1.ConditionFalse, | ||
Reason: "PoweredOff", | ||
Message: "The service is powered off", | ||
LastTransitionTime: metav1.Now(), | ||
} | ||
} | ||
|
||
// Rebalancing returns a Ready condition where the service is rebalancing. | ||
func Rebalancing() xpv1.Condition { | ||
return xpv1.Condition{ | ||
Type: xpv1.TypeReady, | ||
Status: corev1.ConditionFalse, | ||
Reason: "Rebalancing", | ||
Message: "The service is being rebalanced", | ||
LastTransitionTime: metav1.Now(), | ||
} | ||
} | ||
|
||
// Running returns a Ready condition where the service is running. | ||
func Running() xpv1.Condition { | ||
c := xpv1.Available() | ||
c.Message = "The service is running" | ||
return c | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
package v1 | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
func TestTimeOfDay_Parse(t *testing.T) { | ||
tests := []struct { | ||
givenInput string | ||
expectedMinute int64 | ||
expectedHour int64 | ||
expectedSecond int64 | ||
expectedError string | ||
}{ | ||
{givenInput: "00:00:00", expectedHour: 0, expectedMinute: 0, expectedSecond: 0}, | ||
{givenInput: "23:59:00", expectedHour: 23, expectedMinute: 59, expectedSecond: 0}, | ||
{givenInput: "01:01:01", expectedHour: 1, expectedMinute: 1, expectedSecond: 1}, | ||
{givenInput: "1:59:59", expectedHour: 1, expectedMinute: 59, expectedSecond: 59}, | ||
{givenInput: "19:59:01", expectedHour: 19, expectedMinute: 59, expectedSecond: 1}, | ||
{givenInput: "4:59:01", expectedHour: 4, expectedMinute: 59, expectedSecond: 1}, | ||
{givenInput: "04:59:01", expectedHour: 4, expectedMinute: 59, expectedSecond: 1}, | ||
{givenInput: "9:01:01", expectedHour: 9, expectedMinute: 1, expectedSecond: 1}, | ||
{givenInput: "", expectedError: "time cannot be empty"}, | ||
{givenInput: "invalid", expectedError: "invalid format for time of day (hh:mm:ss): invalid"}, | ||
{givenInput: "🕗", expectedError: "invalid format for time of day (hh:mm:ss): 🕗"}, | ||
{givenInput: "-1:1", expectedError: "invalid format for time of day (hh:mm:ss): -1:1"}, | ||
{givenInput: "24:01:30", expectedError: "invalid format for time of day (hh:mm:ss): 24:01:30"}, | ||
{givenInput: "23:60:00", expectedError: "invalid format for time of day (hh:mm:ss): 23:60:00"}, | ||
{givenInput: "foo:01:02", expectedError: "invalid format for time of day (hh:mm:ss): foo:01:02"}, | ||
{givenInput: "01:bar:02", expectedError: "invalid format for time of day (hh:mm:ss): 01:bar:02"}, | ||
} | ||
for _, tc := range tests { | ||
t.Run(tc.givenInput, func(t *testing.T) { | ||
hour, minute, second, err := TimeOfDay(tc.givenInput).Parse() | ||
if tc.expectedError != "" { | ||
assert.EqualError(t, err, tc.expectedError) | ||
assert.Equal(t, int64(-1), hour) | ||
assert.Equal(t, int64(-1), minute) | ||
assert.Equal(t, int64(-1), second) | ||
|
||
} else { | ||
assert.NoError(t, err) | ||
assert.Equal(t, tc.expectedHour, hour) | ||
assert.Equal(t, tc.expectedMinute, minute) | ||
assert.Equal(t, tc.expectedSecond, second) | ||
} | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
package v1 | ||
|
||
import ( | ||
"reflect" | ||
|
||
xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1" | ||
"github.com/crossplane/crossplane-runtime/pkg/meta" | ||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||
"k8s.io/apimachinery/pkg/runtime" | ||
"k8s.io/apimachinery/pkg/runtime/schema" | ||
) | ||
|
||
// PostgreSQLParameters are the configurable fields of a PostgreSQL. | ||
type PostgreSQLParameters struct { | ||
Maintenance MaintenanceSpec `json:"maintenance,omitempty"` | ||
Backup BackupSpec `json:"backup,omitempty"` | ||
|
||
// +kubebuilder:validation:Enum=ch-gva-2;ch-dk-2;de-fra-1;de-muc-1;at-vie-1;bg-sof-1 | ||
// +kubebuilder:validation:Required | ||
|
||
// Zone is the datacenter identifier in which the instance runs in. | ||
Zone string `json:"zone"` | ||
|
||
DBaaSParameters `json:",inline"` | ||
|
||
// PGSettings contains additional PostgreSQL settings. | ||
PGSettings runtime.RawExtension `json:"pgSettings,omitempty"` | ||
} | ||
|
||
// SizeSpec contains settings to control the sizing of a service. | ||
type SizeSpec struct { | ||
Plan string `json:"plan,omitempty"` | ||
} | ||
|
||
// IPFilter is a list of allowed IPv4 CIDR ranges that can access the service. | ||
// If no IP Filter is set, you may not be able to reach the service. | ||
// A value of `0.0.0.0/0` will open the service to all addresses on the public internet. | ||
type IPFilter []string | ||
|
||
// PostgreSQLSpec defines the desired state of a PostgreSQL. | ||
type PostgreSQLSpec struct { | ||
xpv1.ResourceSpec `json:",inline"` | ||
ForProvider PostgreSQLParameters `json:"forProvider"` | ||
} | ||
|
||
// PostgreSQLObservation are the observable fields of a PostgreSQL. | ||
type PostgreSQLObservation struct { | ||
DBaaSParameters `json:",inline"` | ||
Maintenance MaintenanceSpec `json:"maintenance,omitempty"` | ||
Backup BackupSpec `json:"backup,omitempty"` | ||
NoteStates []NodeState `json:"noteStates,omitempty"` | ||
PGSettings runtime.RawExtension `json:"pgSettings,omitempty"` | ||
} | ||
|
||
// PostgreSQLStatus represents the observed state of a PostgreSQL. | ||
type PostgreSQLStatus struct { | ||
xpv1.ResourceStatus `json:",inline"` | ||
AtProvider PostgreSQLObservation `json:"atProvider,omitempty"` | ||
} | ||
|
||
// +kubebuilder:object:root=true | ||
// +kubebuilder:printcolumn:name="State",type="string",JSONPath=".status.conditions[?(@.type=='Ready')].reason" | ||
// +kubebuilder:printcolumn:name="Ready",type="string",JSONPath=".status.conditions[?(@.type=='Ready')].status" | ||
// +kubebuilder:printcolumn:name="Synced",type="string",JSONPath=".status.conditions[?(@.type=='Synced')].status" | ||
// +kubebuilder:printcolumn:name="External Name",type="string",JSONPath=".metadata.annotations.crossplane\\.io/external-name" | ||
// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp" | ||
// +kubebuilder:subresource:status | ||
// +kubebuilder:resource:scope=Cluster,categories={crossplane,exoscale} | ||
// +kubebuilder:webhook:verbs=create;update,path=/validate-exoscale-crossplane-io-v1-postgresql,mutating=false,failurePolicy=fail,groups=exoscale.crossplane.io,resources=postgresqls,versions=v1,name=postgresqls.exoscale.crossplane.io,sideEffects=None,admissionReviewVersions=v1 | ||
|
||
// PostgreSQL is the API for creating PostgreSQL. | ||
type PostgreSQL struct { | ||
metav1.TypeMeta `json:",inline"` | ||
metav1.ObjectMeta `json:"metadata,omitempty"` | ||
|
||
Spec PostgreSQLSpec `json:"spec"` | ||
Status PostgreSQLStatus `json:"status,omitempty"` | ||
} | ||
|
||
// GetProviderConfigName returns the name of the ProviderConfig. | ||
// Returns empty string if reference not given. | ||
func (in *PostgreSQL) GetProviderConfigName() string { | ||
if ref := in.GetProviderConfigReference(); ref != nil { | ||
return ref.Name | ||
} | ||
return "" | ||
} | ||
|
||
// GetInstanceName returns the external name of the instance in the following precedence: | ||
// | ||
// .metadata.annotations."crossplane.io/external-name" | ||
// .metadata.name | ||
func (in *PostgreSQL) GetInstanceName() string { | ||
if name := meta.GetExternalName(in); name != "" { | ||
return name | ||
} | ||
return in.Name | ||
} | ||
|
||
// +kubebuilder:object:root=true | ||
|
||
// PostgreSQLList contains a list of PostgreSQL | ||
type PostgreSQLList struct { | ||
metav1.TypeMeta `json:",inline"` | ||
metav1.ListMeta `json:"metadata,omitempty"` | ||
Items []PostgreSQL `json:"items"` | ||
} | ||
|
||
// PostgreSQL type metadata. | ||
var ( | ||
PostgreSQLKind = reflect.TypeOf(PostgreSQL{}).Name() | ||
PostgreSQLGroupKind = schema.GroupKind{Group: Group, Kind: PostgreSQLKind}.String() | ||
PostgreSQLKindAPIVersion = PostgreSQLKind + "." + SchemeGroupVersion.String() | ||
PostgreSQLGroupVersionKind = SchemeGroupVersion.WithKind(PostgreSQLKind) | ||
) | ||
|
||
func init() { | ||
SchemeBuilder.Register(&PostgreSQL{}, &PostgreSQLList{}) | ||
} |
Oops, something went wrong.