github.com/rstandt/terraform@v0.12.32-0.20230710220336-b1063613405c/builtin/provisioners/habitat/linux_provisioner.go (about) 1 package habitat 2 3 import ( 4 "bytes" 5 "errors" 6 "fmt" 7 "github.com/hashicorp/terraform/communicator" 8 "github.com/hashicorp/terraform/terraform" 9 "path" 10 "path/filepath" 11 "strings" 12 "text/template" 13 ) 14 15 const installURL = "https://raw.githubusercontent.com/habitat-sh/habitat/master/components/hab/install.sh" 16 const systemdUnit = `[Unit] 17 Description=Habitat Supervisor 18 19 [Service] 20 ExecStart=/bin/hab sup run{{ .SupOptions }} 21 Restart=on-failure 22 {{ if .GatewayAuthToken -}} 23 Environment="HAB_SUP_GATEWAY_AUTH_TOKEN={{ .GatewayAuthToken }}" 24 {{ end -}} 25 {{ if .BuilderAuthToken -}} 26 Environment="HAB_AUTH_TOKEN={{ .BuilderAuthToken }}" 27 {{ end -}} 28 29 [Install] 30 WantedBy=default.target 31 ` 32 33 func (p *provisioner) linuxInstallHabitat(o terraform.UIOutput, comm communicator.Communicator) error { 34 // Download the hab installer 35 if err := p.runCommand(o, comm, p.linuxGetCommand(fmt.Sprintf("curl --silent -L0 %s > install.sh", installURL))); err != nil { 36 return err 37 } 38 39 // Run the install script 40 var command string 41 if p.Version == "" { 42 command = fmt.Sprintf("bash ./install.sh ") 43 } else { 44 command = fmt.Sprintf("bash ./install.sh -v %s", p.Version) 45 } 46 47 if err := p.runCommand(o, comm, p.linuxGetCommand(command)); err != nil { 48 return err 49 } 50 51 // Accept the license 52 if p.AcceptLicense { 53 var cmd string 54 55 if p.UseSudo == true { 56 cmd = "env HAB_LICENSE=accept sudo -E /bin/bash -c 'hab -V'" 57 } else { 58 cmd = "env HAB_LICENSE=accept /bin/bash -c 'hab -V'" 59 } 60 61 if err := p.runCommand(o, comm, cmd); err != nil { 62 return err 63 } 64 } 65 66 // Create the hab user 67 if err := p.createHabUser(o, comm); err != nil { 68 return err 69 } 70 71 // Cleanup the installer 72 return p.runCommand(o, comm, p.linuxGetCommand(fmt.Sprintf("rm -f install.sh"))) 73 } 74 75 func (p *provisioner) createHabUser(o terraform.UIOutput, comm communicator.Communicator) error { 76 var addUser bool 77 78 // Install busybox to get us the user tools we need 79 if err := p.runCommand(o, comm, p.linuxGetCommand(fmt.Sprintf("hab install core/busybox"))); err != nil { 80 return err 81 } 82 83 // Check for existing hab user 84 if err := p.runCommand(o, comm, p.linuxGetCommand(fmt.Sprintf("hab pkg exec core/busybox id hab"))); err != nil { 85 o.Output("No existing hab user detected, creating...") 86 addUser = true 87 } 88 89 if addUser { 90 return p.runCommand(o, comm, p.linuxGetCommand(fmt.Sprintf("hab pkg exec core/busybox adduser -D -g \"\" hab"))) 91 } 92 93 return nil 94 } 95 96 func (p *provisioner) linuxStartHabitat(o terraform.UIOutput, comm communicator.Communicator) error { 97 // Install the supervisor first 98 var command string 99 if p.Version == "" { 100 command += p.linuxGetCommand(fmt.Sprintf("hab install core/hab-sup")) 101 } else { 102 command += p.linuxGetCommand(fmt.Sprintf("hab install core/hab-sup/%s", p.Version)) 103 } 104 105 if err := p.runCommand(o, comm, command); err != nil { 106 return err 107 } 108 109 // Build up supervisor options 110 options := "" 111 if p.PermanentPeer { 112 options += " --permanent-peer" 113 } 114 115 if p.ListenCtl != "" { 116 options += fmt.Sprintf(" --listen-ctl %s", p.ListenCtl) 117 } 118 119 if p.ListenGossip != "" { 120 options += fmt.Sprintf(" --listen-gossip %s", p.ListenGossip) 121 } 122 123 if p.ListenHTTP != "" { 124 options += fmt.Sprintf(" --listen-http %s", p.ListenHTTP) 125 } 126 127 if p.Peer != "" { 128 options += fmt.Sprintf(" %s", p.Peer) 129 } 130 131 if len(p.Peers) > 0 { 132 if len(p.Peers) == 1 { 133 options += fmt.Sprintf(" --peer %s", p.Peers[0]) 134 } else { 135 options += fmt.Sprintf(" --peer %s", strings.Join(p.Peers, " --peer ")) 136 } 137 } 138 139 if p.RingKey != "" { 140 options += fmt.Sprintf(" --ring %s", p.RingKey) 141 } 142 143 if p.URL != "" { 144 options += fmt.Sprintf(" --url %s", p.URL) 145 } 146 147 if p.Channel != "" { 148 options += fmt.Sprintf(" --channel %s", p.Channel) 149 } 150 151 if p.Events != "" { 152 options += fmt.Sprintf(" --events %s", p.Events) 153 } 154 155 if p.Organization != "" { 156 options += fmt.Sprintf(" --org %s", p.Organization) 157 } 158 159 if p.HttpDisable == true { 160 options += fmt.Sprintf(" --http-disable") 161 } 162 163 if p.AutoUpdate == true { 164 options += fmt.Sprintf(" --auto-update") 165 } 166 167 p.SupOptions = options 168 169 // Start hab depending on service type 170 switch p.ServiceType { 171 case "unmanaged": 172 return p.linuxStartHabitatUnmanaged(o, comm, options) 173 case "systemd": 174 return p.linuxStartHabitatSystemd(o, comm, options) 175 default: 176 return errors.New("unsupported service type") 177 } 178 } 179 180 // This func is a little different than the others since we need to expose HAB_AUTH_TOKEN to a shell 181 // sub-process that's actually running the supervisor. 182 func (p *provisioner) linuxStartHabitatUnmanaged(o terraform.UIOutput, comm communicator.Communicator, options string) error { 183 var token string 184 185 // Create the sup directory for the log file 186 if err := p.runCommand(o, comm, p.linuxGetCommand("mkdir -p /hab/sup/default && chmod o+w /hab/sup/default")); err != nil { 187 return err 188 } 189 190 // Set HAB_AUTH_TOKEN if provided 191 if p.BuilderAuthToken != "" { 192 token = fmt.Sprintf("env HAB_AUTH_TOKEN=%s ", p.BuilderAuthToken) 193 } 194 195 return p.runCommand(o, comm, p.linuxGetCommand(fmt.Sprintf("(%ssetsid hab sup run%s > /hab/sup/default/sup.log 2>&1 <&1 &) ; sleep 1", token, options))) 196 } 197 198 func (p *provisioner) linuxStartHabitatSystemd(o terraform.UIOutput, comm communicator.Communicator, options string) error { 199 // Create a new template and parse the client config into it 200 unitString := template.Must(template.New("hab-supervisor.service").Parse(systemdUnit)) 201 202 var buf bytes.Buffer 203 err := unitString.Execute(&buf, p) 204 if err != nil { 205 return fmt.Errorf("error executing %s.service template: %s", p.ServiceName, err) 206 } 207 208 if err := p.linuxUploadSystemdUnit(o, comm, &buf); err != nil { 209 return err 210 } 211 212 return p.runCommand(o, comm, p.linuxGetCommand(fmt.Sprintf("systemctl enable %s && systemctl start %s", p.ServiceName, p.ServiceName))) 213 } 214 215 func (p *provisioner) linuxUploadSystemdUnit(o terraform.UIOutput, comm communicator.Communicator, contents *bytes.Buffer) error { 216 destination := fmt.Sprintf("/etc/systemd/system/%s.service", p.ServiceName) 217 218 if p.UseSudo { 219 tempPath := fmt.Sprintf("/tmp/%s.service", p.ServiceName) 220 if err := comm.Upload(tempPath, contents); err != nil { 221 return err 222 } 223 224 return p.runCommand(o, comm, p.linuxGetCommand(fmt.Sprintf("mv %s %s", tempPath, destination))) 225 } 226 227 return comm.Upload(destination, contents) 228 } 229 230 func (p *provisioner) linuxUploadRingKey(o terraform.UIOutput, comm communicator.Communicator) error { 231 return p.runCommand(o, comm, p.linuxGetCommand(fmt.Sprintf(`echo -e "%s" | hab ring key import`, p.RingKeyContent))) 232 } 233 234 func (p *provisioner) linuxUploadCtlSecret(o terraform.UIOutput, comm communicator.Communicator) error { 235 destination := fmt.Sprintf("/hab/sup/default/CTL_SECRET") 236 // Create the destination directory 237 err := p.runCommand(o, comm, p.linuxGetCommand(fmt.Sprintf("mkdir -p %s", filepath.Dir(destination)))) 238 if err != nil { 239 return err 240 } 241 242 keyContent := strings.NewReader(p.CtlSecret) 243 if p.UseSudo { 244 tempPath := fmt.Sprintf("/tmp/CTL_SECRET") 245 if err := comm.Upload(tempPath, keyContent); err != nil { 246 return err 247 } 248 249 return p.runCommand(o, comm, p.linuxGetCommand(fmt.Sprintf("mv %s %s && chown root:root %s && chmod 0600 %s", tempPath, destination, destination, destination))) 250 } 251 252 return comm.Upload(destination, keyContent) 253 } 254 255 // 256 // Habitat Services 257 // 258 func (p *provisioner) linuxStartHabitatService(o terraform.UIOutput, comm communicator.Communicator, service Service) error { 259 var options string 260 261 if err := p.linuxInstallHabitatPackage(o, comm, service); err != nil { 262 return err 263 } 264 if err := p.uploadUserTOML(o, comm, service); err != nil { 265 return err 266 } 267 268 // Upload service group key 269 if service.ServiceGroupKey != "" { 270 err := p.uploadServiceGroupKey(o, comm, service.ServiceGroupKey) 271 if err != nil { 272 return err 273 } 274 } 275 276 if service.Topology != "" { 277 options += fmt.Sprintf(" --topology %s", service.Topology) 278 } 279 280 if service.Strategy != "" { 281 options += fmt.Sprintf(" --strategy %s", service.Strategy) 282 } 283 284 if service.Channel != "" { 285 options += fmt.Sprintf(" --channel %s", service.Channel) 286 } 287 288 if service.URL != "" { 289 options += fmt.Sprintf(" --url %s", service.URL) 290 } 291 292 if service.Group != "" { 293 options += fmt.Sprintf(" --group %s", service.Group) 294 } 295 296 for _, bind := range service.Binds { 297 options += fmt.Sprintf(" --bind %s", bind.toBindString()) 298 } 299 300 return p.runCommand(o, comm, p.linuxGetCommand(fmt.Sprintf("hab svc load %s %s", service.Name, options))) 301 } 302 303 // In the future we'll remove the dedicated install once the synchronous load feature in hab-sup is 304 // available. Until then we install here to provide output and a noisy failure mechanism because 305 // if you install with the pkg load, it occurs asynchronously and fails quietly. 306 func (p *provisioner) linuxInstallHabitatPackage(o terraform.UIOutput, comm communicator.Communicator, service Service) error { 307 var options string 308 309 if service.Channel != "" { 310 options += fmt.Sprintf(" --channel %s", service.Channel) 311 } 312 313 if service.URL != "" { 314 options += fmt.Sprintf(" --url %s", service.URL) 315 } 316 317 return p.runCommand(o, comm, p.linuxGetCommand(fmt.Sprintf("hab pkg install %s %s", service.Name, options))) 318 } 319 320 func (p *provisioner) uploadServiceGroupKey(o terraform.UIOutput, comm communicator.Communicator, key string) error { 321 keyName := strings.Split(key, "\n")[1] 322 o.Output("Uploading service group key: " + keyName) 323 keyFileName := fmt.Sprintf("%s.box.key", keyName) 324 destPath := path.Join("/hab/cache/keys", keyFileName) 325 keyContent := strings.NewReader(key) 326 if p.UseSudo { 327 tempPath := path.Join("/tmp", keyFileName) 328 if err := comm.Upload(tempPath, keyContent); err != nil { 329 return err 330 } 331 332 return p.runCommand(o, comm, p.linuxGetCommand(fmt.Sprintf("mv %s %s", tempPath, destPath))) 333 } 334 335 return comm.Upload(destPath, keyContent) 336 } 337 338 func (p *provisioner) uploadUserTOML(o terraform.UIOutput, comm communicator.Communicator, service Service) error { 339 // Create the hab svc directory to lay down the user.toml before loading the service 340 o.Output("Uploading user.toml for service: " + service.Name) 341 destDir := fmt.Sprintf("/hab/user/%s/config", service.getPackageName(service.Name)) 342 command := p.linuxGetCommand(fmt.Sprintf("mkdir -p %s", destDir)) 343 if err := p.runCommand(o, comm, command); err != nil { 344 return err 345 } 346 347 userToml := strings.NewReader(service.UserTOML) 348 349 if p.UseSudo { 350 if err := comm.Upload(fmt.Sprintf("/tmp/user-%s.toml", service.getServiceNameChecksum()), userToml); err != nil { 351 return err 352 } 353 command = p.linuxGetCommand(fmt.Sprintf("mv /tmp/user-%s.toml %s/user.toml", service.getServiceNameChecksum(), destDir)) 354 return p.runCommand(o, comm, command) 355 } 356 357 return comm.Upload(path.Join(destDir, "user.toml"), userToml) 358 } 359 360 func (p *provisioner) linuxGetCommand(command string) string { 361 // Always set HAB_NONINTERACTIVE & HAB_NOCOLORING 362 env := fmt.Sprintf("env HAB_NONINTERACTIVE=true HAB_NOCOLORING=true") 363 364 // Set builder auth token 365 if p.BuilderAuthToken != "" { 366 env += fmt.Sprintf(" HAB_AUTH_TOKEN=%s", p.BuilderAuthToken) 367 } 368 369 if p.UseSudo { 370 command = fmt.Sprintf("%s sudo -E /bin/bash -c '%s'", env, command) 371 } else { 372 command = fmt.Sprintf("%s /bin/bash -c '%s'", env, command) 373 } 374 375 return command 376 }