197 lines
5.0 KiB
Go
197 lines
5.0 KiB
Go
package nomad
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/gob"
|
|
"fmt"
|
|
"log"
|
|
"reflect"
|
|
"strings"
|
|
|
|
"github.com/hashicorp/nomad/api"
|
|
"github.com/hashicorp/nomad/jobspec"
|
|
"github.com/hashicorp/nomad/nomad/structs"
|
|
"github.com/hashicorp/terraform/helper/schema"
|
|
)
|
|
|
|
func resourceJob() *schema.Resource {
|
|
return &schema.Resource{
|
|
Create: resourceJobRegister,
|
|
Update: resourceJobRegister,
|
|
Delete: resourceJobDeregister,
|
|
Read: resourceJobRead,
|
|
Exists: resourceJobExists,
|
|
|
|
Schema: map[string]*schema.Schema{
|
|
"jobspec": {
|
|
Description: "Job specification. If you want to point to a file use the file() function.",
|
|
Required: true,
|
|
Type: schema.TypeString,
|
|
DiffSuppressFunc: jobspecDiffSuppress,
|
|
},
|
|
|
|
"deregister_on_destroy": {
|
|
Description: "If true, the job will be deregistered on destroy.",
|
|
Optional: true,
|
|
Default: true,
|
|
Type: schema.TypeBool,
|
|
},
|
|
|
|
"deregister_on_id_change": {
|
|
Description: "If true, the job will be deregistered when the job ID changes.",
|
|
Optional: true,
|
|
Default: true,
|
|
Type: schema.TypeBool,
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
func resourceJobRegister(d *schema.ResourceData, meta interface{}) error {
|
|
client := meta.(*api.Client)
|
|
|
|
// Get the jobspec itself
|
|
jobspecRaw := d.Get("jobspec").(string)
|
|
|
|
// Parse it
|
|
jobspecStruct, err := jobspec.Parse(strings.NewReader(jobspecRaw))
|
|
if err != nil {
|
|
return fmt.Errorf("error parsing jobspec: %s", err)
|
|
}
|
|
|
|
// Initialize and validate
|
|
jobspecStruct.Canonicalize()
|
|
if err := jobspecStruct.Validate(); err != nil {
|
|
return fmt.Errorf("Error validating job: %v", err)
|
|
}
|
|
|
|
// If we have an ID and its not equal to this jobspec, then we
|
|
// have to deregister the old job before we register the new job.
|
|
prevId := d.Id()
|
|
if !d.Get("deregister_on_id_change").(bool) {
|
|
// If we aren't deregistering on ID change, just pretend we
|
|
// don't have a prior ID.
|
|
prevId = ""
|
|
}
|
|
if prevId != "" && prevId != jobspecStruct.ID {
|
|
log.Printf(
|
|
"[INFO] Deregistering %q before registering %q",
|
|
prevId, jobspecStruct.ID)
|
|
|
|
log.Printf("[DEBUG] Deregistering job: %q", prevId)
|
|
_, _, err := client.Jobs().Deregister(prevId, nil)
|
|
if err != nil {
|
|
return fmt.Errorf(
|
|
"error deregistering previous job %q "+
|
|
"before registering new job %q: %s",
|
|
prevId, jobspecStruct.ID, err)
|
|
}
|
|
|
|
// Success! Clear our state.
|
|
d.SetId("")
|
|
}
|
|
|
|
// Convert it so that we can use it with the API
|
|
jobspecAPI, err := convertStructJob(jobspecStruct)
|
|
if err != nil {
|
|
return fmt.Errorf("error converting jobspec: %s", err)
|
|
}
|
|
|
|
// Register the job
|
|
_, _, err = client.Jobs().Register(jobspecAPI, nil)
|
|
if err != nil {
|
|
return fmt.Errorf("error applying jobspec: %s", err)
|
|
}
|
|
|
|
d.SetId(jobspecAPI.ID)
|
|
|
|
return nil
|
|
}
|
|
|
|
func resourceJobDeregister(d *schema.ResourceData, meta interface{}) error {
|
|
client := meta.(*api.Client)
|
|
|
|
// If deregistration is disabled, then do nothing
|
|
if !d.Get("deregister_on_destroy").(bool) {
|
|
log.Printf(
|
|
"[WARN] Job %q will not deregister since 'deregister_on_destroy'"+
|
|
" is false", d.Id())
|
|
return nil
|
|
}
|
|
|
|
id := d.Id()
|
|
log.Printf("[DEBUG] Deregistering job: %q", id)
|
|
_, _, err := client.Jobs().Deregister(id, nil)
|
|
if err != nil {
|
|
return fmt.Errorf("error deregistering job: %s", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func resourceJobExists(d *schema.ResourceData, meta interface{}) (bool, error) {
|
|
client := meta.(*api.Client)
|
|
|
|
id := d.Id()
|
|
log.Printf("[DEBUG] Checking if job exists: %q", id)
|
|
_, _, err := client.Jobs().Info(id, nil)
|
|
if err != nil {
|
|
// As of Nomad 0.4.1, the API client returns an error for 404
|
|
// rather than a nil result, so we must check this way.
|
|
if strings.Contains(err.Error(), "404") {
|
|
return false, nil
|
|
}
|
|
|
|
return true, fmt.Errorf("error checking for job: %#v", err)
|
|
}
|
|
|
|
return true, nil
|
|
}
|
|
|
|
func resourceJobRead(d *schema.ResourceData, meta interface{}) error {
|
|
// We don't do anything at the moment. Exists is used to
|
|
// remove non-existent jobs but read doesn't have to do anything.
|
|
return nil
|
|
}
|
|
|
|
// convertStructJob is used to take a *structs.Job and convert it to an *api.Job.
|
|
//
|
|
// This is unfortunate but it is how Nomad itself does it (this is copied
|
|
// line for line from Nomad). We'll mimic them exactly to get this done.
|
|
func convertStructJob(in *structs.Job) (*api.Job, error) {
|
|
gob.Register([]map[string]interface{}{})
|
|
gob.Register([]interface{}{})
|
|
var apiJob *api.Job
|
|
buf := new(bytes.Buffer)
|
|
if err := gob.NewEncoder(buf).Encode(in); err != nil {
|
|
return nil, err
|
|
}
|
|
if err := gob.NewDecoder(buf).Decode(&apiJob); err != nil {
|
|
return nil, err
|
|
}
|
|
return apiJob, nil
|
|
}
|
|
|
|
// jobspecDiffSuppress is the DiffSuppressFunc used by the schema to
|
|
// check if two jobspecs are equal.
|
|
func jobspecDiffSuppress(k, old, new string, d *schema.ResourceData) bool {
|
|
// Parse the old job
|
|
oldJob, err := jobspec.Parse(strings.NewReader(old))
|
|
if err != nil {
|
|
return false
|
|
}
|
|
|
|
// Parse the new job
|
|
newJob, err := jobspec.Parse(strings.NewReader(new))
|
|
if err != nil {
|
|
return false
|
|
}
|
|
|
|
// Init
|
|
oldJob.Canonicalize()
|
|
newJob.Canonicalize()
|
|
|
|
// Check for jobspec equality
|
|
return reflect.DeepEqual(oldJob, newJob)
|
|
}
|