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  }