Skip to content

Commit 8aafa58

Browse files
authored
Merge pull request #25 from terraform-providers/paddy_quota
Manage quota specifications.
2 parents 849bb75 + 4894e26 commit 8aafa58

5 files changed

+690
-3
lines changed

nomad/provider.go

+4-3
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,10 @@ func Provider() terraform.ResourceProvider {
5353
ConfigureFunc: providerConfigure,
5454

5555
ResourcesMap: map[string]*schema.Resource{
56-
"nomad_acl_policy": resourceACLPolicy(),
57-
"nomad_acl_token": resourceACLToken(),
58-
"nomad_job": resourceJob(),
56+
"nomad_acl_policy": resourceACLPolicy(),
57+
"nomad_acl_token": resourceACLToken(),
58+
"nomad_job": resourceJob(),
59+
"nomad_quota_specification": resourceQuotaSpecification(),
5960
},
6061
}
6162
}

nomad/resource_quota_specification.go

+246
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,246 @@
1+
package nomad
2+
3+
import (
4+
"fmt"
5+
"log"
6+
"strings"
7+
8+
"github.com/hashicorp/nomad/api"
9+
"github.com/hashicorp/terraform/helper/schema"
10+
)
11+
12+
func resourceQuotaSpecification() *schema.Resource {
13+
return &schema.Resource{
14+
Create: resourceQuotaSpecificationWrite,
15+
Update: resourceQuotaSpecificationWrite,
16+
Delete: resourceQuotaSpecificationDelete,
17+
Read: resourceQuotaSpecificationRead,
18+
Exists: resourceQuotaSpecificationExists,
19+
20+
Importer: &schema.ResourceImporter{
21+
State: schema.ImportStatePassthrough,
22+
},
23+
24+
Schema: map[string]*schema.Schema{
25+
"name": {
26+
Description: "Unique name for this quota specification.",
27+
Required: true,
28+
Type: schema.TypeString,
29+
ForceNew: true,
30+
},
31+
32+
"description": {
33+
Description: "Description for this quota specification.",
34+
Optional: true,
35+
Type: schema.TypeString,
36+
},
37+
38+
"limits": {
39+
Description: "Limits encapsulated by this quota specification.",
40+
Required: true,
41+
Type: schema.TypeSet,
42+
Elem: resourceQuotaSpecificationLimits(),
43+
},
44+
},
45+
}
46+
}
47+
48+
func resourceQuotaSpecificationLimits() *schema.Resource {
49+
return &schema.Resource{
50+
Schema: map[string]*schema.Schema{
51+
"region": {
52+
Description: "Region in which this limit has affect.",
53+
Type: schema.TypeString,
54+
Required: true,
55+
},
56+
"region_limit": {
57+
Required: true,
58+
Description: "The limit applied to this region.",
59+
Type: schema.TypeSet,
60+
MaxItems: 1,
61+
Elem: resourceQuotaSpecificationRegionLimits(),
62+
},
63+
},
64+
}
65+
}
66+
67+
func resourceQuotaSpecificationRegionLimits() *schema.Resource {
68+
return &schema.Resource{
69+
Schema: map[string]*schema.Schema{
70+
"cpu": {
71+
Type: schema.TypeInt,
72+
Optional: true,
73+
},
74+
"memory_mb": {
75+
Type: schema.TypeInt,
76+
Optional: true,
77+
},
78+
},
79+
}
80+
}
81+
82+
func resourceQuotaSpecificationWrite(d *schema.ResourceData, meta interface{}) error {
83+
client := meta.(*api.Client)
84+
85+
spec := api.QuotaSpec{
86+
Name: d.Get("name").(string),
87+
Description: d.Get("description").(string),
88+
}
89+
limits, err := expandQuotaLimits(d)
90+
if err != nil {
91+
return err
92+
}
93+
spec.Limits = limits
94+
95+
log.Printf("[DEBUG] Upserting quota specification %q", spec.Name)
96+
_, err = client.Quotas().Register(&spec, nil)
97+
if err != nil {
98+
return fmt.Errorf("error upserting quota specification %q: %s", spec.Name, err.Error())
99+
}
100+
log.Printf("[DEBUG] Upserted quota specification %q", spec.Name)
101+
d.SetId(spec.Name)
102+
103+
return resourceQuotaSpecificationRead(d, meta)
104+
}
105+
106+
func resourceQuotaSpecificationDelete(d *schema.ResourceData, meta interface{}) error {
107+
client := meta.(*api.Client)
108+
name := d.Id()
109+
110+
// delete the quota spec
111+
log.Printf("[DEBUG] Deleting quota specification %q", name)
112+
_, err := client.Quotas().Delete(name, nil)
113+
if err != nil {
114+
return fmt.Errorf("error deleting quota specification %q: %s", name, err.Error())
115+
}
116+
log.Printf("[DEBUG] Deleted quota specification %q", name)
117+
118+
return nil
119+
}
120+
121+
func resourceQuotaSpecificationRead(d *schema.ResourceData, meta interface{}) error {
122+
client := meta.(*api.Client)
123+
name := d.Id()
124+
125+
// retrieve the policy
126+
log.Printf("[DEBUG] Reading quota specification %q", name)
127+
spec, _, err := client.Quotas().Info(name, nil)
128+
if err != nil {
129+
// we have Exists, so no need to handle 404
130+
return fmt.Errorf("error reading quota specification %q: %s", name, err.Error())
131+
}
132+
log.Printf("[DEBUG] Read quota specification %q", name)
133+
134+
d.Set("name", spec.Name)
135+
d.Set("description", spec.Description)
136+
err = d.Set("limits", flattenQuotaLimits(spec.Limits))
137+
if err != nil {
138+
return fmt.Errorf("error setting quota specification limits for %q: %s", name, err.Error())
139+
}
140+
141+
return nil
142+
}
143+
144+
func resourceQuotaSpecificationExists(d *schema.ResourceData, meta interface{}) (bool, error) {
145+
client := meta.(*api.Client)
146+
147+
name := d.Id()
148+
log.Printf("[DEBUG] Checking if quota specification %q exists", name)
149+
resp, _, err := client.Quotas().Info(name, nil)
150+
if err != nil {
151+
// As of Nomad 0.4.1, the API client returns an error for 404
152+
// rather than a nil result, so we must check this way.
153+
if strings.Contains(err.Error(), "404") {
154+
return false, nil
155+
}
156+
157+
return true, fmt.Errorf("error checking for quota specification %q: %#v", name, err)
158+
}
159+
// just to be safe
160+
if resp == nil {
161+
log.Printf("[DEBUG] Response was nil, so assuming quota specification %q doesn't exist", name)
162+
return false, nil
163+
}
164+
165+
return true, nil
166+
}
167+
168+
func flattenQuotaLimits(limits []*api.QuotaLimit) *schema.Set {
169+
var results []interface{}
170+
for _, limit := range limits {
171+
res := map[string]interface{}{
172+
"region": limit.Region,
173+
"region_limit": flattenQuotaRegionLimit(limit.RegionLimit),
174+
}
175+
results = append(results, res)
176+
}
177+
return schema.NewSet(schema.HashResource(resourceQuotaSpecificationLimits()), results)
178+
}
179+
180+
func flattenQuotaRegionLimit(limit *api.Resources) *schema.Set {
181+
if limit == nil {
182+
return nil
183+
}
184+
result := map[string]interface{}{}
185+
if limit.CPU != nil {
186+
result["cpu"] = *limit.CPU
187+
}
188+
if limit.MemoryMB != nil {
189+
result["memory_mb"] = *limit.MemoryMB
190+
}
191+
return schema.NewSet(schema.HashResource(resourceQuotaSpecificationRegionLimits()),
192+
[]interface{}{result})
193+
}
194+
195+
func expandQuotaLimits(d *schema.ResourceData) ([]*api.QuotaLimit, error) {
196+
configs := d.Get("limits")
197+
limits := configs.(*schema.Set).List()
198+
results := make([]*api.QuotaLimit, 0, len(limits))
199+
for _, lim := range limits {
200+
limit, ok := lim.(map[string]interface{})
201+
if !ok {
202+
return nil, fmt.Errorf("expected limit to be a map[string]interface{}, got %T instead", lim)
203+
}
204+
region, ok := limit["region"].(string)
205+
if !ok {
206+
return nil, fmt.Errorf("expected region to be string, got %T instead", limit["region"])
207+
}
208+
res := &api.QuotaLimit{
209+
Region: region,
210+
}
211+
regLimit, err := expandRegionLimit(limit["region_limit"])
212+
if err != nil {
213+
return nil, fmt.Errorf("error parsing region limit for region %q: %s", limit["region"], err.Error())
214+
}
215+
res.RegionLimit = regLimit
216+
results = append(results, res)
217+
}
218+
return results, nil
219+
}
220+
221+
func expandRegionLimit(limit interface{}) (*api.Resources, error) {
222+
regLimits := limit.(*schema.Set).List()
223+
if len(regLimits) < 1 {
224+
return nil, nil
225+
}
226+
regLimit, ok := regLimits[0].(map[string]interface{})
227+
if !ok {
228+
return nil, fmt.Errorf("expected map[string]interface{} for region limit, got %T", regLimits[0])
229+
}
230+
var res api.Resources
231+
if cpu, ok := regLimit["cpu"]; ok {
232+
c, ok := cpu.(int)
233+
if !ok {
234+
return nil, fmt.Errorf("expected CPU to be int, got %T", cpu)
235+
}
236+
res.CPU = &c
237+
}
238+
if mem, ok := regLimit["memory_mb"]; ok {
239+
m, ok := mem.(int)
240+
if !ok {
241+
return nil, fmt.Errorf("expected memory to be int, got %T", mem)
242+
}
243+
res.MemoryMB = &m
244+
}
245+
return &res, nil
246+
}

0 commit comments

Comments
 (0)