Skip to content
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
3 changes: 2 additions & 1 deletion .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ defaults:

env:
TERM: xterm
GO_VERSION: 1.22.0

concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
Expand All @@ -28,7 +29,7 @@ jobs:
uses: actions/setup-go@6edd4406fa81c3da01a34fa6f6343087c207a568 # v3.5.0
with:
cache: true
go-version-file: 'go.mod'
go-version: ${{ env.GO_VERSION }}
- name: ci/setup-node
uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # v3.6.0
with:
Expand Down
24 changes: 4 additions & 20 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,10 @@ build_desktop_desktop: build_desktop_macos

.PHONY: lint-server
lint-server:
@echo Running staticcheck
@echo Running lint
@echo $(GOBIN)
GOBIN=$(GOBIN) $(GO) install honnef.co/go/tools/cmd/staticcheck@latest
$(GOBIN)/staticcheck ./...
GOBIN=$(GOBIN) $(GO) install golang.org/x/lint/golint
$(GOBIN)/golint -set_exit_status $(./...)
@echo lint success

.PHONY: govet
Expand All @@ -47,20 +47,4 @@ lint-webapp: node_modules
cd webapp; npm run lint

.PHONY: check-style
check-style: lint-server govet lint-webapp

.PHONY: build-webapp
build-webapp: node_modules
@echo Building webapp for production
cd webapp; npm run build

.PHONY: build-server-embedded
build-server-embedded: build-webapp
@echo Building server with embedded frontend
@echo Copying webapp build files to static/build
rm -rf static/build
mkdir -p static
cp -r webapp/build static/
go build -o build/mcnb-server ./cmd/mcnb
@echo Cleaning up copied files
rm -rf static/build
check-style: lint-server govet lint-webapp
1 change: 0 additions & 1 deletion api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,4 @@ func Register(rootRouter *mux.Router, c *Context) {
apiRouter := rootRouter.PathPrefix("/api/v1").Subrouter()
initBootstrapper(apiRouter, c)
initState(apiRouter, c)
initTelemetry(apiRouter, c)
}
102 changes: 74 additions & 28 deletions api/bootstrapper.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,9 @@ func initBootstrapper(apiRouter *mux.Router, context *Context) {
}

func handleSetCredentials(c *Context, w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
cloudProvider := vars["cloudProvider"]

var credentials model.Credentials
json.NewDecoder(r.Body).Decode(&credentials)

Expand All @@ -99,7 +102,8 @@ func handleSetCredentials(c *Context, w http.ResponseWriter, r *http.Request) {
return
}

err = UpdateStateCredentials(c.BootstrapperState, &credentials)
// Update both credentials and provider in state
err = UpdateStateCredentialsAndProvider(c.BootstrapperState, &credentials, cloudProvider)
if err != nil {
logger.FromContext(c.Ctx).WithError(err).Error("Failed to update state credentials - settings will not be persisted")
}
Expand Down Expand Up @@ -199,6 +203,12 @@ func handleGetCluster(c *Context, w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusInternalServerError)
}

// Update cluster name in state when accessing a cluster
err = UpdateStateClusterName(c.BootstrapperState, clusterName)
if err != nil {
logger.FromContext(c.Ctx).WithError(err).Error("Failed to update cluster name in state")
}

json.NewEncoder(w).Encode(result)
}

Expand Down Expand Up @@ -431,7 +441,7 @@ func handleDeployNginxOperator(c *Context, w http.ResponseWriter, r *http.Reques
annotations:
service.beta.kubernetes.io/aws-load-balancer-backend-protocol: "tcp"
service.beta.kubernetes.io/aws-load-balancer-ssl-ports: "https"
service.beta.kubernetes.io/aws-load-balancer-ssl-cert: arn:aws:acm:us-east-1:926412419614:certificate/e13f9426-e452-4670-9f6a-f56b3f346bf1`
service.beta.kubernetes.io/aws-load-balancer-ssl-cert: arn:aws:acm:us-east-1:110643744285:certificate/8fcc5250-8a60-4ab8-8337-7491fb447906`

chartSpec := helmclient.ChartSpec{
ReleaseName: "ingress-nginx",
Expand Down Expand Up @@ -754,6 +764,7 @@ func handlePatchMattermostInstallation(c *Context, w http.ResponseWriter, r *htt
logger.FromContext(c.Ctx).Infof("Patch request: %+v", patchRequest.FilestorePatch)

if !patchRequest.IsValid() {
logger.FromContext(c.Ctx).Errorf("Invalid patch request - version validation failed for version: %s", patchRequest.Version)
w.WriteHeader(http.StatusBadRequest)
return
}
Expand Down Expand Up @@ -899,6 +910,12 @@ func handlePatchMattermostInstallation(c *Context, w http.ResponseWriter, r *htt
database.External.Secret = updatedSecret.ObjectMeta.Name
}

// Update environment variables if provided
if len(patchRequest.MattermostEnv) > 0 {
logger.FromContext(c.Ctx).Infof("Updating environment variables, count: %d", len(patchRequest.MattermostEnv))
installation.Spec.MattermostEnv = patchRequest.MattermostEnv
}

installation.Spec.Version = patchRequest.Version
installation.Spec.Image = patchRequest.Image
installation.Spec.FileStore = MMFilestore
Expand Down Expand Up @@ -1009,6 +1026,7 @@ func handleCreateMattermostInstallation(c *Context, w http.ResponseWriter, r *ht

var writer string
var reader string
var databaseSecretName string

if create.DBConnectionOption == model.DatabaseOptionCreateForMe {
dbCluster := &cnpgv1.Cluster{
Expand Down Expand Up @@ -1066,29 +1084,38 @@ func handleCreateMattermostInstallation(c *Context, w http.ResponseWriter, r *ht
writer = strings.Replace(initial, "postgresql:", "postgres:", 1) // Replace once
reader = strings.Replace(writer, fmt.Sprintf("%s-rw:", secretName), fmt.Sprintf("%s-ro:", secretName), 1)
} else if create.DBConnectionOption == model.DatabaseOptionExisting {
writer = create.ExistingDBConnection.ConnectionString
reader = create.ExistingDBConnection.ConnectionString
if create.ExistingDBSecretName != "" {
databaseSecretName = create.ExistingDBSecretName
} else {
writer = create.ExistingDBConnection.ConnectionString
reader = create.ExistingDBConnection.ConnectionString
}
}

databaseSecret := &v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: model.SecretNameDatabase,
Namespace: namespaceName,
},
Type: v1.SecretTypeOpaque,
StringData: map[string]string{
"DB_CONNECTION_CHECK_URL": writer,
"DB_CONNECTION_STRING": writer,
"MM_SQLSETTINGS_DATASOURCEREPLICAS": reader, // Assuming read replicas for now
},
}
if databaseSecretName == "" {
databaseSecret := &v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: model.SecretNameDatabase,
Namespace: namespaceName,
},
Type: v1.SecretTypeOpaque,
StringData: map[string]string{
"DB_CONNECTION_CHECK_URL": writer,
"DB_CONNECTION_STRING": writer,
"MM_SQLSETTINGS_DATASOURCEREPLICAS": reader, // Assuming read replicas for now
"MM_CONFIG": writer,
},
}

// Create the database secret
_, err = kubeClient.Clientset.CoreV1().Secrets(namespaceName).Create(context.TODO(), databaseSecret, metav1.CreateOptions{})
if err != nil {
logger.FromContext(c.Ctx).Errorf("Error creating database secret:", err)
w.WriteHeader(http.StatusInternalServerError)
return
// Create the database secret
_, err = kubeClient.Clientset.CoreV1().Secrets(namespaceName).Create(context.TODO(), databaseSecret, metav1.CreateOptions{})
if err != nil {
logger.FromContext(c.Ctx).Errorf("Error creating database secret:", err)
w.WriteHeader(http.StatusInternalServerError)
return
}

databaseSecretName = databaseSecret.ObjectMeta.Name
}

// License Secret
Expand All @@ -1114,6 +1141,9 @@ func handleCreateMattermostInstallation(c *Context, w http.ResponseWriter, r *ht
}

filestore := create.GetMMOperatorFilestore(namespaceName, filestoreSecret)
if filestore.External != nil && filestore.External.Secret == "" && create.FilestoreSecretName != "" {
filestore.External.Secret = create.FilestoreSecretName
}

mattermostCRD := &mmv1beta1.Mattermost{
ObjectMeta: metav1.ObjectMeta{
Expand All @@ -1133,7 +1163,12 @@ func handleCreateMattermostInstallation(c *Context, w http.ResponseWriter, r *ht
},
Database: mmv1beta1.Database{
External: &mmv1beta1.ExternalDatabase{
Secret: model.SecretNameDatabase,
Secret: func() string {
if databaseSecretName != "" {
return databaseSecretName
}
return model.SecretNameDatabase
}(),
},
},
FileStore: filestore,
Expand All @@ -1143,6 +1178,17 @@ func handleCreateMattermostInstallation(c *Context, w http.ResponseWriter, r *ht
{Name: model.MMENVLicense, ValueFrom: &v1.EnvVarSource{
SecretKeyRef: &v1.SecretKeySelector{Key: "license", LocalObjectReference: v1.LocalObjectReference{Name: licenseSecret.ObjectMeta.Name}, Optional: aws.Bool(true)}, // Add comma to separate items
}},
{Name: "MM_CONFIG", ValueFrom: &v1.EnvVarSource{
SecretKeyRef: &v1.SecretKeySelector{
Key: "MM_CONFIG",
LocalObjectReference: v1.LocalObjectReference{Name: func() string {
if databaseSecretName != "" {
return databaseSecretName
}
return model.SecretNameDatabase
}()},
},
}},
},
PodTemplate: &mmv1beta1.PodTemplate{
SecurityContext: &v1.PodSecurityContext{
Expand Down Expand Up @@ -1219,11 +1265,11 @@ func handleDeployMattermostOperator(c *Context, w http.ResponseWriter, r *http.R
}
Copy link

Copilot AI Oct 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The commented-out version should either be removed if no longer needed, or the comment should explain why it's commented out and under what conditions it should be used.

Suggested change
}
UpgradeCRDs: true,
// To pin the chart to a specific version, uncomment the line below and set the desired version.

Copilot uses AI. Check for mistakes.

chartSpec := helmclient.ChartSpec{
ReleaseName: "mattermost-operator",
ChartName: "mattermost/mattermost-operator",
Namespace: "mattermost-operator",
UpgradeCRDs: true,
Version: "v1.22.0",
ReleaseName: "mattermost-operator",
ChartName: "mattermost/mattermost-operator",
Namespace: "mattermost-operator",
UpgradeCRDs: true,
// Version: "1.25.2",
Wait: true,
Timeout: 300 * time.Second,
CreateNamespace: true,
Expand Down
10 changes: 0 additions & 10 deletions api/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import (
"github.com/mattermost/mattermost-cloudnative-bootstrapper/internal/logger"
"github.com/mattermost/mattermost-cloudnative-bootstrapper/model"
"github.com/mattermost/mattermost-cloudnative-bootstrapper/providers"
"github.com/mattermost/mattermost-cloudnative-bootstrapper/telemetry"
)

type BootstrapperState struct {
Expand All @@ -30,7 +29,6 @@ type Context struct {
CloudProviderName string
CloudProvider providers.CloudProvider
BootstrapperState BootstrapperState
TelemetryProvider *telemetry.TelemetryProvider
}

func NewContext(ctx context.Context, statePath string, telemetryDisabled bool) (*Context, error) {
Expand Down Expand Up @@ -74,18 +72,11 @@ func NewContext(ctx context.Context, statePath string, telemetryDisabled bool) (
}
}

// Initialize the telemetry provider
telemetryProvider, err := telemetry.NewTelemetryProvider(state.Telemetry.TelemetryID, state.Telemetry.TelemetryDisabled)
if err != nil {
return nil, err
}

return &Context{
Ctx: ctx,
BootstrapperState: state,
// TODO: this is redundant, use the state.Provider instead everywhere
CloudProviderName: state.Provider,
TelemetryProvider: telemetryProvider,
}, nil
}

Expand All @@ -97,7 +88,6 @@ func (c *Context) Clone() *Context {
CloudProviderName: c.CloudProviderName,
CloudProvider: c.CloudProvider,
BootstrapperState: c.BootstrapperState,
TelemetryProvider: c.TelemetryProvider,
}
}

Expand Down
2 changes: 1 addition & 1 deletion api/response_writer_wrapper.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ func (rw *ResponseWriterWrapper) Write(data []byte) (int, error) {
// Hijack calls the underlying writer's Hijack output.
func (rw *ResponseWriterWrapper) Hijack() (net.Conn, *bufio.ReadWriter, error) {
if rw.hijacker == nil {
return nil, nil, errors.New("hijacker interface not supported by the wrapped ResponseWriter")
return nil, nil, errors.New("Hijacker interface not supported by the wrapped ResponseWriter")
}
return rw.hijacker.Hijack()
}
Expand Down
72 changes: 72 additions & 0 deletions api/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ func initState(apiRouter *mux.Router, context *Context) {

stateRouter := apiRouter.PathPrefix("/state").Subrouter()
stateRouter.Handle("/hydrate", addContext(handleHydrateState)).Methods("GET")
stateRouter.Handle("/check", addContext(handleCheckState)).Methods("GET")
stateRouter.Handle("", addContext(handlePatchState)).Methods("PATCH")
}

Expand All @@ -28,6 +29,42 @@ func handleHydrateState(c *Context, w http.ResponseWriter, r *http.Request) {
json.NewEncoder(w).Encode(state)
}

// SessionInfo represents a summary of an existing session for UI display
type SessionInfo struct {
Provider string `json:"provider"`
ClusterName string `json:"clusterName"`
HasState bool `json:"hasState"`
}

func handleCheckState(c *Context, w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")

// Check if a state file exists
exists, err := CheckStateExists(c.BootstrapperState.StateFilePath)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}

if !exists {
// No state exists
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(SessionInfo{HasState: false})
return
}

// State exists, return session info
state := c.BootstrapperState
sessionInfo := SessionInfo{
Provider: state.Provider,
ClusterName: state.ClusterName,
HasState: true,
}

w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(sessionInfo)
}

func handlePatchState(c *Context, w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
Expand Down Expand Up @@ -156,3 +193,38 @@ func UpdateStateCredentials(existingState BootstrapperState, credentials *model.

return nil
}

// UpdateStateCredentialsAndProvider updates credentials and provider in state
func UpdateStateCredentialsAndProvider(existingState BootstrapperState, credentials *model.Credentials, provider string) error {
state, err := GetState(existingState.StateFilePath)
if err != nil {
return err
}

state.Credentials = credentials
state.Provider = provider

err = SetState(existingState.StateFilePath, state)
if err != nil {
return err
}

return nil
}

// UpdateStateClusterName updates the cluster name in state
func UpdateStateClusterName(existingState BootstrapperState, clusterName string) error {
state, err := GetState(existingState.StateFilePath)
if err != nil {
return err
}

state.ClusterName = clusterName

err = SetState(existingState.StateFilePath, state)
if err != nil {
return err
}

return nil
}
Loading
Loading