provider/ns1: Add notify list resource (#12373)

* Allow for local development with ns1 provider.

* Adds first implementation of ns1 notification list resource.

* NS1 record.use_client_subnet defaults to true, and added test for field.

* Adds more test cases for monitoring jobs.

* Adds webhook/datafeed notifier types and acctests for notifylists.

* Adds docs for notifylists resource.

* Updates ns1-go rest client via govendor

* Fix typos in record docs
This commit is contained in:
Pasha Palangpour 2017-03-05 09:21:06 -05:00 committed by Paul Stack
parent 120e3af178
commit ce633f2321
28 changed files with 561 additions and 504 deletions

View File

@ -1,6 +1,7 @@
package ns1
import (
"crypto/tls"
"net/http"
"github.com/hashicorp/terraform/helper/schema"
@ -19,6 +20,18 @@ func Provider() terraform.ResourceProvider {
DefaultFunc: schema.EnvDefaultFunc("NS1_APIKEY", nil),
Description: descriptions["api_key"],
},
"endpoint": &schema.Schema{
Type: schema.TypeString,
Optional: true,
DefaultFunc: schema.EnvDefaultFunc("NS1_ENDPOINT", nil),
Description: descriptions["endpoint"],
},
"ignore_ssl": &schema.Schema{
Type: schema.TypeBool,
Optional: true,
DefaultFunc: schema.EnvDefaultFunc("NS1_IGNORE_SSL", nil),
Description: descriptions["ignore_ssl"],
},
},
ResourcesMap: map[string]*schema.Resource{
"ns1_zone": zoneResource(),
@ -26,6 +39,7 @@ func Provider() terraform.ResourceProvider {
"ns1_datasource": dataSourceResource(),
"ns1_datafeed": dataFeedResource(),
"ns1_monitoringjob": monitoringJobResource(),
"ns1_notifylist": notifyListResource(),
"ns1_user": userResource(),
"ns1_apikey": apikeyResource(),
"ns1_team": teamResource(),
@ -36,7 +50,19 @@ func Provider() terraform.ResourceProvider {
func ns1Configure(d *schema.ResourceData) (interface{}, error) {
httpClient := &http.Client{}
n := ns1.NewClient(httpClient, ns1.SetAPIKey(d.Get("apikey").(string)))
decos := []func(*ns1.Client){}
decos = append(decos, ns1.SetAPIKey(d.Get("apikey").(string)))
if v, ok := d.GetOk("endpoint"); ok {
decos = append(decos, ns1.SetEndpoint(v.(string)))
}
if _, ok := d.GetOk("ignore_ssl"); ok {
tr := &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
httpClient.Transport = tr
}
n := ns1.NewClient(httpClient, decos...)
n.RateLimitStrategySleep()
return n, nil
}

View File

@ -166,6 +166,7 @@ func monitoringJobToResourceData(d *schema.ResourceData, r *monitor.Job) error {
m["key"] = r.Key
rules[i] = m
}
d.Set("rules", rules)
}
return nil
}

View File

@ -31,8 +31,11 @@ func TestAccMonitoringJob_basic(t *testing.T) {
testAccCheckMonitoringJobRapidRecheck(&mj, false),
testAccCheckMonitoringJobPolicy(&mj, "quorum"),
testAccCheckMonitoringJobConfigSend(&mj, "HEAD / HTTP/1.0\r\n\r\n"),
testAccCheckMonitoringJobConfigPort(&mj, 80),
testAccCheckMonitoringJobConfigHost(&mj, "1.1.1.1"),
testAccCheckMonitoringJobConfigPort(&mj, 443),
testAccCheckMonitoringJobConfigHost(&mj, "1.2.3.4"),
testAccCheckMonitoringJobRuleValue(&mj, "200 OK"),
testAccCheckMonitoringJobRuleComparison(&mj, "contains"),
testAccCheckMonitoringJobRuleKey(&mj, "output"),
),
},
},
@ -58,8 +61,11 @@ func TestAccMonitoringJob_updated(t *testing.T) {
testAccCheckMonitoringJobRapidRecheck(&mj, false),
testAccCheckMonitoringJobPolicy(&mj, "quorum"),
testAccCheckMonitoringJobConfigSend(&mj, "HEAD / HTTP/1.0\r\n\r\n"),
testAccCheckMonitoringJobConfigPort(&mj, 80),
testAccCheckMonitoringJobConfigHost(&mj, "1.1.1.1"),
testAccCheckMonitoringJobConfigPort(&mj, 443),
testAccCheckMonitoringJobConfigHost(&mj, "1.2.3.4"),
testAccCheckMonitoringJobRuleValue(&mj, "200 OK"),
testAccCheckMonitoringJobRuleComparison(&mj, "contains"),
testAccCheckMonitoringJobRuleKey(&mj, "output"),
),
},
resource.TestStep{
@ -76,6 +82,9 @@ func TestAccMonitoringJob_updated(t *testing.T) {
testAccCheckMonitoringJobConfigSend(&mj, "HEAD / HTTP/1.0\r\n\r\n"),
testAccCheckMonitoringJobConfigPort(&mj, 443),
testAccCheckMonitoringJobConfigHost(&mj, "1.1.1.1"),
testAccCheckMonitoringJobRuleValue(&mj, "200"),
testAccCheckMonitoringJobRuleComparison(&mj, "<="),
testAccCheckMonitoringJobRuleKey(&mj, "connect"),
),
},
},
@ -242,6 +251,33 @@ func testAccCheckMonitoringJobConfigHost(mj *monitor.Job, expected string) resou
}
}
func testAccCheckMonitoringJobRuleValue(mj *monitor.Job, expected string) resource.TestCheckFunc {
return func(s *terraform.State) error {
if mj.Rules[0].Value.(string) != expected {
return fmt.Errorf("Rules[0].Value: got: %#v want: %#v", mj.Rules[0].Value.(string), expected)
}
return nil
}
}
func testAccCheckMonitoringJobRuleComparison(mj *monitor.Job, expected string) resource.TestCheckFunc {
return func(s *terraform.State) error {
if mj.Rules[0].Comparison != expected {
return fmt.Errorf("Rules[0].Comparison: got: %#v want: %#v", mj.Rules[0].Comparison, expected)
}
return nil
}
}
func testAccCheckMonitoringJobRuleKey(mj *monitor.Job, expected string) resource.TestCheckFunc {
return func(s *terraform.State) error {
if mj.Rules[0].Key != expected {
return fmt.Errorf("Rules[0].Key: got: %#v want: %#v", mj.Rules[0].Key, expected)
}
return nil
}
}
const testAccMonitoringJobBasic = `
resource "ns1_monitoringjob" "it" {
job_type = "tcp"
@ -250,10 +286,16 @@ resource "ns1_monitoringjob" "it" {
regions = ["lga"]
frequency = 60
config {
config = {
ssl = "1",
send = "HEAD / HTTP/1.0\r\n\r\n"
port = 80
host = "1.1.1.1"
port = 443
host = "1.2.3.4"
}
rules = {
value = "200 OK"
comparison = "contains"
key = "output"
}
}
`
@ -269,10 +311,16 @@ resource "ns1_monitoringjob" "it" {
rapid_recheck = true
policy = "all"
config {
config = {
ssl = "1",
send = "HEAD / HTTP/1.0\r\n\r\n"
port = 443
host = "1.1.1.1"
}
rules = {
value = 200
comparison = "<="
key = "connect"
}
}
`

View File

@ -0,0 +1,140 @@
package ns1
import (
"github.com/hashicorp/terraform/helper/schema"
ns1 "gopkg.in/ns1/ns1-go.v2/rest"
"gopkg.in/ns1/ns1-go.v2/rest/model/monitor"
)
func notifyListResource() *schema.Resource {
return &schema.Resource{
Schema: map[string]*schema.Schema{
"id": &schema.Schema{
Type: schema.TypeString,
Computed: true,
},
"name": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
"notifications": &schema.Schema{
Type: schema.TypeList,
Optional: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"type": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
"config": &schema.Schema{
Type: schema.TypeMap,
Required: true,
},
},
},
},
},
Create: NotifyListCreate,
Read: NotifyListRead,
Update: NotifyListUpdate,
Delete: NotifyListDelete,
}
}
func notifyListToResourceData(d *schema.ResourceData, nl *monitor.NotifyList) error {
d.SetId(nl.ID)
d.Set("name", nl.Name)
if len(nl.Notifications) > 0 {
notifications := make([]map[string]interface{}, len(nl.Notifications))
for i, n := range nl.Notifications {
ni := make(map[string]interface{})
ni["type"] = n.Type
if n.Config != nil {
ni["config"] = n.Config
}
notifications[i] = ni
}
d.Set("notifications", notifications)
}
return nil
}
func resourceDataToNotifyList(nl *monitor.NotifyList, d *schema.ResourceData) error {
nl.ID = d.Id()
if rawNotifications := d.Get("notifications").([]interface{}); len(rawNotifications) > 0 {
ns := make([]*monitor.Notification, len(rawNotifications))
for i, notificationRaw := range rawNotifications {
ni := notificationRaw.(map[string]interface{})
config := ni["config"].(map[string]interface{})
switch ni["type"].(string) {
case "webhook":
ns[i] = monitor.NewWebNotification(config["url"].(string))
case "email":
ns[i] = monitor.NewEmailNotification(config["email"].(string))
case "datafeed":
ns[i] = monitor.NewFeedNotification(config["sourceid"].(string))
}
}
nl.Notifications = ns
}
return nil
}
// NotifyListCreate creates an ns1 notifylist
func NotifyListCreate(d *schema.ResourceData, meta interface{}) error {
client := meta.(*ns1.Client)
nl := monitor.NewNotifyList(d.Get("name").(string))
if err := resourceDataToNotifyList(nl, d); err != nil {
return err
}
if _, err := client.Notifications.Create(nl); err != nil {
return err
}
return notifyListToResourceData(d, nl)
}
// NotifyListRead fetches info for the given notifylist from ns1
func NotifyListRead(d *schema.ResourceData, meta interface{}) error {
client := meta.(*ns1.Client)
nl, _, err := client.Notifications.Get(d.Id())
if err != nil {
return err
}
return notifyListToResourceData(d, nl)
}
// NotifyListDelete deletes the given notifylist from ns1
func NotifyListDelete(d *schema.ResourceData, meta interface{}) error {
client := meta.(*ns1.Client)
_, err := client.Notifications.Delete(d.Id())
d.SetId("")
return err
}
// NotifyListUpdate updates the notifylist with given parameters
func NotifyListUpdate(d *schema.ResourceData, meta interface{}) error {
client := meta.(*ns1.Client)
nl := monitor.NewNotifyList(d.Get("name").(string))
if err := resourceDataToNotifyList(nl, d); err != nil {
return err
}
if _, err := client.Notifications.Update(nl); err != nil {
return err
}
return notifyListToResourceData(d, nl)
}

View File

@ -0,0 +1,158 @@
package ns1
import (
"fmt"
"testing"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
ns1 "gopkg.in/ns1/ns1-go.v2/rest"
"gopkg.in/ns1/ns1-go.v2/rest/model/monitor"
)
func TestAccNotifyList_basic(t *testing.T) {
var nl monitor.NotifyList
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckNotifyListDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccNotifyListBasic,
Check: resource.ComposeTestCheckFunc(
testAccCheckNotifyListExists("ns1_notifylist.test", &nl),
testAccCheckNotifyListName(&nl, "terraform test"),
),
},
},
})
}
func TestAccNotifyList_updated(t *testing.T) {
var nl monitor.NotifyList
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckNotifyListDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccNotifyListBasic,
Check: resource.ComposeTestCheckFunc(
testAccCheckNotifyListExists("ns1_notifylist.test", &nl),
testAccCheckNotifyListName(&nl, "terraform test"),
),
},
resource.TestStep{
Config: testAccNotifyListUpdated,
Check: resource.ComposeTestCheckFunc(
testAccCheckNotifyListExists("ns1_notifylist.test", &nl),
testAccCheckNotifyListName(&nl, "terraform test"),
),
},
},
})
}
func testAccCheckNotifyListState(key, value string) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources["ns1_notifylist.test"]
if !ok {
return fmt.Errorf("Not found: %s", "ns1_notifylist.test")
}
if rs.Primary.ID == "" {
return fmt.Errorf("No ID is set")
}
p := rs.Primary
if p.Attributes[key] != value {
return fmt.Errorf(
"%s != %s (actual: %s)", key, value, p.Attributes[key])
}
return nil
}
}
func testAccCheckNotifyListExists(n string, nl *monitor.NotifyList) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[n]
if !ok {
return fmt.Errorf("Resource not found: %v", n)
}
id := rs.Primary.ID
if id == "" {
return fmt.Errorf("ID is not set")
}
client := testAccProvider.Meta().(*ns1.Client)
foundNl, _, err := client.Notifications.Get(id)
if err != nil {
return err
}
if foundNl.ID != id {
return fmt.Errorf("Notify List not found want: %#v, got %#v", id, foundNl)
}
*nl = *foundNl
return nil
}
}
func testAccCheckNotifyListDestroy(s *terraform.State) error {
client := testAccProvider.Meta().(*ns1.Client)
for _, rs := range s.RootModule().Resources {
if rs.Type != "ns1_notifylist" {
continue
}
nl, _, err := client.Notifications.Get(rs.Primary.Attributes["id"])
if err == nil {
return fmt.Errorf("Notify List still exists %#v: %#v", err, nl)
}
}
return nil
}
func testAccCheckNotifyListName(nl *monitor.NotifyList, expected string) resource.TestCheckFunc {
return func(s *terraform.State) error {
if nl.Name != expected {
return fmt.Errorf("Name: got: %#v want: %#v", nl.Name, expected)
}
return nil
}
}
const testAccNotifyListBasic = `
resource "ns1_notifylist" "test" {
name = "terraform test"
notifications = {
type = "webhook"
config = {
url = "http://localhost:9090"
}
}
}
`
const testAccNotifyListUpdated = `
resource "ns1_notifylist" "test" {
name = "terraform test"
notifications = {
type = "webhook"
config = {
url = "http://localhost:9091"
}
}
}
`

View File

@ -69,7 +69,7 @@ func recordResource() *schema.Resource {
"use_client_subnet": &schema.Schema{
Type: schema.TypeBool,
Optional: true,
Default: false,
Default: true,
},
"answers": &schema.Schema{
Type: schema.TypeSet,
@ -264,10 +264,8 @@ func resourceDataToRecord(r *dns.Record, d *schema.ResourceData) error {
// if v, ok := d.GetOk("meta"); ok {
// metaDynamicToStruct(r.Meta, v)
// }
if v, ok := d.GetOk("use_client_subnet"); ok {
copy := v.(bool)
r.UseClientSubnet = &copy
}
useClientSubnet := d.Get("use_client_subnet").(bool)
r.UseClientSubnet = &useClientSubnet
if rawFilters := d.Get("filters").([]interface{}); len(rawFilters) > 0 {
f := make([]*filter.Filter, len(rawFilters))

View File

@ -26,6 +26,7 @@ func TestAccRecord_basic(t *testing.T) {
testAccCheckRecordExists("ns1_record.it", &record),
testAccCheckRecordDomain(&record, "test.terraform-record-test.io"),
testAccCheckRecordTTL(&record, 60),
testAccCheckRecordUseClientSubnet(&record, true),
testAccCheckRecordRegionName(&record, []string{"cal"}),
// testAccCheckRecordAnswerMetaWeight(&record, 10),
testAccCheckRecordAnswerRdata(&record, "test1.terraform-record-test.io"),
@ -48,6 +49,7 @@ func TestAccRecord_updated(t *testing.T) {
testAccCheckRecordExists("ns1_record.it", &record),
testAccCheckRecordDomain(&record, "test.terraform-record-test.io"),
testAccCheckRecordTTL(&record, 60),
testAccCheckRecordUseClientSubnet(&record, true),
testAccCheckRecordRegionName(&record, []string{"cal"}),
// testAccCheckRecordAnswerMetaWeight(&record, 10),
testAccCheckRecordAnswerRdata(&record, "test1.terraform-record-test.io"),
@ -59,6 +61,7 @@ func TestAccRecord_updated(t *testing.T) {
testAccCheckRecordExists("ns1_record.it", &record),
testAccCheckRecordDomain(&record, "test.terraform-record-test.io"),
testAccCheckRecordTTL(&record, 120),
testAccCheckRecordUseClientSubnet(&record, false),
testAccCheckRecordRegionName(&record, []string{"ny", "wa"}),
// testAccCheckRecordAnswerMetaWeight(&record, 5),
testAccCheckRecordAnswerRdata(&record, "test2.terraform-record-test.io"),
@ -143,6 +146,15 @@ func testAccCheckRecordTTL(r *dns.Record, expected int) resource.TestCheckFunc {
}
}
func testAccCheckRecordUseClientSubnet(r *dns.Record, expected bool) resource.TestCheckFunc {
return func(s *terraform.State) error {
if *r.UseClientSubnet != expected {
return fmt.Errorf("UseClientSubnet: got: %#v want: %#v", *r.UseClientSubnet, expected)
}
return nil
}
}
func testAccCheckRecordRegionName(r *dns.Record, expected []string) resource.TestCheckFunc {
return func(s *terraform.State) error {
regions := make([]string, len(r.Regions))
@ -240,7 +252,7 @@ resource "ns1_record" "it" {
domain = "test.${ns1_zone.test.zone}"
type = "CNAME"
ttl = 120
use_client_subnet = true
use_client_subnet = false
// meta {
// weight = 5

View File

@ -1,11 +0,0 @@
.PHONY: all clean
all: .git/hooks/pre-commit
go build .
clean:
rm -f terraform-provider-nsone
.git/hooks/pre-commit:
if [ ! -f .git/hooks/pre-commit ]; then ln -s ../../git-hooks/pre-commit .git/hooks/pre-commit; fi

View File

@ -2,7 +2,7 @@
# NS1 Golang SDK
The golang client for the NS1 API: https://api.nsone.net/
The golang client for the NS1 API: https://ns1.com/api/
# Installing

View File

@ -137,7 +137,7 @@ func (s *APIKeysService) Delete(keyID string) (*http.Response, error) {
var (
// ErrKeyExists bundles PUT create error.
ErrKeyExists = errors.New("Key already exists.")
ErrKeyExists = errors.New("key already exists")
// ErrKeyMissing bundles GET/POST/DELETE error.
ErrKeyMissing = errors.New("Key does not exist.")
ErrKeyMissing = errors.New("key does not exist")
)

View File

@ -136,7 +136,7 @@ func (s *TeamsService) Delete(id string) (*http.Response, error) {
var (
// ErrTeamExists bundles PUT create error.
ErrTeamExists = errors.New("Team already exists.")
ErrTeamExists = errors.New("team already exists")
// ErrTeamMissing bundles GET/POST/DELETE error.
ErrTeamMissing = errors.New("Team does not exist.")
ErrTeamMissing = errors.New("team does not exist")
)

View File

@ -136,7 +136,7 @@ func (s *UsersService) Delete(username string) (*http.Response, error) {
var (
// ErrUserExists bundles PUT create error.
ErrUserExists = errors.New("User already exists.")
ErrUserExists = errors.New("user already exists")
// ErrUserMissing bundles GET/POST/DELETE error.
ErrUserMissing = errors.New("User does not exist.")
ErrUserMissing = errors.New("user does not exist")
)

View File

@ -271,3 +271,18 @@ func parseRate(resp *http.Response) RateLimit {
return rl
}
// SetTimeParam sets a url timestamp query param given the parameters name.
func SetTimeParam(key string, t time.Time) func(*url.Values) {
return func(v *url.Values) { v.Set(key, strconv.Itoa(int(t.Unix()))) }
}
// SetBoolParam sets a url boolean query param given the parameters name.
func SetBoolParam(key string, b bool) func(*url.Values) {
return func(v *url.Values) { v.Set(key, strconv.FormatBool(b)) }
}
// SetStringParam sets a url string query param given the parameters name.
func SetStringParam(key, val string) func(*url.Values) {
return func(v *url.Values) { v.Set(key, val) }
}

View File

@ -1,30 +0,0 @@
package rest
import (
"testing"
"time"
)
func TestRateLimit(t *testing.T) {
r := RateLimit{
Limit: 10,
Remaining: 10,
Period: 10,
}
if r.WaitTime() != time.Second {
t.Error("WaitTime is wrong duration ", r.WaitTime())
}
if r.PercentageLeft() != 100 {
t.Error("PercentLeft != 100")
}
r.Remaining = 5
if r.PercentageLeft() != 50 {
t.Error("PercentLeft != 50")
}
if r.WaitTime() != time.Second {
t.Error("WaitTime is wrong duration ", r.WaitTime())
}
if r.WaitTimeRemaining() != (time.Duration(2) * time.Second) {
t.Error("WaitTimeRemaining is wrong duration ", r.WaitTimeRemaining())
}
}

View File

@ -1,117 +0,0 @@
package account
import (
"encoding/json"
"testing"
"github.com/stretchr/testify/assert"
)
func TestUnmarshalUsers(t *testing.T) {
d := []byte(`[
{
"permissions": {},
"teams": [],
"email": "support@nsone.net",
"last_access": 1376325771.0,
"notify": {
"billing": true
},
"name": "API Example",
"username": "apiexample"
},
{
"permissions": {
"dns": {
"view_zones": true,
"manage_zones": true,
"zones_allow_by_default": false,
"zones_deny": [],
"zones_allow": ["example.com"]
},
"data": {
"push_to_datafeeds": false,
"manage_datasources": false,
"manage_datafeeds": false
},
"account": {
"manage_payment_methods": false,
"manage_plan": false,
"manage_teams": false,
"manage_apikeys": false,
"manage_account_settings": false,
"view_activity_log": false,
"view_invoices": false,
"manage_users": false
},
"monitoring": {
"manage_lists": false,
"manage_jobs": false,
"view_jobs": false
}
},
"teams": ["520422919f782d37dffb588a"],
"email": "newuser@example.com",
"last_access": null,
"notify": {
"billing": true
},
"name": "New User",
"username": "newuser"
}
]
`)
ul := []*User{}
if err := json.Unmarshal(d, &ul); err != nil {
t.Error(err)
}
assert.Equal(t, len(ul), 2, "Userlist should have 2 users")
u := ul[0]
assert.Equal(t, u.TeamIDs, []string{}, "User should have empty teams")
assert.Equal(t, u.Email, "support@nsone.net", "User wrong email")
assert.Equal(t, u.LastAccess, 1376325771.0, "User wrong last access")
assert.Equal(t, u.Name, "API Example", "User wrong name")
assert.Equal(t, u.Username, "apiexample", "User wrong username")
assert.Equal(t, u.Notify, NotificationSettings{true}, "User wrong notify")
assert.Equal(t, u.Permissions, PermissionsMap{}, "User should have empty permissions")
u2 := ul[1]
assert.Equal(t, u2.TeamIDs, []string{"520422919f782d37dffb588a"}, "User should have empty teams")
assert.Equal(t, u2.Email, "newuser@example.com", "User wrong email")
assert.Equal(t, u2.LastAccess, 0.0, "User wrong last access")
assert.Equal(t, u2.Name, "New User", "User wrong name")
assert.Equal(t, u2.Username, "newuser", "User wrong username")
assert.Equal(t, u.Notify, NotificationSettings{true}, "User wrong notify")
permMap := PermissionsMap{
DNS: PermissionsDNS{
ViewZones: true,
ManageZones: true,
ZonesAllowByDefault: false,
ZonesDeny: []string{},
ZonesAllow: []string{"example.com"},
},
Data: PermissionsData{
PushToDatafeeds: false,
ManageDatasources: false,
ManageDatafeeds: false,
},
Account: PermissionsAccount{
ManagePaymentMethods: false,
ManagePlan: false,
ManageTeams: false,
ManageApikeys: false,
ManageAccountSettings: false,
ViewActivityLog: false,
ViewInvoices: false,
ManageUsers: false,
},
Monitoring: PermissionsMonitoring{
ManageLists: false,
ManageJobs: false,
ViewJobs: false,
},
}
assert.Equal(t, u2.Permissions, permMap, "User wrong permissions")
}

View File

@ -1,70 +0,0 @@
package data_test
import (
"fmt"
"gopkg.in/ns1/ns1-go.v2/rest/model/data"
)
func ExampleSource() {
// Construct an NSONE API data source.
source := data.NewSource("my api source", "nsone_v1")
fmt.Println(source.ID) // will be empty string
fmt.Println(source.Name)
fmt.Println(source.Type)
// Output:
// my api source
// nsone_v1
}
func ExampleFeed() {
// Construct the london data feed.
feed := data.NewFeed(
"London Feed",
data.Config{"label": "London-UK"})
fmt.Println(feed.ID) // will be empty string
fmt.Println(feed.Name)
fmt.Println(feed.Config)
// Output:
// London Feed
// map[label:London-UK]
}
func ExampleMeta() {
feedID := "feed_id"
meta := data.Meta{}
meta.Priority = 1
meta.Up = data.FeedPtr{FeedID: feedID}
fmt.Println(meta.Connections) // will be nil
fmt.Println(meta.Priority)
fmt.Println(meta.Up)
// Output:
// <nil>
// 1
// {feed_id}
}
func ExampleRegions() {
feedPtr := data.FeedPtr{FeedID: "feed_id"}
regions := data.Regions{}
// Set a regions' 'up' metavalue to false('down').
regions["some_region"] = data.Region{
Meta: data.Meta{Up: false},
}
// Set a regions' 'connections' metavalue to receive from a feed.
regions["other_region"] = data.Region{
Meta: data.Meta{Connections: feedPtr},
}
fmt.Println(regions["some_region"].Meta.Up)
fmt.Println(regions["some_region"].Meta.Priority)
fmt.Println(regions["other_region"].Meta.Connections)
fmt.Println(regions["other_region"].Meta.Priority)
// Output:
// false
// <nil>
// {feed_id}
// <nil>
}

View File

@ -38,7 +38,7 @@ type Meta struct {
// Indicates the "load average".
// Values must be positive, and will be rounded to the nearest tenth.
// float64 or FeedPtr.
LoadAvg interface{} `json:",loadavg,omitempty"`
LoadAvg interface{} `json:"loadavg,omitempty"`
// The Job ID of a Pulsar telemetry gathering job and routing granularities
// to associate with.

View File

@ -1,141 +0,0 @@
package dns_test
import (
"encoding/json"
"fmt"
"gopkg.in/ns1/ns1-go.v2/rest/model/data"
"gopkg.in/ns1/ns1-go.v2/rest/model/dns"
"gopkg.in/ns1/ns1-go.v2/rest/model/filter"
)
func ExampleZone() {
z := dns.NewZone("example.com")
fmt.Println(z)
// Output:
// example.com
}
// Example references https://ns1.com/articles/primary-dns-with-ns1
func ExamplePrimaryZone() {
// Secondary/slave dns server info.
secondary := dns.ZoneSecondaryServer{
IP: "1.2.3.4",
Port: 53,
Notify: true,
}
// Construct the primary/master zone.
domain := "masterzone.example"
masterZone := dns.NewZone(domain)
masterZone.MakePrimary(secondary)
b, _ := json.MarshalIndent(masterZone, "", " ")
fmt.Println(string(b))
// Output:
// {
// "zone": "masterzone.example",
// "primary": {
// "enabled": true,
// "secondaries": [
// {
// "ip": "1.2.3.4",
// "port": 53,
// "notify": true
// }
// ]
// }
// }
}
func ExampleRecord() {
// Construct the A record
record := dns.NewRecord("test.com", "a", "A")
record.TTL = 300
// Construct primary answer(higher priority)
pAns := dns.NewAv4Answer("1.1.1.1")
pAns.Meta.Priority = 1
pAns.Meta.Up = data.FeedPtr{FeedID: "feed1_id"}
// Construct secondary answer(lower priority)
sAns := dns.NewAv4Answer("2.2.2.2")
sAns.Meta.Priority = 2
sAns.Meta.Up = data.FeedPtr{FeedID: "feed2_id"}
// Add both answers to record
record.AddAnswer(pAns)
record.AddAnswer(sAns)
// Construct and add both filters to the record(ORDER MATTERS)
record.AddFilter(filter.NewUp())
record.AddFilter(filter.NewSelFirstN(1))
// Add region 'test' to record(set as down)
record.Regions["test"] = data.Region{Meta: data.Meta{Up: false}}
fmt.Println(record)
fmt.Println(record.TTL)
fmt.Println("Primary answer:")
fmt.Println(record.Answers[0])
fmt.Println(record.Answers[0].Meta.Priority)
fmt.Println(record.Answers[0].Meta.Up)
fmt.Println("Secondary answer:")
fmt.Println(record.Answers[1])
fmt.Println(record.Answers[1].Meta.Priority)
fmt.Println(record.Answers[1].Meta.Up)
fmt.Println("First Filter in Chain:")
fmt.Println(record.Filters[0].Type)
fmt.Println(record.Filters[0].Config)
fmt.Println("Second Filter in Chain:")
fmt.Println(record.Filters[1].Type)
fmt.Println(record.Filters[1].Config)
fmt.Println("Regions:")
fmt.Println(record.Regions["test"].Meta.Up)
// Output:
// a.test.com A
// 300
// Primary answer:
// 1.1.1.1
// 1
// {feed1_id}
// Secondary answer:
// 2.2.2.2
// 2
// {feed2_id}
// First Filter in Chain:
// up
// map[]
// Second Filter in Chain:
// select_first_n
// map[N:1]
// Regions:
// false
}
func ExampleRecordLink() {
// Construct the src record
srcRecord := dns.NewRecord("test.com", "a", "A")
srcRecord.TTL = 300
srcRecord.Meta.Priority = 2
linkedRecord := dns.NewRecord("test.com", "l", "A")
linkedRecord.LinkTo(srcRecord.Domain)
fmt.Println(linkedRecord)
fmt.Println(linkedRecord.Meta)
fmt.Println(linkedRecord.Answers)
// Output:
// l.test.com A
// <nil>
// []
}

View File

@ -19,10 +19,10 @@ type Job struct {
Config Config `json:"config"`
// The current status of the monitor.
Status map[string]Status `json:"status,omitempty"`
Status map[string]*Status `json:"status,omitempty"`
// Rules for determining failure conditions.
Rules []*Rule `json:"rules"`
Rules []*Rule `json:"rules,omitempty"`
// List of regions in which to run the monitor.
// eg, ["dal", "sin", "sjc", "lga", "ams"]
@ -66,7 +66,7 @@ type Job struct {
// If true, notifications are sent for any regional failure (and failback if desired),
// in addition to global state notifications.
NotifyRegional bool `json:"notidy_regional"`
NotifyRegional bool `json:"notify_regional"`
// If true, a notification is sent when a job returns to an "up" state.
NotifyFailback bool `json:"notify_failback"`
@ -99,6 +99,15 @@ type Status struct {
Status string `json:"status"`
}
// StatusLog wraps an NS1 /monitoring/history resource
type StatusLog struct {
Job string `json:"job"`
Region string `json:"region"`
Status string `json:"status"`
Since int `json:"since"`
Until int `json:"until"`
}
// Rule wraps an element of a Job's "rules" attribute
type Rule struct {
Key string `json:"key"`

View File

@ -1,97 +0,0 @@
package monitor
import (
"encoding/json"
"fmt"
"testing"
"github.com/stretchr/testify/assert"
)
func TestUnmarshalJobs(t *testing.T) {
data := []byte(`[
{
"id": "52a27d4397d5f07003fdbe7b",
"config": {
"host": "1.2.3.4"
},
"status": {
"lga": {
"since": 1389407609,
"status": "up"
},
"global": {
"since": 1389407609,
"status": "up"
},
"sjc": {
"since": 1389404014,
"status": "up"
}
},
"rules": [
{
"key": "rtt",
"value": 100,
"comparison": "<"
}
],
"job_type": "ping",
"regions": [
"lga",
"sjc"
],
"active": true,
"frequency": 60,
"policy": "quorum",
"region_scope": "fixed"
}
]`)
mjl := []*Job{}
if err := json.Unmarshal(data, &mjl); err != nil {
t.Error(err)
}
if len(mjl) != 1 {
fmt.Println(mjl)
t.Error("Do not have any jobs")
}
j := mjl[0]
if j.ID != "52a27d4397d5f07003fdbe7b" {
t.Error("Wrong ID")
}
conf := j.Config
if conf["host"] != "1.2.3.4" {
t.Error("Wrong host")
}
status := j.Status["global"]
if status.Since != 1389407609 {
t.Error("since has unexpected value")
}
if status.Status != "up" {
t.Error("Status is not up")
}
r := j.Rules[0]
assert.Equal(t, r.Key, "rtt", "RTT rule key is wrong")
assert.Equal(t, r.Value.(float64), float64(100), "RTT rule value is wrong")
if r.Comparison != "<" {
t.Error("RTT rule comparison is wrong")
}
if j.Type != "ping" {
t.Error("Jobtype is wrong")
}
if j.Regions[0] != "lga" {
t.Error("First region is not lga")
}
if !j.Active {
t.Error("Job is not active")
}
if j.Frequency != 60 {
t.Error("Job frequency != 60")
}
if j.Policy != "quorum" {
t.Error("Job policy is not quorum")
}
if j.RegionScope != "fixed" {
t.Error("Job region scope is not fixed")
}
}

View File

@ -3,6 +3,7 @@ package rest
import (
"fmt"
"net/http"
"net/url"
"gopkg.in/ns1/ns1-go.v2/rest/model/monitor"
)
@ -106,3 +107,28 @@ func (s *JobsService) Delete(id string) (*http.Response, error) {
return resp, nil
}
// History takes an ID and returns status log history for a specific monitoring job.
//
// NS1 API docs: https://ns1.com/api/#history-get
func (s *JobsService) History(id string, opts ...func(*url.Values)) ([]*monitor.StatusLog, *http.Response, error) {
v := url.Values{}
for _, opt := range opts {
opt(&v)
}
path := fmt.Sprintf("%s/%s?%s", "monitoring/history", id, v.Encode())
req, err := s.client.NewRequest("GET", path, nil)
if err != nil {
return nil, nil, err
}
var slgs []*monitor.StatusLog
resp, err := s.client.Do(req, &slgs)
if err != nil {
return nil, resp, err
}
return slgs, resp, nil
}

View File

@ -122,7 +122,7 @@ func (s *NotificationsService) Delete(listID string) (*http.Response, error) {
var (
// ErrListExists bundles PUT create error.
ErrListExists = errors.New("Notify List already exists.")
ErrListExists = errors.New("notify List already exists")
// ErrListMissing bundles GET/POST/DELETE error.
ErrListMissing = errors.New("Notify List does not exist.")
ErrListMissing = errors.New("notify List does not exist")
)

View File

@ -128,7 +128,7 @@ func (s *RecordsService) Delete(zone string, domain string, t string) (*http.Res
var (
// ErrRecordExists bundles PUT create error.
ErrRecordExists = errors.New("Record already exists.")
ErrRecordExists = errors.New("record already exists")
// ErrRecordMissing bundles GET/POST/DELETE error.
ErrRecordMissing = errors.New("Record does not exist.")
ErrRecordMissing = errors.New("record does not exist")
)

View File

@ -138,7 +138,7 @@ func (s *ZonesService) Delete(zone string) (*http.Response, error) {
var (
// ErrZoneExists bundles PUT create error.
ErrZoneExists = errors.New("Zone already exists.")
ErrZoneExists = errors.New("zone already exists")
// ErrZoneMissing bundles GET/POST/DELETE error.
ErrZoneMissing = errors.New("Zone does not exist.")
ErrZoneMissing = errors.New("zone does not exist")
)

48
vendor/vendor.json vendored
View File

@ -3270,10 +3270,52 @@
"revisionTime": "2017-01-17T13:00:17Z"
},
{
"checksumSHA1": "U0s6Vq/AxUhEEsnORw2DnZ4cw1c=",
"checksumSHA1": "IOhjrvLMN5Mw8PeiRF/xAfSxvew=",
"path": "gopkg.in/ns1/ns1-go.v2",
"revision": "d8d10b7f448291ddbdce48d4594fb1b667014c8b",
"revisionTime": "2016-11-05T01:14:08Z"
"revision": "49e3a8a0b594e847e01cdac77810ba49f9564ccf",
"revisionTime": "2017-03-02T13:56:36Z"
},
{
"checksumSHA1": "t20/HSVruhTb/TVwgc9mpw/oMTA=",
"path": "gopkg.in/ns1/ns1-go.v2/rest",
"revision": "49e3a8a0b594e847e01cdac77810ba49f9564ccf",
"revisionTime": "2017-03-02T13:56:36Z"
},
{
"checksumSHA1": "euh1cYwe0t2erigdvOMueyniPH0=",
"path": "gopkg.in/ns1/ns1-go.v2/rest/model",
"revision": "49e3a8a0b594e847e01cdac77810ba49f9564ccf",
"revisionTime": "2017-03-02T13:56:36Z"
},
{
"checksumSHA1": "tdMxXKsUHn3yZpur14ZNLMVyQJM=",
"path": "gopkg.in/ns1/ns1-go.v2/rest/model/account",
"revision": "49e3a8a0b594e847e01cdac77810ba49f9564ccf",
"revisionTime": "2017-03-02T13:56:36Z"
},
{
"checksumSHA1": "gBVND8veklEQV0gxF3lERV6mSZk=",
"path": "gopkg.in/ns1/ns1-go.v2/rest/model/data",
"revision": "49e3a8a0b594e847e01cdac77810ba49f9564ccf",
"revisionTime": "2017-03-02T13:56:36Z"
},
{
"checksumSHA1": "GbL7ThrBZfKs1lhzguxzscIynac=",
"path": "gopkg.in/ns1/ns1-go.v2/rest/model/dns",
"revision": "49e3a8a0b594e847e01cdac77810ba49f9564ccf",
"revisionTime": "2017-03-02T13:56:36Z"
},
{
"checksumSHA1": "CuurmNep8iMdYFodxRxAeewowsQ=",
"path": "gopkg.in/ns1/ns1-go.v2/rest/model/filter",
"revision": "49e3a8a0b594e847e01cdac77810ba49f9564ccf",
"revisionTime": "2017-03-02T13:56:36Z"
},
{
"checksumSHA1": "B0C8F5th11AHl1fo8k0I8+DvrjE=",
"path": "gopkg.in/ns1/ns1-go.v2/rest/model/monitor",
"revision": "49e3a8a0b594e847e01cdac77810ba49f9564ccf",
"revisionTime": "2017-03-02T13:56:36Z"
},
{
"checksumSHA1": "mkLQOQwQwoUc9Kr9+PaVGrKUzI4=",

View File

@ -0,0 +1,45 @@
---
layout: "ns1"
page_title: "NS1: ns1_notifylist"
sidebar_current: "docs-ns1-resource-notifylist"
description: |-
Provides a NS1 Notify List resource.
---
# ns1\_notifylist
Provides a NS1 Notify List resource. This can be used to create, modify, and delete notify lists.
## Example Usage
```
resource "ns1_notifylist" "nl" {
name = "my notify list"
notifications = {
type = "webhook"
config = {
url = "http://www.mywebhook.com"
}
}
notifications = {
type = "email"
config = {
email = "test@test.com"
}
}
}
```
## Argument Reference
The following arguments are supported:
* `name` - (Required) The free-form display name for the notify list.
* `notifications` - (Optional) A list of notifiers. All notifiers in a notification list will receive notifications whenever an event is send to the list (e.g., when a monitoring job fails). Notifiers are documented below.
Notify List Notifiers (`notifications`) support the following:
* `type` - (Required) The type of notifier. Available notifiers are indicated in /notifytypes endpoint.
* `config` - (Required) Configuration details for the given notifier type.

View File

@ -24,11 +24,11 @@ resource "ns1_record" "www" {
ttl = 60
answers = {
answer = ["sub1.${ns1_zone.tld.zone}"]
answer = "sub1.${ns1_zone.tld.zone}"
}
answer = {
answer = ["sub2.${ns1_zone.tld.zone}"]
answers = {
answer = "sub2.${ns1_zone.tld.zone}"
}
filters = {

View File

@ -22,6 +22,9 @@
<li<%= sidebar_current("docs-ns1-resource-monitoringjob") %>>
<a href="/docs/providers/ns1/r/monitoringjob.html">ns1_monitoringjob</a>
</li>
<li<%= sidebar_current("docs-ns1-resource-notifylist") %>>
<a href="/docs/providers/ns1/r/notifylist.html">ns1_notifylist</a>
</li>
<li<%= sidebar_current("docs-ns1-resource-datasource") %>>
<a href="/docs/providers/ns1/r/datasource.html">ns1_datasource</a>
</li>