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  }