Skip to content

Commit a45522d

Browse files
Revert "In-memory env secrets"
This reverts commit 6e29b96.
1 parent 004bb98 commit a45522d

9 files changed

Lines changed: 439 additions & 671 deletions

File tree

cmd/env.go

Lines changed: 86 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package cmd
22

33
import (
44
"fmt"
5+
"time"
56

67
"github.com/InitiatDev/initiat-cli/internal/client"
78
"github.com/InitiatDev/initiat-cli/internal/env"
@@ -36,15 +37,34 @@ var envListCmd = &cobra.Command{
3637
}
3738

3839
activeEnv, _ := env.GetActiveEnvironment()
40+
localEnvs, _ := env.ListLocalEnvironments()
41+
localEnvMap := make(map[string]env.EnvironmentInfo)
42+
for _, localEnv := range localEnvs {
43+
localEnvMap[localEnv.Slug] = localEnv
44+
}
3945

4046
fmt.Println("Environments:")
4147
for _, environment := range environments {
48+
status := "not synced"
49+
syncTime := "never"
50+
51+
if localEnv, exists := localEnvMap[environment.Slug]; exists {
52+
if localEnv.HasSecrets {
53+
status = "synced"
54+
if !localEnv.Synced.IsZero() {
55+
syncTime = formatTimeAgo(localEnv.Synced)
56+
}
57+
} else {
58+
status = "no secrets"
59+
}
60+
}
61+
4262
marker := " "
4363
if environment.Slug == activeEnv {
4464
marker = "*"
4565
}
4666

47-
fmt.Printf("%s %-10s (%d secrets)\n", marker, environment.Slug, environment.SecretsCount)
67+
fmt.Printf("%s %-10s %-10s %s (%d secrets)\n", marker, environment.Slug, status, syncTime, environment.SecretsCount)
4868
}
4969

5070
return nil
@@ -101,6 +121,48 @@ var envSwitchCmd = &cobra.Command{
101121
},
102122
}
103123

124+
var envSyncCmd = &cobra.Command{
125+
Use: "sync [--env <slug>]",
126+
Short: "Sync secrets from cloud",
127+
Long: `Sync secrets from Initiat Cloud to local environment(s).`,
128+
RunE: func(cmd *cobra.Command, args []string) error {
129+
if !env.IsInitCompleted() {
130+
return fmt.Errorf("initiat environment not initialized. Run 'initiat env init' first")
131+
}
132+
133+
projectCtx, err := GetProjectContext()
134+
if err != nil {
135+
return fmt.Errorf("failed to get project context: %w", err)
136+
}
137+
138+
envSlug, _ := cmd.Flags().GetString("env")
139+
140+
if envSlug != "" {
141+
apiClient := client.New()
142+
_, err = apiClient.GetEnvironment(projectCtx.OrgSlug, projectCtx.ProjectSlug, envSlug)
143+
if err != nil {
144+
return fmt.Errorf("environment '%s' does not exist: %w", envSlug, err)
145+
}
146+
147+
err = env.SyncEnvironment(envSlug, projectCtx.OrgSlug, projectCtx.ProjectSlug)
148+
if err != nil {
149+
return fmt.Errorf("failed to sync environment %s: %w", envSlug, err)
150+
}
151+
152+
fmt.Printf("Synced environment '%s'\n", envSlug)
153+
} else {
154+
err = env.SyncAllEnvironments(projectCtx.OrgSlug, projectCtx.ProjectSlug)
155+
if err != nil {
156+
return fmt.Errorf("failed to sync environments: %w", err)
157+
}
158+
159+
fmt.Printf("Synced all environments\n")
160+
}
161+
162+
return nil
163+
},
164+
}
165+
104166
var envCurrentCmd = &cobra.Command{
105167
Use: "current",
106168
Short: "Show current environment",
@@ -160,27 +222,6 @@ var envUnsetCmd = &cobra.Command{
160222
},
161223
}
162224

163-
var envLoadCmd = &cobra.Command{
164-
Use: "load",
165-
Short: "Load environment secrets (for direnv)",
166-
Long: `Load and export secrets for the active environment. This command is intended to be used by direnv via eval.`,
167-
Hidden: true,
168-
RunE: func(cmd *cobra.Command, args []string) error {
169-
projectCtx, err := GetProjectContext()
170-
if err != nil {
171-
return fmt.Errorf("failed to get project context: %w", err)
172-
}
173-
174-
output, err := env.LoadEnvironmentSecrets(projectCtx.OrgSlug, projectCtx.ProjectSlug)
175-
if err != nil {
176-
return err
177-
}
178-
179-
fmt.Print(output)
180-
return nil
181-
},
182-
}
183-
184225
var envInitCmd = &cobra.Command{
185226
Use: "init",
186227
Short: "Initialize environment setup",
@@ -267,13 +308,35 @@ var envInitCmd = &cobra.Command{
267308
},
268309
}
269310

311+
func formatTimeAgo(t time.Time) string {
312+
now := time.Now()
313+
duration := now.Sub(t)
314+
315+
switch {
316+
case duration < time.Minute:
317+
return "just now"
318+
case duration < time.Hour:
319+
minutes := int(duration.Minutes())
320+
return fmt.Sprintf("%dm ago", minutes)
321+
case duration < 24*time.Hour:
322+
hours := int(duration.Hours())
323+
return fmt.Sprintf("%dh ago", hours)
324+
default:
325+
const hoursPerDay = 24
326+
days := int(duration.Hours() / hoursPerDay)
327+
return fmt.Sprintf("%dd ago", days)
328+
}
329+
}
330+
270331
func init() {
271332
envCmd.AddCommand(envListCmd)
272333
envCmd.AddCommand(envSwitchCmd)
334+
envCmd.AddCommand(envSyncCmd)
273335
envCmd.AddCommand(envCurrentCmd)
274336
envCmd.AddCommand(envUnsetCmd)
275337
envCmd.AddCommand(envInitCmd)
276-
envCmd.AddCommand(envLoadCmd)
338+
339+
envSyncCmd.Flags().String("env", "", "Sync specific environment")
277340

278341
rootCmd.AddCommand(envCmd)
279342
}

cmd/env_test.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"os"
55
"strings"
66
"testing"
7+
"time"
78

89
"github.com/InitiatDev/initiat-cli/internal/env"
910
)
@@ -24,6 +25,11 @@ func TestEnvListCommand(t *testing.T) {
2425
t.Fatalf("CreateEnvironmentDir failed: %v", err)
2526
}
2627

28+
err = env.WriteSecrets("dev", "API_KEY=secret123")
29+
if err != nil {
30+
t.Fatalf("WriteSecrets failed: %v", err)
31+
}
32+
2733
cmd := envListCmd
2834
cmd.SetArgs([]string{})
2935

@@ -200,3 +206,26 @@ func TestEnvInitCommand(t *testing.T) {
200206
t.Errorf("Expected .initiat directory to be created: %v", err)
201207
}
202208
}
209+
210+
func TestFormatTimeAgo(t *testing.T) {
211+
now := time.Now()
212+
213+
tests := []struct {
214+
duration time.Duration
215+
expected string
216+
}{
217+
{30 * time.Second, "just now"},
218+
{2 * time.Minute, "2m ago"},
219+
{2 * time.Hour, "2h ago"},
220+
{2 * 24 * time.Hour, "2d ago"},
221+
}
222+
223+
for _, test := range tests {
224+
t.Run(test.expected, func(t *testing.T) {
225+
result := formatTimeAgo(now.Add(-test.duration))
226+
if result != test.expected {
227+
t.Errorf("Expected '%s', got '%s'", test.expected, result)
228+
}
229+
})
230+
}
231+
}

internal/env/direnv.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,12 @@ func GenerateEnvrc() error {
3434

3535
const (
3636
envrcContentUnix = `if [ -e ".initiat/active" ]; then
37-
eval "$(initiat env load 2>/dev/null)" || true
37+
dotenv ".initiat/active/secrets.env"
38+
export INITIAT_ENV=$(basename "$(readlink .initiat/active 2>/dev/null || cat .initiat/active)")
3839
fi`
3940
envrcContentWindows = `if [ -e ".initiat/active" ]; then
40-
eval "$(initiat env load 2>/dev/null)" || true
41+
dotenv ".initiat/active/secrets.env"
42+
export INITIAT_ENV=$(cat .initiat/active)
4143
fi`
4244
)
4345

internal/env/direnv_test.go

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,12 +48,14 @@ func TestGenerateEnvrc(t *testing.T) {
4848
}
4949

5050
expectedContent := `if [ -e ".initiat/active" ]; then
51-
eval "$(initiat env load 2>/dev/null)" || true
51+
dotenv ".initiat/active/secrets.env"
52+
export INITIAT_ENV=$(basename "$(readlink .initiat/active 2>/dev/null || cat .initiat/active)")
5253
fi`
5354

5455
if runtime.GOOS == "windows" {
5556
expectedContent = `if [ -e ".initiat/active" ]; then
56-
eval "$(initiat env load 2>/dev/null)" || true
57+
dotenv ".initiat/active/secrets.env"
58+
export INITIAT_ENV=$(cat .initiat/active)
5759
fi`
5860
}
5961

@@ -111,12 +113,14 @@ func TestGenerateEnvrcToPath(t *testing.T) {
111113
}
112114

113115
expectedContent := `if [ -e ".initiat/active" ]; then
114-
eval "$(initiat env load 2>/dev/null)" || true
116+
dotenv ".initiat/active/secrets.env"
117+
export INITIAT_ENV=$(basename "$(readlink .initiat/active 2>/dev/null || cat .initiat/active)")
115118
fi`
116119

117120
if runtime.GOOS == "windows" {
118121
expectedContent = `if [ -e ".initiat/active" ]; then
119-
eval "$(initiat env load 2>/dev/null)" || true
122+
dotenv ".initiat/active/secrets.env"
123+
export INITIAT_ENV=$(cat .initiat/active)
120124
fi`
121125
}
122126

internal/env/fs.go

Lines changed: 60 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"path/filepath"
77
"runtime"
88
"strings"
9+
"time"
910

1011
"github.com/InitiatDev/initiat-cli/internal/file"
1112
)
@@ -14,6 +15,7 @@ const (
1415
InitiatDir = ".initiat"
1516
EnvironmentsDir = "environments"
1617
ActiveFile = "active"
18+
SecretsFile = "secrets.env"
1719
EnvrcFile = ".envrc"
1820
WindowsOS = "windows"
1921
GitignoreConfigured = "configured"
@@ -49,6 +51,14 @@ func GetEnvironmentPath(slug string) (string, error) {
4951
return fileHandler.GetSubPath(envsPath, slug)
5052
}
5153

54+
func GetSecretsPath(envSlug string) (string, error) {
55+
envPath, err := GetEnvironmentPath(envSlug)
56+
if err != nil {
57+
return "", err
58+
}
59+
return fileHandler.GetFilePath(envPath, SecretsFile)
60+
}
61+
5262
func CreateInitiatDir() error {
5363
initiatPath, err := GetInitiatPath()
5464
if err != nil {
@@ -152,16 +162,63 @@ func ListLocalEnvironments() ([]EnvironmentInfo, error) {
152162
}
153163

154164
envSlug := entry.Name()
165+
envPath := fileHandler.JoinPaths(envsPath, envSlug)
166+
secretsPath, _ := fileHandler.GetFilePath(envPath, SecretsFile)
167+
168+
var info os.FileInfo
169+
hasSecrets := false
170+
if stat, err := os.Stat(secretsPath); err == nil {
171+
info = stat
172+
hasSecrets = true
173+
}
174+
175+
var synced time.Time
176+
if hasSecrets {
177+
synced = info.ModTime()
178+
}
179+
155180
envs = append(envs, EnvironmentInfo{
156-
Slug: envSlug,
157-
Name: envSlug,
158-
IsActive: envSlug == activeEnv,
181+
Slug: envSlug,
182+
Name: envSlug,
183+
IsActive: envSlug == activeEnv,
184+
Synced: synced,
185+
HasSecrets: hasSecrets,
159186
})
160187
}
161188

162189
return envs, nil
163190
}
164191

192+
func WriteSecrets(envSlug string, content string) error {
193+
secretsPath, err := GetSecretsPath(envSlug)
194+
if err != nil {
195+
return err
196+
}
197+
198+
if err := CreateEnvironmentDir(envSlug); err != nil {
199+
return err
200+
}
201+
202+
return fileHandler.WriteFile(secretsPath, content)
203+
}
204+
205+
func ReadSecrets(envSlug string) (string, error) {
206+
secretsPath, err := GetSecretsPath(envSlug)
207+
if err != nil {
208+
return "", err
209+
}
210+
211+
content, err := fileHandler.ReadFile(secretsPath)
212+
if err != nil {
213+
if os.IsNotExist(err) {
214+
return "", nil
215+
}
216+
return "", err
217+
}
218+
219+
return content, nil
220+
}
221+
165222
func LocalEnvironmentExists(slug string) bool {
166223
envPath, err := GetEnvironmentPath(slug)
167224
if err != nil {

0 commit comments

Comments
 (0)