-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathinit_env.go
More file actions
215 lines (188 loc) · 5.34 KB
/
init_env.go
File metadata and controls
215 lines (188 loc) · 5.34 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
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
package main
import (
"cmp"
"flag"
"fmt"
"maps"
"os"
"path/filepath"
"strings"
)
var hardcodedEnvMap = map[string]string{
"GOPRIVATE": "github.com/quickfeed/cm",
}
// initEnv initializes the environment variables for the course.
// The environment variables are saved to a .env file in the root of the git repository.
//
// Run with the following command:
//
// cm init-env -year 2025 -course dat520 -name "Distributed Systems"
func initEnv(args []string) {
fs := flag.NewFlagSet(initEnvCmd, flag.ExitOnError)
var (
year int
course string
courseName string
discordJoinURL string
botUser string
websiteRepo string
)
fs.IntVar(&year, "year", 0, "Course year (required)")
fs.StringVar(&courseName, "name", "", "Course name (required)")
fs.StringVar(&course, "course", "", "Course code (default: git repo name)")
fs.StringVar(&discordJoinURL, "discord-join-url", "", "Discord join URL")
fs.StringVar(&botUser, "bot-user", "", "Help bot user (default: helpbot)")
fs.StringVar(&websiteRepo, "website-repo", "", "Website repository name (default: $COURSE.github.io)")
if err := fs.Parse(args); err != nil {
exitErr(err, "Error parsing flags")
}
// if course is not set explicitly, use the git repo name
if course == "" {
course = filepath.Base(gitRoot)
}
// validate required flags
if year == 0 || course == "" || courseName == "" {
fs.Usage()
os.Exit(1)
}
// set environment variables to be saved to .env file
env := make(map[string]string)
// set the required environment variables
maps.Copy(env, hardcodedEnvMap)
fs.VisitAll(func(f *flag.Flag) {
envName := toEnvKey(f.Name)
envValue := toEnvValue(f.Value.String())
env[envName] = cmp.Or(envValue, defaultValues[envName])
})
if err := saveEnv(env); err != nil {
exitErr(err, "Error saving environment variables")
}
}
// toEnvKey converts a flag name to an environment variable key.
func toEnvKey(flagName string) string {
upper := strings.ToUpper(flagName)
return strings.ReplaceAll(upper, "-", "_")
}
// toEnvValue converts a flag value to an environment variable value.
func toEnvValue(val string) string {
// if the provided flag value contains spaces wrap it in quotes
if strings.Contains(val, " ") {
return fmt.Sprintf(`"%s"`, val)
}
return val
}
// saveEnv saves the env map to the .env file.
// It will not update an existing .env file.
func saveEnv(env map[string]string) error {
envFile := envFilePath()
if exists(envFile) {
return fmt.Errorf("file %s already exists", envFile)
}
file, err := os.OpenFile(envFile, os.O_CREATE|os.O_WRONLY, 0o644)
if err != nil {
return err
}
defer func() {
if closeErr := file.Close(); err == nil {
err = closeErr
}
}()
fmt.Printf("Saving environment variables to %s\n", lastDirFile(envFile))
for k, v := range env {
_, err = fmt.Fprintf(file, "%s=%s\n", k, v)
if err != nil {
return err
}
}
return nil
}
func exists(filename string) bool {
_, err := os.Stat(filename)
return err == nil
}
// loadEnv loads environment variables from the .env file.
// Each variable's value is expanded with existing variables from the environment.
// It will not override a variable that already exists in the environment.
func loadEnv() error {
envFile := envFilePath()
if err := load(envFile); err != nil {
return err
}
if err := checkRequiredEnv(); err != nil {
return err
}
return nil
}
func load(filename string) error {
b, err := os.ReadFile(filename)
if err != nil {
return err
}
fmt.Printf("Loading environment variables from %s\n", filename)
for line := range strings.Lines(string(b)) {
if ignore(line) {
continue
}
key, val, found := strings.Cut(line, "=")
if !found {
continue
}
k := strings.TrimSpace(key)
if os.Getenv(k) != "" {
// Ignore .env entries already set in the environment.
continue
}
val = os.ExpandEnv(strings.Trim(strings.TrimSpace(val), `"`))
os.Setenv(k, val)
}
return nil
}
// ignore returns true if the line is empty or a comment.
func ignore(line string) bool {
trimmedLine := strings.TrimSpace(line)
return trimmedLine == "" || strings.HasPrefix(trimmedLine, "#")
}
func checkRequiredEnv() error {
for _, key := range []string{"YEAR", "COURSE", "NAME"} {
if os.Getenv(key) == "" {
return fmt.Errorf("required environment variable $%s not set", key)
}
}
return nil
}
func envFilePath() string {
return filepath.Join(gitRoot, ".env")
}
// lastDirFile returns the last directory and file name from a path.
func lastDirFile(path string) string {
return fmt.Sprintf("%s/%s", filepath.Base(filepath.Dir(path)), filepath.Base(path))
}
func course() string {
return os.Getenv("COURSE")
}
func name() string {
return os.Getenv("NAME")
}
func year() string {
return os.Getenv("YEAR")
}
func courseOrg() string {
return cmp.Or(os.Getenv("COURSE_ORG"), os.ExpandEnv("$COURSE-$YEAR"))
}
func websiteRepo() string {
return os.ExpandEnv(cmp.Or(os.Getenv("WEBSITE_REPO"), defaultValues["WEBSITE_REPO"]))
}
func replacements() map[string]string {
return map[string]string{
"COURSE_CODE": course(),
"COURSE_NAME": name(),
"COURSE_ORG": courseOrg(),
"BOT_USER": cmp.Or(os.Getenv("BOT_USER"), defaultValues["BOT_USER"]),
"DISCORD_JOIN_URL": os.Getenv("DISCORD_JOIN_URL"),
"WEBSITE_REPO": websiteRepo(),
}
}
var defaultValues = map[string]string{
"BOT_USER": "helpbot",
"WEBSITE_REPO": "$COURSE.github.io",
}