Skip to content

Commit

Permalink
Add custom installation volume support (#1092)
Browse files Browse the repository at this point in the history
Kubernetes volumes can now be added to installation deployments.
Volumes backed by secrets can be created, updated, and deleted
after an installation has been created. The volume secret data is
first stored in AWS Secrets Manager and then appended as a k8s
secret in an installation update. Volumes are mounted in the
specified mount path of the container which should support many
cases where sensitive information is required at the filesystem
level of Mattermost.
  • Loading branch information
gabrieljackson authored Dec 23, 2024
1 parent 6a79c68 commit 5112293
Show file tree
Hide file tree
Showing 23 changed files with 1,507 additions and 12 deletions.
101 changes: 101 additions & 0 deletions cmd/cloud/installation.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ func newCmdInstallation() *cobra.Command {
cmd.AddCommand(newCmdInstallationCreate())
cmd.AddCommand(newCmdInstallationUpdate())
cmd.AddCommand(newCmdInstallationDelete())
cmd.AddCommand(newCmdInstallationVolumeCreate())
cmd.AddCommand(newCmdInstallationVolumeUpdate())
cmd.AddCommand(newCmdInstallationVolumeDelete())
cmd.AddCommand(newCmdInstallationUpdateDeletion())
cmd.AddCommand(newCmdInstallationCancelDeletion())
cmd.AddCommand(newCmdInstallationHibernate())
Expand Down Expand Up @@ -173,6 +176,104 @@ func newCmdInstallationUpdate() *cobra.Command {
return cmd
}

func newCmdInstallationVolumeCreate() *cobra.Command {
var flags installationCreateVolumeFlags

cmd := &cobra.Command{
Use: "create-volume",
Short: "Creates a new volume for an installation.",
RunE: func(command *cobra.Command, args []string) error {
command.SilenceUsage = true
client := createClient(command.Context(), flags.clusterFlags)

request := flags.GetCreateInstallationVolumeRequest()

if flags.dryRun {
return runDryRun(request)
}

installation, err := client.CreateInstallationVolume(flags.installationID, request)
if err != nil {
return errors.Wrap(err, "failed to create installation volume")
}

return printJSON(installation)
},
PreRun: func(cmd *cobra.Command, args []string) {
flags.clusterFlags.addFlags(cmd)
},
}

flags.addFlags(cmd)

return cmd
}

func newCmdInstallationVolumeUpdate() *cobra.Command {
var flags installationUpdateVolumeFlags

cmd := &cobra.Command{
Use: "update-volume",
Short: "Updates an existing volume for an installation.",
RunE: func(command *cobra.Command, args []string) error {
command.SilenceUsage = true
client := createClient(command.Context(), flags.clusterFlags)

err := flags.Validate()
if err != nil {
return err
}
request := flags.GetUpdateInstallationVolumeRequest()

if flags.dryRun {
return runDryRun(request)
}

installation, err := client.UpdateInstallationVolume(flags.installationID, flags.volumeName, request)
if err != nil {
return errors.Wrap(err, "failed to update installation volume")
}

return printJSON(installation)
},
PreRun: func(cmd *cobra.Command, args []string) {
flags.clusterFlags.addFlags(cmd)
flags.installationUpdateVolumeChanges.addFlags(cmd)
},
}

flags.addFlags(cmd)

return cmd
}

func newCmdInstallationVolumeDelete() *cobra.Command {
var flags installationDeleteVolumeFlags

cmd := &cobra.Command{
Use: "delete-volume",
Short: "Delete an existing volume from an installation.",
RunE: func(command *cobra.Command, args []string) error {
command.SilenceUsage = true
client := createClient(command.Context(), flags.clusterFlags)

installation, err := client.DeleteInstallationVolume(flags.installationID, flags.volumeName)
if err != nil {
return errors.Wrap(err, "failed to delete installation volume")
}

return printJSON(installation)
},
PreRun: func(cmd *cobra.Command, args []string) {
flags.clusterFlags.addFlags(cmd)
},
}

flags.addFlags(cmd)

return cmd
}

func newCmdInstallationDelete() *cobra.Command {
var flags installationDeleteFlags

Expand Down
127 changes: 124 additions & 3 deletions cmd/cloud/installation_flag.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"time"

"github.com/mattermost/mattermost-cloud/model"
"github.com/pkg/errors"
"github.com/spf13/cobra"
)

Expand Down Expand Up @@ -79,7 +80,7 @@ func (flags *installationCreateFlags) addFlags(command *cobra.Command) {

type installationPatchRequestChanges struct {
ownerIDChanged bool
versionCHanged bool
versionChanged bool
imageChanged bool
sizeChanged bool
licenseChanged bool
Expand All @@ -89,7 +90,7 @@ type installationPatchRequestChanges struct {

func (flags *installationPatchRequestChanges) addFlags(command *cobra.Command) {
flags.ownerIDChanged = command.Flags().Changed("owner")
flags.versionCHanged = command.Flags().Changed("version")
flags.versionChanged = command.Flags().Changed("version")
flags.imageChanged = command.Flags().Changed("image")
flags.sizeChanged = command.Flags().Changed("size")
flags.licenseChanged = command.Flags().Changed("license")
Expand Down Expand Up @@ -129,7 +130,7 @@ func (flags *installationPatchRequestOptions) GetPatchInstallationRequest() *mod
request.OwnerID = &flags.ownerID
}

if flags.versionCHanged {
if flags.versionChanged {
request.Version = &flags.version
}

Expand Down Expand Up @@ -209,6 +210,126 @@ func (flags *installationDeleteFlags) addFlags(command *cobra.Command) {
_ = command.MarkFlagRequired("installation")
}

type baseVolumeFlags struct {
mountPath string
readOnly bool
filename string
data string
}
type installationCreateVolumeFlags struct {
clusterFlags
baseVolumeFlags
installationID string
volumeName string
}

func (flags *installationCreateVolumeFlags) addFlags(command *cobra.Command) {
command.Flags().StringVar(&flags.installationID, "installation", "", "The id of the installation to create a volume for.")
command.Flags().StringVar(&flags.volumeName, "volume-name", "", "The name of the volume to create.")
command.Flags().StringVar(&flags.mountPath, "mount-path", "", "The container path to mount the volume in.")
command.Flags().BoolVar(&flags.readOnly, "read-only", true, "Whether the volume should be read only or not.")

command.Flags().StringVar(&flags.filename, "filename", "", "The name of the file that will be mounted in the volume mount path.")
command.Flags().StringVar(&flags.data, "data", "", "The data contained in the file.")

_ = command.MarkFlagRequired("installation")
_ = command.MarkFlagRequired("volume-name")
_ = command.MarkFlagRequired("filename")
_ = command.MarkFlagRequired("data")
}

func (flags *installationCreateVolumeFlags) GetCreateInstallationVolumeRequest() *model.CreateInstallationVolumeRequest {
return &model.CreateInstallationVolumeRequest{
Name: flags.volumeName,
Volume: &model.Volume{
Type: model.VolumeTypeSecret,
MountPath: flags.mountPath,
ReadOnly: flags.readOnly,
},
Data: map[string][]byte{
flags.filename: []byte(flags.data),
},
}
}

type installationUpdateVolumeChanges struct {
mountPathChanged bool
readOnlyChanged bool
filenameChanged bool
dataChanged bool
}

func (flags *installationUpdateVolumeChanges) addFlags(command *cobra.Command) {
flags.mountPathChanged = command.Flags().Changed("mount-path")
flags.readOnlyChanged = command.Flags().Changed("read-only")
flags.filenameChanged = command.Flags().Changed("filename")
flags.dataChanged = command.Flags().Changed("data")
}

type installationUpdateVolumeFlags struct {
installationUpdateVolumeChanges
clusterFlags
baseVolumeFlags
installationID string
volumeName string
}

func (flags *installationUpdateVolumeFlags) addFlags(command *cobra.Command) {
command.Flags().StringVar(&flags.installationID, "installation", "", "The id of the installation to update a volume for.")
command.Flags().StringVar(&flags.volumeName, "volume-name", "", "The name of the volume to update.")
command.Flags().StringVar(&flags.mountPath, "mount-path", "", "The container path to mount the volume in.")
command.Flags().BoolVar(&flags.readOnly, "read-only", true, "Whether the volume should be read only or not.")

command.Flags().StringVar(&flags.filename, "filename", "", "The name of the file that will be mounted in the volume mount path.")
command.Flags().StringVar(&flags.data, "data", "", "The data contained in the file.")

_ = command.MarkFlagRequired("installation")
_ = command.MarkFlagRequired("volume-name")
}

func (flags *installationUpdateVolumeFlags) Validate() error {
if flags.filenameChanged && !flags.dataChanged {
return errors.New("must provide --data when changing --filename")
}
if !flags.filenameChanged && flags.dataChanged {
return errors.New("must provide --filename when changing --data")
}

return nil
}

func (flags *installationUpdateVolumeFlags) GetUpdateInstallationVolumeRequest() *model.PatchInstallationVolumeRequest {
patch := &model.PatchInstallationVolumeRequest{}

if flags.mountPathChanged {
patch.MountPath = &flags.mountPath
}
if flags.readOnlyChanged {
patch.ReadOnly = &flags.readOnly
}
if flags.filenameChanged && flags.dataChanged {
patch.Data = map[string][]byte{
flags.filename: []byte(flags.data),
}
}

return patch
}

type installationDeleteVolumeFlags struct {
clusterFlags
installationID string
volumeName string
}

func (flags *installationDeleteVolumeFlags) addFlags(command *cobra.Command) {
command.Flags().StringVar(&flags.installationID, "installation", "", "The id of the installation to delete a volume from.")
command.Flags().StringVar(&flags.volumeName, "volume-name", "", "The name of the volume to delete.")

_ = command.MarkFlagRequired("installation")
_ = command.MarkFlagRequired("volume-name")
}

type installationDeletionPatchRequestOptions struct {
futureDeletionTime time.Duration
}
Expand Down
3 changes: 3 additions & 0 deletions internal/api/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,9 @@ type Provisioner interface {
type AwsClient interface {
EnsureVPCExists(vpcID string) error
SwitchClusterTags(clusterID string, targetClusterID string, logger log.FieldLogger) error
SecretsManagerCreateSecret(secretName, description string, secretBytes []byte, logger log.FieldLogger) error
SecretsManagerUpdateSecret(secretName string, secretBytes []byte, logger log.FieldLogger) error
SecretsManagerEnsureSecretDeleted(secretName string, logger log.FieldLogger) error
SecretsManagerValidateExternalClusterSecret(name string) error
SecretsManagerValidateExternalDatabaseSecret(name string) error
RDSDBCLusterExists(awsID string) (bool, error)
Expand Down
12 changes: 12 additions & 0 deletions internal/api/db_multitenant_database_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,18 @@ func (m mockAWSClient) DeletePGBouncerLogicalDatabase(multitenantDatabase *model
return nil
}

func (m mockAWSClient) SecretsManagerCreateSecret(secretName, description string, secretBytes []byte, logger log.FieldLogger) error {
return nil
}

func (m mockAWSClient) SecretsManagerUpdateSecret(secretName string, secretBytes []byte, logger log.FieldLogger) error {
return nil
}

func (m mockAWSClient) SecretsManagerEnsureSecretDeleted(secretName string, logger log.FieldLogger) error {
return nil
}

func TestDeleteMultitenantDatabase(t *testing.T) {
logger := testlib.MakeLogger(t)
sqlStore := store.MakeTestSQLStore(t, logger)
Expand Down
Loading

0 comments on commit 5112293

Please sign in to comment.