diff --git a/cmd/image-builder/export_test.go b/cmd/image-builder/export_test.go index 55371648..a9a9a793 100644 --- a/cmd/image-builder/export_test.go +++ b/cmd/image-builder/export_test.go @@ -19,6 +19,7 @@ var ( DescribeImage = describeImage ProgressFromCmd = progressFromCmd BasenameFor = basenameFor + CacheDirForUid = cacheDirForUid ) type DescribeImgYAML describeImgYAML diff --git a/cmd/image-builder/main.go b/cmd/image-builder/main.go index 7a765ef3..446ca48e 100644 --- a/cmd/image-builder/main.go +++ b/cmd/image-builder/main.go @@ -9,6 +9,7 @@ import ( "log" "os" "os/signal" + "path/filepath" "strings" "syscall" @@ -37,6 +38,29 @@ var ( osStderr io.Writer = os.Stderr ) +// cacheDirForUid returns the cache directory for the given uid. +// When root (uid 0) it uses the system-wide /var/cache path. +// When non-root it follows the XDG Base Directory specification +// and falls back to ~/.cache. +func cacheDirForUid(uid int) string { + if uid == 0 { + return "/var/cache/image-builder/store" + } + if cacheHome := os.Getenv("XDG_CACHE_HOME"); cacheHome != "" { + return filepath.Join(cacheHome, "image-builder", "store") + } + home, err := os.UserHomeDir() + if err != nil { + return "/var/cache/image-builder/store" + } + return filepath.Join(home, ".cache", "image-builder", "store") +} + +// defaultCacheDir returns the cache directory for the current user. +func defaultCacheDir() string { + return cacheDirForUid(os.Getuid()) +} + // basenameFor returns the basename for directory and filenames // for the given imageType. This can be user overriden via userBasename. func basenameFor(img *imagefilter.Result, userBasename string) string { @@ -434,7 +458,11 @@ func cmdBuild(cmd *cobra.Command, args []string) error { if err != nil { return err } - // XXX: check env here, i.e. if user is root and osbuild is installed + // Fail early if the cache directory is not writable, instead of + // waiting for osbuild to fail after slow manifest generation. + if err := os.MkdirAll(cacheDir, 0755); err != nil { + return fmt.Errorf("cannot create cache directory %q: %w\nHint: use --cache to specify a writable path", cacheDir, err) + } // Setup osbuild environment if running in a container if setup.IsContainer() { @@ -697,7 +725,7 @@ operating systems like Fedora, CentOS and RHEL with easy customizations support. buildCmd.Flags().AddFlagSet(manifestCmd.Flags()) buildCmd.Flags().Bool("with-manifest", false, `export osbuild manifest`) buildCmd.Flags().Bool("with-buildlog", false, `export osbuild buildlog`) - buildCmd.Flags().String("cache", "/var/cache/image-builder/store", `osbuild directory to cache intermediate build artifacts"`) + buildCmd.Flags().String("cache", defaultCacheDir(), `osbuild directory to cache intermediate build artifacts"`) // XXX: add "--verbose" here, similar to how bib is doing this // (see https://github.com/osbuild/bootc-image-builder/pull/790/commits/5cec7ffd8a526e2ca1e8ada0ea18f927695dfe43) buildCmd.Flags().String("progress", "auto", "type of progress bar to use (e.g. verbose,term)") diff --git a/cmd/image-builder/main_test.go b/cmd/image-builder/main_test.go index 05ae3418..10515c60 100644 --- a/cmd/image-builder/main_test.go +++ b/cmd/image-builder/main_test.go @@ -1115,3 +1115,20 @@ customizations.FIPS = true }) } } + +func TestCacheDirForUidRoot(t *testing.T) { + assert.Equal(t, "/var/cache/image-builder/store", main.CacheDirForUid(0)) +} + +func TestCacheDirForUidNonRootXDG(t *testing.T) { + t.Setenv("XDG_CACHE_HOME", "/tmp/test-xdg-cache") + assert.Equal(t, "/tmp/test-xdg-cache/image-builder/store", main.CacheDirForUid(1000)) +} + +func TestCacheDirForUidNonRootFallback(t *testing.T) { + t.Setenv("XDG_CACHE_HOME", "") + home, err := os.UserHomeDir() + require.NoError(t, err) + expected := filepath.Join(home, ".cache", "image-builder", "store") + assert.Equal(t, expected, main.CacheDirForUid(1000)) +}