Skip to content

Commit 314d303

Browse files
committed
Better handling --root option
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
1 parent b2e3d16 commit 314d303

File tree

5 files changed

+209
-28
lines changed

5 files changed

+209
-28
lines changed

main.go

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,35 @@ func init() {
7777
for _, cmd := range commands.Commands {
7878
rootCmd.AddCommand(cmd)
7979
}
80+
81+
// Add PersistentPreRunE to auto-detect project root if --root flag was not explicitly set
82+
originalPersistentPreRunE := rootCmd.PersistentPreRunE
83+
rootCmd.PersistentPreRunE = func(cmd *cobra.Command, args []string) error {
84+
// Auto-detect project root if --root flag was not explicitly set
85+
if !cmd.PersistentFlags().Changed("root") {
86+
currentDir, err := os.Getwd()
87+
if err == nil {
88+
detectedRoot, err := commands.DetectProjectRoot(currentDir)
89+
if err == nil && detectedRoot != "" {
90+
commands.Config.RootDir = detectedRoot
91+
}
92+
}
93+
}
94+
95+
// Load config after root detection (skip for init and completion commands)
96+
cmdName := cmd.Use
97+
if !strings.HasPrefix(cmdName, "init") && !strings.HasPrefix(cmdName, "completion") {
98+
configFile := filepath.Join(commands.Config.RootDir, "Chart.yaml")
99+
if err := loadConfig(configFile); err != nil {
100+
return fmt.Errorf("error loading configuration: %w", err)
101+
}
102+
}
103+
104+
if originalPersistentPreRunE != nil {
105+
return originalPersistentPreRunE(cmd, args)
106+
}
107+
return nil
108+
}
80109
}
81110

82111
func initConfig() {
@@ -88,20 +117,13 @@ func initConfig() {
88117
if cmd.HasParent() && cmd.Parent() != rootCmd {
89118
cmd = cmd.Parent()
90119
}
120+
91121
if strings.HasPrefix(cmd.Use, "init") {
92122
if strings.HasPrefix(Version, "v") {
93123
commands.Config.InitOptions.Version = strings.TrimPrefix(Version, `v`)
94124
} else {
95125
commands.Config.InitOptions.Version = "0.1.0"
96126
}
97-
} else {
98-
if !strings.HasPrefix(cmd.Use, "completion") {
99-
configFile := filepath.Join(commands.Config.RootDir, "Chart.yaml")
100-
if err := loadConfig(configFile); err != nil {
101-
fmt.Fprintf(os.Stderr, "Error loading configuration: %v\n", err)
102-
os.Exit(1)
103-
}
104-
}
105127
}
106128
}
107129

pkg/commands/init.go

Lines changed: 66 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -304,12 +304,13 @@ var initCmd = &cobra.Command{
304304
talosconfigFileExists := fileExists(talosconfigFile)
305305
encryptedTalosconfigFileExists := fileExists(encryptedTalosconfigFile)
306306

307-
// If encrypted file exists, decrypt it
307+
// If encrypted file exists, decrypt it (don't require key - will generate if needed)
308308
if encryptedTalosconfigFileExists && !talosconfigFileExists {
309-
if err := age.DecryptYAMLFile(Config.RootDir, "talosconfig.encrypted", "talosconfig"); err != nil {
310-
return fmt.Errorf("failed to decrypt talosconfig: %w", err)
309+
_, err := handleTalosconfigEncryption(false)
310+
if err != nil {
311+
// If decryption fails (e.g., no key), continue to generate
311312
}
312-
talosconfigFileExists = true
313+
talosconfigFileExists = fileExists(talosconfigFile)
313314
}
314315

315316
// Generate talosconfig only if it doesn't exist
@@ -331,22 +332,13 @@ var initCmd = &cobra.Command{
331332
talosconfigFileExists = true
332333
}
333334

334-
// If talosconfig exists but encrypted file doesn't, encrypt it
335-
if talosconfigFileExists && !encryptedTalosconfigFileExists {
336-
// Ensure key exists
337-
if !keyFileExists {
338-
_, keyCreated, err := age.GenerateKey(Config.RootDir)
339-
if err != nil {
340-
return fmt.Errorf("failed to generate key: %w", err)
341-
}
342-
keyFileExists = true // Update flag after creation
343-
keyWasCreated = keyCreated
344-
}
345-
346-
// Encrypt talosconfig
347-
if err := age.EncryptYAMLFile(Config.RootDir, "talosconfig", "talosconfig.encrypted"); err != nil {
348-
return fmt.Errorf("failed to encrypt talosconfig: %w", err)
349-
}
335+
// Encrypt talosconfig if needed
336+
talosKeyCreated, err := handleTalosconfigEncryption(false)
337+
if err != nil {
338+
return err
339+
}
340+
if talosKeyCreated {
341+
keyWasCreated = true
350342
}
351343

352344
// Handle kubeconfig encryption logic (check if kubeconfig exists from Chart.yaml)
@@ -627,6 +619,60 @@ func printSecretsWarning() {
627619
fmt.Fprintf(os.Stderr, "\n")
628620
}
629621

622+
// handleTalosconfigEncryption handles encryption/decryption logic for talosconfig file.
623+
// It decrypts if encrypted file exists, encrypts if plain file exists.
624+
// requireKeyForDecrypt: if true, returns error if key is missing when trying to decrypt.
625+
// Returns true if key was created during this call, false otherwise.
626+
func handleTalosconfigEncryption(requireKeyForDecrypt bool) (bool, error) {
627+
talosconfigFile := filepath.Join(Config.RootDir, "talosconfig")
628+
encryptedTalosconfigFile := filepath.Join(Config.RootDir, "talosconfig.encrypted")
629+
talosconfigFileExists := fileExists(talosconfigFile)
630+
encryptedTalosconfigFileExists := fileExists(encryptedTalosconfigFile)
631+
keyFile := filepath.Join(Config.RootDir, "talm.key")
632+
keyFileExists := fileExists(keyFile)
633+
keyWasCreated := false
634+
635+
// If encrypted file exists, decrypt it
636+
if encryptedTalosconfigFileExists && !talosconfigFileExists {
637+
if !keyFileExists {
638+
if requireKeyForDecrypt {
639+
return false, fmt.Errorf("talosconfig.encrypted exists but talm.key is missing. Cannot decrypt without key")
640+
}
641+
// If key is not required, just return (don't decrypt)
642+
return false, nil
643+
}
644+
fmt.Fprintf(os.Stderr, "Decrypting talosconfig.encrypted -> talosconfig\n")
645+
if err := age.DecryptYAMLFile(Config.RootDir, "talosconfig.encrypted", "talosconfig"); err != nil {
646+
return false, fmt.Errorf("failed to decrypt talosconfig: %w", err)
647+
}
648+
talosconfigFileExists = true
649+
}
650+
651+
// If talosconfig exists but encrypted file doesn't, encrypt it
652+
if talosconfigFileExists && !encryptedTalosconfigFileExists {
653+
// Ensure key exists
654+
if !keyFileExists {
655+
_, keyCreated, err := age.GenerateKey(Config.RootDir)
656+
if err != nil {
657+
return false, fmt.Errorf("failed to generate key: %w", err)
658+
}
659+
keyWasCreated = keyCreated
660+
if keyCreated {
661+
fmt.Fprintf(os.Stderr, "Generated new encryption key: talm.key\n")
662+
}
663+
keyFileExists = true
664+
}
665+
666+
// Encrypt talosconfig
667+
fmt.Fprintf(os.Stderr, "Encrypting talosconfig -> talosconfig.encrypted\n")
668+
if err := age.EncryptYAMLFile(Config.RootDir, "talosconfig", "talosconfig.encrypted"); err != nil {
669+
return false, fmt.Errorf("failed to encrypt talosconfig: %w", err)
670+
}
671+
}
672+
673+
return keyWasCreated, nil
674+
}
675+
630676
func writeToDestination(data []byte, destination string, permissions os.FileMode) error {
631677
if err := validateFileExists(destination); err != nil {
632678
return err

pkg/commands/root.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ import (
1818
"context"
1919
"errors"
2020
"fmt"
21+
"os"
22+
"path/filepath"
2123
"time"
2224

2325
"github.com/cozystack/talm/pkg/modeline"
@@ -112,6 +114,45 @@ func addCommand(cmd *cobra.Command) {
112114
Commands = append(Commands, cmd)
113115
}
114116

117+
// DetectProjectRoot automatically detects the project root directory by looking
118+
// for Chart.yaml and secrets.yaml files in the current directory and parent directories.
119+
// Returns the absolute path to the project root, or empty string if not found.
120+
func DetectProjectRoot(startDir string) (string, error) {
121+
absStartDir, err := filepath.Abs(startDir)
122+
if err != nil {
123+
return "", fmt.Errorf("failed to get absolute path: %w", err)
124+
}
125+
126+
currentDir := absStartDir
127+
for {
128+
chartYaml := filepath.Join(currentDir, "Chart.yaml")
129+
secretsYaml := filepath.Join(currentDir, "secrets.yaml")
130+
131+
chartExists := false
132+
secretsExists := false
133+
134+
if _, err := os.Stat(chartYaml); err == nil {
135+
chartExists = true
136+
}
137+
if _, err := os.Stat(secretsYaml); err == nil {
138+
secretsExists = true
139+
}
140+
141+
if chartExists && secretsExists {
142+
return currentDir, nil
143+
}
144+
145+
parentDir := filepath.Dir(currentDir)
146+
if parentDir == currentDir {
147+
// Reached filesystem root
148+
break
149+
}
150+
currentDir = parentDir
151+
}
152+
153+
return "", nil
154+
}
155+
115156
func processModelineAndUpdateGlobals(configFile string, nodesFromArgs bool, endpointsFromArgs bool, owerwrite bool) error {
116157
modelineConfig, err := modeline.ReadAndParseModeline(configFile)
117158
if err != nil {

pkg/commands/talosconfig.go

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
// Copyright Cozystack Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package commands
16+
17+
import (
18+
"fmt"
19+
"os"
20+
21+
"github.com/spf13/cobra"
22+
)
23+
24+
// talosconfigCmd represents the `talosconfig` command.
25+
var talosconfigCmd = &cobra.Command{
26+
Use: "talosconfig",
27+
Short: "Manage talosconfig file (decrypt/encrypt)",
28+
Long: `Manage talosconfig file: decrypt if encrypted file exists, encrypt if plain file exists.`,
29+
Args: cobra.NoArgs,
30+
RunE: func(cmd *cobra.Command, args []string) error {
31+
// Handle talosconfig encryption/decryption logic
32+
if _, err := handleTalosconfigEncryption(true); err != nil {
33+
return err
34+
}
35+
36+
// Update .gitignore if needed
37+
if err := writeGitignoreFile(); err != nil {
38+
// Don't fail the command if gitignore update fails, but log warning
39+
fmt.Fprintf(os.Stderr, "Warning: failed to update .gitignore: %v\n", err)
40+
}
41+
42+
return nil
43+
},
44+
}
45+
46+
func init() {
47+
addCommand(talosconfigCmd)
48+
}
49+

pkg/commands/talosctl_wrapper.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,28 @@ func wrapTalosCommand(cmd *cobra.Command, cmdName string) *cobra.Command {
119119
return err
120120
}
121121
}
122+
123+
// Ensure talosconfig path is set to project root if not explicitly set via flag
124+
if !cmd.PersistentFlags().Changed("talosconfig") {
125+
var talosconfigPath string
126+
if GlobalArgs.Talosconfig != "" {
127+
// Use existing path from Chart.yaml or default
128+
talosconfigPath = GlobalArgs.Talosconfig
129+
} else {
130+
// Use talosconfig from project root
131+
talosconfigPath = Config.GlobalOptions.Talosconfig
132+
if talosconfigPath == "" {
133+
talosconfigPath = "talosconfig"
134+
}
135+
}
136+
// Make it absolute path relative to project root if it's relative
137+
if !filepath.IsAbs(talosconfigPath) {
138+
GlobalArgs.Talosconfig = filepath.Join(Config.RootDir, talosconfigPath)
139+
} else {
140+
GlobalArgs.Talosconfig = talosconfigPath
141+
}
142+
}
143+
122144
// Sync GlobalArgs to talosctl commands
123145
taloscommands.GlobalArgs = GlobalArgs
124146
if originalPreRunE != nil {
@@ -161,6 +183,7 @@ func init() {
161183
"config": true, // talm manages config differently
162184
"patch": true, // not needed in talm
163185
"upgrade-k8s": true, // not needed in talm
186+
"talosconfig": true, // talm has its own talosconfig command
164187
}
165188

166189
// Import and wrap each command from talosctl

0 commit comments

Comments
 (0)