@@ -225,18 +225,67 @@ func (c *connector) Connect(ctx context.Context, mg resource.Managed) (managed.E
225225 }
226226 }
227227
228+ // Calculate the final tofu working directory (including entrypoint if specified)
229+ // This is where tofu will actually run and create .terraform directory
230+ tofuWorkDir := dir
231+ if len (cr .Spec .ForProvider .Entrypoint ) > 0 {
232+ entrypoint := strings .ReplaceAll (cr .Spec .ForProvider .Entrypoint , "../" , "" )
233+ tofuWorkDir = filepath .Join (dir , entrypoint )
234+ }
235+
228236 switch cr .Spec .ForProvider .Source {
229237 case v1beta1 .ModuleSourceRemote :
230- gc := getter.Client {
231- Src : cr .Spec .ForProvider .Module ,
232- Dst : dir ,
233- Pwd : dir ,
238+ shouldPull := false
239+
240+ // Determine if we should pull the remote source
241+ switch {
242+ case cr .Spec .ForProvider .RemotePullPolicy == nil ||
243+ * cr .Spec .ForProvider .RemotePullPolicy == v1beta1 .RemotePullPolicyAlways :
244+ // Always pull (default behavior)
245+ shouldPull = true
246+ l .Debug ("Remote module pull policy: Always" )
247+
248+ case * cr .Spec .ForProvider .RemotePullPolicy == v1beta1 .RemotePullPolicyIfNotPresent :
249+ // Check if .terraform.lock.hcl exists (indicates successful init)
250+ // This file is created at the END of tofu init, making it the
251+ // most reliable indicator that module initialization completed
252+ lockFile := filepath .Join (tofuWorkDir , ".terraform.lock.hcl" )
253+ lockFileValid , err := validateTofuLockFile (c .fs , lockFile )
254+ if err != nil {
255+ return nil , errors .Wrap (err , "failed to validate tofu lock file" )
256+ }
234257
235- Mode : getter .ClientModeDir ,
258+ if lockFileValid {
259+ // Module already initialized - check if spec changed
260+ if cr .Spec .ForProvider .Module != cr .Status .AtProvider .RemoteSource {
261+ l .Debug ("Remote module URL changed" , "old" , cr .Status .AtProvider .RemoteSource , "new" , cr .Spec .ForProvider .Module )
262+ shouldPull = true
263+ } else {
264+ l .Debug ("Remote module already initialized, skipping download" , "lockFile" , lockFile )
265+ shouldPull = false
266+ }
267+ } else {
268+ l .Debug ("Tofu not initialized, downloading module" , "lockFile" , lockFile )
269+ shouldPull = true
270+ }
236271 }
237- err := gc .Get ()
238- if err != nil {
239- return nil , errors .Wrap (err , errRemoteModule )
272+
273+ // Pull remote source if needed
274+ if shouldPull {
275+ gc := getter.Client {
276+ Src : cr .Spec .ForProvider .Module ,
277+ Dst : dir ,
278+ Pwd : dir ,
279+ Mode : getter .ClientModeDir ,
280+ }
281+ err := gc .Get ()
282+ if err != nil {
283+ return nil , errors .Wrap (err , errRemoteModule )
284+ }
285+
286+ // Update status with downloaded module URL
287+ cr .Status .AtProvider .RemoteSource = cr .Spec .ForProvider .Module
288+ l .Debug ("Remote module downloaded" , "url" , cr .Spec .ForProvider .Module )
240289 }
241290
242291 case v1beta1 .ModuleSourceInline :
@@ -389,13 +438,15 @@ func (c *external) Observe(ctx context.Context, mg resource.Managed) (managed.Ex
389438 if err != nil {
390439 return managed.ExternalObservation {}, errors .Wrap (err , errOutputs )
391440 }
392- cr .Status .AtProvider = generateWorkspaceObservation (op )
393-
441+ // Generate checksum first
394442 checksum , err := c .tofu .GenerateChecksum (ctx )
395443 if err != nil {
396444 return managed.ExternalObservation {}, errors .Wrap (err , errChecksum )
397445 }
398- cr .Status .AtProvider .Checksum = checksum
446+
447+ // Preserve remoteSource from previous status (set in Connect)
448+ // Generate observation with all persistent fields
449+ cr .Status .AtProvider = generateWorkspaceObservation (op , checksum , cr .Status .AtProvider .RemoteSource )
399450
400451 if ! differs {
401452 // TODO(negz): Allow Workspaces to optionally derive their readiness from an
@@ -438,7 +489,16 @@ func (c *external) Update(ctx context.Context, mg resource.Managed) (managed.Ext
438489 if err != nil {
439490 return managed.ExternalUpdate {}, errors .Wrap (err , errOutputs )
440491 }
441- cr .Status .AtProvider = generateWorkspaceObservation (op )
492+
493+ // Generate checksum after apply
494+ checksum , err := c .tofu .GenerateChecksum (ctx )
495+ if err != nil {
496+ return managed.ExternalUpdate {}, errors .Wrap (err , errChecksum )
497+ }
498+
499+ // Preserve remoteSource and update observation with checksum
500+ cr .Status .AtProvider = generateWorkspaceObservation (op , checksum , cr .Status .AtProvider .RemoteSource )
501+
442502 // TODO(negz): Allow Workspaces to optionally derive their readiness from an
443503 // output - similar to the logic XRs use to derive readiness from a field of
444504 // a composed resource.
@@ -528,11 +588,38 @@ func op2cd(o []opentofu.Output) managed.ConnectionDetails {
528588 return cd
529589}
530590
591+ // validateTofuLockFile checks if .terraform.lock.hcl exists and is valid.
592+ // This file is created at the END of successful tofu init, making it the
593+ // most reliable indicator that module initialization completed successfully.
594+ func validateTofuLockFile (fs afero.Afero , lockFilePath string ) (bool , error ) {
595+ data , err := fs .ReadFile (lockFilePath )
596+ if err != nil {
597+ if os .IsNotExist (err ) {
598+ return false , nil // File doesn't exist, not an error
599+ }
600+ return false , err // Read error
601+ }
602+
603+ if len (data ) == 0 {
604+ return false , nil // Empty file (interrupted write)
605+ }
606+
607+ // Basic validation: lock file should contain "provider" declarations
608+ content := string (data )
609+ if ! strings .Contains (content , "provider" ) {
610+ return false , nil // Doesn't look like a valid lock file
611+ }
612+
613+ return true , nil
614+ }
615+
531616// generateWorkspaceObservation is used to produce v1beta1.WorkspaceObservation from
532617// workspace_type.Workspace.
533- func generateWorkspaceObservation (op []opentofu.Output ) v1beta1.WorkspaceObservation {
618+ func generateWorkspaceObservation (op []opentofu.Output , checksum , remoteSource string ) v1beta1.WorkspaceObservation {
534619 wo := v1beta1.WorkspaceObservation {
535- Outputs : make (map [string ]extensionsV1.JSON , len (op )),
620+ Outputs : make (map [string ]extensionsV1.JSON , len (op )),
621+ Checksum : checksum ,
622+ RemoteSource : remoteSource ,
536623 }
537624 for _ , o := range op {
538625 if ! o .Sensitive {
0 commit comments