e04ed9c211
CF: Deploy Dev Docs / deploy (push) Waiting to run
Sync Labels / build (push) Waiting to run
tests / unit tests (macos-latest) (push) Waiting to run
tests / unit tests (ubuntu-latest) (push) Waiting to run
tests / unit tests (windows-latest) (push) Waiting to run
134 lines
4.3 KiB
Go
134 lines
4.3 KiB
Go
// Copyright 2026 Google LLC
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package migrate
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
|
|
"github.com/google/go-cmp/cmp"
|
|
"github.com/googleapis/mcp-toolbox/cmd/internal"
|
|
"github.com/spf13/cobra"
|
|
)
|
|
|
|
// migrateCmd is the command for migrating configuration files.
|
|
type migrateCmd struct {
|
|
*cobra.Command
|
|
dryRun bool
|
|
}
|
|
|
|
func NewCommand(opts *internal.ToolboxOptions) *cobra.Command {
|
|
cmd := &migrateCmd{}
|
|
cmd.Command = &cobra.Command{
|
|
Use: "migrate",
|
|
Short: "Migrate all configuration files to flat format",
|
|
Long: "Migrate all configuration files provided to the flat format, updating deprecated fields and ensuring compatibility.",
|
|
}
|
|
flags := cmd.Flags()
|
|
internal.ConfigFileFlags(cmd.Command, flags, opts)
|
|
flags.BoolVar(&cmd.dryRun, "dry-run", false, "Preview the converted format without applying actual changes.")
|
|
cmd.RunE = func(*cobra.Command, []string) error { return runMigrate(cmd, opts) }
|
|
return cmd.Command
|
|
}
|
|
|
|
func runMigrate(cmd *migrateCmd, opts *internal.ToolboxOptions) error {
|
|
ctx, cancel := context.WithCancel(cmd.Context())
|
|
defer cancel()
|
|
|
|
ctx, shutdown, err := opts.Setup(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer func() {
|
|
_ = shutdown(ctx)
|
|
}()
|
|
|
|
logger := opts.Logger
|
|
filePaths, _, err := opts.GetCustomConfigFiles(ctx)
|
|
if err != nil {
|
|
errMsg := fmt.Errorf("error retrieving configuration file: %w", err)
|
|
logger.ErrorContext(ctx, errMsg.Error())
|
|
return errMsg
|
|
}
|
|
|
|
logger.InfoContext(ctx, "migration process will start; any comments (except for top-level comments) presented in the original configuration files will not be preserved in the migrated files")
|
|
var errs []error
|
|
// process each files independently.
|
|
for _, filePath := range filePaths {
|
|
buf, err := os.ReadFile(filePath)
|
|
if err != nil {
|
|
errMsg := fmt.Errorf("unable to read tool file at %q: %w", filePath, err)
|
|
logger.ErrorContext(ctx, errMsg.Error())
|
|
errs = append(errs, errMsg)
|
|
continue
|
|
}
|
|
newBuf, err := internal.ConvertConfig(buf)
|
|
if err != nil {
|
|
logger.ErrorContext(ctx, err.Error())
|
|
errs = append(errs, err)
|
|
continue
|
|
}
|
|
if cmp.Equal(buf, newBuf) {
|
|
continue
|
|
}
|
|
|
|
if cmd.dryRun {
|
|
logger.DebugContext(ctx, fmt.Sprintf("printing migration to output for file: %s", filePath))
|
|
fmt.Fprintln(opts.IOStreams.Out, string(newBuf))
|
|
} else {
|
|
info, err := os.Stat(filePath)
|
|
if err != nil {
|
|
errMsg := fmt.Errorf("failed to stat file: %w", err)
|
|
logger.ErrorContext(ctx, errMsg.Error())
|
|
errs = append(errs, errMsg)
|
|
continue
|
|
}
|
|
backupFile := filePath + ".bak"
|
|
err = os.Rename(filePath, backupFile)
|
|
if err != nil {
|
|
errMsg := fmt.Errorf("failed to rename file: %w", err)
|
|
logger.ErrorContext(ctx, errMsg.Error())
|
|
errs = append(errs, errMsg)
|
|
continue
|
|
}
|
|
logger.DebugContext(ctx, fmt.Sprintf("successfully renamed %s to %s", filePath, backupFile))
|
|
|
|
// set the permission to the original file's permission.
|
|
err = os.WriteFile(filePath, newBuf, info.Mode().Perm())
|
|
if err != nil {
|
|
errMsg := fmt.Errorf("failed to write to file: %w", err)
|
|
// restoring original file
|
|
if removeErr := os.Remove(filePath); removeErr != nil { // Attempt to remove the possibly partial file to ensure Rename succeeds.
|
|
errMsg = errors.Join(errMsg, removeErr)
|
|
}
|
|
if restoreErr := os.Rename(backupFile, filePath); restoreErr != nil {
|
|
fullRestoreErr := fmt.Errorf("failed to restore original file: %w", restoreErr)
|
|
errMsg = errors.Join(errMsg, fullRestoreErr)
|
|
}
|
|
logger.ErrorContext(ctx, errMsg.Error())
|
|
errs = append(errs, errMsg)
|
|
continue
|
|
}
|
|
logger.DebugContext(ctx, fmt.Sprintf("migration completed for file: %s", filePath))
|
|
}
|
|
}
|
|
|
|
logger.InfoContext(ctx, "migration ended!")
|
|
// If errs is empty, errors.Join returns nil
|
|
return errors.Join(errs...)
|
|
}
|