Skip to content

Commit 9e06c9e

Browse files
Add mssql server dag config command
Signed-off-by: Neaj Morshad <neaj@appscode.com>
1 parent b43c9c1 commit 9e06c9e

File tree

3 files changed

+218
-0
lines changed

3 files changed

+218
-0
lines changed

pkg/cmds/mssql.go

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
/*
2+
Copyright AppsCode Inc. and Contributors
3+
4+
Licensed under the AppsCode Community License 1.0.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
https://github.com/appscode/licenses/raw/1.0.0/AppsCode-Community-1.0.0.md
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package cmds
18+
19+
import (
20+
"context"
21+
"fmt"
22+
"kubedb.dev/cli/pkg/common"
23+
"os"
24+
25+
"github.com/spf13/cobra"
26+
core "k8s.io/api/core/v1"
27+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
28+
cmdutil "k8s.io/kubectl/pkg/cmd/util"
29+
_ "kubedb.dev/apimachinery/apis/kubedb/v1alpha2"
30+
"sigs.k8s.io/yaml"
31+
)
32+
33+
// NewCmdMSSQL creates the parent `mssql` command
34+
func NewCmdMSSQL(f cmdutil.Factory) *cobra.Command {
35+
cmd := &cobra.Command{
36+
Use: "mssql",
37+
Short: "MSSQLServer database commands",
38+
Long: "Commands for managing KubeDB MSSQLServer instances.",
39+
Run: func(cmd *cobra.Command, args []string) {
40+
_ = cmd.Help()
41+
},
42+
}
43+
cmd.AddCommand(NewCmdDAGConfig(f))
44+
return cmd
45+
}
46+
47+
// NewCmdDAGConfig creates the `kubectl dba mssql dag-config` command.
48+
func NewCmdDAGConfig(f cmdutil.Factory) *cobra.Command {
49+
var (
50+
namespace string
51+
outputDir string
52+
desLong = `Generates a YAML file with the necessary secrets for setting up a MSSQLServer Distributed Availability Group (DAG) remote replica.`
53+
exampleStr = ` # Generate DAG configuration secrets from MSSQLServer 'ag1' in namespace 'demo'
54+
kubectl dba mssql dag-config ag1 -n demo`
55+
)
56+
57+
cmd := &cobra.Command{
58+
Use: "dag-config [mssqlserver-name]",
59+
Short: "Generate Distributed Availability Group configuration from a source MSSQLServer",
60+
Long: desLong,
61+
Example: exampleStr,
62+
Args: cobra.ExactArgs(1),
63+
Run: func(cmd *cobra.Command, args []string) {
64+
mssqlServerName := args[0]
65+
// Pass the command's context for cancellation handling
66+
cmdutil.CheckErr(runDAGConfig(cmd.Context(), f, namespace, outputDir, mssqlServerName))
67+
},
68+
}
69+
70+
cmd.Flags().StringVarP(&namespace, "namespace", "n", "default", "Namespace of the source MSSQLServer")
71+
cmd.Flags().StringVar(&outputDir, "output-dir", ".", "Directory where the configuration YAML file will be saved")
72+
return cmd
73+
}
74+
75+
// runDAGConfig is now much simpler. It just orchestrates the steps.
76+
func runDAGConfig(ctx context.Context, f cmdutil.Factory, namespace, outputDir, mssqlServerName string) error {
77+
fmt.Printf("🔎 Generating DAG configuration for MSSQLServer '%s' in namespace '%s'...\n", mssqlServerName, namespace)
78+
79+
// Use the new common constructor to get a validated options object
80+
opts, err := common.NewMSSQLOpts(f, mssqlServerName, namespace)
81+
if err != nil {
82+
return err // The error from NewMSSQLOpts will be very informative
83+
}
84+
85+
// Generate the YAML buffer using the opts object
86+
yamlBuffer, err := generateMSSQLDAGConfig(ctx, opts)
87+
if err != nil {
88+
return err
89+
}
90+
91+
// Write the buffer to a file
92+
if err := os.MkdirAll(outputDir, 0755); err != nil {
93+
return fmt.Errorf("failed to create output directory '%s': %w", outputDir, err)
94+
}
95+
96+
outputFile := fmt.Sprintf("%s/%s-dag-config.yaml", outputDir, mssqlServerName)
97+
if err := os.WriteFile(outputFile, yamlBuffer, 0644); err != nil {
98+
return fmt.Errorf("failed to write DAG config to file '%s': %w", outputFile, err)
99+
}
100+
101+
fmt.Printf("Successfully generated DAG configuration.\n")
102+
fmt.Printf("Apply this file in your remote cluster: kubectl apply -f %s\n", outputFile)
103+
104+
return nil
105+
}
106+
107+
// generateMSSQLDAGConfig now takes the opts object and is much more robust.
108+
func generateMSSQLDAGConfig(ctx context.Context, opts *common.MSSQLOpts) ([]byte, error) {
109+
// IMPROVEMENT: Get secret names directly from the CR status, not by guessing.
110+
secretNames := []string{
111+
opts.DB.DbmLoginSecretName(),
112+
opts.DB.MasterKeySecretName(),
113+
opts.DB.EndpointCertSecretName(),
114+
}
115+
116+
var finalYAML []byte
117+
for _, secretName := range secretNames {
118+
fmt.Printf(" - Fetching secret '%s'...\n", secretName)
119+
// Use the client from the opts object to fetch the secret
120+
secret, err := opts.Client.CoreV1().Secrets(opts.DB.Namespace).Get(ctx, secretName, metav1.GetOptions{})
121+
if err != nil {
122+
// No special error handling needed; if it's not found, something is wrong.
123+
return nil, fmt.Errorf("failed to get required secret '%s': %w", secretName, err)
124+
}
125+
126+
cleanedSecret := cleanupSecretForExport(secret)
127+
secretYAML, err := yaml.Marshal(cleanedSecret)
128+
if err != nil {
129+
return nil, fmt.Errorf("failed to marshal secret '%s' to YAML: %w", secretName, err)
130+
}
131+
finalYAML = append(finalYAML, secretYAML...)
132+
finalYAML = append(finalYAML, []byte("---\n")...)
133+
}
134+
return finalYAML, nil
135+
}
136+
137+
// cleanupSecretForExport creates a clean, portable version of a Secret.
138+
func cleanupSecretForExport(secret *core.Secret) *core.Secret {
139+
return &core.Secret{
140+
TypeMeta: metav1.TypeMeta{
141+
APIVersion: "v1",
142+
Kind: "Secret",
143+
},
144+
ObjectMeta: metav1.ObjectMeta{
145+
Name: secret.Name,
146+
Namespace: secret.Namespace,
147+
},
148+
Data: secret.Data,
149+
Type: secret.Type,
150+
}
151+
}

pkg/cmds/root.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,12 @@ func NewKubeDBCommand(in io.Reader, out, err io.Writer) *cobra.Command {
108108
NewCmdGenApb(f),
109109
},
110110
},
111+
{
112+
Message: "MSSQLServer specific commands",
113+
Commands: []*cobra.Command{
114+
NewCmdMSSQL(f),
115+
},
116+
},
111117
{
112118
Message: "Metric related CMDs",
113119
Commands: []*cobra.Command{

pkg/common/mssql.go

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package common
2+
3+
import (
4+
"context"
5+
"fmt"
6+
7+
// Import the correct MSSQLServer API version
8+
dbapi "kubedb.dev/apimachinery/apis/kubedb/v1alpha2"
9+
cs "kubedb.dev/apimachinery/client/clientset/versioned"
10+
11+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
12+
"k8s.io/client-go/kubernetes"
13+
"k8s.io/client-go/rest"
14+
cmdutil "k8s.io/kubectl/pkg/cmd/util"
15+
)
16+
17+
// MSSQLOpts holds clients and the fetched MSSQLServer object for a command.
18+
type MSSQLOpts struct {
19+
DB *dbapi.MSSQLServer
20+
Config *rest.Config
21+
Client *kubernetes.Clientset
22+
DBClient *cs.Clientset
23+
}
24+
25+
// NewMSSQLOpts creates a new MSSQLOpts instance, fetches the MSSQLServer CR,
26+
// and performs initial validation.
27+
func NewMSSQLOpts(f cmdutil.Factory, dbName, namespace string) (*MSSQLOpts, error) {
28+
config, err := f.ToRESTConfig()
29+
if err != nil {
30+
return nil, err
31+
}
32+
33+
client, err := kubernetes.NewForConfig(config)
34+
if err != nil {
35+
return nil, err
36+
}
37+
38+
dbClient, err := cs.NewForConfig(config)
39+
if err != nil {
40+
return nil, err
41+
}
42+
43+
// Fetch the source MSSQLServer custom resource
44+
mssql, err := dbClient.KubedbV1alpha2().MSSQLServers(namespace).Get(context.TODO(), dbName, metav1.GetOptions{})
45+
if err != nil {
46+
return nil, err
47+
}
48+
49+
// IMPORTANT VALIDATION: Check if the database is in a state
50+
// where it has generated the necessary DAG secrets.
51+
if mssql.Status.Phase != dbapi.DatabasePhaseReady {
52+
return nil, fmt.Errorf("source MSSQLServer %s/%s is not ready (current phase: %s)", namespace, dbName, mssql.Status.Phase)
53+
}
54+
55+
return &MSSQLOpts{
56+
DB: mssql,
57+
Config: config,
58+
Client: client,
59+
DBClient: dbClient,
60+
}, nil
61+
}

0 commit comments

Comments
 (0)