github.com/opentofu/opentofu@v1.7.1/internal/communicator/winrm/provisioner.go (about) 1 // Copyright (c) The OpenTofu Authors 2 // SPDX-License-Identifier: MPL-2.0 3 // Copyright (c) 2023 HashiCorp, Inc. 4 // SPDX-License-Identifier: MPL-2.0 5 6 package winrm 7 8 import ( 9 "fmt" 10 "log" 11 "path/filepath" 12 "strings" 13 "time" 14 15 "github.com/opentofu/opentofu/internal/communicator/shared" 16 "github.com/zclconf/go-cty/cty" 17 "github.com/zclconf/go-cty/cty/gocty" 18 ) 19 20 const ( 21 // DefaultUser is used if there is no user given 22 DefaultUser = "Administrator" 23 24 // DefaultPort is used if there is no port given 25 DefaultPort = 5985 26 27 // DefaultHTTPSPort is used if there is no port given and HTTPS is true 28 DefaultHTTPSPort = 5986 29 30 // DefaultScriptPath is used as the path to copy the file to 31 // for remote execution if not provided otherwise. 32 DefaultScriptPath = "C:/Temp/terraform_%RAND%.cmd" 33 34 // DefaultTimeout is used if there is no timeout given 35 DefaultTimeout = 5 * time.Minute 36 ) 37 38 // connectionInfo is decoded from the ConnInfo of the resource. These are the 39 // only keys we look at. If a KeyFile is given, that is used instead 40 // of a password. 41 type connectionInfo struct { 42 User string 43 Password string 44 Host string 45 Port uint16 46 HTTPS bool 47 Insecure bool 48 NTLM bool `mapstructure:"use_ntlm"` 49 CACert string `mapstructure:"cacert"` 50 Timeout string 51 ScriptPath string `mapstructure:"script_path"` 52 TimeoutVal time.Duration `mapstructure:"-"` 53 } 54 55 // decodeConnInfo decodes the given cty.Value using the same behavior as the 56 // lgeacy mapstructure decoder in order to preserve as much of the existing 57 // logic as possible for compatibility. 58 func decodeConnInfo(v cty.Value) (*connectionInfo, error) { 59 connInfo := &connectionInfo{} 60 if v.IsNull() { 61 return connInfo, nil 62 } 63 64 for k, v := range v.AsValueMap() { 65 if v.IsNull() { 66 continue 67 } 68 69 switch k { 70 case "user": 71 connInfo.User = v.AsString() 72 case "password": 73 connInfo.Password = v.AsString() 74 case "host": 75 connInfo.Host = v.AsString() 76 case "port": 77 if err := gocty.FromCtyValue(v, &connInfo.Port); err != nil { 78 return nil, err 79 } 80 case "https": 81 connInfo.HTTPS = v.True() 82 case "insecure": 83 connInfo.Insecure = v.True() 84 case "use_ntlm": 85 connInfo.NTLM = v.True() 86 case "cacert": 87 connInfo.CACert = v.AsString() 88 case "script_path": 89 connInfo.ScriptPath = v.AsString() 90 case "timeout": 91 connInfo.Timeout = v.AsString() 92 } 93 } 94 return connInfo, nil 95 } 96 97 // parseConnectionInfo is used to convert the ConnInfo of the InstanceState into 98 // a ConnectionInfo struct 99 func parseConnectionInfo(v cty.Value) (*connectionInfo, error) { 100 v, err := shared.ConnectionBlockSupersetSchema.CoerceValue(v) 101 if err != nil { 102 return nil, err 103 } 104 105 connInfo, err := decodeConnInfo(v) 106 if err != nil { 107 return nil, err 108 } 109 // Check on script paths which point to the default Windows TEMP folder because files 110 // which are put in there very early in the boot process could get cleaned/deleted 111 // before you had the change to execute them. 112 // 113 // TODO (SvH) Needs some more debugging to fully understand the exact sequence of events 114 // causing this... 115 if strings.HasPrefix(filepath.ToSlash(connInfo.ScriptPath), "C:/Windows/Temp") { 116 return nil, fmt.Errorf( 117 `Using the C:\Windows\Temp folder is not supported. Please use a different 'script_path'.`) 118 } 119 120 if connInfo.User == "" { 121 connInfo.User = DefaultUser 122 } 123 124 // Format the host if needed. 125 // Needed for IPv6 support. 126 connInfo.Host = shared.IpFormat(connInfo.Host) 127 128 if connInfo.Port == 0 { 129 if connInfo.HTTPS { 130 connInfo.Port = DefaultHTTPSPort 131 } else { 132 connInfo.Port = DefaultPort 133 } 134 } 135 if connInfo.ScriptPath == "" { 136 connInfo.ScriptPath = DefaultScriptPath 137 } 138 if connInfo.Timeout != "" { 139 connInfo.TimeoutVal = safeDuration(connInfo.Timeout, DefaultTimeout) 140 } else { 141 connInfo.TimeoutVal = DefaultTimeout 142 } 143 144 return connInfo, nil 145 } 146 147 // safeDuration returns either the parsed duration or a default value 148 func safeDuration(dur string, defaultDur time.Duration) time.Duration { 149 d, err := time.ParseDuration(dur) 150 if err != nil { 151 log.Printf("Invalid duration '%s', using default of %s", dur, defaultDur) 152 return defaultDur 153 } 154 return d 155 } 156 157 func formatDuration(duration time.Duration) string { 158 h := int(duration.Hours()) 159 m := int(duration.Minutes()) - h*60 160 s := int(duration.Seconds()) - (h*3600 + m*60) 161 162 res := "PT" 163 if h > 0 { 164 res = fmt.Sprintf("%s%dH", res, h) 165 } 166 if m > 0 { 167 res = fmt.Sprintf("%s%dM", res, m) 168 } 169 if s > 0 { 170 res = fmt.Sprintf("%s%dS", res, s) 171 } 172 173 return res 174 }