-
Notifications
You must be signed in to change notification settings - Fork 1.5k
Expand file tree
/
Copy pathcommand.go
More file actions
133 lines (121 loc) · 4.21 KB
/
command.go
File metadata and controls
133 lines (121 loc) · 4.21 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
// 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(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 present 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 completed!")
// If errs is empty, errors.Join returns nil
return errors.Join(errs...)
}