Skip to content

Commit

Permalink
add member resource
Browse files Browse the repository at this point in the history
  • Loading branch information
cormacrelf committed May 31, 2018
1 parent 320e8fd commit 23415ca
Show file tree
Hide file tree
Showing 5 changed files with 332 additions and 17 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ resource "zerotier_network" "your_network" {
route {
target = "${var.zt_cidr}"
}
rules_source = "${file(ztr.conf)}"
rules_source = "${file("ztr.conf")}"
}
```

Expand Down
120 changes: 108 additions & 12 deletions zerotier/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,18 +92,31 @@ type TagByName struct {
Flags map[string]int `json:"flags"`
}

func (n *Network) Compile() error {
return nil
// compiled, err := CompileRulesSource([]byte(n.RulesSource))
// if err != nil {
// return err
// }
// n.Config.Rules = compiled.Config.Rules
// n.Config.Tags = compiled.Config.Tags
// n.Config.Capabilities = compiled.Config.Capabilities
// n.TagsByName = compiled.TagsByName
// n.CapabilitiesByName = compiled.CapabilitiesByName
// return nil
type Member struct {
Id string `json:"id"`
NetworkId string `json:"networkId"`
NodeId string `json:"nodeId"`
OfflineNotifyDelay int `json:"offlineNotifyDelay"` // milliseconds
Name string `json:"name"`
Description string `json:"description"`
Hidden bool `json:"hidden"`
Config *MemberConfig `json:"config"`
}
type MemberConfig struct {
Authorized bool `json:"authorized"`
Capabilities []int `json:"capabilities"`
Tags [][]int `json:"tags"` // array of [tag id, value] tuples
ActiveBridge bool `json:"activeBridge"`
NoAutoAssignIps bool `json:"noAutoAssignIps"`
IpAssignments []string `json:"ipAssignments"`
}
type MemberConfigReadOnly struct {
CreationTime int `json:"creationTime"`
LastAuthorizedTime int `json:"lastAuthorizedTime"`
VMajor int `json:"vMajor"`
VMinor int `json:"vMinor"`
VRev int `json:"vRev"`
VProto int `json:"vProto"`
}

func CIDRToRange(cidr string) (net.IP, net.IP, error) {
Expand Down Expand Up @@ -269,3 +282,86 @@ func (client *ZeroTierClient) DeleteNetwork(id string) error {
_, err = client.doRequest("DeleteNetwork", req)
return err
}

/////////////
// members //
/////////////

func (client *ZeroTierClient) GetMember(nwid string, nodeId string) (*Member, error) {
url := fmt.Sprintf(baseUrl+"/network/%s/member/%s", nwid, nodeId)
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return nil, err
}
bytes, err := client.doRequest("GetMember", req)
if err != nil {
return nil, err
}
var data Member
err = json.Unmarshal(bytes, &data)
if err != nil {
return nil, err
}
return &data, nil
}

func (client *ZeroTierClient) postMember(member *Member, reqName string) (*Member, error) {
url := fmt.Sprintf(baseUrl+"/network/%s/member/%s", member.NetworkId, member.NodeId)
j, err := json.Marshal(member)
if err != nil {
return nil, err
}
req, err := http.NewRequest("POST", url, bytes.NewBuffer(j))
if err != nil {
return nil, err
}
bytes, err := client.doRequest(reqName, req)
if err != nil {
return nil, err
}
var data Member
err = json.Unmarshal(bytes, &data)
if err != nil {
return nil, err
}
return &data, nil
}

func (client *ZeroTierClient) CreateMember(member *Member) (*Member, error) {
return client.postMember(member, "CreateMember")
}

func (client *ZeroTierClient) UpdateMember(member *Member) (*Member, error) {
return client.postMember(member, "UpdateMember")
}

// Careful: this one isn't documented in the Zt API,
// but this is what the Central web client does.
func (client *ZeroTierClient) DeleteMember(member *Member) error {
url := fmt.Sprintf(baseUrl+"/network/%s/member/%s", member.NetworkId, member.NodeId)
req, err := http.NewRequest("DELETE", url, nil)
if err != nil {
return err
}
_, err = client.doRequest("DeleteMember", req)
return err
}

func (client *ZeroTierClient) CheckMemberExists(nwid string, nodeId string) (bool, error) {
url := fmt.Sprintf(baseUrl+"/network/%s/member/%s", nwid, nodeId)
req, err := http.NewRequest("HEAD", url, nil)
if err != nil {
return false, err
}
resp, err := client.headRequest(req)
if resp.StatusCode == 404 {
return false, nil
}
if resp.StatusCode == 403 {
return false, fmt.Errorf("CheckMemberExists received a %s response. Check your ZEROTIER_API_KEY.", resp.Status)
}
if resp.StatusCode != 200 {
return false, fmt.Errorf("CheckMemberExists received response: %s", resp.Status)
}
return true, err
}
1 change: 1 addition & 0 deletions zerotier/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ func Provider() terraform.ResourceProvider {
},
ResourcesMap: map[string]*schema.Resource{
"zerotier_network": resourceZeroTierNetwork(),
"zerotier_member": resourceZeroTierMember(),
},
ConfigureFunc: configureProvider,
}
Expand Down
221 changes: 221 additions & 0 deletions zerotier/resource_zerotier_member.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
package main

import (
"fmt"
"strconv"

"github.com/hashicorp/terraform/helper/schema"
)

func resourceZeroTierMember() *schema.Resource {
return &schema.Resource{
Create: resourceMemberCreate,
Read: resourceMemberRead,
Update: resourceMemberUpdate,
Delete: resourceMemberDelete,
Exists: resourceMemberExists,

Schema: map[string]*schema.Schema{
"network_id": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"node_id": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"name": {
Type: schema.TypeString,
Optional: true,
},
"description": {
Type: schema.TypeString,
Optional: true,
Default: "Managed by Terraform",
},
"hidden": {
Type: schema.TypeBool,
Optional: true,
Default: false,
},
"offline_notify_delay": {
Type: schema.TypeInt,
Optional: true,
Default: 0,
},
"authorized": {
Type: schema.TypeBool,
Optional: true,
Default: true,
},
"allow_ethernet_bridging": {
Type: schema.TypeBool,
Optional: true,
Default: false,
},
"no_auto_assign_ips": {
Type: schema.TypeBool,
Optional: true,
Default: false,
},
"ip_assignments": {
Type: schema.TypeList,
Optional: true,
Elem: &schema.Schema{
Type: schema.TypeString,
},
},
"capabilities": {
Type: schema.TypeList,
Optional: true,
Elem: &schema.Schema{
Type: schema.TypeInt,
},
},
"tags": {
Type: schema.TypeMap,
Optional: true,
Elem: &schema.Schema{
Type: schema.TypeInt,
},
},
},
}
}

func resourceMemberCreate(d *schema.ResourceData, m interface{}) error {
client := m.(*ZeroTierClient)
stored, err := memberFromResourceData(d)
if err != nil {
return err
}
created, err := client.CreateMember(stored)
if err != nil {
return err
}
d.SetId(created.Id)
setTags(d, created)
return nil
}

func resourceMemberUpdate(d *schema.ResourceData, m interface{}) error {
client := m.(*ZeroTierClient)
stored, err := memberFromResourceData(d)
if err != nil {
return err
}
updated, err := client.UpdateMember(stored)
if err != nil {
return fmt.Errorf("unable to update member using ZeroTier API: %s", err)
}
setTags(d, updated)
return nil
}

func setTags(d *schema.ResourceData, member *Member) {
rawTags := map[string]int{}
for _, tuple := range member.Config.Tags {
key := fmt.Sprintf("%d", tuple[0])
val := tuple[1]
rawTags[key] = val
}
}

func resourceMemberDelete(d *schema.ResourceData, m interface{}) error {
client := m.(*ZeroTierClient)
member, err := memberFromResourceData(d)
if err != nil {
return err
}
err = client.DeleteMember(member)
return err
}

func memberFromResourceData(d *schema.ResourceData) (*Member, error) {
tags := d.Get("tags").(map[string]interface{})
tagTuples := [][]int{}
for key, val := range tags {
i, err := strconv.Atoi(key)
if err != nil {
break
}
tagTuples = append(tagTuples, []int{i, val.(int)})
}
capsRaw := d.Get("capabilities").([]interface{})
caps := make([]int, len(capsRaw))
for i := range capsRaw {
caps[i] = capsRaw[i].(int)
}
ipsRaw := d.Get("ip_assignments").([]interface{})
ips := make([]string, len(ipsRaw))
for i := range ipsRaw {
ips[i] = ipsRaw[i].(string)
}
n := &Member{
Id: d.Id(),
NetworkId: d.Get("network_id").(string),
NodeId: d.Get("node_id").(string),
Hidden: d.Get("hidden").(bool),
OfflineNotifyDelay: d.Get("offline_notify_delay").(int),
Name: d.Get("name").(string),
Description: d.Get("description").(string),
Config: &MemberConfig{
Authorized: d.Get("authorized").(bool),
ActiveBridge: d.Get("allow_ethernet_bridging").(bool),
NoAutoAssignIps: d.Get("no_auto_assign_ips").(bool),
Capabilities: caps,
Tags: tagTuples,
IpAssignments: ips,
},
}
return n, nil
}
func resourceMemberRead(d *schema.ResourceData, m interface{}) error {
client := m.(*ZeroTierClient)

// Attempt to read from an upstream API
nwid := d.Get("network_id").(string)
nodeId := d.Get("node_id").(string)
member, err := client.GetMember(nwid, nodeId)

// If the resource does not exist, inform Terraform. We want to immediately
// return here to prevent further processing.
if err != nil {
return fmt.Errorf("unable to read network from API: %s", err)
}
if member == nil {
d.SetId("")
return nil
}

d.SetId(member.Id)
d.Set("name", member.Name)
d.Set("description", member.Description)
d.Set("hidden", member.Hidden)
d.Set("offline_notify_delay", member.OfflineNotifyDelay)
d.Set("authorized", member.Config.Authorized)
d.Set("allow_ethernet_bridging", member.Config.ActiveBridge)
d.Set("no_auto_assign_ips", member.Config.NoAutoAssignIps)
d.Set("ip_assignments", member.Config.IpAssignments)
d.Set("capabilities", member.Config.Capabilities)
setTags(d, member)

return nil
}

func resourceMemberExists(d *schema.ResourceData, m interface{}) (b bool, e error) {
client := m.(*ZeroTierClient)
nwid := d.Get("network_id").(string)
nodeId := d.Get("node_id").(string)
exists, err := client.CheckMemberExists(nwid, nodeId)
if err != nil {
return exists, err
}

if !exists {
d.SetId("")
}
return exists, nil
}
5 changes: 1 addition & 4 deletions zerotier/resource_zerotier_network.go
Original file line number Diff line number Diff line change
Expand Up @@ -149,9 +149,6 @@ func fromResourceData(d *schema.ResourceData) (*Network, error) {
IpAssignmentPools: pools,
},
}
if err := n.Compile(); err != nil {
return nil, err
}
return n, nil
}

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

0 comments on commit 23415ca

Please sign in to comment.