github.phpd.cn/hashicorp/packer@v1.3.2/builder/vmware/common/step_shutdown.go (about) 1 package common 2 3 import ( 4 "bytes" 5 "context" 6 "errors" 7 "fmt" 8 "log" 9 "regexp" 10 "strings" 11 "time" 12 13 "github.com/hashicorp/packer/helper/multistep" 14 "github.com/hashicorp/packer/packer" 15 ) 16 17 // This step shuts down the machine. It first attempts to do so gracefully, 18 // but ultimately forcefully shuts it down if that fails. 19 // 20 // Uses: 21 // communicator packer.Communicator 22 // dir OutputDir 23 // driver Driver 24 // ui packer.Ui 25 // vmx_path string 26 // 27 // Produces: 28 // <nothing> 29 type StepShutdown struct { 30 Command string 31 Timeout time.Duration 32 33 // Set this to true if we're testing 34 Testing bool 35 } 36 37 func (s *StepShutdown) Run(_ context.Context, state multistep.StateBag) multistep.StepAction { 38 comm := state.Get("communicator").(packer.Communicator) 39 dir := state.Get("dir").(OutputDir) 40 driver := state.Get("driver").(Driver) 41 ui := state.Get("ui").(packer.Ui) 42 vmxPath := state.Get("vmx_path").(string) 43 44 if s.Command != "" { 45 ui.Say("Gracefully halting virtual machine...") 46 log.Printf("Executing shutdown command: %s", s.Command) 47 48 var stdout, stderr bytes.Buffer 49 cmd := &packer.RemoteCmd{ 50 Command: s.Command, 51 Stdout: &stdout, 52 Stderr: &stderr, 53 } 54 if err := comm.Start(cmd); err != nil { 55 err := fmt.Errorf("Failed to send shutdown command: %s", err) 56 state.Put("error", err) 57 ui.Error(err.Error()) 58 return multistep.ActionHalt 59 } 60 61 // Wait for the machine to actually shut down 62 log.Printf("Waiting max %s for shutdown to complete", s.Timeout) 63 shutdownTimer := time.After(s.Timeout) 64 for { 65 running, _ := driver.IsRunning(vmxPath) 66 if !running { 67 break 68 } 69 70 select { 71 case <-shutdownTimer: 72 log.Printf("Shutdown stdout: %s", stdout.String()) 73 log.Printf("Shutdown stderr: %s", stderr.String()) 74 err := errors.New("Timeout while waiting for machine to shut down.") 75 state.Put("error", err) 76 ui.Error(err.Error()) 77 return multistep.ActionHalt 78 default: 79 time.Sleep(150 * time.Millisecond) 80 } 81 } 82 } else { 83 ui.Say("Forcibly halting virtual machine...") 84 if err := driver.Stop(vmxPath); err != nil { 85 err := fmt.Errorf("Error stopping VM: %s", err) 86 state.Put("error", err) 87 ui.Error(err.Error()) 88 return multistep.ActionHalt 89 } 90 } 91 92 ui.Message("Waiting for VMware to clean up after itself...") 93 lockRegex := regexp.MustCompile(`(?i)\.lck$`) 94 timer := time.After(120 * time.Second) 95 LockWaitLoop: 96 for { 97 files, err := dir.ListFiles() 98 if err != nil { 99 log.Printf("Error listing files in outputdir: %s", err) 100 } else { 101 var locks []string 102 for _, file := range files { 103 if lockRegex.MatchString(file) { 104 locks = append(locks, file) 105 } 106 } 107 108 if len(locks) == 0 { 109 log.Println("No more lock files found. VMware is clean.") 110 break 111 } 112 113 if len(locks) == 1 && strings.HasSuffix(locks[0], ".vmx.lck") { 114 log.Println("Only waiting on VMX lock. VMware is clean.") 115 break 116 } 117 118 log.Printf("Waiting on lock files: %#v", locks) 119 } 120 121 select { 122 case <-timer: 123 log.Println("Reached timeout on waiting for clean VMware. Assuming clean.") 124 break LockWaitLoop 125 case <-time.After(150 * time.Millisecond): 126 } 127 } 128 129 if !s.Testing { 130 // Windows takes a while to yield control of the files when the 131 // process is exiting. Ubuntu and OS X will yield control of the files 132 // but VMWare may overwrite the VMX cleanup steps that run after this, 133 // so we wait to ensure VMWare has exited and flushed the VMX. 134 135 // We just sleep here. In the future, it'd be nice to find a better 136 // solution to this. 137 time.Sleep(5 * time.Second) 138 } 139 140 log.Println("VM shut down.") 141 return multistep.ActionContinue 142 } 143 144 func (s *StepShutdown) Cleanup(state multistep.StateBag) {}