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