Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Azure SQL local user #3701

Merged
merged 1 commit into from
Jan 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 24 additions & 24 deletions docs/hugo/content/reference/_index.md

Large diffs are not rendered by default.

48 changes: 24 additions & 24 deletions docs/hugo/content/reference/sql/_index.md

Large diffs are not rendered by default.

8 changes: 8 additions & 0 deletions v2/api/sql/v1/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/*
Copyright (c) Microsoft Corporation.
Licensed under the MIT license.
*/

// Package v1 contains hand-crafted API Schema definitions for the sql v1 API group
// +groupName=sql.azure.com
package v1
27 changes: 27 additions & 0 deletions v2/api/sql/v1/groupversion_info.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
Copyright (c) Microsoft Corporation.
Licensed under the MIT license.
*/

// Package v1 contains API Schema definitions for sql data plane APIs
// +kubebuilder:object:generate=true
// All object properties are optional by default, this will be overridden when needed:
// +kubebuilder:validation:Optional
// +groupName=sql.azure.com
package v1

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: "sql.azure.com", Version: "v1"}

// 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
)
303 changes: 303 additions & 0 deletions v2/api/sql/v1/user_types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,303 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
package v1

import (
"github.com/pkg/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
kerrors "k8s.io/apimachinery/pkg/util/errors"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
"sigs.k8s.io/controller-runtime/pkg/conversion"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"

"github.com/Azure/azure-service-operator/v2/pkg/genruntime"
"github.com/Azure/azure-service-operator/v2/pkg/genruntime/conditions"
)

// +kubebuilder:rbac:groups=sql.azure.com,resources=users,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=sql.azure.com,resources={users/status,users/finalizers},verbs=get;update;patch

// +kubebuilder:object:root=true
// +kubebuilder:subresource:status
// +kubebuilder:printcolumn:name="Ready",type="string",JSONPath=".status.conditions[?(@.type=='Ready')].status"
// +kubebuilder:printcolumn:name="Severity",type="string",JSONPath=".status.conditions[?(@.type=='Ready')].severity"
// +kubebuilder:printcolumn:name="Reason",type="string",JSONPath=".status.conditions[?(@.type=='Ready')].reason"
// +kubebuilder:printcolumn:name="Message",type="string",JSONPath=".status.conditions[?(@.type=='Ready')].message"
// +kubebuilder:storageversion
// User is an Azure SQL user
type User struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec UserSpec `json:"spec,omitempty"`
Status UserStatus `json:"status,omitempty"`
}

var _ conditions.Conditioner = &User{}

// GetConditions returns the conditions of the resource
func (user *User) GetConditions() conditions.Conditions {
return user.Status.Conditions
}

// SetConditions sets the conditions on the resource status
func (user *User) SetConditions(conditions conditions.Conditions) {
user.Status.Conditions = conditions
}

// +kubebuilder:webhook:path=/mutate-sql-azure-com-v1-user,mutating=true,sideEffects=None,matchPolicy=Exact,failurePolicy=fail,groups=sql.azure.com,resources=users,verbs=create;update,versions=v1,name=default.v1.users.sql.azure.com,admissionReviewVersions=v1

var _ admission.Defaulter = &User{}

// Default applies defaults to the FlexibleServer resource
func (user *User) Default() {
user.defaultImpl()
var temp interface{} = user
if runtimeDefaulter, ok := temp.(genruntime.Defaulter); ok {
runtimeDefaulter.CustomDefault()
}
}

// defaultAzureName defaults the Azure name of the resource to the Kubernetes name
func (user *User) defaultAzureName() {
if user.Spec.AzureName == "" {
user.Spec.AzureName = user.Name
}
}

// defaultImpl applies the code generated defaults to the FlexibleServer resource
func (user *User) defaultImpl() { user.defaultAzureName() }

var _ genruntime.ARMOwned = &User{}

// AzureName returns the Azure name of the resource
func (user *User) AzureName() string {
return user.Spec.AzureName
}

// Owner returns the ResourceReference of the owner, or nil if there is no owner
func (user *User) Owner() *genruntime.ResourceReference {
group, kind := genruntime.LookupOwnerGroupKind(user.Spec)
return &genruntime.ResourceReference{
Group: group,
Kind: kind,
Name: user.Spec.Owner.Name,
}
}

// +kubebuilder:webhook:path=/validate-sql-azure-com-v1-user,mutating=false,sideEffects=None,matchPolicy=Exact,failurePolicy=fail,groups=sql.azure.com,resources=users,verbs=create;update,versions=v1,name=validate.v1.users.sql.azure.com,admissionReviewVersions=v1

var _ admission.Validator = &User{}

// ValidateCreate validates the creation of the resource
func (user *User) ValidateCreate() (admission.Warnings, error) {
validations := user.createValidations()
var temp interface{} = user
if runtimeValidator, ok := temp.(genruntime.Validator); ok {
validations = append(validations, runtimeValidator.CreateValidations()...)
}
return genruntime.ValidateCreate(validations)
}

// ValidateDelete validates the deletion of the resource
func (user *User) ValidateDelete() (admission.Warnings, error) {
validations := user.deleteValidations()
var temp interface{} = user
if runtimeValidator, ok := temp.(genruntime.Validator); ok {
validations = append(validations, runtimeValidator.DeleteValidations()...)
}
return genruntime.ValidateDelete(validations)
}

// ValidateUpdate validates an update of the resource
func (user *User) ValidateUpdate(old runtime.Object) (admission.Warnings, error) {
validations := user.updateValidations()
var temp interface{} = user
if runtimeValidator, ok := temp.(genruntime.Validator); ok {
validations = append(validations, runtimeValidator.UpdateValidations()...)
}
return genruntime.ValidateUpdate(old, validations)
}

// createValidations validates the creation of the resource
func (user *User) createValidations() []func() (admission.Warnings, error) {
return nil
}

// deleteValidations validates the deletion of the resource
func (user *User) deleteValidations() []func() (admission.Warnings, error) {
return nil
}

// updateValidations validates the update of the resource
func (user *User) updateValidations() []func(old runtime.Object) (admission.Warnings, error) {
return []func(old runtime.Object) (admission.Warnings, error){
user.validateWriteOncePropertiesNotChanged,
}
}

// validateWriteOncePropertiesNotChanged function validates the update on WriteOnce properties.
// TODO: Note this should be kept in sync with admissions.ValidateWriteOnceProperties
func (user *User) validateWriteOncePropertiesNotChanged(oldObj runtime.Object) (admission.Warnings, error) {
var errs []error

oldUser, ok := oldObj.(*User)
if !ok {
// This shouldn't happen, but if it does, don't block things
return nil, nil
}

// If we don't have a finalizer yet, it's OK to change things
hasFinalizer := controllerutil.ContainsFinalizer(oldUser, genruntime.ReconcilerFinalizer)
if !hasFinalizer {
return nil, nil
}

if oldUser.Spec.AzureName != user.Spec.AzureName {
err := errors.Errorf(
"updating 'AzureName' is not allowed for '%s : %s",
oldObj.GetObjectKind().GroupVersionKind(),
oldUser.GetName())
errs = append(errs, err)
}

// Ensure that owner has not been changed
oldOwner := oldUser.Owner()
newOwner := user.Owner()

bothHaveOwner := oldOwner != nil && newOwner != nil
ownerAdded := oldOwner == nil && newOwner != nil
ownerRemoved := oldOwner != nil && newOwner == nil

if (bothHaveOwner && oldOwner.Name != newOwner.Name) || ownerAdded {
err := errors.Errorf(
"updating 'Owner.Name' is not allowed for '%s : %s",
oldObj.GetObjectKind().GroupVersionKind(),
oldUser.GetName())
errs = append(errs, err)
} else if ownerRemoved {
err := errors.Errorf(
"removing 'Owner' is not allowed for '%s : %s",
oldObj.GetObjectKind().GroupVersionKind(),
oldUser.GetName())
errs = append(errs, err)
}

return nil, kerrors.NewAggregate(errs)
}

var _ conversion.Hub = &User{}

// Hub marks that this userSpec is the hub type for conversion
func (user *User) Hub() {}

// +kubebuilder:object:root=true
type UserList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []User `json:"items"`
}

type UserSpec struct {
// AzureName: The name of the resource in Azure. This is often the same as the name of the resource in Kubernetes but it
// doesn't have to be.
// If not specified, the default is the name of the Kubernetes object.
// When creating a local user, this will be the name of the user created.
// When creating an AAD user, this must have a specific format depending on the type of AAD user being created.
// For managed identity: "my-managed-identity-name"
// For standard AAD user: "myuser@mydomain.onmicrosoft.com"
// For AAD group: "my-group"
AzureName string `json:"azureName,omitempty"`

// +kubebuilder:validation:Required
// Owner: The owner of the resource. The owner controls where the resource goes when it is deployed. The owner also
// controls the resources lifecycle. When the owner is deleted the resource will also be deleted. Owner is expected to be a
// reference to an sql.azure.com/ServersDatabase resource
Owner *genruntime.KnownResourceReference `group:"sql.azure.com" json:"owner,omitempty" kind:"ServersDatabase"`

// The roles assigned to the user.
// See https://learn.microsoft.com/sql/relational-databases/security/authentication-access/database-level-roles?view=sql-server-ver16#fixed-database-roles
// for the fixed set of roles supported by Azure SQL.
// Roles include the following: db_owner, db_securityadmin, db_accessadmin, db_backupoperator,
// db_ddladmin, db_datawriter, db_datareader, db_denydatawriter, and db_denydatareader.
Roles []string `json:"roles,omitempty"`

// +kubebuilder:validation:Required
// LocalUser contains details for creating a standard (non-aad) Azure SQL User
LocalUser *LocalUserSpec `json:"localUser,omitempty"`
}

// OriginalVersion returns the original API version used to create the resource.
func (userSpec *UserSpec) OriginalVersion() string {
return GroupVersion.Version
}

// SetAzureName sets the Azure name of the resource
func (userSpec *UserSpec) SetAzureName(azureName string) { userSpec.AzureName = azureName }

//var _ genruntime.ConvertibleSpec = &UserSpec{}
//
//// ConvertSpecFrom populates our ConfigurationStore_Spec from the provided source
//func (userSpec *UserSpec) ConvertSpecFrom(source genruntime.ConvertibleSpec) error {
// if source == userSpec {
// return errors.New("attempted conversion between unrelated implementations of github.com/Azure/azure-service-operator/v2/pkg/genruntime/ConvertibleSpec")
// }
//
// return source.ConvertSpecTo(userSpec)
//}
//
//// ConvertSpecTo populates the provided destination from our ConfigurationStore_Spec
//func (userSpec *UserSpec) ConvertSpecTo(destination genruntime.ConvertibleSpec) error {
// if destination == userSpec {
// return errors.New("attempted conversion between unrelated implementations of github.com/Azure/azure-service-operator/v2/pkg/genruntime/ConvertibleSpec")
// }
//
// return destination.ConvertSpecFrom(userSpec)
//}

type LocalUserSpec struct {
// +kubebuilder:validation:Required
// ServerAdminUsername is the username of the Server administrator. If the
// administrator is a group, the ServerAdminUsername should be the group name, not the actual username of the
// identity to log in with. For example if the administrator group is "admin-group" and identity "my-identity" is
// a member of that group, the ServerAdminUsername should be "admin-group".
ServerAdminUsername string `json:"serverAdminUsername,omitempty"`

// +kubebuilder:validation:Required
// ServerAdminPassword is a reference to a secret containing the servers administrator password.
ServerAdminPassword *genruntime.SecretReference `json:"serverAdminPassword,omitempty"`

// +kubebuilder:validation:Required
// Password is the password to use for the user
Password *genruntime.SecretReference `json:"password,omitempty"`
}

type UserStatus struct {
//Conditions: The observed state of the resource
Conditions []conditions.Condition `json:"conditions,omitempty"`
}

// TODO: Where are these (and the spec flavors) called? Or are these just placehodlers for if/when there's a newer version?
//var _ genruntime.ConvertibleStatus = &UserStatus{}
//
//// ConvertStatusFrom populates our ConfigurationStore_STATUS from the provided source
//func (userStatus *UserStatus) ConvertStatusFrom(source genruntime.ConvertibleStatus) error {
// if source == userStatus {
// return errors.New("attempted conversion between unrelated implementations of github.com/Azure/azure-service-operator/v2/pkg/genruntime/ConvertibleStatus")
// }
//
// return source.ConvertStatusTo(userStatus)
//}
//
//// ConvertStatusTo populates the provided destination from our ConfigurationStore_STATUS
//func (userStatus *UserStatus) ConvertStatusTo(destination genruntime.ConvertibleStatus) error {
// if destination == userStatus {
// return errors.New("attempted conversion between unrelated implementations of github.com/Azure/azure-service-operator/v2/pkg/genruntime/ConvertibleStatus")
// }
//
// return destination.ConvertStatusFrom(userStatus)
//}

func init() {
SchemeBuilder.Register(&User{}, &UserList{})
}
Loading