github.com/ezbercih/terraform@v0.1.1-0.20140729011846-3c33865e0839/helper/ssh/provisioner.go (about) 1 package ssh 2 3 import ( 4 "fmt" 5 "io/ioutil" 6 "log" 7 "time" 8 9 "code.google.com/p/go.crypto/ssh" 10 "github.com/hashicorp/terraform/terraform" 11 "github.com/mitchellh/mapstructure" 12 ) 13 14 const ( 15 // DefaultUser is used if there is no default user given 16 DefaultUser = "root" 17 18 // DefaultPort is used if there is no port given 19 DefaultPort = 22 20 21 // DefaultScriptPath is used as the path to copy the file to 22 // for remote execution if not provided otherwise. 23 DefaultScriptPath = "/tmp/script.sh" 24 25 // DefaultTimeout is used if there is no timeout given 26 DefaultTimeout = 5 * time.Minute 27 ) 28 29 // SSHConfig is decoded from the ConnInfo of the resource. These 30 // are the only keys we look at. If a KeyFile is given, that is used 31 // instead of a password. 32 type SSHConfig struct { 33 User string 34 Password string 35 KeyFile string `mapstructure:"key_file"` 36 Host string 37 Port int 38 Timeout string 39 ScriptPath string `mapstructure:"script_path"` 40 TimeoutVal time.Duration `mapstructure:"-"` 41 } 42 43 // verifySSH is used to verify the ConnInfo is usable by remote-exec 44 func VerifySSH(s *terraform.ResourceState) error { 45 connType := s.ConnInfo["type"] 46 switch connType { 47 case "": 48 case "ssh": 49 default: 50 return fmt.Errorf("Connection type '%s' not supported", connType) 51 } 52 return nil 53 } 54 55 // ParseSSHConfig is used to convert the ConnInfo of the ResourceState into 56 // a SSHConfig struct 57 func ParseSSHConfig(s *terraform.ResourceState) (*SSHConfig, error) { 58 sshConf := &SSHConfig{} 59 decConf := &mapstructure.DecoderConfig{ 60 WeaklyTypedInput: true, 61 Result: sshConf, 62 } 63 dec, err := mapstructure.NewDecoder(decConf) 64 if err != nil { 65 return nil, err 66 } 67 if err := dec.Decode(s.ConnInfo); err != nil { 68 return nil, err 69 } 70 if sshConf.User == "" { 71 sshConf.User = DefaultUser 72 } 73 if sshConf.Port == 0 { 74 sshConf.Port = DefaultPort 75 } 76 if sshConf.ScriptPath == "" { 77 sshConf.ScriptPath = DefaultScriptPath 78 } 79 if sshConf.Timeout != "" { 80 sshConf.TimeoutVal = safeDuration(sshConf.Timeout, DefaultTimeout) 81 } else { 82 sshConf.TimeoutVal = DefaultTimeout 83 } 84 return sshConf, nil 85 } 86 87 // safeDuration returns either the parsed duration or a default value 88 func safeDuration(dur string, defaultDur time.Duration) time.Duration { 89 d, err := time.ParseDuration(dur) 90 if err != nil { 91 log.Printf("Invalid duration '%s', using default of %s", dur, defaultDur) 92 return defaultDur 93 } 94 return d 95 } 96 97 // PrepareConfig is used to turn the *SSHConfig provided into a 98 // usable *Config for client initialization. 99 func PrepareConfig(conf *SSHConfig) (*Config, error) { 100 sshConf := &ssh.ClientConfig{ 101 User: conf.User, 102 } 103 if conf.KeyFile != "" { 104 key, err := ioutil.ReadFile(conf.KeyFile) 105 if err != nil { 106 return nil, fmt.Errorf("Failed to read key file '%s': %v", conf.KeyFile, err) 107 } 108 signer, err := ssh.ParsePrivateKey(key) 109 if err != nil { 110 return nil, fmt.Errorf("Failed to parse key file '%s': %v", conf.KeyFile, err) 111 } 112 sshConf.Auth = append(sshConf.Auth, ssh.PublicKeys(signer)) 113 } 114 if conf.Password != "" { 115 sshConf.Auth = append(sshConf.Auth, 116 ssh.Password(conf.Password)) 117 sshConf.Auth = append(sshConf.Auth, 118 ssh.KeyboardInteractive(PasswordKeyboardInteractive(conf.Password))) 119 } 120 host := fmt.Sprintf("%s:%d", conf.Host, conf.Port) 121 config := &Config{ 122 SSHConfig: sshConf, 123 Connection: ConnectFunc("tcp", host), 124 } 125 return config, nil 126 }