github.com/vtorhonen/terraform@v0.9.0-beta2.0.20170307220345-5d894e4ffda7/builtin/providers/nomad/resource_job.go (about) 1 package nomad 2 3 import ( 4 "bytes" 5 "encoding/gob" 6 "fmt" 7 "log" 8 "reflect" 9 "strings" 10 11 "github.com/hashicorp/nomad/api" 12 "github.com/hashicorp/nomad/jobspec" 13 "github.com/hashicorp/nomad/nomad/structs" 14 "github.com/hashicorp/terraform/helper/schema" 15 ) 16 17 func resourceJob() *schema.Resource { 18 return &schema.Resource{ 19 Create: resourceJobRegister, 20 Update: resourceJobRegister, 21 Delete: resourceJobDeregister, 22 Read: resourceJobRead, 23 Exists: resourceJobExists, 24 25 Schema: map[string]*schema.Schema{ 26 "jobspec": { 27 Description: "Job specification. If you want to point to a file use the file() function.", 28 Required: true, 29 Type: schema.TypeString, 30 DiffSuppressFunc: jobspecDiffSuppress, 31 }, 32 33 "deregister_on_destroy": { 34 Description: "If true, the job will be deregistered on destroy.", 35 Optional: true, 36 Default: true, 37 Type: schema.TypeBool, 38 }, 39 40 "deregister_on_id_change": { 41 Description: "If true, the job will be deregistered when the job ID changes.", 42 Optional: true, 43 Default: true, 44 Type: schema.TypeBool, 45 }, 46 }, 47 } 48 } 49 50 func resourceJobRegister(d *schema.ResourceData, meta interface{}) error { 51 client := meta.(*api.Client) 52 53 // Get the jobspec itself 54 jobspecRaw := d.Get("jobspec").(string) 55 56 // Parse it 57 jobspecStruct, err := jobspec.Parse(strings.NewReader(jobspecRaw)) 58 if err != nil { 59 return fmt.Errorf("error parsing jobspec: %s", err) 60 } 61 62 // Initialize and validate 63 jobspecStruct.Canonicalize() 64 if err := jobspecStruct.Validate(); err != nil { 65 return fmt.Errorf("Error validating job: %v", err) 66 } 67 68 // If we have an ID and its not equal to this jobspec, then we 69 // have to deregister the old job before we register the new job. 70 prevId := d.Id() 71 if !d.Get("deregister_on_id_change").(bool) { 72 // If we aren't deregistering on ID change, just pretend we 73 // don't have a prior ID. 74 prevId = "" 75 } 76 if prevId != "" && prevId != jobspecStruct.ID { 77 log.Printf( 78 "[INFO] Deregistering %q before registering %q", 79 prevId, jobspecStruct.ID) 80 81 log.Printf("[DEBUG] Deregistering job: %q", prevId) 82 _, _, err := client.Jobs().Deregister(prevId, nil) 83 if err != nil { 84 return fmt.Errorf( 85 "error deregistering previous job %q "+ 86 "before registering new job %q: %s", 87 prevId, jobspecStruct.ID, err) 88 } 89 90 // Success! Clear our state. 91 d.SetId("") 92 } 93 94 // Convert it so that we can use it with the API 95 jobspecAPI, err := convertStructJob(jobspecStruct) 96 if err != nil { 97 return fmt.Errorf("error converting jobspec: %s", err) 98 } 99 100 // Register the job 101 _, _, err = client.Jobs().Register(jobspecAPI, nil) 102 if err != nil { 103 return fmt.Errorf("error applying jobspec: %s", err) 104 } 105 106 d.SetId(jobspecAPI.ID) 107 108 return nil 109 } 110 111 func resourceJobDeregister(d *schema.ResourceData, meta interface{}) error { 112 client := meta.(*api.Client) 113 114 // If deregistration is disabled, then do nothing 115 if !d.Get("deregister_on_destroy").(bool) { 116 log.Printf( 117 "[WARN] Job %q will not deregister since 'deregister_on_destroy'"+ 118 " is false", d.Id()) 119 return nil 120 } 121 122 id := d.Id() 123 log.Printf("[DEBUG] Deregistering job: %q", id) 124 _, _, err := client.Jobs().Deregister(id, nil) 125 if err != nil { 126 return fmt.Errorf("error deregistering job: %s", err) 127 } 128 129 return nil 130 } 131 132 func resourceJobExists(d *schema.ResourceData, meta interface{}) (bool, error) { 133 client := meta.(*api.Client) 134 135 id := d.Id() 136 log.Printf("[DEBUG] Checking if job exists: %q", id) 137 _, _, err := client.Jobs().Info(id, nil) 138 if err != nil { 139 // As of Nomad 0.4.1, the API client returns an error for 404 140 // rather than a nil result, so we must check this way. 141 if strings.Contains(err.Error(), "404") { 142 return false, nil 143 } 144 145 return true, fmt.Errorf("error checking for job: %#v", err) 146 } 147 148 return true, nil 149 } 150 151 func resourceJobRead(d *schema.ResourceData, meta interface{}) error { 152 // We don't do anything at the moment. Exists is used to 153 // remove non-existent jobs but read doesn't have to do anything. 154 return nil 155 } 156 157 // convertStructJob is used to take a *structs.Job and convert it to an *api.Job. 158 // 159 // This is unfortunate but it is how Nomad itself does it (this is copied 160 // line for line from Nomad). We'll mimic them exactly to get this done. 161 func convertStructJob(in *structs.Job) (*api.Job, error) { 162 gob.Register([]map[string]interface{}{}) 163 gob.Register([]interface{}{}) 164 var apiJob *api.Job 165 buf := new(bytes.Buffer) 166 if err := gob.NewEncoder(buf).Encode(in); err != nil { 167 return nil, err 168 } 169 if err := gob.NewDecoder(buf).Decode(&apiJob); err != nil { 170 return nil, err 171 } 172 return apiJob, nil 173 } 174 175 // jobspecDiffSuppress is the DiffSuppressFunc used by the schema to 176 // check if two jobspecs are equal. 177 func jobspecDiffSuppress(k, old, new string, d *schema.ResourceData) bool { 178 // Parse the old job 179 oldJob, err := jobspec.Parse(strings.NewReader(old)) 180 if err != nil { 181 return false 182 } 183 184 // Parse the new job 185 newJob, err := jobspec.Parse(strings.NewReader(new)) 186 if err != nil { 187 return false 188 } 189 190 // Init 191 oldJob.Canonicalize() 192 newJob.Canonicalize() 193 194 // Check for jobspec equality 195 return reflect.DeepEqual(oldJob, newJob) 196 }