Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
add controller to create and delete individual usernames in mariadb
Browse files Browse the repository at this point in the history
this is a first draft of a "create /drop account" controller that is
separate from the main "create/drop database" controller, for the
purpose of producing rotating username/passwords. The background for the
change is based on discussions surrounding
https://issues.redhat.com/browse/OSPRH-92 where internal control plane
services such as Galera , Rabbit, Redis etc. would provide interfaces to
add /remove arbitrary usernames, where a "password rotation" would
involve adding a new username/password and having services switch there,
retiring the old account once all finalizers have been removed.
zzzeek committed Nov 22, 2023
1 parent d9d9d1f commit 670f352
Showing 12 changed files with 648 additions and 57 deletions.
60 changes: 60 additions & 0 deletions api/bases/mariadb.openstack.org_mariadbaccounts.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.11.1
creationTimestamp: null
name: mariadbaccounts.mariadb.openstack.org
spec:
group: mariadb.openstack.org
names:
kind: MariaDBAccount
listKind: MariaDBAccountList
plural: mariadbaccounts
singular: mariadbaccount
scope: Namespaced
versions:
- name: v1beta1
schema:
openAPIV3Schema:
description: MariaDBAccount is the Schema for the mariadbaccounts 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/sig-architecture/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/sig-architecture/api-conventions.md#types-kinds'
type: string
metadata:
type: object
spec:
description: MariaDBAccountSpec defines the desired state of MariaDBAccount
properties:
secret:
description: Name of secret which contains DatabasePassword
type: string
userName:
description: UserName for new account
type: string
type: object
status:
description: MariaDBAccountStatus defines the observed state of MariaDBAccount
properties:
completed:
type: boolean
hash:
additionalProperties:
type: string
description: Map of hashes to track e.g. job status
type: object
type: object
type: object
served: true
storage: true
subresources:
status: {}
22 changes: 14 additions & 8 deletions api/v1beta1/mariadbaccount_types.go
Original file line number Diff line number Diff line change
@@ -20,22 +20,28 @@ import (
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.
const (
// AccountCreateHash hash
AccountCreateHash = "accountcreate"

// AccountDeleteHash hash
AccountDeleteHash = "accountdelete"
)

// MariaDBAccountSpec defines the desired state of MariaDBAccount
type MariaDBAccountSpec struct {
// INSERT ADDITIONAL SPEC FIELDS - desired state of cluster
// Important: Run "make" to regenerate code after modifying this file
// UserName for new account
UserName string `json:"userName,omitempty"`

// Foo is an example field of MariaDBAccount. Edit mariadbaccount_types.go to remove/update
Foo string `json:"foo,omitempty"`
// Name of secret which contains DatabasePassword
Secret string `json:"secret,omitempty"`
}

// MariaDBAccountStatus defines the observed state of MariaDBAccount
type MariaDBAccountStatus struct {
// INSERT ADDITIONAL STATUS FIELD - define observed state of cluster
// Important: Run "make" to regenerate code after modifying this file
Completed bool `json:"completed,omitempty"`
// Map of hashes to track e.g. job status
Hash map[string]string `json:"hash,omitempty"`
}

//+kubebuilder:object:root=true
60 changes: 60 additions & 0 deletions config/crd/bases/mariadb.openstack.org_mariadbaccounts.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.11.1
creationTimestamp: null
name: mariadbaccounts.mariadb.openstack.org
spec:
group: mariadb.openstack.org
names:
kind: MariaDBAccount
listKind: MariaDBAccountList
plural: mariadbaccounts
singular: mariadbaccount
scope: Namespaced
versions:
- name: v1beta1
schema:
openAPIV3Schema:
description: MariaDBAccount is the Schema for the mariadbaccounts 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/sig-architecture/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/sig-architecture/api-conventions.md#types-kinds'
type: string
metadata:
type: object
spec:
description: MariaDBAccountSpec defines the desired state of MariaDBAccount
properties:
secret:
description: Name of secret which contains DatabasePassword
type: string
userName:
description: UserName for new account
type: string
type: object
status:
description: MariaDBAccountStatus defines the observed state of MariaDBAccount
properties:
completed:
type: boolean
hash:
additionalProperties:
type: string
description: Map of hashes to track e.g. job status
type: object
type: object
type: object
served: true
storage: true
subresources:
status: {}
26 changes: 26 additions & 0 deletions config/rbac/role.yaml
Original file line number Diff line number Diff line change
@@ -151,6 +151,32 @@ rules:
- list
- patch
- update
- apiGroups:
- mariadb.openstack.org
resources:
- mariadbaccounts
verbs:
- create
- delete
- get
- list
- patch
- update
- watch
- apiGroups:
- mariadb.openstack.org
resources:
- mariadbaccounts/finalizers
verbs:
- update
- apiGroups:
- mariadb.openstack.org
resources:
- mariadbaccounts/status
verbs:
- get
- patch
- update
- apiGroups:
- mariadb.openstack.org
resources:
17 changes: 15 additions & 2 deletions config/samples/mariadb_v1beta1_mariadbaccount.yaml
Original file line number Diff line number Diff line change
@@ -7,6 +7,19 @@ metadata:
app.kubernetes.io/part-of: mariadb-operator
app.kubernetes.io/managed-by: kustomize
app.kubernetes.io/created-by: mariadb-operator
name: mariadbaccount-sample
mariaDBDatabaseName: neutron
name: neutron1
spec:
# TODO(user): Add fields here
userName: neutron1
secret: neutrondb-secret

---

apiVersion: v1
data:
# neutron123
DatabasePassword: bmV1dHJvbjEyMw==
kind: Secret
metadata:
name: neutrondb-secret
type: Opaque
44 changes: 44 additions & 0 deletions controllers/galera_controller.go
Original file line number Diff line number Diff line change
@@ -28,6 +28,7 @@ import (
corev1 "k8s.io/api/core/v1"
rbacv1 "k8s.io/api/rbac/v1"
k8s_errors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
"k8s.io/kubectl/pkg/util/podutils"
@@ -49,6 +50,7 @@ import (
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
"sigs.k8s.io/controller-runtime/pkg/log"

databasev1beta1 "github.com/openstack-k8s-operators/mariadb-operator/api/v1beta1"
mariadbv1 "github.com/openstack-k8s-operators/mariadb-operator/api/v1beta1"
mariadb "github.com/openstack-k8s-operators/mariadb-operator/pkg/mariadb"
)
@@ -665,3 +667,45 @@ func (r *GaleraReconciler) SetupWithManager(mgr ctrl.Manager) error {
Owns(&rbacv1.RoleBinding{}).
Complete(r)
}

// GetDatabaseObject - returns either a Galera or MariaDB object (and an associated client.Object interface).
// used by both MariaDBDatabaseReconciler and MariaDBAccountReconciler
// this will later return only Galera objects, so as a lookup it's part of the galera controller

func GetDatabaseObject(clientObj client.Client, ctx context.Context, name string, namespace string) (client.Object, *databasev1beta1.Galera, *databasev1beta1.MariaDB, error) {

dbGalera := &databasev1beta1.Galera{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: namespace,
},
}

objectKey := client.ObjectKeyFromObject(dbGalera)

err := clientObj.Get(ctx, objectKey, dbGalera)
if err != nil && !k8s_errors.IsNotFound(err) {
return nil, nil, nil, err
}

if err != nil {
// Try to fetch MariaDB when Galera is not used
dbMariadb := &databasev1beta1.MariaDB{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: namespace,
},
}

objectKey = client.ObjectKeyFromObject(dbMariadb)

err = clientObj.Get(ctx, objectKey, dbMariadb)
if err != nil {
return nil, nil, nil, err
}

return dbMariadb, nil, dbMariadb, nil
}

return dbGalera, dbGalera, nil, nil
}
289 changes: 277 additions & 12 deletions controllers/mariadbaccount_controller.go
Original file line number Diff line number Diff line change
@@ -18,19 +18,36 @@ package controllers

import (
"context"
"fmt"
"time"

"github.com/go-logr/logr"
helper "github.com/openstack-k8s-operators/lib-common/modules/common/helper"
job "github.com/openstack-k8s-operators/lib-common/modules/common/job"
databasev1beta1 "github.com/openstack-k8s-operators/mariadb-operator/api/v1beta1"
mariadb "github.com/openstack-k8s-operators/mariadb-operator/pkg/mariadb"
k8s_errors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/kubernetes"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/log"

mariadbv1beta1 "github.com/openstack-k8s-operators/mariadb-operator/api/v1beta1"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
)

// MariaDBAccountReconciler reconciles a MariaDBAccount object
type MariaDBAccountReconciler struct {
client.Client
Scheme *runtime.Scheme
Kclient kubernetes.Interface
Log logr.Logger
Scheme *runtime.Scheme
}

// SetupWithManager -
func (r *MariaDBAccountReconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
For(&databasev1beta1.MariaDBAccount{}).
Complete(r)
}

//+kubebuilder:rbac:groups=mariadb.openstack.org,resources=mariadbaccounts,verbs=get;list;watch;create;update;patch;delete
@@ -46,17 +63,265 @@ type MariaDBAccountReconciler struct {
//
// For more details, check Reconcile and its Result here:
// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.14.1/pkg/reconcile
func (r *MariaDBAccountReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
_ = log.FromContext(ctx)
func (r *MariaDBAccountReconciler) Reconcile(ctx context.Context, req ctrl.Request) (result ctrl.Result, _err error) {
_ = r.Log.WithValues("MariaDBAccount", req.NamespacedName)

var err error

instance := &databasev1beta1.MariaDBAccount{}
err = r.Client.Get(ctx, req.NamespacedName, instance)
if err != nil {
return ctrl.Result{}, client.IgnoreNotFound(err)
}

helper, err := helper.NewHelper(
instance,
r.Client,
r.Kclient,
r.Scheme,
r.Log,
)
if err != nil {
return ctrl.Result{}, err
}

// Always patch the instance status when exiting this function so we can persist any changes.
defer func() {
err := helper.PatchInstance(ctx, instance)
if err != nil {
_err = err
return
}
}()

isDelete := !instance.DeletionTimestamp.IsZero()

// locate the MariaDBDatabase object that this account is associated with
mariadbDatabase, err := r.getMariaDBDatabaseObject(ctx, instance)

// if not found because the label is missing, don't requeue, will be
// reconciled again when label is fixed
if err != nil && k8s_errors.IsNotFound(err) && instance.ObjectMeta.Labels["mariaDBDatabaseName"] == "" {
r.Log.Info(fmt.Sprintf("MariaDBAccount '%s' does not have a 'mariaDBDatabaseName' label; returning", instance.Name))
return ctrl.Result{}, err
}

// not found, or found but not in completed status. requeue for a create,
// exit operation for a delete
if (err != nil && k8s_errors.IsNotFound(err)) || (err == nil && !mariadbDatabase.Status.Completed) {
if !isDelete {
// for the create case, need to wait for the MariaDBDatabase to exists before we can continue;
// requeue
if err != nil {
r.Log.Info(fmt.Sprintf(
"MariaDBAccount '%s' didn't find MariaDBDatabase '%s'; requeueing",
instance.Name, instance.ObjectMeta.Labels["mariaDBDatabaseName"]))
} else {
r.Log.Info(fmt.Sprintf(
"MariaDBAccount '%s' MariaDBDatabase '%s' not yet complete; requeueing",
instance.Name, instance.ObjectMeta.Labels["mariaDBDatabaseName"]))
}
return ctrl.Result{RequeueAfter: time.Duration(10) * time.Second}, nil
} else {
// for the delete case, the database doesn't exist. so
// that means we don't, either. remove finalizer from
// our own instance and return
if err != nil {
r.Log.Info(fmt.Sprintf(
"MariaDBAccount '%s' Didn't find MariaDBDatabase '%s'; no account delete needed",
instance.Name, instance.ObjectMeta.Labels["mariaDBDatabaseName"]))

} else {
r.Log.Info(fmt.Sprintf(
"MariaDBAccount '%s' MariaDBDatabase '%s' not yet complete; no account delete needed",
instance.Name, instance.ObjectMeta.Labels["mariaDBDatabaseName"]))
}
controllerutil.RemoveFinalizer(instance, helper.GetFinalizer())

return ctrl.Result{}, nil
}
} else if err != nil {
// unhandled error; exit
return ctrl.Result{}, nil
}

if !isDelete {
// MariaDBdatabase exists and we are a create case. ensure finalizers set up
if controllerutil.AddFinalizer(mariadbDatabase, fmt.Sprintf("%s-%s", helper.GetFinalizer(), instance.Name)) {
err := r.Update(ctx, mariadbDatabase)
if err != nil {
return ctrl.Result{}, err
}
}

if controllerutil.AddFinalizer(instance, helper.GetFinalizer()) {
// we need to persist this right away
return ctrl.Result{}, nil
}
}

// now proceed to do actual work. acquire the galera/mariadb instance
// referenced by the MariaDBDatabase which will lead us to the hostname
// and container image to target
dbGalera, dbMariadb, err := r.getDatabaseObject(ctx, mariadbDatabase, instance)
if err != nil {
return ctrl.Result{}, err
}

var dbInstance, dbAdminSecret, dbContainerImage, serviceAccountName string

// ensure Galera/MariaDB instance itself is ready
// TODO: not sure to what extent galera / mariadb would not be ready
// at this point, considering MariaDBDatabase is dependent on this state
// as well and that's been assured as "ready"
if dbGalera != nil {
if !dbGalera.Status.Bootstrapped {
r.Log.Info("DB bootstrap not complete. Requeue...")
return ctrl.Result{RequeueAfter: time.Second * 10}, nil
}

dbInstance = dbGalera.Name
dbAdminSecret = dbGalera.Spec.Secret
dbContainerImage = dbGalera.Spec.ContainerImage
serviceAccountName = dbGalera.RbacResourceName()
} else if dbMariadb != nil {
if dbMariadb.Status.DbInitHash == "" {
r.Log.Info("DB initialization not complete. Requeue...")
return ctrl.Result{RequeueAfter: time.Duration(10) * time.Second}, nil
}

// TODO(user): your logic here
dbInstance = dbMariadb.Name
dbAdminSecret = dbMariadb.Spec.Secret
dbContainerImage = dbMariadb.Spec.ContainerImage
serviceAccountName = dbMariadb.RbacResourceName()
} else {
r.Log.Error(err, "no mariadb or galera, should not be here")
return ctrl.Result{}, err
}

if !isDelete {
// account create

jobDef, err := mariadb.CreateDbAccountJob(instance, mariadbDatabase.Name, dbInstance, dbAdminSecret, dbContainerImage, serviceAccountName)
if err != nil {
return ctrl.Result{}, err
}

accountCreateHash := instance.Status.Hash[databasev1beta1.AccountCreateHash]
accountCreateJob := job.NewJob(
jobDef,
databasev1beta1.AccountCreateHash,
false,
time.Duration(5)*time.Second,
accountCreateHash,
)
ctrlResult, err := accountCreateJob.DoJob(
ctx,
helper,
)
if (ctrlResult != ctrl.Result{}) {
return ctrlResult, nil
}
if err != nil {
return ctrl.Result{}, err
}
if accountCreateJob.HasChanged() {
if instance.Status.Hash == nil {
instance.Status.Hash = make(map[string]string)
}
instance.Status.Hash[databasev1beta1.AccountCreateHash] = accountCreateJob.GetHash()
r.Log.Info(fmt.Sprintf("Job %s hash added - %s", jobDef.Name, instance.Status.Hash[databasev1beta1.AccountCreateHash]))
}

// database creation finished
instance.Status.Completed = true

} else {
// account delete

jobDef, err := mariadb.DeleteDbAccountJob(instance, mariadbDatabase.Name, dbInstance, dbAdminSecret, dbContainerImage, serviceAccountName)
if err != nil {
return ctrl.Result{}, err
}

accountDeleteHash := instance.Status.Hash[databasev1beta1.AccountDeleteHash]
accountDeleteJob := job.NewJob(
jobDef,
databasev1beta1.AccountDeleteHash,
false,
time.Duration(5)*time.Second,
accountDeleteHash,
)
ctrlResult, err := accountDeleteJob.DoJob(
ctx,
helper,
)
if (ctrlResult != ctrl.Result{}) {
return ctrlResult, nil
}
if err != nil {
return ctrl.Result{}, err
}
if accountDeleteJob.HasChanged() {
if instance.Status.Hash == nil {
instance.Status.Hash = make(map[string]string)
}
// TODO: do we set a hash for deletes?
instance.Status.Hash[databasev1beta1.AccountDeleteHash] = accountDeleteJob.GetHash()
r.Log.Info(fmt.Sprintf("Job %s hash added - %s", jobDef.Name, instance.Status.Hash[databasev1beta1.AccountDeleteHash]))
}

// remove finalizer from the MariaDBDatabase instance
if controllerutil.RemoveFinalizer(mariadbDatabase, fmt.Sprintf("%s-%s", helper.GetFinalizer(), instance.Name)) {
err = r.Update(ctx, mariadbDatabase)
}

// remove finalizer from our own instance
controllerutil.RemoveFinalizer(instance, helper.GetFinalizer())

if err != nil {
return ctrl.Result{}, err
}
}

return ctrl.Result{}, nil
}

// SetupWithManager sets up the controller with the Manager.
func (r *MariaDBAccountReconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
For(&mariadbv1beta1.MariaDBAccount{}).
Complete(r)
// getDatabaseObject - returns either a Galera or MariaDB object (and an associated client.Object interface)
func (r *MariaDBAccountReconciler) getDatabaseObject(ctx context.Context, mariaDBDatabase *databasev1beta1.MariaDBDatabase, instance *databasev1beta1.MariaDBAccount) (*databasev1beta1.Galera, *databasev1beta1.MariaDB, error) {
dbName := mariaDBDatabase.ObjectMeta.Labels["dbName"]
_, dbGalera, dbMariaDB, err := GetDatabaseObject(
r.Client, ctx,
dbName,
instance.Namespace,
)

return dbGalera, dbMariaDB, err
}

// getMariaDBDatabaseObject - returns either a Galera or MariaDB object (and an associated client.Object interface)
func (r *MariaDBAccountReconciler) getMariaDBDatabaseObject(ctx context.Context, instance *databasev1beta1.MariaDBAccount) (*databasev1beta1.MariaDBDatabase, error) {
// this is following from how the MariaDBDatabase CRD works.
// the related Galera / MariaDB object is given as a label, while
// the reference to the secret itself is given in the spec
// the convention appears to be: "things we are dependent on are named in labels,
// things we are setting up are named in the spec"
mariadbDatabaseName := instance.ObjectMeta.Labels["mariaDBDatabaseName"]

mariaDBDatabase := &databasev1beta1.MariaDBDatabase{
ObjectMeta: metav1.ObjectMeta{
Name: mariadbDatabaseName,
Namespace: instance.Namespace,
},
}

objectKey := client.ObjectKeyFromObject(mariaDBDatabase)

err := r.Client.Get(ctx, objectKey, mariaDBDatabase)
if err != nil {
return nil, err
}

return mariaDBDatabase, err

}
40 changes: 5 additions & 35 deletions controllers/mariadbdatabase_controller.go
Original file line number Diff line number Diff line change
@@ -23,7 +23,6 @@ import (

"github.com/go-logr/logr"
k8s_errors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/kubernetes"
ctrl "sigs.k8s.io/controller-runtime"
@@ -206,38 +205,9 @@ func (r *MariaDBDatabaseReconciler) SetupWithManager(mgr ctrl.Manager) error {

// getDatabaseObject - returns either a Galera or MariaDB object (and an associated client.Object interface)
func (r *MariaDBDatabaseReconciler) getDatabaseObject(ctx context.Context, instance *databasev1beta1.MariaDBDatabase) (client.Object, *databasev1beta1.Galera, *databasev1beta1.MariaDB, error) {
dbGalera := &databasev1beta1.Galera{
ObjectMeta: metav1.ObjectMeta{
Name: instance.ObjectMeta.Labels["dbName"],
Namespace: instance.Namespace,
},
}

objectKey := client.ObjectKeyFromObject(dbGalera)

err := r.Client.Get(ctx, objectKey, dbGalera)
if err != nil && !k8s_errors.IsNotFound(err) {
return nil, nil, nil, err
}

if err != nil {
// Try to fetch MariaDB when Galera is not used
dbMariadb := &databasev1beta1.MariaDB{
ObjectMeta: metav1.ObjectMeta{
Name: instance.ObjectMeta.Labels["dbName"],
Namespace: instance.Namespace,
},
}

objectKey = client.ObjectKeyFromObject(dbMariadb)

err = r.Client.Get(ctx, objectKey, dbMariadb)
if err != nil {
return nil, nil, nil, err
}

return dbMariadb, nil, dbMariadb, nil
}

return dbGalera, dbGalera, nil, nil
return GetDatabaseObject(
r.Client, ctx,
instance.ObjectMeta.Labels["dbName"],
instance.Namespace,
)
}
9 changes: 9 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
@@ -129,6 +129,15 @@ func main() {
setupLog.Error(err, "unable to create controller", "controller", "MariaDBDatabase")
os.Exit(1)
}
if err = (&controllers.MariaDBAccountReconciler{
Client: mgr.GetClient(),
Kclient: kclient,
Log: ctrl.Log.WithName("controllers").WithName("MariaDBAccount"),
Scheme: mgr.GetScheme(),
}).SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "MariaDBAccount")
os.Exit(1)
}

// Acquire environmental defaults and initialize operator defaults with them
mariadbv1beta1.SetupDefaults()
131 changes: 131 additions & 0 deletions pkg/mariadb/account.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
package mariadb

import (
"strings"

util "github.com/openstack-k8s-operators/lib-common/modules/common/util"
databasev1beta1 "github.com/openstack-k8s-operators/mariadb-operator/api/v1beta1"
batchv1 "k8s.io/api/batch/v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

type accountCreateOrDeleteOptions struct {
UserName string
DatabaseName string
DatabaseHostname string
DatabaseAdminUsername string
}

func CreateDbAccountJob(account *databasev1beta1.MariaDBAccount, databaseName string, databaseHostName string, databaseSecret string, containerImage string, serviceAccountName string) (*batchv1.Job, error) {

opts := accountCreateOrDeleteOptions{account.Spec.UserName, databaseName, databaseHostName, "root"}
dbCmd, err := util.ExecuteTemplateFile("account.sh", &opts)
if err != nil {
return nil, err
}
labels := map[string]string{
"owner": "mariadb-operator", "cr": account.Spec.UserName, "app": "mariadbschema",
}
job := &batchv1.Job{
ObjectMeta: metav1.ObjectMeta{
// provided db name is used as metadata name where underscore is a not allowed
// character. Lets replace all underscores with hypen. Underscores in the db name are
// possible.
Name: strings.Replace(account.Spec.UserName, "_", "-", -1) + "-account-create",
Namespace: account.Namespace,
Labels: labels,
},
Spec: batchv1.JobSpec{
Template: corev1.PodTemplateSpec{
Spec: corev1.PodSpec{
RestartPolicy: corev1.RestartPolicyOnFailure,
ServiceAccountName: serviceAccountName,
Containers: []corev1.Container{
{
Name: "mariadb-account-create",
Image: containerImage,
Command: []string{"/bin/sh", "-c", dbCmd},
Env: []corev1.EnvVar{
{
Name: "MYSQL_PWD",
ValueFrom: &corev1.EnvVarSource{
SecretKeyRef: &corev1.SecretKeySelector{
LocalObjectReference: corev1.LocalObjectReference{
Name: databaseSecret,
},
Key: "DbRootPassword",
},
},
},
{
Name: "DatabasePassword",
ValueFrom: &corev1.EnvVarSource{
SecretKeyRef: &corev1.SecretKeySelector{
LocalObjectReference: corev1.LocalObjectReference{
Name: account.Spec.Secret,
},
Key: "DatabasePassword",
},
},
},
},
},
},
},
},
},
}

return job, nil
}

func DeleteDbAccountJob(account *databasev1beta1.MariaDBAccount, databaseName string, databaseHostName string, databaseSecret string, containerImage string, serviceAccountName string) (*batchv1.Job, error) {

opts := accountCreateOrDeleteOptions{account.Spec.UserName, databaseName, databaseHostName, "root"}

delCmd, err := util.ExecuteTemplateFile("delete_account.sh", &opts)
if err != nil {
return nil, err
}
labels := map[string]string{
"owner": "mariadb-operator", "cr": account.Spec.UserName, "app": "mariadbschema",
}
job := &batchv1.Job{
ObjectMeta: metav1.ObjectMeta{
Name: strings.Replace(account.Spec.UserName, "_", "", -1) + "-account-delete",
Namespace: account.Namespace,
Labels: labels,
},
Spec: batchv1.JobSpec{
Template: corev1.PodTemplateSpec{
Spec: corev1.PodSpec{
RestartPolicy: corev1.RestartPolicyOnFailure,
ServiceAccountName: serviceAccountName,
Containers: []corev1.Container{
{
Name: "mariadb-account-delete",
Image: containerImage,
Command: []string{"/bin/sh", "-c", delCmd},
Env: []corev1.EnvVar{
{
Name: "MYSQL_PWD",
ValueFrom: &corev1.EnvVarSource{
SecretKeyRef: &corev1.SecretKeySelector{
LocalObjectReference: corev1.LocalObjectReference{
Name: databaseSecret,
},
Key: "DbRootPassword",
},
},
},
},
},
},
},
},
},
}

return job, nil
}
4 changes: 4 additions & 0 deletions templates/account.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#!/bin/bash
export DatabasePassword=${DatabasePassword:?"Please specify a DatabasePassword variable."}

mysql -h {{.DatabaseHostname}} -u {{.DatabaseAdminUsername}} -P 3306 -e "GRANT ALL PRIVILEGES ON {{.DatabaseName}}.* TO '{{.UserName}}'@'localhost' IDENTIFIED BY '$DatabasePassword';GRANT ALL PRIVILEGES ON {{.DatabaseName}}.* TO '{{.UserName}}'@'%' IDENTIFIED BY '$DatabasePassword';"
3 changes: 3 additions & 0 deletions templates/delete_account.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#!/bin/bash

mysql -h {{.DatabaseHostname}} -u {{.DatabaseAdminUsername}} -P 3306 -e "DROP USER IF EXISTS '{{.UserName}}'@'localhost'; DROP USER IF EXISTS '{{.UserName}}'@'%';"

0 comments on commit 670f352

Please sign in to comment.