Skip to content

Commit 9dfda5a

Browse files
authored
refactor: move defaulting logic to config package (#1731)
As we add more commands this defaulting will need to take place for more of them. In order to better facilitate this I upstreamed some of this logic we we can just rely on the config being defaulted by the time it reaching the command execution code. Updates: #1009
1 parent 5d9bd1d commit 9dfda5a

File tree

8 files changed

+371
-207
lines changed

8 files changed

+371
-207
lines changed

internal/cli/cli.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ func (c *Command) Init() *Command {
110110
c.Flags.Usage = func() {
111111
c.usage(c.Flags.Output())
112112
}
113-
c.Config = config.New()
113+
c.Config = config.New(c.Name())
114114
return c
115115
}
116116

internal/config/config.go

Lines changed: 95 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,13 @@ package config
1818
import (
1919
"errors"
2020
"fmt"
21+
"log/slog"
2122
"os"
2223
"os/user"
24+
"path/filepath"
2325
"regexp"
2426
"strings"
27+
"time"
2528
)
2629

2730
const (
@@ -48,6 +51,16 @@ const (
4851
LibrarianDir = ".librarian"
4952
// ReleaseInitRequest is a JSON file that describes which library to release.
5053
ReleaseInitRequest = "release-init-request.json"
54+
55+
pipelineStateFile = "state.yaml"
56+
versionCmdName = "version"
57+
)
58+
59+
// are variables so it can be replaced during testing.
60+
var (
61+
now = time.Now
62+
tempDir = os.TempDir
63+
currentUser = user.Current
5164
)
5265

5366
var (
@@ -201,22 +214,26 @@ type Config struct {
201214
//
202215
// WorkRoot is used by all librarian commands.
203216
WorkRoot string
217+
218+
// commandName is the name of the command being executed.
219+
//
220+
// commandName is populated automatically after flag parsing. No user setup is
221+
// expected.
222+
commandName string
204223
}
205224

206225
// New returns a new Config populated with environment variables.
207-
func New() *Config {
226+
func New(cmdName string) *Config {
208227
return &Config{
228+
commandName: cmdName,
209229
GitHubToken: os.Getenv("LIBRARIAN_GITHUB_TOKEN"),
210230
}
211231
}
212232

213-
// currentUser is a variable, so it can be replaced during testing.
214-
var currentUser = user.Current
215-
216-
// SetupUser performs late initialization of user-specific configuration,
233+
// setupUser performs late initialization of user-specific configuration,
217234
// determining the current user. This is in a separate method as it
218235
// can fail, and is called after flag parsing.
219-
func (c *Config) SetupUser() error {
236+
func (c *Config) setupUser() error {
220237
user, err := currentUser()
221238
if err != nil {
222239
return fmt.Errorf("failed to get current user: %w", err)
@@ -226,6 +243,55 @@ func (c *Config) SetupUser() error {
226243
return nil
227244
}
228245

246+
func (c *Config) createWorkRoot() error {
247+
if c.commandName == versionCmdName {
248+
return nil
249+
}
250+
if c.WorkRoot != "" {
251+
slog.Info("Using specified working directory", "dir", c.WorkRoot)
252+
return nil
253+
}
254+
t := now()
255+
path := filepath.Join(tempDir(), fmt.Sprintf("librarian-%s", formatTimestamp(t)))
256+
257+
_, err := os.Stat(path)
258+
switch {
259+
case os.IsNotExist(err):
260+
if err := os.Mkdir(path, 0755); err != nil {
261+
return fmt.Errorf("unable to create temporary working directory '%s': %w", path, err)
262+
}
263+
case err == nil:
264+
return fmt.Errorf("temporary working directory already exists: %s", path)
265+
default:
266+
return fmt.Errorf("unable to check directory '%s': %w", path, err)
267+
}
268+
269+
slog.Info("Temporary working directory", "dir", path)
270+
c.WorkRoot = path
271+
return nil
272+
}
273+
274+
func (c *Config) deriveRepo() error {
275+
if c.commandName == versionCmdName {
276+
return nil
277+
}
278+
if c.Repo != "" {
279+
slog.Debug("repo value provided by user", "repo", c.Repo)
280+
return nil
281+
}
282+
wd, err := os.Getwd()
283+
if err != nil {
284+
return fmt.Errorf("getting working directory: %w", err)
285+
}
286+
stateFile := filepath.Join(wd, LibrarianDir, pipelineStateFile)
287+
if _, err := os.Stat(stateFile); err != nil {
288+
return fmt.Errorf("repo flag not specified and no state file found in current working directory: %w", err)
289+
}
290+
slog.Info("repo not specified, using current working directory as repo root", "path", wd)
291+
c.Repo = wd
292+
return nil
293+
}
294+
229295
// IsValid ensures the values contained in a Config are valid.
230296
func (c *Config) IsValid() (bool, error) {
231297
if c.Push && c.GitHubToken == "" {
@@ -247,9 +313,27 @@ func (c *Config) IsValid() (bool, error) {
247313
return false, err
248314
}
249315

316+
if c.commandName != versionCmdName && c.Repo == "" {
317+
return false, errors.New("language repository not specified or detected")
318+
}
319+
250320
return true, nil
251321
}
252322

323+
// SetDefaults initializes values not set directly by the user.
324+
func (c *Config) SetDefaults() error {
325+
if err := c.setupUser(); err != nil {
326+
return err
327+
}
328+
if err := c.createWorkRoot(); err != nil {
329+
return err
330+
}
331+
if err := c.deriveRepo(); err != nil {
332+
return err
333+
}
334+
return nil
335+
}
336+
253337
func validateHostMount(hostMount, defaultValue string) (bool, error) {
254338
if hostMount == defaultValue {
255339
return true, nil
@@ -262,3 +346,8 @@ func validateHostMount(hostMount, defaultValue string) (bool, error) {
262346

263347
return true, nil
264348
}
349+
350+
func formatTimestamp(t time.Time) string {
351+
const yyyyMMddHHmmss = "20060102T150405Z" // Expected format by time library
352+
return t.Format(yyyyMMddHHmmss)
353+
}

0 commit comments

Comments
 (0)