github.com/GoogleCloudPlatform/compute-image-tools/cli_tools@v0.0.0-20240516224744-de2dabc4ed1b/gce_windows_upgrade/upgrader/workflows.go (about)

     1  //  Copyright 2020 Google Inc. All Rights Reserved.
     2  //
     3  //  Licensed under the Apache License, Version 2.0 (the "License");
     4  //  you may not use this file except in compliance with the License.
     5  //  You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  //  Unless required by applicable law or agreed to in writing, software
    10  //  distributed under the License is distributed on an "AS IS" BASIS,
    11  //  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  //  See the License for the specific language governing permissions and
    13  //  limitations under the License.
    14  
    15  package upgrader
    16  
    17  import (
    18  	"fmt"
    19  	"os"
    20  
    21  	daisy "github.com/GoogleCloudPlatform/compute-daisy"
    22  	"google.golang.org/api/compute/v1"
    23  
    24  	"github.com/GoogleCloudPlatform/compute-image-tools/cli_tools/common/utils/daisyutils"
    25  	"github.com/GoogleCloudPlatform/compute-image-tools/cli_tools/common/utils/path"
    26  )
    27  
    28  var (
    29  	upgradeSteps = map[string]func(*upgrader, *daisy.Workflow) error{
    30  		versionWindows2012r2: populateUpgradeStepsFrom2008r2To2012r2,
    31  		versionWindows2016:   populateUpgradeStepsTo2016,
    32  		versionWindows2019:   populateUpgradeStepsTo2019,
    33  		versionWindows2022:   populateUpgradeStepsTo2022,
    34  	}
    35  	retryUpgradeSteps = map[string]func(*upgrader, *daisy.Workflow) error{versionWindows2012r2: populateRetryUpgradeStepsFrom2008r2To2012r2}
    36  )
    37  
    38  func (u *upgrader) prepare() (daisyutils.DaisyWorker, error) {
    39  	if u.prepareFn != nil {
    40  		return u.prepareFn()
    41  	}
    42  
    43  	return u.runWorkflowWithSteps("windows-upgrade-preparation", u.Timeout, populatePrepareSteps)
    44  }
    45  
    46  func populatePrepareSteps(u *upgrader, w *daisy.Workflow) error {
    47  	currentExecutablePath := os.Args[0]
    48  	w.Sources = map[string]string{"upgrade_script.ps1": path.ToWorkingDir("upgrade_script.ps1", currentExecutablePath)}
    49  
    50  	stepStopInstance, err := daisyutils.NewStep(w, "stop-instance")
    51  	if err != nil {
    52  		return err
    53  	}
    54  	stepStopInstance.StopInstances = &daisy.StopInstances{
    55  		Instances: []string{u.instanceURI},
    56  	}
    57  	prevStep := stepStopInstance
    58  
    59  	if u.CreateMachineBackup {
    60  		stepBackupMachineImage, err := daisyutils.NewStep(w, "backup-machine-image", stepStopInstance)
    61  		if err != nil {
    62  			return err
    63  		}
    64  		stepBackupMachineImage.CreateMachineImages = &daisy.CreateMachineImages{
    65  			&daisy.MachineImage{
    66  				MachineImage: compute.MachineImage{
    67  					Name:           u.machineImageBackupName,
    68  					SourceInstance: u.instanceURI,
    69  				},
    70  				Resource: daisy.Resource{
    71  					ExactName: true,
    72  					NoCleanup: true,
    73  				},
    74  			},
    75  		}
    76  		prevStep = stepBackupMachineImage
    77  	}
    78  
    79  	stepBackupOSDiskSnapshot, err := daisyutils.NewStep(w, "backup-os-disk-snapshot", prevStep)
    80  	if err != nil {
    81  		return err
    82  	}
    83  	stepBackupOSDiskSnapshot.CreateSnapshots = &daisy.CreateSnapshots{
    84  		&daisy.Snapshot{
    85  			Snapshot: compute.Snapshot{
    86  				Name:       u.osDiskSnapshotName,
    87  				SourceDisk: u.osDiskURI,
    88  			},
    89  			Resource: daisy.Resource{
    90  				ExactName: true,
    91  				NoCleanup: true,
    92  			},
    93  		},
    94  	}
    95  
    96  	stepCreateNewOSDisk, err := daisyutils.NewStep(w, "create-new-os-disk", stepBackupOSDiskSnapshot)
    97  	if err != nil {
    98  		return err
    99  	}
   100  	stepCreateNewOSDisk.CreateDisks = &daisy.CreateDisks{
   101  		&daisy.Disk{
   102  			Disk: compute.Disk{
   103  				Name:           u.newOSDiskName,
   104  				Zone:           u.instanceZone,
   105  				Type:           u.osDiskType,
   106  				SourceSnapshot: u.osDiskSnapshotName,
   107  				Licenses:       []string{upgradePaths[u.SourceOS][u.TargetOS].licenseToAdd},
   108  			},
   109  			Resource: daisy.Resource{
   110  				ExactName: true,
   111  				NoCleanup: true,
   112  			},
   113  		},
   114  	}
   115  
   116  	stepDetachOldOSDisk, err := daisyutils.NewStep(w, "detach-old-os-disk", stepCreateNewOSDisk)
   117  	if err != nil {
   118  		return err
   119  	}
   120  	stepDetachOldOSDisk.DetachDisks = &daisy.DetachDisks{
   121  		&daisy.DetachDisk{
   122  			Instance:   u.instanceURI,
   123  			DeviceName: daisyutils.GetDeviceURI(u.instanceProject, u.instanceZone, u.osDiskDeviceName),
   124  		},
   125  	}
   126  
   127  	stepAttachNewOSDisk, err := daisyutils.NewStep(w, "attach-new-os-disk", stepDetachOldOSDisk)
   128  	if err != nil {
   129  		return err
   130  	}
   131  	stepAttachNewOSDisk.AttachDisks = &daisy.AttachDisks{
   132  		&daisy.AttachDisk{
   133  			Instance: u.instanceURI,
   134  			AttachedDisk: compute.AttachedDisk{
   135  				Source:     u.newOSDiskName,
   136  				DeviceName: u.osDiskDeviceName,
   137  				AutoDelete: u.osDiskAutoDelete,
   138  				Boot:       true,
   139  			},
   140  		},
   141  	}
   142  
   143  	stepCreateInstallDisk, err := daisyutils.NewStep(w, "create-install-disk", stepAttachNewOSDisk)
   144  	if err != nil {
   145  		return err
   146  	}
   147  	stepCreateInstallDisk.CreateDisks = &daisy.CreateDisks{
   148  		&daisy.Disk{
   149  			Disk: compute.Disk{
   150  				Name:        u.installMediaDiskName,
   151  				Zone:        u.instanceZone,
   152  				Type:        "pd-ssd",
   153  				SourceImage: "projects/compute-image-tools/global/images/family/windows-install-media",
   154  			},
   155  			Resource: daisy.Resource{
   156  				ExactName: true,
   157  				NoCleanup: true,
   158  			},
   159  		},
   160  	}
   161  	if u.UseStagingInstallMedia {
   162  		(*stepCreateInstallDisk.CreateDisks)[0].SourceImage = "projects/bct-prod-images/global/images/family/windows-install-media"
   163  	}
   164  
   165  	stepAttachInstallDisk, err := daisyutils.NewStep(w, "attach-install-disk", stepCreateInstallDisk)
   166  	if err != nil {
   167  		return err
   168  	}
   169  	stepAttachInstallDisk.AttachDisks = &daisy.AttachDisks{
   170  		&daisy.AttachDisk{
   171  			Instance: u.instanceURI,
   172  			AttachedDisk: compute.AttachedDisk{
   173  				Source:     u.installMediaDiskName,
   174  				AutoDelete: true,
   175  			},
   176  		},
   177  	}
   178  	prevStep = stepAttachInstallDisk
   179  
   180  	// If there isn't an original url, just skip the backup step.
   181  	if u.originalWindowsStartupScriptURL != nil {
   182  		fmt.Printf("\nDetected an existing metadata for key '%v', value='%v'. Will backup to '%v'.\n\n", metadataWindowsStartupScriptURL,
   183  			*u.originalWindowsStartupScriptURL, metadataWindowsStartupScriptURLBackup)
   184  
   185  		stepBackupScript, err := daisyutils.NewStep(w, "backup-script", stepAttachInstallDisk)
   186  		if err != nil {
   187  			return err
   188  		}
   189  		stepBackupScript.UpdateInstancesMetadata = &daisy.UpdateInstancesMetadata{
   190  			&daisy.UpdateInstanceMetadata{
   191  				Instance: u.instanceURI,
   192  				Metadata: map[string]string{metadataWindowsStartupScriptURLBackup: *u.originalWindowsStartupScriptURL},
   193  			},
   194  		}
   195  		prevStep = stepBackupScript
   196  	}
   197  
   198  	stepSetScript, err := daisyutils.NewStep(w, "set-script", prevStep)
   199  	if err != nil {
   200  		return err
   201  	}
   202  	stepSetScript.UpdateInstancesMetadata = &daisy.UpdateInstancesMetadata{
   203  		&daisy.UpdateInstanceMetadata{
   204  			Instance: u.instanceURI,
   205  			Metadata: map[string]string{
   206  				metadataWindowsStartupScriptURL: "${SOURCESPATH}/upgrade_script.ps1",
   207  				"expected-current-version":      upgradePaths[u.SourceOS][u.TargetOS].expectedCurrentVersion,
   208  				"expected-new-version":          upgradePaths[u.SourceOS][u.TargetOS].expectedNewVersion,
   209  				"install-folder":                upgradePaths[u.SourceOS][u.TargetOS].installFolder,
   210  			},
   211  		},
   212  	}
   213  	return nil
   214  }
   215  
   216  func (u *upgrader) upgrade() (daisyutils.DaisyWorker, error) {
   217  	if u.upgradeFn != nil {
   218  		return u.upgradeFn()
   219  	}
   220  
   221  	return u.runWorkflowWithSteps("upgrade", u.Timeout, upgradeSteps[u.TargetOS])
   222  }
   223  
   224  func populateUpgradeStepsFrom2008r2To2012r2(u *upgrader, w *daisy.Workflow) error {
   225  	cleanupWorkflow, err := u.generateWorkflowWithSteps("cleanup", "10m", populateCleanupSteps)
   226  	if err != nil {
   227  		return nil
   228  	}
   229  
   230  	w.Steps = map[string]*daisy.Step{
   231  		"start-instance": {
   232  			StartInstances: &daisy.StartInstances{
   233  				Instances: []string{u.instanceURI},
   234  			},
   235  		},
   236  		"wait-for-boot": {
   237  			Timeout: "15m",
   238  			WaitForInstancesSignal: &daisy.WaitForInstancesSignal{
   239  				{
   240  					Name: u.instanceURI,
   241  					SerialOutput: &daisy.SerialOutput{
   242  						Port:         1,
   243  						SuccessMatch: "Beginning upgrade startup script.",
   244  					},
   245  				},
   246  			},
   247  		},
   248  		"wait-for-upgrade": {
   249  			WaitForAnyInstancesSignal: &daisy.WaitForAnyInstancesSignal{
   250  				{
   251  					Name: u.instanceURI,
   252  					SerialOutput: &daisy.SerialOutput{
   253  						Port:         1,
   254  						SuccessMatch: "windows_upgrade_current_version='Windows Server 2012 R2 Datacenter'",
   255  						FailureMatch: []string{"UpgradeFailed:"},
   256  						StatusMatch:  "GCEMetadataScripts:",
   257  					},
   258  				},
   259  				{
   260  					Name: u.instanceURI,
   261  					SerialOutput: &daisy.SerialOutput{
   262  						Port: 3,
   263  						// These errors were thrown from setup.exe.
   264  						FailureMatch: []string{"Windows needs to be restarted", "CheckDiskSpaceRequirements not satisfied"},
   265  						// This is the prefix of error log emitted from install media. Catch it and write to daisy log for debugging.
   266  						StatusMatch: "$WINDOWS.~BT setuperr$",
   267  					},
   268  				},
   269  			},
   270  		},
   271  		"cleanup-temp-resources": {
   272  			IncludeWorkflow: &daisy.IncludeWorkflow{
   273  				Workflow: cleanupWorkflow,
   274  			},
   275  		},
   276  	}
   277  	w.Dependencies = map[string][]string{
   278  		"wait-for-boot":          {"start-instance"},
   279  		"wait-for-upgrade":       {"start-instance"},
   280  		"cleanup-temp-resources": {"wait-for-upgrade"},
   281  	}
   282  	return nil
   283  }
   284  
   285  func populateUpgradeStepsTo2016(u *upgrader, w *daisy.Workflow) error {
   286  	return populateUpgradeStepsTemplate(u, w, versionStringForWindows2016)
   287  }
   288  
   289  func populateUpgradeStepsTo2019(u *upgrader, w *daisy.Workflow) error {
   290  	return populateUpgradeStepsTemplate(u, w, versionStringForWindows2019)
   291  }
   292  
   293  func populateUpgradeStepsTo2022(u *upgrader, w *daisy.Workflow) error {
   294  	return populateUpgradeStepsTemplate(u, w, versionStringForWindows2022)
   295  }
   296  
   297  func populateUpgradeStepsTemplate(u *upgrader, w *daisy.Workflow, newVersionString string) error {
   298  	cleanupWorkflow, err := u.generateWorkflowWithSteps("cleanup", "10m", populateCleanupSteps)
   299  	if err != nil {
   300  		return nil
   301  	}
   302  
   303  	w.Steps = map[string]*daisy.Step{
   304  		"start-instance": {
   305  			StartInstances: &daisy.StartInstances{
   306  				Instances: []string{u.instanceURI},
   307  			},
   308  		},
   309  		"wait-for-boot": {
   310  			Timeout: "15m",
   311  			WaitForInstancesSignal: &daisy.WaitForInstancesSignal{
   312  				{
   313  					Name: u.instanceURI,
   314  					SerialOutput: &daisy.SerialOutput{
   315  						Port:         1,
   316  						SuccessMatch: "Beginning upgrade startup script.",
   317  					},
   318  				},
   319  			},
   320  		},
   321  		"wait-for-upgrade": {
   322  			WaitForAnyInstancesSignal: &daisy.WaitForAnyInstancesSignal{
   323  				{
   324  					Name: u.instanceURI,
   325  					SerialOutput: &daisy.SerialOutput{
   326  						Port:         1,
   327  						SuccessMatch: fmt.Sprintf("windows_upgrade_current_version='%v'", newVersionString),
   328  						FailureMatch: []string{"UpgradeFailed:"},
   329  						StatusMatch:  "GCEMetadataScripts:",
   330  					},
   331  				},
   332  				{
   333  					Name: u.instanceURI,
   334  					SerialOutput: &daisy.SerialOutput{
   335  						Port: 3,
   336  						// These errors were thrown from setup.exe.
   337  						FailureMatch: []string{"CheckDiskSpaceRequirements not satisfied"}, //TODO: disk issue, CPU/mem issue
   338  						// This is the prefix of error log emitted from install media. Catch it and write to daisy log for debugging.
   339  						StatusMatch: "$WINDOWS.~BT setuperr$",
   340  					},
   341  				},
   342  			},
   343  		},
   344  		"cleanup-temp-resources": {
   345  			IncludeWorkflow: &daisy.IncludeWorkflow{
   346  				Workflow: cleanupWorkflow,
   347  			},
   348  		},
   349  	}
   350  	w.Dependencies = map[string][]string{
   351  		"wait-for-boot":          {"start-instance"},
   352  		"wait-for-upgrade":       {"start-instance"},
   353  		"cleanup-temp-resources": {"wait-for-upgrade"},
   354  	}
   355  	return nil
   356  }
   357  
   358  func (u *upgrader) retryUpgrade() (daisyutils.DaisyWorker, error) {
   359  	if u.retryUpgradeFn != nil {
   360  		return u.retryUpgradeFn()
   361  	}
   362  
   363  	return u.runWorkflowWithSteps("retry-upgrade", u.Timeout, retryUpgradeSteps[u.TargetOS])
   364  }
   365  
   366  func populateRetryUpgradeStepsFrom2008r2To2012r2(u *upgrader, w *daisy.Workflow) error {
   367  	cleanupWorkflow, err := u.generateWorkflowWithSteps("cleanup", "10m", populateCleanupSteps)
   368  	if err != nil {
   369  		return nil
   370  	}
   371  
   372  	w.Steps = map[string]*daisy.Step{
   373  		"wait-for-boot": {
   374  			Timeout: "15m",
   375  			WaitForInstancesSignal: &daisy.WaitForInstancesSignal{
   376  				{
   377  					Name: u.instanceURI,
   378  					SerialOutput: &daisy.SerialOutput{
   379  						Port:         1,
   380  						SuccessMatch: "Beginning upgrade startup script.",
   381  					},
   382  				},
   383  			},
   384  		},
   385  		"wait-for-upgrade": {
   386  			WaitForAnyInstancesSignal: &daisy.WaitForAnyInstancesSignal{
   387  				{
   388  					Name: u.instanceURI,
   389  					SerialOutput: &daisy.SerialOutput{
   390  						Port:         1,
   391  						SuccessMatch: "windows_upgrade_current_version='Windows Server 2012 R2 Datacenter'",
   392  						FailureMatch: []string{"UpgradeFailed:"},
   393  						StatusMatch:  "GCEMetadataScripts:",
   394  					},
   395  				},
   396  				{
   397  					Name: u.instanceURI,
   398  					SerialOutput: &daisy.SerialOutput{
   399  						Port: 3,
   400  						// These errors were thrown from setup.exe.
   401  						FailureMatch: []string{"Windows needs to be restarted", "CheckDiskSpaceRequirements not satisfied"},
   402  					},
   403  				},
   404  			},
   405  		},
   406  		"cleanup-temp-resources": {
   407  			IncludeWorkflow: &daisy.IncludeWorkflow{
   408  				Workflow: cleanupWorkflow,
   409  			},
   410  		},
   411  	}
   412  	w.Dependencies = map[string][]string{
   413  		"cleanup-temp-resources": {"wait-for-upgrade"},
   414  	}
   415  	return nil
   416  }
   417  
   418  func (u *upgrader) reboot() (daisyutils.DaisyWorker, error) {
   419  	if u.rebootFn != nil {
   420  		return u.rebootFn()
   421  	}
   422  
   423  	return u.runWorkflowWithSteps("reboot", "15m", populateRebootSteps)
   424  }
   425  
   426  func populateRebootSteps(u *upgrader, w *daisy.Workflow) error {
   427  	w.Steps = map[string]*daisy.Step{
   428  		"stop-instance": {
   429  			StopInstances: &daisy.StopInstances{
   430  				Instances: []string{u.instanceURI},
   431  			},
   432  		},
   433  		"start-instance": {
   434  			StartInstances: &daisy.StartInstances{
   435  				Instances: []string{u.instanceURI},
   436  			},
   437  		},
   438  	}
   439  	w.Dependencies = map[string][]string{
   440  		"start-instance": {"stop-instance"},
   441  	}
   442  	return nil
   443  }
   444  
   445  func (u *upgrader) cleanup() (daisyutils.DaisyWorker, error) {
   446  	if u.cleanupFn != nil {
   447  		return u.cleanupFn()
   448  	}
   449  
   450  	return u.runWorkflowWithSteps("cleanup", "20m", populateCleanupSteps)
   451  }
   452  
   453  func populateCleanupSteps(u *upgrader, w *daisy.Workflow) error {
   454  	w.Steps = map[string]*daisy.Step{
   455  		"restore-script": {
   456  			UpdateInstancesMetadata: &daisy.UpdateInstancesMetadata{
   457  				{
   458  					Instance: u.instanceURI,
   459  					Metadata: map[string]string{
   460  						metadataWindowsStartupScriptURL:       u.getOriginalStartupScriptURL(),
   461  						metadataWindowsStartupScriptURLBackup: "",
   462  					},
   463  				},
   464  			},
   465  		},
   466  		"detach-install-media-disk": {
   467  			DetachDisks: &daisy.DetachDisks{
   468  				{
   469  					Instance:   u.instanceURI,
   470  					DeviceName: daisyutils.GetDeviceURI(u.instanceProject, u.instanceZone, u.installMediaDiskName),
   471  				},
   472  			},
   473  		},
   474  		"delete-install-media-disk": {
   475  			DeleteResources: &daisy.DeleteResources{
   476  				Disks: []string{
   477  					daisyutils.GetDiskURI(u.instanceProject, u.instanceZone, u.installMediaDiskName),
   478  				},
   479  			},
   480  		},
   481  		// TODO: use a flag to determine whether to stop the instance. b/156668741
   482  		"stop-instance": {
   483  			StopInstances: &daisy.StopInstances{
   484  				Instances: []string{u.instanceURI},
   485  			},
   486  		},
   487  	}
   488  	w.Dependencies = map[string][]string{
   489  		"delete-install-media-disk": {"detach-install-media-disk"},
   490  	}
   491  	return nil
   492  }
   493  
   494  func (u *upgrader) rollback() (daisyutils.DaisyWorker, error) {
   495  	if u.rollbackFn != nil {
   496  		return u.rollbackFn()
   497  	}
   498  
   499  	return u.runWorkflowWithSteps("rollback", u.Timeout, populateRollbackSteps)
   500  }
   501  
   502  func populateRollbackSteps(u *upgrader, w *daisy.Workflow) error {
   503  	stepStopInstance, err := daisyutils.NewStep(w, "stop-instance")
   504  	if err != nil {
   505  		return err
   506  	}
   507  	stepStopInstance.StopInstances = &daisy.StopInstances{
   508  		Instances: []string{u.instanceURI},
   509  	}
   510  
   511  	stepDetachNewOSDisk, err := daisyutils.NewStep(w, "detach-new-os-disk", stepStopInstance)
   512  	if err != nil {
   513  		return err
   514  	}
   515  	stepDetachNewOSDisk.DetachDisks = &daisy.DetachDisks{
   516  		{
   517  			Instance:   u.instanceURI,
   518  			DeviceName: daisyutils.GetDeviceURI(u.instanceProject, u.instanceZone, u.osDiskDeviceName),
   519  		},
   520  	}
   521  
   522  	stepAttachOldOSDisk, err := daisyutils.NewStep(w, "attach-old-os-disk", stepDetachNewOSDisk)
   523  	if err != nil {
   524  		return err
   525  	}
   526  	stepAttachOldOSDisk.AttachDisks = &daisy.AttachDisks{
   527  		{
   528  			Instance: u.instanceURI,
   529  			AttachedDisk: compute.AttachedDisk{
   530  				Source:     u.osDiskURI,
   531  				DeviceName: u.osDiskDeviceName,
   532  				AutoDelete: u.osDiskAutoDelete,
   533  				Boot:       true,
   534  			},
   535  		},
   536  	}
   537  
   538  	stepDetachInstallMediaDisk, err := daisyutils.NewStep(w, "detach-install-media-disk", stepAttachOldOSDisk)
   539  	if err != nil {
   540  		return err
   541  	}
   542  	stepDetachInstallMediaDisk.DetachDisks = &daisy.DetachDisks{
   543  		{
   544  			Instance:   u.instanceURI,
   545  			DeviceName: daisyutils.GetDeviceURI(u.instanceProject, u.instanceZone, u.installMediaDiskName),
   546  		},
   547  	}
   548  
   549  	stepRestoreScript, err := daisyutils.NewStep(w, "restore-script", stepDetachInstallMediaDisk)
   550  	if err != nil {
   551  		return err
   552  	}
   553  	stepRestoreScript.UpdateInstancesMetadata = &daisy.UpdateInstancesMetadata{
   554  		{
   555  			Instance: u.instanceURI,
   556  			Metadata: map[string]string{
   557  				metadataWindowsStartupScriptURL:       u.getOriginalStartupScriptURL(),
   558  				metadataWindowsStartupScriptURLBackup: "",
   559  			},
   560  		},
   561  	}
   562  
   563  	// TODO: use a flag to determine whether to start the instance. b/156668741
   564  
   565  	stepDeleteNewOSDiskAndInstallMediaDisk, err := daisyutils.NewStep(w, "delete-new-os-disk-and-install-media-disk", stepRestoreScript)
   566  	if err != nil {
   567  		return err
   568  	}
   569  	stepDeleteNewOSDiskAndInstallMediaDisk.DeleteResources = &daisy.DeleteResources{
   570  		Disks: []string{
   571  			daisyutils.GetDiskURI(u.instanceProject, u.instanceZone, u.newOSDiskName),
   572  			daisyutils.GetDiskURI(u.instanceProject, u.instanceZone, u.installMediaDiskName),
   573  		},
   574  	}
   575  	return nil
   576  }
   577  
   578  func (u *upgrader) getOriginalStartupScriptURL() string {
   579  	originalStartupScriptURL := ""
   580  	if u.originalWindowsStartupScriptURL != nil {
   581  		originalStartupScriptURL = *u.originalWindowsStartupScriptURL
   582  	}
   583  	return originalStartupScriptURL
   584  }
   585  
   586  func (u *upgrader) runWorkflowWithSteps(workflowName string, timeout string, populateStepsFunc func(*upgrader, *daisy.Workflow) error) (daisyutils.DaisyWorker, error) {
   587  
   588  	workflowProvider := func() (*daisy.Workflow, error) {
   589  		return u.generateWorkflowWithSteps(workflowName, timeout, populateStepsFunc)
   590  	}
   591  
   592  	env := daisyutils.EnvironmentSettings{
   593  		Project:           u.instanceProject,
   594  		Zone:              u.instanceZone,
   595  		GCSPath:           u.ScratchBucketGcsPath,
   596  		OAuth:             u.Oauth,
   597  		Timeout:           u.Timeout,
   598  		ComputeEndpoint:   u.Ce,
   599  		DisableGCSLogs:    u.GcsLogsDisabled,
   600  		DisableCloudLogs:  u.CloudLogsDisabled,
   601  		DisableStdoutLogs: u.StdoutLogsDisabled,
   602  		ExecutionID:       u.executionID,
   603  		Tool: daisyutils.Tool{
   604  			HumanReadableName: "windows upgrade",
   605  			ResourceLabelName: "windows-upgrade",
   606  		},
   607  	}
   608  
   609  	worker := daisyutils.NewDaisyWorker(workflowProvider, env, u.logger)
   610  	err := worker.Run(map[string]string{})
   611  	return worker, err
   612  }
   613  
   614  func (u *upgrader) generateWorkflowWithSteps(workflowName string, timeout string, populateStepsFunc func(*upgrader, *daisy.Workflow) error) (*daisy.Workflow, error) {
   615  	w := daisy.New()
   616  	w.Name = workflowName
   617  	w.DefaultTimeout = timeout
   618  	err := populateStepsFunc(u, w)
   619  	return w, err
   620  }