Skip to content

Commit 23415ca

Browse files
committed
add member resource
1 parent 320e8fd commit 23415ca

File tree

5 files changed

+332
-17
lines changed

5 files changed

+332
-17
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,7 @@ resource "zerotier_network" "your_network" {
179179
route {
180180
target = "${var.zt_cidr}"
181181
}
182-
rules_source = "${file(ztr.conf)}"
182+
rules_source = "${file("ztr.conf")}"
183183
}
184184
```
185185

zerotier/client.go

Lines changed: 108 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -92,18 +92,31 @@ type TagByName struct {
9292
Flags map[string]int `json:"flags"`
9393
}
9494

95-
func (n *Network) Compile() error {
96-
return nil
97-
// compiled, err := CompileRulesSource([]byte(n.RulesSource))
98-
// if err != nil {
99-
// return err
100-
// }
101-
// n.Config.Rules = compiled.Config.Rules
102-
// n.Config.Tags = compiled.Config.Tags
103-
// n.Config.Capabilities = compiled.Config.Capabilities
104-
// n.TagsByName = compiled.TagsByName
105-
// n.CapabilitiesByName = compiled.CapabilitiesByName
106-
// return nil
95+
type Member struct {
96+
Id string `json:"id"`
97+
NetworkId string `json:"networkId"`
98+
NodeId string `json:"nodeId"`
99+
OfflineNotifyDelay int `json:"offlineNotifyDelay"` // milliseconds
100+
Name string `json:"name"`
101+
Description string `json:"description"`
102+
Hidden bool `json:"hidden"`
103+
Config *MemberConfig `json:"config"`
104+
}
105+
type MemberConfig struct {
106+
Authorized bool `json:"authorized"`
107+
Capabilities []int `json:"capabilities"`
108+
Tags [][]int `json:"tags"` // array of [tag id, value] tuples
109+
ActiveBridge bool `json:"activeBridge"`
110+
NoAutoAssignIps bool `json:"noAutoAssignIps"`
111+
IpAssignments []string `json:"ipAssignments"`
112+
}
113+
type MemberConfigReadOnly struct {
114+
CreationTime int `json:"creationTime"`
115+
LastAuthorizedTime int `json:"lastAuthorizedTime"`
116+
VMajor int `json:"vMajor"`
117+
VMinor int `json:"vMinor"`
118+
VRev int `json:"vRev"`
119+
VProto int `json:"vProto"`
107120
}
108121

109122
func CIDRToRange(cidr string) (net.IP, net.IP, error) {
@@ -269,3 +282,86 @@ func (client *ZeroTierClient) DeleteNetwork(id string) error {
269282
_, err = client.doRequest("DeleteNetwork", req)
270283
return err
271284
}
285+
286+
/////////////
287+
// members //
288+
/////////////
289+
290+
func (client *ZeroTierClient) GetMember(nwid string, nodeId string) (*Member, error) {
291+
url := fmt.Sprintf(baseUrl+"/network/%s/member/%s", nwid, nodeId)
292+
req, err := http.NewRequest("GET", url, nil)
293+
if err != nil {
294+
return nil, err
295+
}
296+
bytes, err := client.doRequest("GetMember", req)
297+
if err != nil {
298+
return nil, err
299+
}
300+
var data Member
301+
err = json.Unmarshal(bytes, &data)
302+
if err != nil {
303+
return nil, err
304+
}
305+
return &data, nil
306+
}
307+
308+
func (client *ZeroTierClient) postMember(member *Member, reqName string) (*Member, error) {
309+
url := fmt.Sprintf(baseUrl+"/network/%s/member/%s", member.NetworkId, member.NodeId)
310+
j, err := json.Marshal(member)
311+
if err != nil {
312+
return nil, err
313+
}
314+
req, err := http.NewRequest("POST", url, bytes.NewBuffer(j))
315+
if err != nil {
316+
return nil, err
317+
}
318+
bytes, err := client.doRequest(reqName, req)
319+
if err != nil {
320+
return nil, err
321+
}
322+
var data Member
323+
err = json.Unmarshal(bytes, &data)
324+
if err != nil {
325+
return nil, err
326+
}
327+
return &data, nil
328+
}
329+
330+
func (client *ZeroTierClient) CreateMember(member *Member) (*Member, error) {
331+
return client.postMember(member, "CreateMember")
332+
}
333+
334+
func (client *ZeroTierClient) UpdateMember(member *Member) (*Member, error) {
335+
return client.postMember(member, "UpdateMember")
336+
}
337+
338+
// Careful: this one isn't documented in the Zt API,
339+
// but this is what the Central web client does.
340+
func (client *ZeroTierClient) DeleteMember(member *Member) error {
341+
url := fmt.Sprintf(baseUrl+"/network/%s/member/%s", member.NetworkId, member.NodeId)
342+
req, err := http.NewRequest("DELETE", url, nil)
343+
if err != nil {
344+
return err
345+
}
346+
_, err = client.doRequest("DeleteMember", req)
347+
return err
348+
}
349+
350+
func (client *ZeroTierClient) CheckMemberExists(nwid string, nodeId string) (bool, error) {
351+
url := fmt.Sprintf(baseUrl+"/network/%s/member/%s", nwid, nodeId)
352+
req, err := http.NewRequest("HEAD", url, nil)
353+
if err != nil {
354+
return false, err
355+
}
356+
resp, err := client.headRequest(req)
357+
if resp.StatusCode == 404 {
358+
return false, nil
359+
}
360+
if resp.StatusCode == 403 {
361+
return false, fmt.Errorf("CheckMemberExists received a %s response. Check your ZEROTIER_API_KEY.", resp.Status)
362+
}
363+
if resp.StatusCode != 200 {
364+
return false, fmt.Errorf("CheckMemberExists received response: %s", resp.Status)
365+
}
366+
return true, err
367+
}

zerotier/provider.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ func Provider() terraform.ResourceProvider {
1616
},
1717
ResourcesMap: map[string]*schema.Resource{
1818
"zerotier_network": resourceZeroTierNetwork(),
19+
"zerotier_member": resourceZeroTierMember(),
1920
},
2021
ConfigureFunc: configureProvider,
2122
}
Lines changed: 221 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"strconv"
6+
7+
"github.com/hashicorp/terraform/helper/schema"
8+
)
9+
10+
func resourceZeroTierMember() *schema.Resource {
11+
return &schema.Resource{
12+
Create: resourceMemberCreate,
13+
Read: resourceMemberRead,
14+
Update: resourceMemberUpdate,
15+
Delete: resourceMemberDelete,
16+
Exists: resourceMemberExists,
17+
18+
Schema: map[string]*schema.Schema{
19+
"network_id": {
20+
Type: schema.TypeString,
21+
Required: true,
22+
ForceNew: true,
23+
},
24+
"node_id": {
25+
Type: schema.TypeString,
26+
Required: true,
27+
ForceNew: true,
28+
},
29+
"name": {
30+
Type: schema.TypeString,
31+
Optional: true,
32+
},
33+
"description": {
34+
Type: schema.TypeString,
35+
Optional: true,
36+
Default: "Managed by Terraform",
37+
},
38+
"hidden": {
39+
Type: schema.TypeBool,
40+
Optional: true,
41+
Default: false,
42+
},
43+
"offline_notify_delay": {
44+
Type: schema.TypeInt,
45+
Optional: true,
46+
Default: 0,
47+
},
48+
"authorized": {
49+
Type: schema.TypeBool,
50+
Optional: true,
51+
Default: true,
52+
},
53+
"allow_ethernet_bridging": {
54+
Type: schema.TypeBool,
55+
Optional: true,
56+
Default: false,
57+
},
58+
"no_auto_assign_ips": {
59+
Type: schema.TypeBool,
60+
Optional: true,
61+
Default: false,
62+
},
63+
"ip_assignments": {
64+
Type: schema.TypeList,
65+
Optional: true,
66+
Elem: &schema.Schema{
67+
Type: schema.TypeString,
68+
},
69+
},
70+
"capabilities": {
71+
Type: schema.TypeList,
72+
Optional: true,
73+
Elem: &schema.Schema{
74+
Type: schema.TypeInt,
75+
},
76+
},
77+
"tags": {
78+
Type: schema.TypeMap,
79+
Optional: true,
80+
Elem: &schema.Schema{
81+
Type: schema.TypeInt,
82+
},
83+
},
84+
},
85+
}
86+
}
87+
88+
func resourceMemberCreate(d *schema.ResourceData, m interface{}) error {
89+
client := m.(*ZeroTierClient)
90+
stored, err := memberFromResourceData(d)
91+
if err != nil {
92+
return err
93+
}
94+
created, err := client.CreateMember(stored)
95+
if err != nil {
96+
return err
97+
}
98+
d.SetId(created.Id)
99+
setTags(d, created)
100+
return nil
101+
}
102+
103+
func resourceMemberUpdate(d *schema.ResourceData, m interface{}) error {
104+
client := m.(*ZeroTierClient)
105+
stored, err := memberFromResourceData(d)
106+
if err != nil {
107+
return err
108+
}
109+
updated, err := client.UpdateMember(stored)
110+
if err != nil {
111+
return fmt.Errorf("unable to update member using ZeroTier API: %s", err)
112+
}
113+
setTags(d, updated)
114+
return nil
115+
}
116+
117+
func setTags(d *schema.ResourceData, member *Member) {
118+
rawTags := map[string]int{}
119+
for _, tuple := range member.Config.Tags {
120+
key := fmt.Sprintf("%d", tuple[0])
121+
val := tuple[1]
122+
rawTags[key] = val
123+
}
124+
}
125+
126+
func resourceMemberDelete(d *schema.ResourceData, m interface{}) error {
127+
client := m.(*ZeroTierClient)
128+
member, err := memberFromResourceData(d)
129+
if err != nil {
130+
return err
131+
}
132+
err = client.DeleteMember(member)
133+
return err
134+
}
135+
136+
func memberFromResourceData(d *schema.ResourceData) (*Member, error) {
137+
tags := d.Get("tags").(map[string]interface{})
138+
tagTuples := [][]int{}
139+
for key, val := range tags {
140+
i, err := strconv.Atoi(key)
141+
if err != nil {
142+
break
143+
}
144+
tagTuples = append(tagTuples, []int{i, val.(int)})
145+
}
146+
capsRaw := d.Get("capabilities").([]interface{})
147+
caps := make([]int, len(capsRaw))
148+
for i := range capsRaw {
149+
caps[i] = capsRaw[i].(int)
150+
}
151+
ipsRaw := d.Get("ip_assignments").([]interface{})
152+
ips := make([]string, len(ipsRaw))
153+
for i := range ipsRaw {
154+
ips[i] = ipsRaw[i].(string)
155+
}
156+
n := &Member{
157+
Id: d.Id(),
158+
NetworkId: d.Get("network_id").(string),
159+
NodeId: d.Get("node_id").(string),
160+
Hidden: d.Get("hidden").(bool),
161+
OfflineNotifyDelay: d.Get("offline_notify_delay").(int),
162+
Name: d.Get("name").(string),
163+
Description: d.Get("description").(string),
164+
Config: &MemberConfig{
165+
Authorized: d.Get("authorized").(bool),
166+
ActiveBridge: d.Get("allow_ethernet_bridging").(bool),
167+
NoAutoAssignIps: d.Get("no_auto_assign_ips").(bool),
168+
Capabilities: caps,
169+
Tags: tagTuples,
170+
IpAssignments: ips,
171+
},
172+
}
173+
return n, nil
174+
}
175+
func resourceMemberRead(d *schema.ResourceData, m interface{}) error {
176+
client := m.(*ZeroTierClient)
177+
178+
// Attempt to read from an upstream API
179+
nwid := d.Get("network_id").(string)
180+
nodeId := d.Get("node_id").(string)
181+
member, err := client.GetMember(nwid, nodeId)
182+
183+
// If the resource does not exist, inform Terraform. We want to immediately
184+
// return here to prevent further processing.
185+
if err != nil {
186+
return fmt.Errorf("unable to read network from API: %s", err)
187+
}
188+
if member == nil {
189+
d.SetId("")
190+
return nil
191+
}
192+
193+
d.SetId(member.Id)
194+
d.Set("name", member.Name)
195+
d.Set("description", member.Description)
196+
d.Set("hidden", member.Hidden)
197+
d.Set("offline_notify_delay", member.OfflineNotifyDelay)
198+
d.Set("authorized", member.Config.Authorized)
199+
d.Set("allow_ethernet_bridging", member.Config.ActiveBridge)
200+
d.Set("no_auto_assign_ips", member.Config.NoAutoAssignIps)
201+
d.Set("ip_assignments", member.Config.IpAssignments)
202+
d.Set("capabilities", member.Config.Capabilities)
203+
setTags(d, member)
204+
205+
return nil
206+
}
207+
208+
func resourceMemberExists(d *schema.ResourceData, m interface{}) (b bool, e error) {
209+
client := m.(*ZeroTierClient)
210+
nwid := d.Get("network_id").(string)
211+
nodeId := d.Get("node_id").(string)
212+
exists, err := client.CheckMemberExists(nwid, nodeId)
213+
if err != nil {
214+
return exists, err
215+
}
216+
217+
if !exists {
218+
d.SetId("")
219+
}
220+
return exists, nil
221+
}

zerotier/resource_zerotier_network.go

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -149,9 +149,6 @@ func fromResourceData(d *schema.ResourceData) (*Network, error) {
149149
IpAssignmentPools: pools,
150150
},
151151
}
152-
if err := n.Compile(); err != nil {
153-
return nil, err
154-
}
155152
return n, nil
156153
}
157154

@@ -232,7 +229,7 @@ func resourceNetworkUpdate(d *schema.ResourceData, m interface{}) error {
232229
updated, err := client.UpdateNetwork(d.Id(), n)
233230
if err != nil {
234231
stringify, _ := json.Marshal(n)
235-
return fmt.Errorf("unable to update network from API: %s\n\n%s", err, stringify)
232+
return fmt.Errorf("unable to update network using ZeroTier API: %s\n\n%s", err, stringify)
236233
}
237234
setAssignmentPools(d, updated)
238235
return nil

0 commit comments

Comments
 (0)