Skip to content

Commit 6da8537

Browse files
committed
feat(mysqldatabase): add commands
1 parent e5a8f98 commit 6da8537

File tree

14 files changed

+570
-1
lines changed

14 files changed

+570
-1
lines changed

create/create.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ type Cmd struct {
2828
Config configCmd `cmd:"" group:"deplo.io" name:"config" help:"Create a new deplo.io Project Configuration."`
2929
Application applicationCmd `cmd:"" group:"deplo.io" name:"application" aliases:"app,application" help:"Create a new deplo.io Application."`
3030
MySQL mySQLCmd `cmd:"" group:"storage.nine.ch" name:"mysql" help:"Create a new MySQL instance."`
31+
MySQLDatabase mysqlDatabaseCmd `cmd:"" group:"storage.nine.ch" name:"mysqldatabase" help:"Create a new MySQL database."`
3132
Postgres postgresCmd `cmd:"" group:"storage.nine.ch" name:"postgres" help:"Create a new PostgreSQL instance."`
3233
PostgresDatabase postgresDatabaseCmd `cmd:"" group:"storage.nine.ch" name:"postgresdatabase" help:"Create a new PostgreSQL database."`
3334
KeyValueStore keyValueStoreCmd `cmd:"" group:"storage.nine.ch" name:"keyvaluestore" aliases:"kvs" help:"Create a new KeyValueStore instance"`

create/mysqldatabase.go

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
package create
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"strings"
7+
8+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
9+
"k8s.io/apimachinery/pkg/watch"
10+
11+
"github.com/alecthomas/kong"
12+
runtimev1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
13+
meta "github.com/ninech/apis/meta/v1alpha1"
14+
storage "github.com/ninech/apis/storage/v1alpha1"
15+
16+
"github.com/ninech/nctl/api"
17+
)
18+
19+
type mysqlDatabaseCmd struct {
20+
resourceCmd
21+
Location string `placeholder:"${mysqldatabase_location_default}" help:"Location where the MySQL database is created. Available locations are: ${mysqldatabase_location_options}"`
22+
MySQLDatabaseVersion storage.MySQLVersion `placeholder:"${mysqldatabase_version_default}" help:"Release version with which the MySQL database is created. Available versions: ${mysqldatabase_versions}"`
23+
CharacterSet string `placeholder:"${mysqldatabase_characterset_default}" help:"Character set for the MySQL database. Available character sets: ${mysqldatabase_characterset_options}"`
24+
}
25+
26+
func (cmd *mysqlDatabaseCmd) Run(ctx context.Context, client *api.Client) error {
27+
fmt.Printf("Creating new MySQLDatabase. (waiting up to %s).\n", cmd.WaitTimeout)
28+
mysqlDatabase := cmd.newMySQLDatabase(client.Project)
29+
30+
c := newCreator(client, mysqlDatabase, "mysqldatabase")
31+
ctx, cancel := context.WithTimeout(ctx, cmd.WaitTimeout)
32+
defer cancel()
33+
34+
if err := c.createResource(ctx); err != nil {
35+
return err
36+
}
37+
38+
if !cmd.Wait {
39+
return nil
40+
}
41+
42+
return c.wait(ctx, waitStage{
43+
objectList: &storage.MySQLDatabaseList{},
44+
onResult: func(event watch.Event) (bool, error) {
45+
if mdb, ok := event.Object.(*storage.MySQLDatabase); ok {
46+
return isAvailable(mdb), nil
47+
}
48+
return false, nil
49+
},
50+
},
51+
)
52+
}
53+
54+
func (cmd *mysqlDatabaseCmd) newMySQLDatabase(namespace string) *storage.MySQLDatabase {
55+
name := getName(cmd.Name)
56+
57+
mysqlDatabase := &storage.MySQLDatabase{
58+
ObjectMeta: metav1.ObjectMeta{
59+
Name: name,
60+
Namespace: namespace,
61+
},
62+
Spec: storage.MySQLDatabaseSpec{
63+
ResourceSpec: runtimev1.ResourceSpec{
64+
WriteConnectionSecretToReference: &runtimev1.SecretReference{
65+
Name: "mysqldatabase-" + name,
66+
Namespace: namespace,
67+
},
68+
},
69+
ForProvider: storage.MySQLDatabaseParameters{
70+
Location: meta.LocationName(cmd.Location),
71+
Version: cmd.MySQLDatabaseVersion,
72+
CharacterSet: storage.MySQLCharacterSet{
73+
Name: cmd.CharacterSet,
74+
},
75+
},
76+
},
77+
}
78+
79+
return mysqlDatabase
80+
}
81+
82+
// MySQLDatabaseKongVars returns all variables which are used in the MySQLDatabase
83+
// create command
84+
func MySQLDatabaseKongVars() kong.Vars {
85+
result := make(kong.Vars)
86+
result["mysqldatabase_location_default"] = string(storage.MySQLDatabaseLocationDefault)
87+
result["mysqldatabase_location_options"] = strings.Join(stringSlice(storage.MySQLDatabaseLocationOptions), ", ")
88+
result["mysqldatabase_version_default"] = string(storage.MySQLDatabaseVersionDefault)
89+
result["mysqldatabase_versions"] = strings.Join(stringSlice(storage.MySQLDatabaseVersions), ", ")
90+
result["mysqldatabase_characterset_default"] = "utf8mb4"
91+
result["mysqldatabase_characterset_options"] = strings.Join([]string{"utf8mb4"}, ", ")
92+
93+
return result
94+
}

create/mysqldatabase_test.go

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
package create
2+
3+
import (
4+
"context"
5+
"errors"
6+
"testing"
7+
"time"
8+
9+
"github.com/google/go-cmp/cmp"
10+
storage "github.com/ninech/apis/storage/v1alpha1"
11+
"github.com/ninech/nctl/api"
12+
"github.com/ninech/nctl/internal/test"
13+
"github.com/stretchr/testify/require"
14+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
15+
"sigs.k8s.io/controller-runtime/pkg/client"
16+
"sigs.k8s.io/controller-runtime/pkg/client/interceptor"
17+
)
18+
19+
func TestMySQLDatabase(t *testing.T) {
20+
ctx := context.Background()
21+
22+
tests := []struct {
23+
name string
24+
create mysqlDatabaseCmd
25+
want storage.MySQLDatabaseParameters
26+
wantErr bool
27+
interceptorFuncs *interceptor.Funcs
28+
}{
29+
{
30+
name: "simple",
31+
create: mysqlDatabaseCmd{},
32+
want: storage.MySQLDatabaseParameters{},
33+
},
34+
{
35+
name: "simpleErrorOnCreation",
36+
create: mysqlDatabaseCmd{},
37+
wantErr: true,
38+
interceptorFuncs: &interceptor.Funcs{
39+
Create: func(_ context.Context, _ client.WithWatch, _ client.Object, _ ...client.CreateOption) error {
40+
return errors.New("error on creation")
41+
},
42+
},
43+
},
44+
{
45+
name: "version and character set",
46+
create: mysqlDatabaseCmd{MySQLDatabaseVersion: storage.MySQLDatabaseVersionDefault, CharacterSet: "ascii"},
47+
want: storage.MySQLDatabaseParameters{Version: storage.MySQLDatabaseVersionDefault, CharacterSet: storage.MySQLCharacterSet{Name: "ascii"}},
48+
},
49+
}
50+
for _, tt := range tests {
51+
t.Run(tt.name, func(t *testing.T) {
52+
tt.create.Name = "test-" + t.Name()
53+
tt.create.Wait = false
54+
tt.create.WaitTimeout = time.Second
55+
56+
opts := []test.ClientSetupOption{}
57+
if tt.interceptorFuncs != nil {
58+
opts = append(opts, test.WithInterceptorFuncs(*tt.interceptorFuncs))
59+
}
60+
apiClient, err := test.SetupClient(opts...)
61+
require.NoError(t, err)
62+
63+
if err := tt.create.Run(ctx, apiClient); (err != nil) != tt.wantErr {
64+
t.Errorf("mysqlDatabaseCmd.Run() error = %v, wantErr %v", err, tt.wantErr)
65+
}
66+
67+
created := &storage.MySQLDatabase{ObjectMeta: metav1.ObjectMeta{Name: tt.create.Name, Namespace: apiClient.Project}}
68+
if err := apiClient.Get(ctx, api.ObjectName(created), created); (err != nil) != tt.wantErr {
69+
t.Fatalf("expected mysqldatabase to exist, got: %s", err)
70+
}
71+
if tt.wantErr {
72+
return
73+
}
74+
75+
require.True(t, cmp.Equal(tt.want, created.Spec.ForProvider))
76+
})
77+
}
78+
}

delete/delete.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ type Cmd struct {
2020
Config configCmd `cmd:"" group:"deplo.io" name:"config" help:"Delete a deplo.io Project Configuration."`
2121
Application applicationCmd `cmd:"" group:"deplo.io" name:"application" aliases:"app,application" help:"Delete a deplo.io Application."`
2222
MySQL mySQLCmd `cmd:"" group:"storage.nine.ch" name:"mysql" help:"Delete a MySQL instance."`
23+
MySQLDatabase mysqlDatabaseCmd `cmd:"" group:"storage.nine.ch" name:"mysqldatabase" help:"Delete a MySQL database."`
2324
Postgres postgresCmd `cmd:"" group:"storage.nine.ch" name:"postgres" help:"Delete a PostgreSQL instance."`
2425
PostgresDatabase postgresDatabaseCmd `cmd:"" group:"storage.nine.ch" name:"postgresdatabase" help:"Delete a PostgreSQL database."`
2526
KeyValueStore keyValueStoreCmd `cmd:"" group:"storage.nine.ch" name:"keyvaluestore" aliases:"kvs" help:"Delete a KeyValueStore instance."`

delete/mysqldatabase.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package delete
2+
3+
import (
4+
"context"
5+
6+
storage "github.com/ninech/apis/storage/v1alpha1"
7+
"github.com/ninech/nctl/api"
8+
)
9+
10+
type mysqlDatabaseCmd struct {
11+
resourceCmd
12+
}
13+
14+
func (cmd *mysqlDatabaseCmd) Run(ctx context.Context, client *api.Client) error {
15+
mysqlDatabase := &storage.MySQLDatabase{}
16+
mysqlDatabase.SetName(cmd.Name)
17+
mysqlDatabase.SetNamespace(client.Project)
18+
19+
return newDeleter(mysqlDatabase, storage.MySQLDatabaseKind).
20+
deleteResource(ctx, client, cmd.WaitTimeout, cmd.Wait, cmd.Force)
21+
}

delete/mysqldatabase_test.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package delete
2+
3+
import (
4+
"context"
5+
"testing"
6+
"time"
7+
8+
"github.com/ninech/nctl/api"
9+
"github.com/ninech/nctl/internal/test"
10+
"github.com/stretchr/testify/require"
11+
"k8s.io/apimachinery/pkg/api/errors"
12+
)
13+
14+
func TestMySQLDatabase(t *testing.T) {
15+
ctx := context.Background()
16+
cmd := mysqlDatabaseCmd{
17+
resourceCmd: resourceCmd{
18+
Name: "test",
19+
Force: true,
20+
Wait: false,
21+
WaitTimeout: time.Second,
22+
},
23+
}
24+
25+
mysqlDatabase := test.MySQLDatabase("test", test.DefaultProject, "nine-es34")
26+
27+
apiClient, err := test.SetupClient()
28+
require.NoError(t, err)
29+
30+
if err := apiClient.Create(ctx, mysqlDatabase); err != nil {
31+
t.Fatalf("mysqldatabase create error, got: %s", err)
32+
}
33+
if err := apiClient.Get(ctx, api.ObjectName(mysqlDatabase), mysqlDatabase); err != nil {
34+
t.Fatalf("expected mysqldatabase to exist, got: %s", err)
35+
}
36+
if err := cmd.Run(ctx, apiClient); err != nil {
37+
t.Fatal(err)
38+
}
39+
err = apiClient.Get(ctx, api.ObjectName(mysqlDatabase), mysqlDatabase)
40+
if err == nil {
41+
t.Fatalf("expected mysqldatabase to be deleted, but exists")
42+
}
43+
if !errors.IsNotFound(err) {
44+
t.Fatalf("expected mysqldatabase to be deleted, got: %s", err.Error())
45+
}
46+
}

get/get.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ type Cmd struct {
2424
Releases releasesCmd `cmd:"" group:"deplo.io" name:"releases" aliases:"release" help:"Get deplo.io Releases."`
2525
Configs configsCmd `cmd:"" group:"deplo.io" name:"configs" aliases:"config" help:"Get deplo.io Project Configuration."`
2626
MySQL mySQLCmd `cmd:"" group:"storage.nine.ch" name:"mysql" help:"Get MySQL instances."`
27+
MySQLDatabase mysqlDatabaseCmd `cmd:"" group:"storage.nine.ch" name:"mysqldatabase" help:"Get MySQL databases."`
2728
Postgres postgresCmd `cmd:"" group:"storage.nine.ch" name:"postgres" help:"Get PostgreSQL instances."`
2829
PostgresDatabase postgresDatabaseCmd `cmd:"" group:"storage.nine.ch" name:"postgresdatabase" help:"Get PostgreSQL databases."`
2930
KeyValueStore keyValueStoreCmd `cmd:"" group:"storage.nine.ch" name:"keyvaluestore" aliases:"kvs" help:"Get KeyValueStore instances."`

get/mysqldatabase.go

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
package get
2+
3+
import (
4+
"context"
5+
"fmt"
6+
7+
"github.com/crossplane/crossplane-runtime/pkg/resource"
8+
storage "github.com/ninech/apis/storage/v1alpha1"
9+
"github.com/ninech/nctl/api"
10+
)
11+
12+
type mysqlDatabaseCmd struct {
13+
databaseCmd
14+
PrintCharacterSet bool `help:"Print the character set of the MySQL database. Requires name to be set." xor:"print"`
15+
}
16+
17+
func (cmd *mysqlDatabaseCmd) Run(ctx context.Context, client *api.Client, get *Cmd) error {
18+
databaseList := &storage.MySQLDatabaseList{}
19+
databaseResources := make([]resource.Managed, 0)
20+
21+
if err := get.list(ctx, client, databaseList, api.MatchName(cmd.Name)); err != nil {
22+
return err
23+
}
24+
25+
for i := range databaseList.Items {
26+
databaseResources = append(databaseResources, &databaseList.Items[i])
27+
}
28+
29+
if cmd.Name != "" && cmd.PrintCharacterSet {
30+
return cmd.printMySQLCharacterSet(databaseResources[0].(*storage.MySQLDatabase))
31+
}
32+
33+
return cmd.runDatabaseCmd(ctx, client, get,
34+
databaseList, databaseResources, storage.MySQLDatabaseKind,
35+
func(ctx context.Context, client *api.Client, mg resource.Managed) error {
36+
return cmd.printConnectionString(ctx, client, mg.(*storage.MySQLDatabase))
37+
},
38+
func(res []resource.Managed, get *Cmd, header bool) error {
39+
dbs := make([]storage.MySQLDatabase, len(res))
40+
for i, r := range res {
41+
dbs[i] = *r.(*storage.MySQLDatabase)
42+
}
43+
44+
return cmd.printMySQLDatabases(dbs, get, header)
45+
},
46+
)
47+
}
48+
49+
func (cmd *mysqlDatabaseCmd) printMySQLDatabases(list []storage.MySQLDatabase, get *Cmd, header bool) error {
50+
databases := make([]Database, len(list))
51+
for i, db := range list {
52+
databases[i] = Database{
53+
Namespace: db.Namespace,
54+
Name: db.Name,
55+
FQDN: db.Status.AtProvider.FQDN,
56+
Location: string(db.Spec.ForProvider.Location),
57+
Size: db.Status.AtProvider.Size.String(),
58+
Connections: fmt.Sprintf("%d", db.Status.AtProvider.Connections),
59+
}
60+
}
61+
62+
return printDatabases(cmd.out, get, databases, header)
63+
}
64+
65+
// printConnectionString according to the MySQL documentation:
66+
// https://dev.mysql.com/doc/refman/8.4/en/connecting-using-uri-or-key-value-pairs.html#connecting-using-uri
67+
func (cmd *mysqlDatabaseCmd) printConnectionString(ctx context.Context, client *api.Client, mdb *storage.MySQLDatabase) error {
68+
secrets, err := getConnectionSecretMap(ctx, client, mdb)
69+
if err != nil {
70+
return err
71+
}
72+
73+
for db, pw := range secrets {
74+
fmt.Fprintf(cmd.out, "mysql://%s:%s@%s/%s",
75+
db,
76+
pw,
77+
mdb.Status.AtProvider.FQDN,
78+
db,
79+
)
80+
break
81+
}
82+
83+
return nil
84+
}
85+
86+
func (cmd *mysqlDatabaseCmd) printMySQLCharacterSet(mdb *storage.MySQLDatabase) error {
87+
fmt.Fprintln(defaultOut(cmd.out), mdb.Spec.ForProvider.CharacterSet.Name)
88+
89+
return nil
90+
}

0 commit comments

Comments
 (0)