@@ -27,6 +27,7 @@ import (
2727 "github.com/gruntwork-io/terragrunt/internal/tf/cliconfig"
2828 "github.com/gruntwork-io/terragrunt/internal/tf/getproviders"
2929 "github.com/gruntwork-io/terragrunt/internal/util"
30+ "github.com/gruntwork-io/terragrunt/internal/vfs"
3031 "github.com/gruntwork-io/terragrunt/pkg/log"
3132 "github.com/gruntwork-io/terragrunt/pkg/options"
3233)
@@ -78,23 +79,46 @@ type ProviderCache struct {
7879 * cache.Server
7980 cliCfg * cliconfig.Config
8081 providerService * services.ProviderService
82+ fs vfs.FS
8183}
8284
83- func InitServer (l log.Logger , opts * options.TerragruntOptions ) (* ProviderCache , error ) {
85+ // NewProviderCache creates a new ProviderCache with sensible defaults.
86+ // Use builder methods like WithFS() to customize the configuration.
87+ func NewProviderCache () * ProviderCache {
88+ return & ProviderCache {
89+ fs : vfs .NewOSFS (),
90+ }
91+ }
92+
93+ // WithFS sets the filesystem for file operations and returns the ProviderCache
94+ // for method chaining. If not called, defaults to the real OS filesystem.
95+ func (pc * ProviderCache ) WithFS (fs vfs.FS ) * ProviderCache {
96+ pc .fs = fs
97+ return pc
98+ }
99+
100+ // FS returns the configured filesystem.
101+ func (pc * ProviderCache ) FS () vfs.FS {
102+ return pc .fs
103+ }
104+
105+ // Init initializes the ProviderCache with the given logger and options.
106+ // Call this after configuring the ProviderCache with builder methods.
107+ func (pc * ProviderCache ) Init (l log.Logger , opts * options.TerragruntOptions ) error {
84108 // ProviderCacheDir has the same file structure as terraform plugin_cache_dir.
85109 // https://developer.hashicorp.com/terraform/cli/config/config-file#provider-plugin-cache
86110 if opts .ProviderCacheDir == "" {
87111 cacheDir , err := util .GetCacheDir ()
88112 if err != nil {
89- return nil , err
113+ return fmt . Errorf ( "failed to get cache directory: %w" , err )
90114 }
91115
92116 opts .ProviderCacheDir = filepath .Join (cacheDir , "providers" )
93117 }
94118
95119 var err error
96120 if opts .ProviderCacheDir , err = filepath .Abs (opts .ProviderCacheDir ); err != nil {
97- return nil , errors .New (err )
121+ return errors .New (err )
98122 }
99123
100124 if opts .ProviderCacheToken == "" {
@@ -105,25 +129,26 @@ func InitServer(l log.Logger, opts *options.TerragruntOptions) (*ProviderCache,
105129 opts .ProviderCacheToken = fmt .Sprintf ("%s:%s" , APIKeyAuth , opts .ProviderCacheToken )
106130 }
107131
108- cliCfg , err := cliconfig .LoadUserConfig ()
132+ // Pass filesystem to LoadUserConfig
133+ cliCfg , err := cliconfig .LoadUserConfig (cliconfig .WithFS (pc .FS ()))
109134 if err != nil {
110- return nil , err
135+ return err
111136 }
112137
113138 userProviderDir , err := cliconfig .UserProviderDir ()
114139 if err != nil {
115- return nil , err
140+ return err
116141 }
117142
118- providerService := services .NewProviderService (opts .ProviderCacheDir , userProviderDir , cliCfg .CredentialsSource (), l )
143+ providerService := services .NewProviderService (opts .ProviderCacheDir , userProviderDir , cliCfg .CredentialsSource (), l , services . WithFS ( pc . FS ()) )
119144 proxyProviderHandler := handlers .NewProxyProviderHandler (l , cliCfg .CredentialsSource ())
120145
121146 providerHandlers , err := handlers .NewProviderHandlers (cliCfg , l , opts .ProviderCacheRegistryNames )
122147 if err != nil {
123- return nil , errors .Errorf ("creating provider handlers failed: %w" , err )
148+ return errors .Errorf ("creating provider handlers failed: %w" , err )
124149 }
125150
126- cache := cache .NewServer (
151+ cacheServer := cache .NewServer (
127152 cache .WithHostname (opts .ProviderCacheHostname ),
128153 cache .WithPort (opts .ProviderCachePort ),
129154 cache .WithToken (opts .ProviderCacheToken ),
@@ -134,18 +159,29 @@ func InitServer(l log.Logger, opts *options.TerragruntOptions) (*ProviderCache,
134159 cache .WithLogger (l ),
135160 )
136161
137- return & ProviderCache {
138- Server : cache ,
139- cliCfg : cliCfg ,
140- providerService : providerService ,
141- }, nil
162+ pc .Server = cacheServer
163+ pc .cliCfg = cliCfg
164+ pc .providerService = providerService
165+
166+ return nil
167+ }
168+
169+ // InitServer creates and initializes a new ProviderCache with the given logger and options.
170+ // This is a convenience function that combines NewProviderCache() and Init().
171+ func InitServer (l log.Logger , opts * options.TerragruntOptions ) (* ProviderCache , error ) {
172+ pc := NewProviderCache ()
173+ if err := pc .Init (l , opts ); err != nil {
174+ return nil , err
175+ }
176+
177+ return pc , nil
142178}
143179
144180// TerraformCommandHook warms up the providers cache, creates `.terraform.lock.hcl` and runs the `tofu/terraform init`
145181// command with using this cache. Used as a hook function that is called after running the target tofu/terraform command.
146182// For example, if the target command is `tofu plan`, it will be intercepted before it is run in the `/shell` package,
147183// then control will be passed to this function to init the working directory using cached providers.
148- func (cache * ProviderCache ) TerraformCommandHook (
184+ func (pc * ProviderCache ) TerraformCommandHook (
149185 ctx context.Context ,
150186 l log.Logger ,
151187 opts * options.TerragruntOptions ,
@@ -182,18 +218,18 @@ func (cache *ProviderCache) TerraformCommandHook(
182218
183219 env := providerCacheEnvironment (opts , cliConfigFilename )
184220
185- if output , err := cache .warmUpCache (ctx , l , opts , cliConfigFilename , args , env ); err != nil {
221+ if output , err := pc .warmUpCache (ctx , l , opts , cliConfigFilename , args , env ); err != nil {
186222 return output , err
187223 }
188224
189225 if skipRunTargetCommand {
190226 return & util.CmdOutput {}, nil
191227 }
192228
193- return cache .runTerraformWithCache (ctx , l , opts , cliConfigFilename , args , env )
229+ return pc .runTerraformWithCache (ctx , l , opts , cliConfigFilename , args , env )
194230}
195231
196- func (cache * ProviderCache ) warmUpCache (
232+ func (pc * ProviderCache ) warmUpCache (
197233 ctx context.Context ,
198234 l log.Logger ,
199235 opts * options.TerragruntOptions ,
@@ -207,7 +243,7 @@ func (cache *ProviderCache) warmUpCache(
207243 )
208244
209245 // Create terraform cli config file that enables provider caching and does not use provider cache dir
210- if err := cache .createLocalCLIConfig (ctx , opts , cliConfigFilename , cacheRequestID ); err != nil {
246+ if err := pc .createLocalCLIConfig (ctx , opts , cliConfigFilename , cacheRequestID ); err != nil {
211247 return nil , err
212248 }
213249
@@ -222,7 +258,7 @@ func (cache *ProviderCache) warmUpCache(
222258 }
223259 }
224260
225- caches , err := cache .providerService .WaitForCacheReady (cacheRequestID )
261+ caches , err := pc .providerService .WaitForCacheReady (cacheRequestID )
226262 if err != nil {
227263 return nil , err
228264 }
@@ -265,7 +301,7 @@ func (cache *ProviderCache) warmUpCache(
265301 return nil , err
266302}
267303
268- func (cache * ProviderCache ) runTerraformWithCache (
304+ func (pc * ProviderCache ) runTerraformWithCache (
269305 ctx context.Context ,
270306 l log.Logger ,
271307 opts * options.TerragruntOptions ,
@@ -274,7 +310,7 @@ func (cache *ProviderCache) runTerraformWithCache(
274310 env map [string ]string ,
275311) (* util.CmdOutput , error ) {
276312 // Create terraform cli config file that uses provider cache dir
277- if err := cache .createLocalCLIConfig (ctx , opts , cliConfigFilename , "" ); err != nil {
313+ if err := pc .createLocalCLIConfig (ctx , opts , cliConfigFilename , "" ); err != nil {
278314 return nil , err
279315 }
280316
@@ -318,8 +354,8 @@ func (cache *ProviderCache) runTerraformWithCache(
318354// It creates two types of configuration depending on the `cacheRequestID` variable set.
319355// 1. If `cacheRequestID` is set, `terraform init` does _not_ use the provider cache directory, the cache server creates a cache for requested providers and returns HTTP status 423. Since for each module we create the CLI config, using `cacheRequestID` we have the opportunity later retrieve from the cache server exactly those cached providers that were requested by `terraform init` using this configuration.
320356// 2. If `cacheRequestID` is empty, 'terraform init` uses provider cache directory, the cache server acts as a proxy.
321- func (cache * ProviderCache ) createLocalCLIConfig (ctx context.Context , opts * options.TerragruntOptions , filename string , cacheRequestID string ) error {
322- cfg := cache .cliCfg .Clone ()
357+ func (pc * ProviderCache ) createLocalCLIConfig (ctx context.Context , opts * options.TerragruntOptions , filename string , cacheRequestID string ) error {
358+ cfg := pc .cliCfg .Clone ()
323359 cfg .PluginCacheDir = ""
324360
325361 // Filter registries based on OpenTofu or Terraform implementation to avoid contacting unnecessary registries
@@ -333,13 +369,13 @@ func (cache *ProviderCache) createLocalCLIConfig(ctx context.Context, opts *opti
333369 for _ , registryName := range filteredRegistryNames {
334370 providerInstallationIncludes = append (providerInstallationIncludes , registryName + "/*/*" )
335371
336- apiURLs , err := cache .DiscoveryURL (ctx , registryName )
372+ apiURLs , err := pc .DiscoveryURL (ctx , registryName )
337373 if err != nil {
338374 return err
339375 }
340376
341377 cfg .AddHost (registryName , map [string ]string {
342- "providers.v1" : fmt .Sprintf ("%s/%s/%s/" , cache .ProviderController .URL (), cacheRequestID , registryName ),
378+ "providers.v1" : fmt .Sprintf ("%s/%s/%s/" , pc .ProviderController .URL (), cacheRequestID , registryName ),
343379 // Since Terragrunt Provider Cache only caches providers, we need to route module requests to the original registry.
344380 "modules.v1" : ResolveModulesURL (registryName , apiURLs .ModulesV1 ),
345381 })
@@ -357,8 +393,17 @@ func (cache *ProviderCache) createLocalCLIConfig(ctx context.Context, opts *opti
357393 cliconfig .NewProviderInstallationDirect (nil , nil ),
358394 )
359395
360- if cfgDir := filepath .Dir (filename ); ! util .FileExists (cfgDir ) {
361- if err := os .MkdirAll (cfgDir , os .ModePerm ); err != nil {
396+ // Use VFS for directory operations
397+ fs := pc .FS ()
398+ cfgDir := filepath .Dir (filename )
399+
400+ cfgDirExists , err := vfs .FileExists (fs , cfgDir )
401+ if err != nil {
402+ return errors .New (err )
403+ }
404+
405+ if ! cfgDirExists {
406+ if err := fs .MkdirAll (cfgDir , os .ModePerm ); err != nil {
362407 return errors .New (err )
363408 }
364409 }
0 commit comments