Ns1 provider (#10782)
* vendor: update gopkg.in/ns1/ns1-go.v2 * provider/ns1: Port the ns1 provider to Terraform core * docs/ns1: Document the ns1 provider * ns1: rename remaining nsone -> ns1 (#10805) * Ns1 provider (#11300) * provider/ns1: Flesh out support for meta structs. Following the structure outlined by @pashap. Using reflection to reduce copy/paste. Putting metas inside single-item lists. This is clunky, but I couldn't figure out how else to have a nested struct. Maybe the Terraform people know a better way? Inside the meta struct, all fields are always written to the state; I can't figure out how to omit fields that aren't used. This is not just verbose, it actually causes issues because you can't have both "up" and "up_feed" set). Also some minor other changes: - Add "terraform" import support to records and zones. - Create helper class StringEnum. * provider/ns1: Make fmt * provider/ns1: Remove stubbed out RecordRead (used for testing metadata change). * provider/ns1: Need to get interface that m contains from Ptr Value with Elem() * provider/ns1: Use empty string to indicate no feed given. * provider/ns1: Remove old record.regions fields. * provider/ns1: Removes redundant testAccCheckRecordState * provider/ns1: Moves account permissions logic to permissions.go * provider/ns1: Adds tests for team resource. * provider/ns1: Move remaining permissions logic to permissions.go * ns1/provider: Adds datasource.config * provider/ns1: Small clean up of datafeed resource tests * provider/ns1: removes testAccCheckZoneState in favor of explicit name check * provider/ns1: More renaming of nsone -> ns1 * provider/ns1: Comment out metadata for the moment. * Ns1 provider (#11347) * Fix the removal of empty containers from a flatmap Removal of empty nested containers from a flatmap would sometimes fail a sanity check when removed in the wrong order. This would only fail sometimes due to map iteration. There was also an off-by-one error in the prefix check which could match the incorrect keys. * provider/ns1: Adds ns1 go client through govendor. * provider/ns1: Removes unused debug line * docs/ns1: Adds docs around apikey/datasource/datafeed/team/user/record. * provider/ns1: Gets go vet green
This commit is contained in:
parent
08dcd58923
commit
987b910828
|
@ -0,0 +1,12 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/hashicorp/terraform/builtin/providers/ns1"
|
||||||
|
"github.com/hashicorp/terraform/plugin"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
plugin.Serve(&plugin.ServeOpts{
|
||||||
|
ProviderFunc: ns1.Provider,
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
package main
|
|
@ -0,0 +1,218 @@
|
||||||
|
package ns1
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/helper/schema"
|
||||||
|
"gopkg.in/ns1/ns1-go.v2/rest/model/data"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TfSchemaBuilder func(*schema.Schema)
|
||||||
|
|
||||||
|
func mtSimple(t schema.ValueType) TfSchemaBuilder {
|
||||||
|
return func(s *schema.Schema) {
|
||||||
|
s.Type = t
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func mtStringEnum(se *StringEnum) TfSchemaBuilder {
|
||||||
|
return func(s *schema.Schema) {
|
||||||
|
s.Type = schema.TypeString
|
||||||
|
s.ValidateFunc = func(v interface{}, k string) ([]string, []error) {
|
||||||
|
_, err := se.Check(v.(string))
|
||||||
|
if err != nil {
|
||||||
|
return nil, []error{err}
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var mtInt TfSchemaBuilder = mtSimple(schema.TypeInt)
|
||||||
|
var mtBool TfSchemaBuilder = mtSimple(schema.TypeBool)
|
||||||
|
var mtString TfSchemaBuilder = mtSimple(schema.TypeString)
|
||||||
|
var mtFloat64 TfSchemaBuilder = mtSimple(schema.TypeFloat)
|
||||||
|
|
||||||
|
func mtList(elementSchemaBuilder TfSchemaBuilder) TfSchemaBuilder {
|
||||||
|
return func(s *schema.Schema) {
|
||||||
|
s.Type = schema.TypeList
|
||||||
|
elementSchema := &schema.Schema{}
|
||||||
|
elementSchemaBuilder(elementSchema)
|
||||||
|
s.Elem = elementSchema
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var mtStringList TfSchemaBuilder = mtList(mtString)
|
||||||
|
|
||||||
|
type MetaFieldSpec struct {
|
||||||
|
NameInDynamic string
|
||||||
|
NameInStruct string
|
||||||
|
SchemaBuilder TfSchemaBuilder
|
||||||
|
}
|
||||||
|
|
||||||
|
type MetaField struct {
|
||||||
|
MetaFieldSpec
|
||||||
|
NameInDynamicForFeed string
|
||||||
|
StructIndex int
|
||||||
|
StructGoType reflect.Type
|
||||||
|
}
|
||||||
|
|
||||||
|
var georegionEnum *StringEnum = NewStringEnum([]string{
|
||||||
|
"US-WEST",
|
||||||
|
"US-EAST",
|
||||||
|
"US-CENTRAL",
|
||||||
|
"EUROPE",
|
||||||
|
"AFRICA",
|
||||||
|
"ASIAPAC",
|
||||||
|
"SOUTH-AMERICA",
|
||||||
|
})
|
||||||
|
|
||||||
|
func makeMetaFields() []MetaField {
|
||||||
|
var specs []MetaFieldSpec = []MetaFieldSpec{
|
||||||
|
{"up", "Up", mtBool},
|
||||||
|
{"connections", "Connections", mtInt},
|
||||||
|
{"requests", "Requests", mtInt},
|
||||||
|
{"loadavg", "LoadAvg", mtFloat64},
|
||||||
|
{"pulsar", "Pulsar", mtInt},
|
||||||
|
{"latitude", "Latitude", mtFloat64},
|
||||||
|
{"longitude", "Longitude", mtFloat64},
|
||||||
|
{"georegion", "Georegion", mtList(mtStringEnum(georegionEnum))},
|
||||||
|
{"country", "Country", mtStringList},
|
||||||
|
{"us_state", "USState", mtStringList},
|
||||||
|
{"ca_province", "CAProvince", mtStringList},
|
||||||
|
{"note", "Note", mtString},
|
||||||
|
{"ip_prefixes", "IPPrefixes", mtStringList},
|
||||||
|
{"asn", "ASN", mtList(mtInt)},
|
||||||
|
{"priority", "Priority", mtInt},
|
||||||
|
{"weight", "Weight", mtFloat64},
|
||||||
|
{"low_watermark", "LowWatermark", mtInt},
|
||||||
|
{"high_watermark", "HighWatermark", mtInt},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Figure out the field indexes (in data.Meta) for all the fields.
|
||||||
|
// This way we can later lookup by index, which should be faster than by name.
|
||||||
|
|
||||||
|
rt := reflect.TypeOf(data.Meta{})
|
||||||
|
fields := make([]MetaField, len(specs))
|
||||||
|
for i, spec := range specs {
|
||||||
|
rf, present := rt.FieldByName(spec.NameInStruct)
|
||||||
|
if !present {
|
||||||
|
panic(fmt.Sprintf("Field %q not present", spec.NameInStruct))
|
||||||
|
}
|
||||||
|
if len(rf.Index) != 1 {
|
||||||
|
panic(fmt.Sprintf("Expecting a single index, got %#v", rf.Index))
|
||||||
|
}
|
||||||
|
index := rf.Index[0]
|
||||||
|
fields[i] = MetaField{
|
||||||
|
MetaFieldSpec: spec,
|
||||||
|
StructIndex: index,
|
||||||
|
NameInDynamicForFeed: spec.NameInDynamic + "_feed",
|
||||||
|
StructGoType: rf.Type,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return fields
|
||||||
|
}
|
||||||
|
|
||||||
|
var metaFields []MetaField = makeMetaFields()
|
||||||
|
|
||||||
|
func makeMetaSchema() *schema.Schema {
|
||||||
|
fields := make(map[string]*schema.Schema)
|
||||||
|
|
||||||
|
for _, f := range metaFields {
|
||||||
|
fieldSchema := &schema.Schema{
|
||||||
|
Optional: true,
|
||||||
|
ForceNew: true,
|
||||||
|
// TODO: Fields that arent in configuration shouldnt show up in resource data
|
||||||
|
// ConflictsWith: []string{f.NameInDynamicForFeed},
|
||||||
|
}
|
||||||
|
f.SchemaBuilder(fieldSchema)
|
||||||
|
|
||||||
|
fields[f.NameInDynamic] = fieldSchema
|
||||||
|
|
||||||
|
// Add an "_feed"-suffixed field for the {"feed":...} value.
|
||||||
|
fields[f.NameInDynamicForFeed] = &schema.Schema{
|
||||||
|
Optional: true,
|
||||||
|
ForceNew: true,
|
||||||
|
// TODO: Fields that arent in configuration shouldnt show up in resource data
|
||||||
|
// ConflictsWith: []string{f.NameInDynamic},
|
||||||
|
Type: schema.TypeString,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
metaSchemaInner := &schema.Resource{
|
||||||
|
Schema: fields,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wrap it in a list because that seems to be the only way to have nested structs.
|
||||||
|
return &schema.Schema{
|
||||||
|
Type: schema.TypeList,
|
||||||
|
Optional: true,
|
||||||
|
MaxItems: 1,
|
||||||
|
Elem: metaSchemaInner,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var metaSchema *schema.Schema = makeMetaSchema()
|
||||||
|
|
||||||
|
func metaStructToDynamic(m *data.Meta) interface{} {
|
||||||
|
d := make(map[string]interface{})
|
||||||
|
mr := reflect.ValueOf(m).Elem()
|
||||||
|
for _, f := range metaFields {
|
||||||
|
fr := mr.Field(f.StructIndex)
|
||||||
|
fv := fr.Interface()
|
||||||
|
|
||||||
|
if fv == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if mapVal, isMap := fv.(map[string]interface{}); isMap {
|
||||||
|
if len(mapVal) == 1 {
|
||||||
|
if feedVal, ok := mapVal["feed"]; ok {
|
||||||
|
if feedStr, ok := feedVal.(string); ok {
|
||||||
|
d[f.NameInDynamicForFeed] = feedStr
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
panic(fmt.Sprintf("expecting feed dict, got %+v", mapVal))
|
||||||
|
}
|
||||||
|
|
||||||
|
d[f.NameInDynamic] = fv
|
||||||
|
}
|
||||||
|
return []interface{}{d}
|
||||||
|
}
|
||||||
|
|
||||||
|
func metaDynamicToStruct(m *data.Meta, raw interface{}) {
|
||||||
|
l := raw.([]interface{})
|
||||||
|
if len(l) > 1 {
|
||||||
|
panic(fmt.Sprintf("list too long %#v", l))
|
||||||
|
}
|
||||||
|
if len(l) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if l[0] == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
d := l[0].(map[string]interface{})
|
||||||
|
|
||||||
|
mr := reflect.ValueOf(m).Elem()
|
||||||
|
for _, f := range metaFields {
|
||||||
|
val, present := d[f.NameInDynamic]
|
||||||
|
if present {
|
||||||
|
fr := mr.Field(f.StructIndex)
|
||||||
|
fr.Set(reflect.ValueOf(val))
|
||||||
|
}
|
||||||
|
|
||||||
|
feed, present := d[f.NameInDynamicForFeed]
|
||||||
|
if present && feed != "" {
|
||||||
|
if feed == nil {
|
||||||
|
panic("unexpected nil")
|
||||||
|
}
|
||||||
|
fr := mr.Field(f.StructIndex)
|
||||||
|
fr.Set(reflect.ValueOf(map[string]interface{}{"feed": feed.(string)}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,184 @@
|
||||||
|
package ns1
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/hashicorp/terraform/helper/schema"
|
||||||
|
"gopkg.in/ns1/ns1-go.v2/rest/model/account"
|
||||||
|
)
|
||||||
|
|
||||||
|
func addPermsSchema(s map[string]*schema.Schema) map[string]*schema.Schema {
|
||||||
|
s["dns_view_zones"] = &schema.Schema{
|
||||||
|
Type: schema.TypeBool,
|
||||||
|
Optional: true,
|
||||||
|
}
|
||||||
|
s["dns_manage_zones"] = &schema.Schema{
|
||||||
|
Type: schema.TypeBool,
|
||||||
|
Optional: true,
|
||||||
|
}
|
||||||
|
s["dns_zones_allow_by_default"] = &schema.Schema{
|
||||||
|
Type: schema.TypeBool,
|
||||||
|
Optional: true,
|
||||||
|
}
|
||||||
|
s["dns_zones_deny"] = &schema.Schema{
|
||||||
|
Type: schema.TypeList,
|
||||||
|
Optional: true,
|
||||||
|
Elem: &schema.Schema{Type: schema.TypeString},
|
||||||
|
}
|
||||||
|
s["dns_zones_allow"] = &schema.Schema{
|
||||||
|
Type: schema.TypeList,
|
||||||
|
Optional: true,
|
||||||
|
Elem: &schema.Schema{Type: schema.TypeString},
|
||||||
|
}
|
||||||
|
s["data_push_to_datafeeds"] = &schema.Schema{
|
||||||
|
Type: schema.TypeBool,
|
||||||
|
Optional: true,
|
||||||
|
}
|
||||||
|
s["data_manage_datasources"] = &schema.Schema{
|
||||||
|
Type: schema.TypeBool,
|
||||||
|
Optional: true,
|
||||||
|
}
|
||||||
|
s["data_manage_datafeeds"] = &schema.Schema{
|
||||||
|
Type: schema.TypeBool,
|
||||||
|
Optional: true,
|
||||||
|
}
|
||||||
|
s["account_manage_users"] = &schema.Schema{
|
||||||
|
Type: schema.TypeBool,
|
||||||
|
Optional: true,
|
||||||
|
}
|
||||||
|
s["account_manage_payment_methods"] = &schema.Schema{
|
||||||
|
Type: schema.TypeBool,
|
||||||
|
Optional: true,
|
||||||
|
}
|
||||||
|
s["account_manage_plan"] = &schema.Schema{
|
||||||
|
Type: schema.TypeBool,
|
||||||
|
Optional: true,
|
||||||
|
}
|
||||||
|
s["account_manage_teams"] = &schema.Schema{
|
||||||
|
Type: schema.TypeBool,
|
||||||
|
Optional: true,
|
||||||
|
}
|
||||||
|
s["account_manage_apikeys"] = &schema.Schema{
|
||||||
|
Type: schema.TypeBool,
|
||||||
|
Optional: true,
|
||||||
|
}
|
||||||
|
s["account_manage_account_settings"] = &schema.Schema{
|
||||||
|
Type: schema.TypeBool,
|
||||||
|
Optional: true,
|
||||||
|
}
|
||||||
|
s["account_view_activity_log"] = &schema.Schema{
|
||||||
|
Type: schema.TypeBool,
|
||||||
|
Optional: true,
|
||||||
|
}
|
||||||
|
s["account_view_invoices"] = &schema.Schema{
|
||||||
|
Type: schema.TypeBool,
|
||||||
|
Optional: true,
|
||||||
|
}
|
||||||
|
s["monitoring_manage_lists"] = &schema.Schema{
|
||||||
|
Type: schema.TypeBool,
|
||||||
|
Optional: true,
|
||||||
|
}
|
||||||
|
s["monitoring_manage_jobs"] = &schema.Schema{
|
||||||
|
Type: schema.TypeBool,
|
||||||
|
Optional: true,
|
||||||
|
}
|
||||||
|
s["monitoring_view_jobs"] = &schema.Schema{
|
||||||
|
Type: schema.TypeBool,
|
||||||
|
Optional: true,
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func permissionsToResourceData(d *schema.ResourceData, permissions account.PermissionsMap) {
|
||||||
|
d.Set("dns_view_zones", permissions.DNS.ViewZones)
|
||||||
|
d.Set("dns_manage_zones", permissions.DNS.ManageZones)
|
||||||
|
d.Set("dns_zones_allow_by_default", permissions.DNS.ZonesAllowByDefault)
|
||||||
|
d.Set("dns_zones_deny", permissions.DNS.ZonesDeny)
|
||||||
|
d.Set("dns_zones_allow", permissions.DNS.ZonesAllow)
|
||||||
|
d.Set("data_push_to_datafeeds", permissions.Data.PushToDatafeeds)
|
||||||
|
d.Set("data_manage_datasources", permissions.Data.ManageDatasources)
|
||||||
|
d.Set("data_manage_datafeeds", permissions.Data.ManageDatafeeds)
|
||||||
|
d.Set("account_manage_users", permissions.Account.ManageUsers)
|
||||||
|
d.Set("account_manage_payment_methods", permissions.Account.ManagePaymentMethods)
|
||||||
|
d.Set("account_manage_plan", permissions.Account.ManagePlan)
|
||||||
|
d.Set("account_manage_teams", permissions.Account.ManageTeams)
|
||||||
|
d.Set("account_manage_apikeys", permissions.Account.ManageApikeys)
|
||||||
|
d.Set("account_manage_account_settings", permissions.Account.ManageAccountSettings)
|
||||||
|
d.Set("account_view_activity_log", permissions.Account.ViewActivityLog)
|
||||||
|
d.Set("account_view_invoices", permissions.Account.ViewInvoices)
|
||||||
|
d.Set("monitoring_manage_lists", permissions.Monitoring.ManageLists)
|
||||||
|
d.Set("monitoring_manage_jobs", permissions.Monitoring.ManageJobs)
|
||||||
|
d.Set("monitoring_view_jobs", permissions.Monitoring.ViewJobs)
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceDataToPermissions(d *schema.ResourceData) account.PermissionsMap {
|
||||||
|
var p account.PermissionsMap
|
||||||
|
if v, ok := d.GetOk("dns_view_zones"); ok {
|
||||||
|
p.DNS.ViewZones = v.(bool)
|
||||||
|
}
|
||||||
|
if v, ok := d.GetOk("dns_manage_zones"); ok {
|
||||||
|
p.DNS.ManageZones = v.(bool)
|
||||||
|
}
|
||||||
|
if v, ok := d.GetOk("dns_zones_allow_by_default"); ok {
|
||||||
|
p.DNS.ZonesAllowByDefault = v.(bool)
|
||||||
|
}
|
||||||
|
if v, ok := d.GetOk("dns_zones_deny"); ok {
|
||||||
|
denyRaw := v.([]interface{})
|
||||||
|
p.DNS.ZonesDeny = make([]string, len(denyRaw))
|
||||||
|
for i, deny := range denyRaw {
|
||||||
|
p.DNS.ZonesDeny[i] = deny.(string)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
p.DNS.ZonesDeny = make([]string, 0)
|
||||||
|
}
|
||||||
|
if v, ok := d.GetOk("dns_zones_allow"); ok {
|
||||||
|
allowRaw := v.([]interface{})
|
||||||
|
p.DNS.ZonesAllow = make([]string, len(allowRaw))
|
||||||
|
for i, allow := range allowRaw {
|
||||||
|
p.DNS.ZonesAllow[i] = allow.(string)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
p.DNS.ZonesAllow = make([]string, 0)
|
||||||
|
}
|
||||||
|
if v, ok := d.GetOk("data_push_to_datafeeds"); ok {
|
||||||
|
p.Data.PushToDatafeeds = v.(bool)
|
||||||
|
}
|
||||||
|
if v, ok := d.GetOk("data_manage_datasources"); ok {
|
||||||
|
p.Data.ManageDatasources = v.(bool)
|
||||||
|
}
|
||||||
|
if v, ok := d.GetOk("data_manage_datafeeds"); ok {
|
||||||
|
p.Data.ManageDatafeeds = v.(bool)
|
||||||
|
}
|
||||||
|
if v, ok := d.GetOk("account_manage_users"); ok {
|
||||||
|
p.Account.ManageUsers = v.(bool)
|
||||||
|
}
|
||||||
|
if v, ok := d.GetOk("account_manage_payment_methods"); ok {
|
||||||
|
p.Account.ManagePaymentMethods = v.(bool)
|
||||||
|
}
|
||||||
|
if v, ok := d.GetOk("account_manage_plan"); ok {
|
||||||
|
p.Account.ManagePlan = v.(bool)
|
||||||
|
}
|
||||||
|
if v, ok := d.GetOk("account_manage_teams"); ok {
|
||||||
|
p.Account.ManageTeams = v.(bool)
|
||||||
|
}
|
||||||
|
if v, ok := d.GetOk("account_manage_apikeys"); ok {
|
||||||
|
p.Account.ManageApikeys = v.(bool)
|
||||||
|
}
|
||||||
|
if v, ok := d.GetOk("account_manage_account_settings"); ok {
|
||||||
|
p.Account.ManageAccountSettings = v.(bool)
|
||||||
|
}
|
||||||
|
if v, ok := d.GetOk("account_view_activity_log"); ok {
|
||||||
|
p.Account.ViewActivityLog = v.(bool)
|
||||||
|
}
|
||||||
|
if v, ok := d.GetOk("account_view_invoices"); ok {
|
||||||
|
p.Account.ViewInvoices = v.(bool)
|
||||||
|
}
|
||||||
|
if v, ok := d.GetOk("monitoring_manage_lists"); ok {
|
||||||
|
p.Monitoring.ManageLists = v.(bool)
|
||||||
|
}
|
||||||
|
if v, ok := d.GetOk("monitoring_manage_jobs"); ok {
|
||||||
|
p.Monitoring.ManageJobs = v.(bool)
|
||||||
|
}
|
||||||
|
if v, ok := d.GetOk("monitoring_view_jobs"); ok {
|
||||||
|
p.Monitoring.ViewJobs = v.(bool)
|
||||||
|
}
|
||||||
|
return p
|
||||||
|
}
|
|
@ -0,0 +1,50 @@
|
||||||
|
package ns1
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/helper/schema"
|
||||||
|
"github.com/hashicorp/terraform/terraform"
|
||||||
|
|
||||||
|
ns1 "gopkg.in/ns1/ns1-go.v2/rest"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Provider returns a terraform.ResourceProvider.
|
||||||
|
func Provider() terraform.ResourceProvider {
|
||||||
|
return &schema.Provider{
|
||||||
|
Schema: map[string]*schema.Schema{
|
||||||
|
"apikey": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
DefaultFunc: schema.EnvDefaultFunc("NS1_APIKEY", nil),
|
||||||
|
Description: descriptions["api_key"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ResourcesMap: map[string]*schema.Resource{
|
||||||
|
"ns1_zone": zoneResource(),
|
||||||
|
"ns1_record": recordResource(),
|
||||||
|
"ns1_datasource": dataSourceResource(),
|
||||||
|
"ns1_datafeed": dataFeedResource(),
|
||||||
|
"ns1_monitoringjob": monitoringJobResource(),
|
||||||
|
"ns1_user": userResource(),
|
||||||
|
"ns1_apikey": apikeyResource(),
|
||||||
|
"ns1_team": teamResource(),
|
||||||
|
},
|
||||||
|
ConfigureFunc: ns1Configure,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ns1Configure(d *schema.ResourceData) (interface{}, error) {
|
||||||
|
httpClient := &http.Client{}
|
||||||
|
n := ns1.NewClient(httpClient, ns1.SetAPIKey(d.Get("apikey").(string)))
|
||||||
|
n.RateLimitStrategySleep()
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var descriptions map[string]string
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
descriptions = map[string]string{
|
||||||
|
"api_key": "The ns1 API key, this is required",
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
package ns1
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/helper/schema"
|
||||||
|
"github.com/hashicorp/terraform/terraform"
|
||||||
|
)
|
||||||
|
|
||||||
|
var testAccProviders map[string]terraform.ResourceProvider
|
||||||
|
var testAccProvider *schema.Provider
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
testAccProvider = Provider().(*schema.Provider)
|
||||||
|
testAccProviders = map[string]terraform.ResourceProvider{
|
||||||
|
"ns1": testAccProvider,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestProvider(t *testing.T) {
|
||||||
|
if err := Provider().(*schema.Provider).InternalValidate(); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestProvider_impl(t *testing.T) {
|
||||||
|
var _ terraform.ResourceProvider = Provider()
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccPreCheck(t *testing.T) {
|
||||||
|
if v := os.Getenv("NS1_APIKEY"); v == "" {
|
||||||
|
t.Fatal("NS1_APIKEY must be set for acceptance tests")
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,109 @@
|
||||||
|
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/account"
|
||||||
|
)
|
||||||
|
|
||||||
|
func apikeyResource() *schema.Resource {
|
||||||
|
s := map[string]*schema.Schema{
|
||||||
|
"id": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Computed: true,
|
||||||
|
},
|
||||||
|
"name": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
"key": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Computed: true,
|
||||||
|
},
|
||||||
|
"teams": &schema.Schema{
|
||||||
|
Type: schema.TypeList,
|
||||||
|
Optional: true,
|
||||||
|
Elem: &schema.Schema{Type: schema.TypeString},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
s = addPermsSchema(s)
|
||||||
|
return &schema.Resource{
|
||||||
|
Schema: s,
|
||||||
|
Create: ApikeyCreate,
|
||||||
|
Read: ApikeyRead,
|
||||||
|
Update: ApikeyUpdate,
|
||||||
|
Delete: ApikeyDelete,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func apikeyToResourceData(d *schema.ResourceData, k *account.APIKey) error {
|
||||||
|
d.SetId(k.ID)
|
||||||
|
d.Set("name", k.Name)
|
||||||
|
d.Set("key", k.Key)
|
||||||
|
d.Set("teams", k.TeamIDs)
|
||||||
|
permissionsToResourceData(d, k.Permissions)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceDataToApikey(k *account.APIKey, d *schema.ResourceData) error {
|
||||||
|
k.ID = d.Id()
|
||||||
|
k.Name = d.Get("name").(string)
|
||||||
|
if v, ok := d.GetOk("teams"); ok {
|
||||||
|
teamsRaw := v.([]interface{})
|
||||||
|
k.TeamIDs = make([]string, len(teamsRaw))
|
||||||
|
for i, team := range teamsRaw {
|
||||||
|
k.TeamIDs[i] = team.(string)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
k.TeamIDs = make([]string, 0)
|
||||||
|
}
|
||||||
|
k.Permissions = resourceDataToPermissions(d)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ApikeyCreate creates ns1 API key
|
||||||
|
func ApikeyCreate(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
client := meta.(*ns1.Client)
|
||||||
|
k := account.APIKey{}
|
||||||
|
if err := resourceDataToApikey(&k, d); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, err := client.APIKeys.Create(&k); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return apikeyToResourceData(d, &k)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ApikeyRead reads API key from ns1
|
||||||
|
func ApikeyRead(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
client := meta.(*ns1.Client)
|
||||||
|
k, _, err := client.APIKeys.Get(d.Id())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return apikeyToResourceData(d, k)
|
||||||
|
}
|
||||||
|
|
||||||
|
//ApikeyDelete deletes the given ns1 api key
|
||||||
|
func ApikeyDelete(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
client := meta.(*ns1.Client)
|
||||||
|
_, err := client.APIKeys.Delete(d.Id())
|
||||||
|
d.SetId("")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
//ApikeyUpdate updates the given api key in ns1
|
||||||
|
func ApikeyUpdate(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
client := meta.(*ns1.Client)
|
||||||
|
k := account.APIKey{
|
||||||
|
ID: d.Id(),
|
||||||
|
}
|
||||||
|
if err := resourceDataToApikey(&k, d); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, err := client.APIKeys.Update(&k); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return apikeyToResourceData(d, &k)
|
||||||
|
}
|
|
@ -0,0 +1,92 @@
|
||||||
|
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/data"
|
||||||
|
)
|
||||||
|
|
||||||
|
func dataFeedResource() *schema.Resource {
|
||||||
|
return &schema.Resource{
|
||||||
|
Schema: map[string]*schema.Schema{
|
||||||
|
"id": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Computed: true,
|
||||||
|
},
|
||||||
|
"source_id": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
ForceNew: true,
|
||||||
|
},
|
||||||
|
"name": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
"config": &schema.Schema{
|
||||||
|
Type: schema.TypeMap,
|
||||||
|
Optional: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Create: DataFeedCreate,
|
||||||
|
Read: DataFeedRead,
|
||||||
|
Update: DataFeedUpdate,
|
||||||
|
Delete: DataFeedDelete,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func dataFeedToResourceData(d *schema.ResourceData, f *data.Feed) {
|
||||||
|
d.SetId(f.ID)
|
||||||
|
d.Set("name", f.Name)
|
||||||
|
d.Set("config", f.Config)
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceDataToDataFeed(d *schema.ResourceData) *data.Feed {
|
||||||
|
return &data.Feed{
|
||||||
|
Name: d.Get("name").(string),
|
||||||
|
SourceID: d.Get("source_id").(string),
|
||||||
|
Config: d.Get("config").(map[string]interface{}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DataFeedCreate creates an ns1 datafeed
|
||||||
|
func DataFeedCreate(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
client := meta.(*ns1.Client)
|
||||||
|
f := resourceDataToDataFeed(d)
|
||||||
|
if _, err := client.DataFeeds.Create(d.Get("source_id").(string), f); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
dataFeedToResourceData(d, f)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DataFeedRead reads the datafeed for the given ID from ns1
|
||||||
|
func DataFeedRead(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
client := meta.(*ns1.Client)
|
||||||
|
f, _, err := client.DataFeeds.Get(d.Get("source_id").(string), d.Id())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
dataFeedToResourceData(d, f)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DataFeedDelete delets the given datafeed from ns1
|
||||||
|
func DataFeedDelete(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
client := meta.(*ns1.Client)
|
||||||
|
_, err := client.DataFeeds.Delete(d.Get("source_id").(string), d.Id())
|
||||||
|
d.SetId("")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// DataFeedUpdate updates the given datafeed with modified parameters
|
||||||
|
func DataFeedUpdate(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
client := meta.(*ns1.Client)
|
||||||
|
f := resourceDataToDataFeed(d)
|
||||||
|
f.ID = d.Id()
|
||||||
|
if _, err := client.DataFeeds.Update(d.Get("source_id").(string), f); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
dataFeedToResourceData(d, f)
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,170 @@
|
||||||
|
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/data"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAccDataFeed_basic(t *testing.T) {
|
||||||
|
var dataFeed data.Feed
|
||||||
|
resource.Test(t, resource.TestCase{
|
||||||
|
PreCheck: func() { testAccPreCheck(t) },
|
||||||
|
Providers: testAccProviders,
|
||||||
|
CheckDestroy: testAccCheckDataFeedDestroy,
|
||||||
|
Steps: []resource.TestStep{
|
||||||
|
resource.TestStep{
|
||||||
|
Config: testAccDataFeedBasic,
|
||||||
|
Check: resource.ComposeTestCheckFunc(
|
||||||
|
testAccCheckDataFeedExists("ns1_datafeed.foobar", "ns1_datasource.api", &dataFeed),
|
||||||
|
testAccCheckDataFeedName(&dataFeed, "terraform test"),
|
||||||
|
testAccCheckDataFeedConfig(&dataFeed, "label", "exampledc2"),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAccDataFeed_updated(t *testing.T) {
|
||||||
|
var dataFeed data.Feed
|
||||||
|
resource.Test(t, resource.TestCase{
|
||||||
|
PreCheck: func() { testAccPreCheck(t) },
|
||||||
|
Providers: testAccProviders,
|
||||||
|
CheckDestroy: testAccCheckDataFeedDestroy,
|
||||||
|
Steps: []resource.TestStep{
|
||||||
|
resource.TestStep{
|
||||||
|
Config: testAccDataFeedBasic,
|
||||||
|
Check: resource.ComposeTestCheckFunc(
|
||||||
|
testAccCheckDataFeedExists("ns1_datafeed.foobar", "ns1_datasource.api", &dataFeed),
|
||||||
|
testAccCheckDataFeedName(&dataFeed, "terraform test"),
|
||||||
|
testAccCheckDataFeedConfig(&dataFeed, "label", "exampledc2"),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
resource.TestStep{
|
||||||
|
Config: testAccDataFeedUpdated,
|
||||||
|
Check: resource.ComposeTestCheckFunc(
|
||||||
|
testAccCheckDataFeedExists("ns1_datafeed.foobar", "ns1_datasource.api", &dataFeed),
|
||||||
|
testAccCheckDataFeedName(&dataFeed, "terraform test"),
|
||||||
|
testAccCheckDataFeedConfig(&dataFeed, "label", "exampledc3"),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccCheckDataFeedExists(n string, dsrc string, dataFeed *data.Feed) resource.TestCheckFunc {
|
||||||
|
return func(s *terraform.State) error {
|
||||||
|
rs, ok := s.RootModule().Resources[n]
|
||||||
|
ds, ok := s.RootModule().Resources[dsrc]
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("Not found: %s", n)
|
||||||
|
}
|
||||||
|
|
||||||
|
if rs.Primary.ID == "" {
|
||||||
|
return fmt.Errorf("NoID is set")
|
||||||
|
}
|
||||||
|
|
||||||
|
if ds.Primary.ID == "" {
|
||||||
|
return fmt.Errorf("NoID is set for the datasource")
|
||||||
|
}
|
||||||
|
|
||||||
|
client := testAccProvider.Meta().(*ns1.Client)
|
||||||
|
|
||||||
|
foundFeed, _, err := client.DataFeeds.Get(ds.Primary.Attributes["id"], rs.Primary.Attributes["id"])
|
||||||
|
|
||||||
|
p := rs.Primary
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if foundFeed.Name != p.Attributes["name"] {
|
||||||
|
return fmt.Errorf("DataFeed not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
*dataFeed = *foundFeed
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccCheckDataFeedDestroy(s *terraform.State) error {
|
||||||
|
client := testAccProvider.Meta().(*ns1.Client)
|
||||||
|
|
||||||
|
var dataFeedID string
|
||||||
|
var dataSourceID string
|
||||||
|
|
||||||
|
for _, rs := range s.RootModule().Resources {
|
||||||
|
|
||||||
|
if rs.Type == "ns1_datasource" {
|
||||||
|
dataSourceID = rs.Primary.Attributes["id"]
|
||||||
|
}
|
||||||
|
|
||||||
|
if rs.Type == "ns1_datafeed" {
|
||||||
|
dataFeedID = rs.Primary.Attributes["id"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
df, _, _ := client.DataFeeds.Get(dataSourceID, dataFeedID)
|
||||||
|
|
||||||
|
if df != nil {
|
||||||
|
return fmt.Errorf("DataFeed still exists: %#v", df)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccCheckDataFeedName(dataFeed *data.Feed, expected string) resource.TestCheckFunc {
|
||||||
|
return func(s *terraform.State) error {
|
||||||
|
if dataFeed.Name != expected {
|
||||||
|
return fmt.Errorf("Name: got: %#v want: %#v", dataFeed.Name, expected)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccCheckDataFeedConfig(dataFeed *data.Feed, key, expected string) resource.TestCheckFunc {
|
||||||
|
return func(s *terraform.State) error {
|
||||||
|
|
||||||
|
if dataFeed.Config[key] != expected {
|
||||||
|
return fmt.Errorf("Config[%s]: got: %#v, want: %s", key, dataFeed.Config[key], expected)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const testAccDataFeedBasic = `
|
||||||
|
resource "ns1_datasource" "api" {
|
||||||
|
name = "terraform test"
|
||||||
|
sourcetype = "nsone_v1"
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "ns1_datafeed" "foobar" {
|
||||||
|
name = "terraform test"
|
||||||
|
source_id = "${ns1_datasource.api.id}"
|
||||||
|
config {
|
||||||
|
label = "exampledc2"
|
||||||
|
}
|
||||||
|
}`
|
||||||
|
|
||||||
|
const testAccDataFeedUpdated = `
|
||||||
|
resource "ns1_datasource" "api" {
|
||||||
|
name = "terraform test"
|
||||||
|
sourcetype = "nsone_v1"
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "ns1_datafeed" "foobar" {
|
||||||
|
name = "terraform test"
|
||||||
|
source_id = "${ns1_datasource.api.id}"
|
||||||
|
config {
|
||||||
|
label = "exampledc3"
|
||||||
|
}
|
||||||
|
}`
|
|
@ -0,0 +1,86 @@
|
||||||
|
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/data"
|
||||||
|
)
|
||||||
|
|
||||||
|
func dataSourceResource() *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,
|
||||||
|
},
|
||||||
|
"sourcetype": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
ForceNew: true,
|
||||||
|
},
|
||||||
|
"config": &schema.Schema{
|
||||||
|
Type: schema.TypeMap,
|
||||||
|
Optional: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Create: DataSourceCreate,
|
||||||
|
Read: DataSourceRead,
|
||||||
|
Update: DataSourceUpdate,
|
||||||
|
Delete: DataSourceDelete,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func dataSourceToResourceData(d *schema.ResourceData, s *data.Source) {
|
||||||
|
d.SetId(s.ID)
|
||||||
|
d.Set("name", s.Name)
|
||||||
|
d.Set("sourcetype", s.Type)
|
||||||
|
d.Set("config", s.Config)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DataSourceCreate creates an ns1 datasource
|
||||||
|
func DataSourceCreate(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
client := meta.(*ns1.Client)
|
||||||
|
s := data.NewSource(d.Get("name").(string), d.Get("sourcetype").(string))
|
||||||
|
s.Config = d.Get("config").(map[string]interface{})
|
||||||
|
if _, err := client.DataSources.Create(s); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
dataSourceToResourceData(d, s)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DataSourceRead fetches info for the given datasource from ns1
|
||||||
|
func DataSourceRead(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
client := meta.(*ns1.Client)
|
||||||
|
s, _, err := client.DataSources.Get(d.Id())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
dataSourceToResourceData(d, s)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DataSourceDelete deteltes the given datasource from ns1
|
||||||
|
func DataSourceDelete(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
client := meta.(*ns1.Client)
|
||||||
|
_, err := client.DataSources.Delete(d.Id())
|
||||||
|
d.SetId("")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// DataSourceUpdate updates the datasource with given parameters
|
||||||
|
func DataSourceUpdate(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
client := meta.(*ns1.Client)
|
||||||
|
s := data.NewSource(d.Get("name").(string), d.Get("sourcetype").(string))
|
||||||
|
s.ID = d.Id()
|
||||||
|
if _, err := client.DataSources.Update(s); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
dataSourceToResourceData(d, s)
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,140 @@
|
||||||
|
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/data"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAccDataSource_basic(t *testing.T) {
|
||||||
|
var dataSource data.Source
|
||||||
|
resource.Test(t, resource.TestCase{
|
||||||
|
PreCheck: func() { testAccPreCheck(t) },
|
||||||
|
Providers: testAccProviders,
|
||||||
|
CheckDestroy: testAccCheckDataSourceDestroy,
|
||||||
|
Steps: []resource.TestStep{
|
||||||
|
resource.TestStep{
|
||||||
|
Config: testAccDataSourceBasic,
|
||||||
|
Check: resource.ComposeTestCheckFunc(
|
||||||
|
testAccCheckDataSourceExists("ns1_datasource.foobar", &dataSource),
|
||||||
|
testAccCheckDataSourceName(&dataSource, "terraform test"),
|
||||||
|
testAccCheckDataSourceType(&dataSource, "nsone_v1"),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAccDataSource_updated(t *testing.T) {
|
||||||
|
var dataSource data.Source
|
||||||
|
resource.Test(t, resource.TestCase{
|
||||||
|
PreCheck: func() { testAccPreCheck(t) },
|
||||||
|
Providers: testAccProviders,
|
||||||
|
CheckDestroy: testAccCheckDataSourceDestroy,
|
||||||
|
Steps: []resource.TestStep{
|
||||||
|
resource.TestStep{
|
||||||
|
Config: testAccDataSourceBasic,
|
||||||
|
Check: resource.ComposeTestCheckFunc(
|
||||||
|
testAccCheckDataSourceExists("ns1_datasource.foobar", &dataSource),
|
||||||
|
testAccCheckDataSourceName(&dataSource, "terraform test"),
|
||||||
|
testAccCheckDataSourceType(&dataSource, "nsone_v1"),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
resource.TestStep{
|
||||||
|
Config: testAccDataSourceUpdated,
|
||||||
|
Check: resource.ComposeTestCheckFunc(
|
||||||
|
testAccCheckDataSourceExists("ns1_datasource.foobar", &dataSource),
|
||||||
|
testAccCheckDataSourceName(&dataSource, "terraform test"),
|
||||||
|
testAccCheckDataSourceType(&dataSource, "nsone_monitoring"),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccCheckDataSourceExists(n string, dataSource *data.Source) resource.TestCheckFunc {
|
||||||
|
return func(s *terraform.State) error {
|
||||||
|
rs, ok := s.RootModule().Resources[n]
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("Not found: %s", n)
|
||||||
|
}
|
||||||
|
|
||||||
|
if rs.Primary.ID == "" {
|
||||||
|
return fmt.Errorf("NoID is set")
|
||||||
|
}
|
||||||
|
|
||||||
|
client := testAccProvider.Meta().(*ns1.Client)
|
||||||
|
|
||||||
|
foundSource, _, err := client.DataSources.Get(rs.Primary.Attributes["id"])
|
||||||
|
|
||||||
|
p := rs.Primary
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if foundSource.Name != p.Attributes["name"] {
|
||||||
|
return fmt.Errorf("Datasource not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
*dataSource = *foundSource
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccCheckDataSourceDestroy(s *terraform.State) error {
|
||||||
|
client := testAccProvider.Meta().(*ns1.Client)
|
||||||
|
|
||||||
|
for _, rs := range s.RootModule().Resources {
|
||||||
|
if rs.Type != "ns1_datasource" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
_, _, err := client.DataSources.Get(rs.Primary.Attributes["id"])
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
return fmt.Errorf("Datasource still exists")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccCheckDataSourceName(dataSource *data.Source, expected string) resource.TestCheckFunc {
|
||||||
|
return func(s *terraform.State) error {
|
||||||
|
if dataSource.Name != expected {
|
||||||
|
return fmt.Errorf("Name: got: %#v want: %#v", dataSource.Name, expected)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccCheckDataSourceType(dataSource *data.Source, expected string) resource.TestCheckFunc {
|
||||||
|
return func(s *terraform.State) error {
|
||||||
|
if dataSource.Type != expected {
|
||||||
|
return fmt.Errorf("Type: got: %#v want: %#v", dataSource.Type, expected)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const testAccDataSourceBasic = `
|
||||||
|
resource "ns1_datasource" "foobar" {
|
||||||
|
name = "terraform test"
|
||||||
|
sourcetype = "nsone_v1"
|
||||||
|
}`
|
||||||
|
|
||||||
|
const testAccDataSourceUpdated = `
|
||||||
|
resource "ns1_datasource" "foobar" {
|
||||||
|
name = "terraform test"
|
||||||
|
sourcetype = "nsone_monitoring"
|
||||||
|
}`
|
|
@ -0,0 +1,297 @@
|
||||||
|
package ns1
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/helper/schema"
|
||||||
|
|
||||||
|
ns1 "gopkg.in/ns1/ns1-go.v2/rest"
|
||||||
|
"gopkg.in/ns1/ns1-go.v2/rest/model/monitor"
|
||||||
|
)
|
||||||
|
|
||||||
|
func monitoringJobResource() *schema.Resource {
|
||||||
|
return &schema.Resource{
|
||||||
|
Schema: map[string]*schema.Schema{
|
||||||
|
// Required
|
||||||
|
"name": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
"job_type": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
ForceNew: true,
|
||||||
|
},
|
||||||
|
"regions": &schema.Schema{
|
||||||
|
Type: schema.TypeList,
|
||||||
|
Required: true,
|
||||||
|
Elem: &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"frequency": &schema.Schema{
|
||||||
|
Type: schema.TypeInt,
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
"config": &schema.Schema{
|
||||||
|
Type: schema.TypeMap,
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
// Optional
|
||||||
|
"active": &schema.Schema{
|
||||||
|
Type: schema.TypeBool,
|
||||||
|
Optional: true,
|
||||||
|
Default: true,
|
||||||
|
},
|
||||||
|
"rapid_recheck": &schema.Schema{
|
||||||
|
Type: schema.TypeBool,
|
||||||
|
Optional: true,
|
||||||
|
Default: false,
|
||||||
|
},
|
||||||
|
"policy": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
Default: "quorum",
|
||||||
|
ValidateFunc: func(v interface{}, k string) (ws []string, es []error) {
|
||||||
|
value := v.(string)
|
||||||
|
if !regexp.MustCompile(`^(all|one|quorum)$`).MatchString(value) {
|
||||||
|
es = append(es, fmt.Errorf(
|
||||||
|
"only all, one, quorum allowed in %q", k))
|
||||||
|
}
|
||||||
|
return
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"notes": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
},
|
||||||
|
"notify_delay": &schema.Schema{
|
||||||
|
Type: schema.TypeInt,
|
||||||
|
Optional: true,
|
||||||
|
},
|
||||||
|
"notify_repeat": &schema.Schema{
|
||||||
|
Type: schema.TypeInt,
|
||||||
|
Optional: true,
|
||||||
|
},
|
||||||
|
"notify_failback": &schema.Schema{
|
||||||
|
Type: schema.TypeBool,
|
||||||
|
Optional: true,
|
||||||
|
},
|
||||||
|
"notify_regional": &schema.Schema{
|
||||||
|
Type: schema.TypeBool,
|
||||||
|
Optional: true,
|
||||||
|
},
|
||||||
|
"notify_list": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
},
|
||||||
|
"rules": &schema.Schema{
|
||||||
|
Type: schema.TypeList,
|
||||||
|
Optional: true,
|
||||||
|
Elem: &schema.Resource{
|
||||||
|
Schema: map[string]*schema.Schema{
|
||||||
|
"value": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
"comparison": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
"key": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// Computed
|
||||||
|
"id": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Computed: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Create: MonitoringJobCreate,
|
||||||
|
Read: MonitoringJobRead,
|
||||||
|
Update: MonitoringJobUpdate,
|
||||||
|
Delete: MonitoringJobDelete,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func monitoringJobToResourceData(d *schema.ResourceData, r *monitor.Job) error {
|
||||||
|
d.SetId(r.ID)
|
||||||
|
d.Set("name", r.Name)
|
||||||
|
d.Set("job_type", r.Type)
|
||||||
|
d.Set("active", r.Active)
|
||||||
|
d.Set("regions", r.Regions)
|
||||||
|
d.Set("frequency", r.Frequency)
|
||||||
|
d.Set("rapid_recheck", r.RapidRecheck)
|
||||||
|
config := make(map[string]string)
|
||||||
|
for k, v := range r.Config {
|
||||||
|
if k == "ssl" {
|
||||||
|
if v.(bool) {
|
||||||
|
config[k] = "1"
|
||||||
|
} else {
|
||||||
|
config[k] = "0"
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
switch t := v.(type) {
|
||||||
|
case string:
|
||||||
|
config[k] = t
|
||||||
|
case float64:
|
||||||
|
config[k] = strconv.FormatFloat(t, 'f', -1, 64)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err := d.Set("config", config)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Errorf("[DEBUG] Error setting Config error: %#v %#v", r.Config, err))
|
||||||
|
}
|
||||||
|
d.Set("policy", r.Policy)
|
||||||
|
d.Set("notes", r.Notes)
|
||||||
|
d.Set("frequency", r.Frequency)
|
||||||
|
d.Set("notify_delay", r.NotifyDelay)
|
||||||
|
d.Set("notify_repeat", r.NotifyRepeat)
|
||||||
|
d.Set("notify_regional", r.NotifyRegional)
|
||||||
|
d.Set("notify_failback", r.NotifyFailback)
|
||||||
|
d.Set("notify_list", r.NotifyListID)
|
||||||
|
if len(r.Rules) > 0 {
|
||||||
|
rules := make([]map[string]interface{}, len(r.Rules))
|
||||||
|
for i, r := range r.Rules {
|
||||||
|
m := make(map[string]interface{})
|
||||||
|
m["value"] = r.Value
|
||||||
|
m["comparison"] = r.Comparison
|
||||||
|
m["key"] = r.Key
|
||||||
|
rules[i] = m
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceDataToMonitoringJob(r *monitor.Job, d *schema.ResourceData) error {
|
||||||
|
r.ID = d.Id()
|
||||||
|
r.Name = d.Get("name").(string)
|
||||||
|
r.Type = d.Get("job_type").(string)
|
||||||
|
r.Active = d.Get("active").(bool)
|
||||||
|
rawRegions := d.Get("regions").([]interface{})
|
||||||
|
r.Regions = make([]string, len(rawRegions))
|
||||||
|
for i, v := range rawRegions {
|
||||||
|
r.Regions[i] = v.(string)
|
||||||
|
}
|
||||||
|
r.Frequency = d.Get("frequency").(int)
|
||||||
|
r.RapidRecheck = d.Get("rapid_recheck").(bool)
|
||||||
|
var rawRules []interface{}
|
||||||
|
if rawRules := d.Get("rules"); rawRules != nil {
|
||||||
|
r.Rules = make([]*monitor.Rule, len(rawRules.([]interface{})))
|
||||||
|
for i, v := range rawRules.([]interface{}) {
|
||||||
|
rule := v.(map[string]interface{})
|
||||||
|
r.Rules[i] = &monitor.Rule{
|
||||||
|
Value: rule["value"].(string),
|
||||||
|
Comparison: rule["comparison"].(string),
|
||||||
|
Key: rule["key"].(string),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
r.Rules = make([]*monitor.Rule, 0)
|
||||||
|
}
|
||||||
|
for i, v := range rawRules {
|
||||||
|
rule := v.(map[string]interface{})
|
||||||
|
r.Rules[i] = &monitor.Rule{
|
||||||
|
Comparison: rule["comparison"].(string),
|
||||||
|
Key: rule["key"].(string),
|
||||||
|
}
|
||||||
|
value := rule["value"].(string)
|
||||||
|
if i, err := strconv.Atoi(value); err == nil {
|
||||||
|
r.Rules[i].Value = i
|
||||||
|
} else {
|
||||||
|
r.Rules[i].Value = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
config := make(map[string]interface{})
|
||||||
|
if rawConfig := d.Get("config"); rawConfig != nil {
|
||||||
|
for k, v := range rawConfig.(map[string]interface{}) {
|
||||||
|
if k == "ssl" {
|
||||||
|
if v.(string) == "1" {
|
||||||
|
config[k] = true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if i, err := strconv.Atoi(v.(string)); err == nil {
|
||||||
|
config[k] = i
|
||||||
|
} else {
|
||||||
|
config[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
r.Config = config
|
||||||
|
r.RegionScope = "fixed"
|
||||||
|
r.Policy = d.Get("policy").(string)
|
||||||
|
if v, ok := d.GetOk("notes"); ok {
|
||||||
|
r.Notes = v.(string)
|
||||||
|
}
|
||||||
|
r.Frequency = d.Get("frequency").(int)
|
||||||
|
if v, ok := d.GetOk("notify_delay"); ok {
|
||||||
|
r.NotifyDelay = v.(int)
|
||||||
|
}
|
||||||
|
if v, ok := d.GetOk("notify_repeat"); ok {
|
||||||
|
r.NotifyRepeat = v.(int)
|
||||||
|
}
|
||||||
|
if v, ok := d.GetOk("notify_regional"); ok {
|
||||||
|
r.NotifyRegional = v.(bool)
|
||||||
|
}
|
||||||
|
if v, ok := d.GetOk("notify_failback"); ok {
|
||||||
|
r.NotifyFailback = v.(bool)
|
||||||
|
}
|
||||||
|
if v, ok := d.GetOk("notify_list"); ok {
|
||||||
|
r.NotifyListID = v.(string)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MonitoringJobCreate Creates monitoring job in ns1
|
||||||
|
func MonitoringJobCreate(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
client := meta.(*ns1.Client)
|
||||||
|
j := monitor.Job{}
|
||||||
|
if err := resourceDataToMonitoringJob(&j, d); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, err := client.Jobs.Create(&j); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return monitoringJobToResourceData(d, &j)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MonitoringJobRead reads the given monitoring job from ns1
|
||||||
|
func MonitoringJobRead(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
client := meta.(*ns1.Client)
|
||||||
|
j, _, err := client.Jobs.Get(d.Id())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return monitoringJobToResourceData(d, j)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MonitoringJobDelete deteltes the given monitoring job from ns1
|
||||||
|
func MonitoringJobDelete(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
client := meta.(*ns1.Client)
|
||||||
|
_, err := client.Jobs.Delete(d.Id())
|
||||||
|
d.SetId("")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// MonitoringJobUpdate updates the given monitoring job
|
||||||
|
func MonitoringJobUpdate(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
client := meta.(*ns1.Client)
|
||||||
|
j := monitor.Job{
|
||||||
|
ID: d.Id(),
|
||||||
|
}
|
||||||
|
if err := resourceDataToMonitoringJob(&j, d); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, err := client.Jobs.Update(&j); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return monitoringJobToResourceData(d, &j)
|
||||||
|
}
|
|
@ -0,0 +1,278 @@
|
||||||
|
package ns1
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"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 TestAccMonitoringJob_basic(t *testing.T) {
|
||||||
|
var mj monitor.Job
|
||||||
|
resource.Test(t, resource.TestCase{
|
||||||
|
PreCheck: func() { testAccPreCheck(t) },
|
||||||
|
Providers: testAccProviders,
|
||||||
|
CheckDestroy: testAccCheckMonitoringJobDestroy,
|
||||||
|
Steps: []resource.TestStep{
|
||||||
|
resource.TestStep{
|
||||||
|
Config: testAccMonitoringJobBasic,
|
||||||
|
Check: resource.ComposeTestCheckFunc(
|
||||||
|
testAccCheckMonitoringJobExists("ns1_monitoringjob.it", &mj),
|
||||||
|
testAccCheckMonitoringJobName(&mj, "terraform test"),
|
||||||
|
testAccCheckMonitoringJobActive(&mj, true),
|
||||||
|
testAccCheckMonitoringJobRegions(&mj, []string{"lga"}),
|
||||||
|
testAccCheckMonitoringJobType(&mj, "tcp"),
|
||||||
|
testAccCheckMonitoringJobFrequency(&mj, 60),
|
||||||
|
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"),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAccMonitoringJob_updated(t *testing.T) {
|
||||||
|
var mj monitor.Job
|
||||||
|
resource.Test(t, resource.TestCase{
|
||||||
|
PreCheck: func() { testAccPreCheck(t) },
|
||||||
|
Providers: testAccProviders,
|
||||||
|
CheckDestroy: testAccCheckMonitoringJobDestroy,
|
||||||
|
Steps: []resource.TestStep{
|
||||||
|
resource.TestStep{
|
||||||
|
Config: testAccMonitoringJobBasic,
|
||||||
|
Check: resource.ComposeTestCheckFunc(
|
||||||
|
testAccCheckMonitoringJobExists("ns1_monitoringjob.it", &mj),
|
||||||
|
testAccCheckMonitoringJobName(&mj, "terraform test"),
|
||||||
|
testAccCheckMonitoringJobActive(&mj, true),
|
||||||
|
testAccCheckMonitoringJobRegions(&mj, []string{"lga"}),
|
||||||
|
testAccCheckMonitoringJobType(&mj, "tcp"),
|
||||||
|
testAccCheckMonitoringJobFrequency(&mj, 60),
|
||||||
|
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"),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
resource.TestStep{
|
||||||
|
Config: testAccMonitoringJobUpdated,
|
||||||
|
Check: resource.ComposeTestCheckFunc(
|
||||||
|
testAccCheckMonitoringJobExists("ns1_monitoringjob.it", &mj),
|
||||||
|
testAccCheckMonitoringJobName(&mj, "terraform test"),
|
||||||
|
testAccCheckMonitoringJobActive(&mj, true),
|
||||||
|
testAccCheckMonitoringJobRegions(&mj, []string{"lga"}),
|
||||||
|
testAccCheckMonitoringJobType(&mj, "tcp"),
|
||||||
|
testAccCheckMonitoringJobFrequency(&mj, 120),
|
||||||
|
testAccCheckMonitoringJobRapidRecheck(&mj, true),
|
||||||
|
testAccCheckMonitoringJobPolicy(&mj, "all"),
|
||||||
|
testAccCheckMonitoringJobConfigSend(&mj, "HEAD / HTTP/1.0\r\n\r\n"),
|
||||||
|
testAccCheckMonitoringJobConfigPort(&mj, 443),
|
||||||
|
testAccCheckMonitoringJobConfigHost(&mj, "1.1.1.1"),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccCheckMonitoringJobState(key, value string) resource.TestCheckFunc {
|
||||||
|
return func(s *terraform.State) error {
|
||||||
|
rs, ok := s.RootModule().Resources["ns1_monitoringjob.it"]
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("Not found: %s", "ns1_monitoringjob.it")
|
||||||
|
}
|
||||||
|
|
||||||
|
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 testAccCheckMonitoringJobExists(n string, monitoringJob *monitor.Job) 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)
|
||||||
|
|
||||||
|
foundMj, _, err := client.Jobs.Get(id)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if foundMj.ID != id {
|
||||||
|
return fmt.Errorf("Monitoring Job not found want: %#v, got %#v", id, foundMj)
|
||||||
|
}
|
||||||
|
|
||||||
|
*monitoringJob = *foundMj
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccCheckMonitoringJobDestroy(s *terraform.State) error {
|
||||||
|
client := testAccProvider.Meta().(*ns1.Client)
|
||||||
|
|
||||||
|
for _, rs := range s.RootModule().Resources {
|
||||||
|
if rs.Type != "ns1_monitoringjob" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
mj, _, err := client.Jobs.Get(rs.Primary.Attributes["id"])
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
return fmt.Errorf("Monitoring Job still exists %#v: %#v", err, mj)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccCheckMonitoringJobName(mj *monitor.Job, expected string) resource.TestCheckFunc {
|
||||||
|
return func(s *terraform.State) error {
|
||||||
|
if mj.Name != expected {
|
||||||
|
return fmt.Errorf("Name: got: %#v want: %#v", mj.Name, expected)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccCheckMonitoringJobActive(mj *monitor.Job, expected bool) resource.TestCheckFunc {
|
||||||
|
return func(s *terraform.State) error {
|
||||||
|
if mj.Active != expected {
|
||||||
|
return fmt.Errorf("Active: got: %#v want: %#v", mj.Active, expected)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccCheckMonitoringJobRegions(mj *monitor.Job, expected []string) resource.TestCheckFunc {
|
||||||
|
return func(s *terraform.State) error {
|
||||||
|
if !reflect.DeepEqual(mj.Regions, expected) {
|
||||||
|
return fmt.Errorf("Regions: got: %#v want: %#v", mj.Regions, expected)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccCheckMonitoringJobType(mj *monitor.Job, expected string) resource.TestCheckFunc {
|
||||||
|
return func(s *terraform.State) error {
|
||||||
|
if mj.Type != expected {
|
||||||
|
return fmt.Errorf("Type: got: %#v want: %#v", mj.Type, expected)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccCheckMonitoringJobFrequency(mj *monitor.Job, expected int) resource.TestCheckFunc {
|
||||||
|
return func(s *terraform.State) error {
|
||||||
|
if mj.Frequency != expected {
|
||||||
|
return fmt.Errorf("Frequency: got: %#v want: %#v", mj.Frequency, expected)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccCheckMonitoringJobRapidRecheck(mj *monitor.Job, expected bool) resource.TestCheckFunc {
|
||||||
|
return func(s *terraform.State) error {
|
||||||
|
if mj.RapidRecheck != expected {
|
||||||
|
return fmt.Errorf("RapidRecheck: got: %#v want: %#v", mj.RapidRecheck, expected)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccCheckMonitoringJobPolicy(mj *monitor.Job, expected string) resource.TestCheckFunc {
|
||||||
|
return func(s *terraform.State) error {
|
||||||
|
if mj.Policy != expected {
|
||||||
|
return fmt.Errorf("Policy: got: %#v want: %#v", mj.Policy, expected)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccCheckMonitoringJobConfigSend(mj *monitor.Job, expected string) resource.TestCheckFunc {
|
||||||
|
return func(s *terraform.State) error {
|
||||||
|
if mj.Config["send"].(string) != expected {
|
||||||
|
return fmt.Errorf("Config.send: got: %#v want: %#v", mj.Config["send"].(string), expected)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccCheckMonitoringJobConfigPort(mj *monitor.Job, expected float64) resource.TestCheckFunc {
|
||||||
|
return func(s *terraform.State) error {
|
||||||
|
if mj.Config["port"].(float64) != expected {
|
||||||
|
return fmt.Errorf("Config.port: got: %#v want: %#v", mj.Config["port"].(float64), expected)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccCheckMonitoringJobConfigHost(mj *monitor.Job, expected string) resource.TestCheckFunc {
|
||||||
|
return func(s *terraform.State) error {
|
||||||
|
if mj.Config["host"].(string) != expected {
|
||||||
|
return fmt.Errorf("Config.host: got: %#v want: %#v", mj.Config["host"].(string), expected)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const testAccMonitoringJobBasic = `
|
||||||
|
resource "ns1_monitoringjob" "it" {
|
||||||
|
job_type = "tcp"
|
||||||
|
name = "terraform test"
|
||||||
|
|
||||||
|
regions = ["lga"]
|
||||||
|
frequency = 60
|
||||||
|
|
||||||
|
config {
|
||||||
|
send = "HEAD / HTTP/1.0\r\n\r\n"
|
||||||
|
port = 80
|
||||||
|
host = "1.1.1.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
const testAccMonitoringJobUpdated = `
|
||||||
|
resource "ns1_monitoringjob" "it" {
|
||||||
|
job_type = "tcp"
|
||||||
|
name = "terraform test"
|
||||||
|
|
||||||
|
active = true
|
||||||
|
regions = ["lga"]
|
||||||
|
frequency = 120
|
||||||
|
rapid_recheck = true
|
||||||
|
policy = "all"
|
||||||
|
|
||||||
|
config {
|
||||||
|
send = "HEAD / HTTP/1.0\r\n\r\n"
|
||||||
|
port = 443
|
||||||
|
host = "1.1.1.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
|
@ -0,0 +1,367 @@
|
||||||
|
package ns1
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/helper/schema"
|
||||||
|
|
||||||
|
"github.com/mitchellh/hashstructure"
|
||||||
|
ns1 "gopkg.in/ns1/ns1-go.v2/rest"
|
||||||
|
"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"
|
||||||
|
)
|
||||||
|
|
||||||
|
var recordTypeStringEnum *StringEnum = NewStringEnum([]string{
|
||||||
|
"A",
|
||||||
|
"AAAA",
|
||||||
|
"ALIAS",
|
||||||
|
"AFSDB",
|
||||||
|
"CNAME",
|
||||||
|
"DNAME",
|
||||||
|
"HINFO",
|
||||||
|
"MX",
|
||||||
|
"NAPTR",
|
||||||
|
"NS",
|
||||||
|
"PTR",
|
||||||
|
"RP",
|
||||||
|
"SPF",
|
||||||
|
"SRV",
|
||||||
|
"TXT",
|
||||||
|
})
|
||||||
|
|
||||||
|
func recordResource() *schema.Resource {
|
||||||
|
return &schema.Resource{
|
||||||
|
Schema: map[string]*schema.Schema{
|
||||||
|
// Required
|
||||||
|
"zone": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
ForceNew: true,
|
||||||
|
},
|
||||||
|
"domain": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
ForceNew: true,
|
||||||
|
},
|
||||||
|
"type": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
ForceNew: true,
|
||||||
|
ValidateFunc: recordTypeStringEnum.ValidateFunc,
|
||||||
|
},
|
||||||
|
// Optional
|
||||||
|
"ttl": &schema.Schema{
|
||||||
|
Type: schema.TypeInt,
|
||||||
|
Optional: true,
|
||||||
|
Computed: true,
|
||||||
|
},
|
||||||
|
// "meta": metaSchema,
|
||||||
|
"link": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
ForceNew: true,
|
||||||
|
},
|
||||||
|
"use_client_subnet": &schema.Schema{
|
||||||
|
Type: schema.TypeBool,
|
||||||
|
Optional: true,
|
||||||
|
Default: false,
|
||||||
|
},
|
||||||
|
"answers": &schema.Schema{
|
||||||
|
Type: schema.TypeSet,
|
||||||
|
Optional: true,
|
||||||
|
Elem: &schema.Resource{
|
||||||
|
Schema: map[string]*schema.Schema{
|
||||||
|
"answer": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
},
|
||||||
|
"region": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
},
|
||||||
|
// "meta": metaSchema,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Set: genericHasher,
|
||||||
|
},
|
||||||
|
"regions": &schema.Schema{
|
||||||
|
Type: schema.TypeSet,
|
||||||
|
Optional: true,
|
||||||
|
Elem: &schema.Resource{
|
||||||
|
Schema: map[string]*schema.Schema{
|
||||||
|
"name": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
// "meta": metaSchema,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Set: genericHasher,
|
||||||
|
},
|
||||||
|
"filters": &schema.Schema{
|
||||||
|
Type: schema.TypeList,
|
||||||
|
Optional: true,
|
||||||
|
Elem: &schema.Resource{
|
||||||
|
Schema: map[string]*schema.Schema{
|
||||||
|
"filter": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
"disabled": &schema.Schema{
|
||||||
|
Type: schema.TypeBool,
|
||||||
|
Optional: true,
|
||||||
|
},
|
||||||
|
"config": &schema.Schema{
|
||||||
|
Type: schema.TypeMap,
|
||||||
|
Optional: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// Computed
|
||||||
|
"id": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Computed: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Create: RecordCreate,
|
||||||
|
Read: RecordRead,
|
||||||
|
Update: RecordUpdate,
|
||||||
|
Delete: RecordDelete,
|
||||||
|
Importer: &schema.ResourceImporter{State: RecordStateFunc},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func genericHasher(v interface{}) int {
|
||||||
|
hash, err := hashstructure.Hash(v, nil)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Sprintf("error computing hash code for %#v: %s", v, err.Error()))
|
||||||
|
}
|
||||||
|
return int(hash)
|
||||||
|
}
|
||||||
|
|
||||||
|
func recordToResourceData(d *schema.ResourceData, r *dns.Record) error {
|
||||||
|
d.SetId(r.ID)
|
||||||
|
d.Set("domain", r.Domain)
|
||||||
|
d.Set("zone", r.Zone)
|
||||||
|
d.Set("type", r.Type)
|
||||||
|
d.Set("ttl", r.TTL)
|
||||||
|
if r.Link != "" {
|
||||||
|
d.Set("link", r.Link)
|
||||||
|
}
|
||||||
|
// if r.Meta != nil {
|
||||||
|
// d.State()
|
||||||
|
// t := metaStructToDynamic(r.Meta)
|
||||||
|
// d.Set("meta", t)
|
||||||
|
// }
|
||||||
|
if len(r.Filters) > 0 {
|
||||||
|
filters := make([]map[string]interface{}, len(r.Filters))
|
||||||
|
for i, f := range r.Filters {
|
||||||
|
m := make(map[string]interface{})
|
||||||
|
m["filter"] = f.Type
|
||||||
|
if f.Disabled {
|
||||||
|
m["disabled"] = true
|
||||||
|
}
|
||||||
|
if f.Config != nil {
|
||||||
|
m["config"] = f.Config
|
||||||
|
}
|
||||||
|
filters[i] = m
|
||||||
|
}
|
||||||
|
d.Set("filters", filters)
|
||||||
|
}
|
||||||
|
if len(r.Answers) > 0 {
|
||||||
|
ans := &schema.Set{
|
||||||
|
F: genericHasher,
|
||||||
|
}
|
||||||
|
log.Printf("Got back from ns1 answers: %+v", r.Answers)
|
||||||
|
for _, answer := range r.Answers {
|
||||||
|
ans.Add(answerToMap(*answer))
|
||||||
|
}
|
||||||
|
log.Printf("Setting answers %+v", ans)
|
||||||
|
err := d.Set("answers", ans)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("[DEBUG] Error setting answers for: %s, error: %#v", r.Domain, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(r.Regions) > 0 {
|
||||||
|
regions := make([]map[string]interface{}, 0, len(r.Regions))
|
||||||
|
for regionName, _ := range r.Regions {
|
||||||
|
newRegion := make(map[string]interface{})
|
||||||
|
newRegion["name"] = regionName
|
||||||
|
// newRegion["meta"] = metaStructToDynamic(®ion.Meta)
|
||||||
|
regions = append(regions, newRegion)
|
||||||
|
}
|
||||||
|
log.Printf("Setting regions %+v", regions)
|
||||||
|
err := d.Set("regions", regions)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("[DEBUG] Error setting regions for: %s, error: %#v", r.Domain, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func answerToMap(a dns.Answer) map[string]interface{} {
|
||||||
|
m := make(map[string]interface{})
|
||||||
|
m["answer"] = strings.Join(a.Rdata, " ")
|
||||||
|
if a.RegionName != "" {
|
||||||
|
m["region"] = a.RegionName
|
||||||
|
}
|
||||||
|
// if a.Meta != nil {
|
||||||
|
// m["meta"] = metaStructToDynamic(a.Meta)
|
||||||
|
// }
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
func btoi(b bool) int {
|
||||||
|
if b {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceDataToRecord(r *dns.Record, d *schema.ResourceData) error {
|
||||||
|
r.ID = d.Id()
|
||||||
|
if answers := d.Get("answers").(*schema.Set); answers.Len() > 0 {
|
||||||
|
al := make([]*dns.Answer, answers.Len())
|
||||||
|
for i, answerRaw := range answers.List() {
|
||||||
|
answer := answerRaw.(map[string]interface{})
|
||||||
|
var a *dns.Answer
|
||||||
|
v := answer["answer"].(string)
|
||||||
|
switch d.Get("type") {
|
||||||
|
case "TXT":
|
||||||
|
a = dns.NewTXTAnswer(v)
|
||||||
|
default:
|
||||||
|
a = dns.NewAnswer(strings.Split(v, " "))
|
||||||
|
}
|
||||||
|
if v, ok := answer["region"]; ok {
|
||||||
|
a.RegionName = v.(string)
|
||||||
|
}
|
||||||
|
|
||||||
|
// if v, ok := answer["meta"]; ok {
|
||||||
|
// metaDynamicToStruct(a.Meta, v)
|
||||||
|
// }
|
||||||
|
al[i] = a
|
||||||
|
}
|
||||||
|
r.Answers = al
|
||||||
|
if _, ok := d.GetOk("link"); ok {
|
||||||
|
return errors.New("Cannot have both link and answers in a record")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if v, ok := d.GetOk("ttl"); ok {
|
||||||
|
r.TTL = v.(int)
|
||||||
|
}
|
||||||
|
if v, ok := d.GetOk("link"); ok {
|
||||||
|
r.LinkTo(v.(string))
|
||||||
|
}
|
||||||
|
// if v, ok := d.GetOk("meta"); ok {
|
||||||
|
// metaDynamicToStruct(r.Meta, v)
|
||||||
|
// }
|
||||||
|
useClientSubnetVal := d.Get("use_client_subnet").(bool)
|
||||||
|
if v := strconv.FormatBool(useClientSubnetVal); v != "" {
|
||||||
|
r.UseClientSubnet = &useClientSubnetVal
|
||||||
|
}
|
||||||
|
|
||||||
|
if rawFilters := d.Get("filters").([]interface{}); len(rawFilters) > 0 {
|
||||||
|
f := make([]*filter.Filter, len(rawFilters))
|
||||||
|
for i, filterRaw := range rawFilters {
|
||||||
|
fi := filterRaw.(map[string]interface{})
|
||||||
|
config := make(map[string]interface{})
|
||||||
|
filter := filter.Filter{
|
||||||
|
Type: fi["filter"].(string),
|
||||||
|
Config: config,
|
||||||
|
}
|
||||||
|
if disabled, ok := fi["disabled"]; ok {
|
||||||
|
filter.Disabled = disabled.(bool)
|
||||||
|
}
|
||||||
|
if rawConfig, ok := fi["config"]; ok {
|
||||||
|
for k, v := range rawConfig.(map[string]interface{}) {
|
||||||
|
if i, err := strconv.Atoi(v.(string)); err == nil {
|
||||||
|
filter.Config[k] = i
|
||||||
|
} else {
|
||||||
|
filter.Config[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
f[i] = &filter
|
||||||
|
}
|
||||||
|
r.Filters = f
|
||||||
|
}
|
||||||
|
if regions := d.Get("regions").(*schema.Set); regions.Len() > 0 {
|
||||||
|
for _, regionRaw := range regions.List() {
|
||||||
|
region := regionRaw.(map[string]interface{})
|
||||||
|
ns1R := data.Region{
|
||||||
|
Meta: data.Meta{},
|
||||||
|
}
|
||||||
|
// if v, ok := region["meta"]; ok {
|
||||||
|
// metaDynamicToStruct(&ns1R.Meta, v)
|
||||||
|
// }
|
||||||
|
|
||||||
|
r.Regions[region["name"].(string)] = ns1R
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RecordCreate creates DNS record in ns1
|
||||||
|
func RecordCreate(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
client := meta.(*ns1.Client)
|
||||||
|
r := dns.NewRecord(d.Get("zone").(string), d.Get("domain").(string), d.Get("type").(string))
|
||||||
|
if err := resourceDataToRecord(r, d); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, err := client.Records.Create(r); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return recordToResourceData(d, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RecordRead reads the DNS record from ns1
|
||||||
|
func RecordRead(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
client := meta.(*ns1.Client)
|
||||||
|
|
||||||
|
r, _, err := client.Records.Get(d.Get("zone").(string), d.Get("domain").(string), d.Get("type").(string))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return recordToResourceData(d, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RecordDelete deltes the DNS record from ns1
|
||||||
|
func RecordDelete(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
client := meta.(*ns1.Client)
|
||||||
|
_, err := client.Records.Delete(d.Get("zone").(string), d.Get("domain").(string), d.Get("type").(string))
|
||||||
|
d.SetId("")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// RecordUpdate updates the given dns record in ns1
|
||||||
|
func RecordUpdate(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
client := meta.(*ns1.Client)
|
||||||
|
r := dns.NewRecord(d.Get("zone").(string), d.Get("domain").(string), d.Get("type").(string))
|
||||||
|
if err := resourceDataToRecord(r, d); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, err := client.Records.Update(r); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return recordToResourceData(d, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func RecordStateFunc(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) {
|
||||||
|
parts := strings.Split(d.Id(), "/")
|
||||||
|
if len(parts) != 3 {
|
||||||
|
return nil, fmt.Errorf("Invalid record specifier. Expecting 2 slashes (\"zone/domain/type\"), got %d.", len(parts)-1)
|
||||||
|
}
|
||||||
|
|
||||||
|
d.Set("zone", parts[0])
|
||||||
|
d.Set("domain", parts[1])
|
||||||
|
d.Set("type", parts[2])
|
||||||
|
|
||||||
|
return []*schema.ResourceData{d}, nil
|
||||||
|
}
|
|
@ -0,0 +1,287 @@
|
||||||
|
package ns1
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"sort"
|
||||||
|
"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/dns"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAccRecord_basic(t *testing.T) {
|
||||||
|
var record dns.Record
|
||||||
|
resource.Test(t, resource.TestCase{
|
||||||
|
PreCheck: func() { testAccPreCheck(t) },
|
||||||
|
Providers: testAccProviders,
|
||||||
|
CheckDestroy: testAccCheckRecordDestroy,
|
||||||
|
Steps: []resource.TestStep{
|
||||||
|
resource.TestStep{
|
||||||
|
Config: testAccRecordBasic,
|
||||||
|
Check: resource.ComposeTestCheckFunc(
|
||||||
|
testAccCheckRecordExists("ns1_record.it", &record),
|
||||||
|
testAccCheckRecordDomain(&record, "test.terraform-record-test.io"),
|
||||||
|
testAccCheckRecordTTL(&record, 60),
|
||||||
|
testAccCheckRecordRegionName(&record, []string{"cal"}),
|
||||||
|
// testAccCheckRecordAnswerMetaWeight(&record, 10),
|
||||||
|
testAccCheckRecordAnswerRdata(&record, "test1.terraform-record-test.io"),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAccRecord_updated(t *testing.T) {
|
||||||
|
var record dns.Record
|
||||||
|
resource.Test(t, resource.TestCase{
|
||||||
|
PreCheck: func() { testAccPreCheck(t) },
|
||||||
|
Providers: testAccProviders,
|
||||||
|
CheckDestroy: testAccCheckRecordDestroy,
|
||||||
|
Steps: []resource.TestStep{
|
||||||
|
resource.TestStep{
|
||||||
|
Config: testAccRecordBasic,
|
||||||
|
Check: resource.ComposeTestCheckFunc(
|
||||||
|
testAccCheckRecordExists("ns1_record.it", &record),
|
||||||
|
testAccCheckRecordDomain(&record, "test.terraform-record-test.io"),
|
||||||
|
testAccCheckRecordTTL(&record, 60),
|
||||||
|
testAccCheckRecordRegionName(&record, []string{"cal"}),
|
||||||
|
// testAccCheckRecordAnswerMetaWeight(&record, 10),
|
||||||
|
testAccCheckRecordAnswerRdata(&record, "test1.terraform-record-test.io"),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
resource.TestStep{
|
||||||
|
Config: testAccRecordUpdated,
|
||||||
|
Check: resource.ComposeTestCheckFunc(
|
||||||
|
testAccCheckRecordExists("ns1_record.it", &record),
|
||||||
|
testAccCheckRecordDomain(&record, "test.terraform-record-test.io"),
|
||||||
|
testAccCheckRecordTTL(&record, 120),
|
||||||
|
testAccCheckRecordRegionName(&record, []string{"ny", "wa"}),
|
||||||
|
// testAccCheckRecordAnswerMetaWeight(&record, 5),
|
||||||
|
testAccCheckRecordAnswerRdata(&record, "test2.terraform-record-test.io"),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccCheckRecordExists(n string, record *dns.Record) resource.TestCheckFunc {
|
||||||
|
return func(s *terraform.State) error {
|
||||||
|
rs, ok := s.RootModule().Resources[n]
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("Not found: %v", n)
|
||||||
|
}
|
||||||
|
|
||||||
|
if rs.Primary.ID == "" {
|
||||||
|
return fmt.Errorf("NoID is set")
|
||||||
|
}
|
||||||
|
|
||||||
|
client := testAccProvider.Meta().(*ns1.Client)
|
||||||
|
|
||||||
|
p := rs.Primary
|
||||||
|
|
||||||
|
foundRecord, _, err := client.Records.Get(p.Attributes["zone"], p.Attributes["domain"], p.Attributes["type"])
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Record not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
if foundRecord.Domain != p.Attributes["domain"] {
|
||||||
|
return fmt.Errorf("Record not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
*record = *foundRecord
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccCheckRecordDestroy(s *terraform.State) error {
|
||||||
|
client := testAccProvider.Meta().(*ns1.Client)
|
||||||
|
|
||||||
|
var recordDomain string
|
||||||
|
var recordZone string
|
||||||
|
var recordType string
|
||||||
|
|
||||||
|
for _, rs := range s.RootModule().Resources {
|
||||||
|
if rs.Type != "ns1_record" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if rs.Type == "ns1_record" {
|
||||||
|
recordType = rs.Primary.Attributes["type"]
|
||||||
|
recordDomain = rs.Primary.Attributes["domain"]
|
||||||
|
recordZone = rs.Primary.Attributes["zone"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foundRecord, _, err := client.Records.Get(recordDomain, recordZone, recordType)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Record still exists: %#v", foundRecord)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccCheckRecordDomain(r *dns.Record, expected string) resource.TestCheckFunc {
|
||||||
|
return func(s *terraform.State) error {
|
||||||
|
if r.Domain != expected {
|
||||||
|
return fmt.Errorf("Domain: got: %#v want: %#v", r.Domain, expected)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccCheckRecordTTL(r *dns.Record, expected int) resource.TestCheckFunc {
|
||||||
|
return func(s *terraform.State) error {
|
||||||
|
if r.TTL != expected {
|
||||||
|
return fmt.Errorf("TTL: got: %#v want: %#v", r.TTL, expected)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccCheckRecordRegionName(r *dns.Record, expected []string) resource.TestCheckFunc {
|
||||||
|
return func(s *terraform.State) error {
|
||||||
|
regions := make([]string, len(r.Regions))
|
||||||
|
|
||||||
|
i := 0
|
||||||
|
for k := range r.Regions {
|
||||||
|
regions[i] = k
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
sort.Strings(regions)
|
||||||
|
sort.Strings(expected)
|
||||||
|
if !reflect.DeepEqual(regions, expected) {
|
||||||
|
return fmt.Errorf("Regions: got: %#v want: %#v", regions, expected)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccCheckRecordAnswerMetaWeight(r *dns.Record, expected float64) resource.TestCheckFunc {
|
||||||
|
return func(s *terraform.State) error {
|
||||||
|
recordAnswer := r.Answers[0]
|
||||||
|
recordMetas := recordAnswer.Meta
|
||||||
|
weight := recordMetas.Weight.(float64)
|
||||||
|
if weight != expected {
|
||||||
|
return fmt.Errorf("Answers[0].Meta.Weight: got: %#v want: %#v", weight, expected)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccCheckRecordAnswerRdata(r *dns.Record, expected string) resource.TestCheckFunc {
|
||||||
|
return func(s *terraform.State) error {
|
||||||
|
recordAnswer := r.Answers[0]
|
||||||
|
recordAnswerString := recordAnswer.Rdata[0]
|
||||||
|
if recordAnswerString != expected {
|
||||||
|
return fmt.Errorf("Answers[0].Rdata[0]: got: %#v want: %#v", recordAnswerString, expected)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const testAccRecordBasic = `
|
||||||
|
resource "ns1_record" "it" {
|
||||||
|
zone = "${ns1_zone.test.zone}"
|
||||||
|
domain = "test.${ns1_zone.test.zone}"
|
||||||
|
type = "CNAME"
|
||||||
|
ttl = 60
|
||||||
|
|
||||||
|
// meta {
|
||||||
|
// weight = 5
|
||||||
|
// connections = 3
|
||||||
|
// // up = false // Ignored by d.GetOk("meta.0.up") due to known issue
|
||||||
|
// }
|
||||||
|
|
||||||
|
answers {
|
||||||
|
answer = "test1.terraform-record-test.io"
|
||||||
|
region = "cal"
|
||||||
|
|
||||||
|
// meta {
|
||||||
|
// weight = 10
|
||||||
|
// up = true
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
regions {
|
||||||
|
name = "cal"
|
||||||
|
// meta {
|
||||||
|
// up = true
|
||||||
|
// us_state = ["CA"]
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
filters {
|
||||||
|
filter = "up"
|
||||||
|
}
|
||||||
|
|
||||||
|
filters {
|
||||||
|
filter = "geotarget_country"
|
||||||
|
}
|
||||||
|
|
||||||
|
filters {
|
||||||
|
filter = "select_first_n"
|
||||||
|
config = {N=1}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "ns1_zone" "test" {
|
||||||
|
zone = "terraform-record-test.io"
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
const testAccRecordUpdated = `
|
||||||
|
resource "ns1_record" "it" {
|
||||||
|
zone = "${ns1_zone.test.zone}"
|
||||||
|
domain = "test.${ns1_zone.test.zone}"
|
||||||
|
type = "CNAME"
|
||||||
|
ttl = 120
|
||||||
|
use_client_subnet = true
|
||||||
|
|
||||||
|
// meta {
|
||||||
|
// weight = 5
|
||||||
|
// connections = 3
|
||||||
|
// // up = false // Ignored by d.GetOk("meta.0.up") due to known issue
|
||||||
|
// }
|
||||||
|
|
||||||
|
answers {
|
||||||
|
answer = "test2.terraform-record-test.io"
|
||||||
|
region = "ny"
|
||||||
|
|
||||||
|
// meta {
|
||||||
|
// weight = 5
|
||||||
|
// up = true
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
regions {
|
||||||
|
name = "wa"
|
||||||
|
// meta {
|
||||||
|
// us_state = ["WA"]
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
regions {
|
||||||
|
name = "ny"
|
||||||
|
// meta {
|
||||||
|
// us_state = ["NY"]
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
filters {
|
||||||
|
filter = "up"
|
||||||
|
}
|
||||||
|
|
||||||
|
filters {
|
||||||
|
filter = "geotarget_country"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "ns1_zone" "test" {
|
||||||
|
zone = "terraform-record-test.io"
|
||||||
|
}
|
||||||
|
`
|
|
@ -0,0 +1,89 @@
|
||||||
|
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/account"
|
||||||
|
)
|
||||||
|
|
||||||
|
func teamResource() *schema.Resource {
|
||||||
|
s := map[string]*schema.Schema{
|
||||||
|
"id": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Computed: true,
|
||||||
|
},
|
||||||
|
"name": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
s = addPermsSchema(s)
|
||||||
|
return &schema.Resource{
|
||||||
|
Schema: s,
|
||||||
|
Create: TeamCreate,
|
||||||
|
Read: TeamRead,
|
||||||
|
Update: TeamUpdate,
|
||||||
|
Delete: TeamDelete,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func teamToResourceData(d *schema.ResourceData, t *account.Team) error {
|
||||||
|
d.SetId(t.ID)
|
||||||
|
d.Set("name", t.Name)
|
||||||
|
permissionsToResourceData(d, t.Permissions)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceDataToTeam(t *account.Team, d *schema.ResourceData) error {
|
||||||
|
t.ID = d.Id()
|
||||||
|
t.Name = d.Get("name").(string)
|
||||||
|
t.Permissions = resourceDataToPermissions(d)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TeamCreate creates the given team in ns1
|
||||||
|
func TeamCreate(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
client := meta.(*ns1.Client)
|
||||||
|
t := account.Team{}
|
||||||
|
if err := resourceDataToTeam(&t, d); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, err := client.Teams.Create(&t); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return teamToResourceData(d, &t)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TeamRead reads the team data from ns1
|
||||||
|
func TeamRead(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
client := meta.(*ns1.Client)
|
||||||
|
t, _, err := client.Teams.Get(d.Id())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return teamToResourceData(d, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TeamDelete deletes the given team from ns1
|
||||||
|
func TeamDelete(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
client := meta.(*ns1.Client)
|
||||||
|
_, err := client.Teams.Delete(d.Id())
|
||||||
|
d.SetId("")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// TeamUpdate updates the given team in ns1
|
||||||
|
func TeamUpdate(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
client := meta.(*ns1.Client)
|
||||||
|
t := account.Team{
|
||||||
|
ID: d.Id(),
|
||||||
|
}
|
||||||
|
if err := resourceDataToTeam(&t, d); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, err := client.Teams.Update(&t); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return teamToResourceData(d, &t)
|
||||||
|
}
|
|
@ -0,0 +1,209 @@
|
||||||
|
package ns1
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"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/account"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAccTeam_basic(t *testing.T) {
|
||||||
|
var team account.Team
|
||||||
|
|
||||||
|
resource.Test(t, resource.TestCase{
|
||||||
|
PreCheck: func() { testAccPreCheck(t) },
|
||||||
|
Providers: testAccProviders,
|
||||||
|
CheckDestroy: testAccCheckTeamDestroy,
|
||||||
|
Steps: []resource.TestStep{
|
||||||
|
resource.TestStep{
|
||||||
|
Config: testAccTeamBasic,
|
||||||
|
Check: resource.ComposeTestCheckFunc(
|
||||||
|
testAccCheckTeamExists("ns1_team.foobar", &team),
|
||||||
|
testAccCheckTeamName(&team, "terraform test"),
|
||||||
|
testAccCheckTeamDNSPermission(&team, "view_zones", true),
|
||||||
|
testAccCheckTeamDNSPermission(&team, "zones_allow_by_default", true),
|
||||||
|
testAccCheckTeamDNSPermissionZones(&team, "zones_allow", []string{"mytest.zone"}),
|
||||||
|
testAccCheckTeamDNSPermissionZones(&team, "zones_deny", []string{"myother.zone"}),
|
||||||
|
testAccCheckTeamDataPermission(&team, "manage_datasources", true),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAccTeam_updated(t *testing.T) {
|
||||||
|
var team account.Team
|
||||||
|
|
||||||
|
resource.Test(t, resource.TestCase{
|
||||||
|
PreCheck: func() { testAccPreCheck(t) },
|
||||||
|
Providers: testAccProviders,
|
||||||
|
CheckDestroy: testAccCheckTeamDestroy,
|
||||||
|
Steps: []resource.TestStep{
|
||||||
|
resource.TestStep{
|
||||||
|
Config: testAccTeamBasic,
|
||||||
|
Check: resource.ComposeTestCheckFunc(
|
||||||
|
testAccCheckTeamExists("ns1_team.foobar", &team),
|
||||||
|
testAccCheckTeamName(&team, "terraform test"),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
resource.TestStep{
|
||||||
|
Config: testAccTeamUpdated,
|
||||||
|
Check: resource.ComposeTestCheckFunc(
|
||||||
|
testAccCheckTeamExists("ns1_team.foobar", &team),
|
||||||
|
testAccCheckTeamName(&team, "terraform test updated"),
|
||||||
|
testAccCheckTeamDNSPermission(&team, "view_zones", true),
|
||||||
|
testAccCheckTeamDNSPermission(&team, "zones_allow_by_default", true),
|
||||||
|
testAccCheckTeamDNSPermissionZones(&team, "zones_allow", []string{}),
|
||||||
|
testAccCheckTeamDNSPermissionZones(&team, "zones_deny", []string{}),
|
||||||
|
testAccCheckTeamDataPermission(&team, "manage_datasources", false),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccCheckTeamExists(n string, team *account.Team) resource.TestCheckFunc {
|
||||||
|
return func(s *terraform.State) error {
|
||||||
|
rs, ok := s.RootModule().Resources[n]
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("Not found: %s", n)
|
||||||
|
}
|
||||||
|
|
||||||
|
if rs.Primary.ID == "" {
|
||||||
|
return fmt.Errorf("NoID is set")
|
||||||
|
}
|
||||||
|
|
||||||
|
client := testAccProvider.Meta().(*ns1.Client)
|
||||||
|
|
||||||
|
foundTeam, _, err := client.Teams.Get(rs.Primary.Attributes["id"])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if foundTeam.Name != rs.Primary.Attributes["name"] {
|
||||||
|
return fmt.Errorf("Team not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
*team = *foundTeam
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccCheckTeamDestroy(s *terraform.State) error {
|
||||||
|
client := testAccProvider.Meta().(*ns1.Client)
|
||||||
|
|
||||||
|
for _, rs := range s.RootModule().Resources {
|
||||||
|
if rs.Type != "ns1_team" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
team, _, err := client.Teams.Get(rs.Primary.Attributes["id"])
|
||||||
|
if err == nil {
|
||||||
|
return fmt.Errorf("Team still exists: %#v: %#v", err, team.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccCheckTeamName(team *account.Team, expected string) resource.TestCheckFunc {
|
||||||
|
return func(s *terraform.State) error {
|
||||||
|
if team.Name != expected {
|
||||||
|
return fmt.Errorf("Name: got: %s want: %s", team.Name, expected)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccCheckTeamDNSPermission(team *account.Team, perm string, expected bool) resource.TestCheckFunc {
|
||||||
|
return func(s *terraform.State) error {
|
||||||
|
dns := team.Permissions.DNS
|
||||||
|
|
||||||
|
switch perm {
|
||||||
|
case "view_zones":
|
||||||
|
if dns.ViewZones != expected {
|
||||||
|
return fmt.Errorf("DNS.ViewZones: got: %t want: %t", dns.ViewZones, expected)
|
||||||
|
}
|
||||||
|
case "manage_zones":
|
||||||
|
if dns.ManageZones != expected {
|
||||||
|
return fmt.Errorf("DNS.ManageZones: got: %t want: %t", dns.ManageZones, expected)
|
||||||
|
}
|
||||||
|
case "zones_allow_by_default":
|
||||||
|
if dns.ZonesAllowByDefault != expected {
|
||||||
|
return fmt.Errorf("DNS.ZonesAllowByDefault: got: %t want: %t", dns.ZonesAllowByDefault, expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccCheckTeamDataPermission(team *account.Team, perm string, expected bool) resource.TestCheckFunc {
|
||||||
|
return func(s *terraform.State) error {
|
||||||
|
data := team.Permissions.Data
|
||||||
|
|
||||||
|
switch perm {
|
||||||
|
case "push_to_datafeeds":
|
||||||
|
if data.PushToDatafeeds != expected {
|
||||||
|
return fmt.Errorf("Data.PushToDatafeeds: got: %t want: %t", data.PushToDatafeeds, expected)
|
||||||
|
}
|
||||||
|
case "manage_datasources":
|
||||||
|
if data.ManageDatasources != expected {
|
||||||
|
return fmt.Errorf("Data.ManageDatasources: got: %t want: %t", data.ManageDatasources, expected)
|
||||||
|
}
|
||||||
|
case "manage_datafeeds":
|
||||||
|
if data.ManageDatafeeds != expected {
|
||||||
|
return fmt.Errorf("Data.ManageDatafeeds: got: %t want: %t", data.ManageDatafeeds, expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccCheckTeamDNSPermissionZones(team *account.Team, perm string, expected []string) resource.TestCheckFunc {
|
||||||
|
return func(s *terraform.State) error {
|
||||||
|
dns := team.Permissions.DNS
|
||||||
|
|
||||||
|
switch perm {
|
||||||
|
case "zones_allow":
|
||||||
|
if !reflect.DeepEqual(dns.ZonesAllow, expected) {
|
||||||
|
return fmt.Errorf("DNS.ZonesAllow: got: %v want: %v", dns.ZonesAllow, expected)
|
||||||
|
}
|
||||||
|
case "zones_deny":
|
||||||
|
if !reflect.DeepEqual(dns.ZonesDeny, expected) {
|
||||||
|
return fmt.Errorf("DNS.ZonesDeny: got: %v want: %v", dns.ZonesDeny, expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const testAccTeamBasic = `
|
||||||
|
resource "ns1_team" "foobar" {
|
||||||
|
name = "terraform test"
|
||||||
|
|
||||||
|
dns_view_zones = true
|
||||||
|
dns_zones_allow_by_default = true
|
||||||
|
dns_zones_allow = ["mytest.zone"]
|
||||||
|
dns_zones_deny = ["myother.zone"]
|
||||||
|
|
||||||
|
data_manage_datasources = true
|
||||||
|
}`
|
||||||
|
|
||||||
|
const testAccTeamUpdated = `
|
||||||
|
resource "ns1_team" "foobar" {
|
||||||
|
name = "terraform test updated"
|
||||||
|
|
||||||
|
dns_view_zones = true
|
||||||
|
dns_zones_allow_by_default = true
|
||||||
|
|
||||||
|
data_manage_datasources = false
|
||||||
|
}`
|
|
@ -0,0 +1,133 @@
|
||||||
|
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/account"
|
||||||
|
)
|
||||||
|
|
||||||
|
func userResource() *schema.Resource {
|
||||||
|
s := map[string]*schema.Schema{
|
||||||
|
"id": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Computed: true,
|
||||||
|
},
|
||||||
|
"name": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
"username": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
"email": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
"notify": &schema.Schema{
|
||||||
|
Type: schema.TypeMap,
|
||||||
|
Optional: true,
|
||||||
|
Elem: &schema.Resource{
|
||||||
|
Schema: map[string]*schema.Schema{
|
||||||
|
"billing": &schema.Schema{
|
||||||
|
Type: schema.TypeBool,
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"teams": &schema.Schema{
|
||||||
|
Type: schema.TypeList,
|
||||||
|
Optional: true,
|
||||||
|
Elem: &schema.Schema{Type: schema.TypeString},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
s = addPermsSchema(s)
|
||||||
|
return &schema.Resource{
|
||||||
|
Schema: s,
|
||||||
|
Create: UserCreate,
|
||||||
|
Read: UserRead,
|
||||||
|
Update: UserUpdate,
|
||||||
|
Delete: UserDelete,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func userToResourceData(d *schema.ResourceData, u *account.User) error {
|
||||||
|
d.SetId(u.Username)
|
||||||
|
d.Set("name", u.Name)
|
||||||
|
d.Set("email", u.Email)
|
||||||
|
d.Set("teams", u.TeamIDs)
|
||||||
|
notify := make(map[string]bool)
|
||||||
|
notify["billing"] = u.Notify.Billing
|
||||||
|
d.Set("notify", notify)
|
||||||
|
permissionsToResourceData(d, u.Permissions)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceDataToUser(u *account.User, d *schema.ResourceData) error {
|
||||||
|
u.Name = d.Get("name").(string)
|
||||||
|
u.Username = d.Get("username").(string)
|
||||||
|
u.Email = d.Get("email").(string)
|
||||||
|
if v, ok := d.GetOk("teams"); ok {
|
||||||
|
teamsRaw := v.([]interface{})
|
||||||
|
u.TeamIDs = make([]string, len(teamsRaw))
|
||||||
|
for i, team := range teamsRaw {
|
||||||
|
u.TeamIDs[i] = team.(string)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
u.TeamIDs = make([]string, 0)
|
||||||
|
}
|
||||||
|
if v, ok := d.GetOk("notify"); ok {
|
||||||
|
notifyRaw := v.(map[string]interface{})
|
||||||
|
u.Notify.Billing = notifyRaw["billing"].(bool)
|
||||||
|
}
|
||||||
|
u.Permissions = resourceDataToPermissions(d)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserCreate creates the given user in ns1
|
||||||
|
func UserCreate(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
client := meta.(*ns1.Client)
|
||||||
|
u := account.User{}
|
||||||
|
if err := resourceDataToUser(&u, d); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, err := client.Users.Create(&u); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return userToResourceData(d, &u)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserRead reads the given users data from ns1
|
||||||
|
func UserRead(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
client := meta.(*ns1.Client)
|
||||||
|
u, _, err := client.Users.Get(d.Id())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return userToResourceData(d, u)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserDelete deletes the given user from ns1
|
||||||
|
func UserDelete(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
client := meta.(*ns1.Client)
|
||||||
|
_, err := client.Users.Delete(d.Id())
|
||||||
|
d.SetId("")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserUpdate updates the user with given parameters in ns1
|
||||||
|
func UserUpdate(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
client := meta.(*ns1.Client)
|
||||||
|
u := account.User{
|
||||||
|
Username: d.Id(),
|
||||||
|
}
|
||||||
|
if err := resourceDataToUser(&u, d); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, err := client.Users.Update(&u); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return userToResourceData(d, &u)
|
||||||
|
}
|
|
@ -0,0 +1,174 @@
|
||||||
|
package ns1
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/helper/schema"
|
||||||
|
|
||||||
|
ns1 "gopkg.in/ns1/ns1-go.v2/rest"
|
||||||
|
"gopkg.in/ns1/ns1-go.v2/rest/model/dns"
|
||||||
|
)
|
||||||
|
|
||||||
|
func zoneResource() *schema.Resource {
|
||||||
|
return &schema.Resource{
|
||||||
|
Schema: map[string]*schema.Schema{
|
||||||
|
// Required
|
||||||
|
"zone": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
ForceNew: true,
|
||||||
|
},
|
||||||
|
// Optional
|
||||||
|
"ttl": &schema.Schema{
|
||||||
|
Type: schema.TypeInt,
|
||||||
|
Optional: true,
|
||||||
|
Computed: true,
|
||||||
|
},
|
||||||
|
// SOA attributes per https://tools.ietf.org/html/rfc1035).
|
||||||
|
"refresh": &schema.Schema{
|
||||||
|
Type: schema.TypeInt,
|
||||||
|
Optional: true,
|
||||||
|
Computed: true,
|
||||||
|
},
|
||||||
|
"retry": &schema.Schema{
|
||||||
|
Type: schema.TypeInt,
|
||||||
|
Optional: true,
|
||||||
|
Computed: true,
|
||||||
|
},
|
||||||
|
"expiry": &schema.Schema{
|
||||||
|
Type: schema.TypeInt,
|
||||||
|
Optional: true,
|
||||||
|
Computed: true,
|
||||||
|
},
|
||||||
|
// SOA MINUMUM overloaded as NX TTL per https://tools.ietf.org/html/rfc2308
|
||||||
|
"nx_ttl": &schema.Schema{
|
||||||
|
Type: schema.TypeInt,
|
||||||
|
Optional: true,
|
||||||
|
Computed: true,
|
||||||
|
},
|
||||||
|
// TODO: test
|
||||||
|
"link": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
ForceNew: true,
|
||||||
|
},
|
||||||
|
// TODO: test
|
||||||
|
"primary": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
ForceNew: true,
|
||||||
|
},
|
||||||
|
// Computed
|
||||||
|
"id": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Computed: true,
|
||||||
|
},
|
||||||
|
"dns_servers": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Computed: true,
|
||||||
|
},
|
||||||
|
"hostmaster": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Computed: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Create: ZoneCreate,
|
||||||
|
Read: ZoneRead,
|
||||||
|
Update: ZoneUpdate,
|
||||||
|
Delete: ZoneDelete,
|
||||||
|
Importer: &schema.ResourceImporter{State: ZoneStateFunc},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func zoneToResourceData(d *schema.ResourceData, z *dns.Zone) {
|
||||||
|
d.SetId(z.ID)
|
||||||
|
d.Set("hostmaster", z.Hostmaster)
|
||||||
|
d.Set("ttl", z.TTL)
|
||||||
|
d.Set("nx_ttl", z.NxTTL)
|
||||||
|
d.Set("refresh", z.Refresh)
|
||||||
|
d.Set("retry", z.Retry)
|
||||||
|
d.Set("expiry", z.Expiry)
|
||||||
|
d.Set("dns_servers", strings.Join(z.DNSServers[:], ","))
|
||||||
|
if z.Secondary != nil && z.Secondary.Enabled {
|
||||||
|
d.Set("primary", z.Secondary.PrimaryIP)
|
||||||
|
}
|
||||||
|
if z.Link != nil && *z.Link != "" {
|
||||||
|
d.Set("link", *z.Link)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceToZoneData(z *dns.Zone, d *schema.ResourceData) {
|
||||||
|
z.ID = d.Id()
|
||||||
|
if v, ok := d.GetOk("hostmaster"); ok {
|
||||||
|
z.Hostmaster = v.(string)
|
||||||
|
}
|
||||||
|
if v, ok := d.GetOk("ttl"); ok {
|
||||||
|
z.TTL = v.(int)
|
||||||
|
}
|
||||||
|
if v, ok := d.GetOk("nx_ttl"); ok {
|
||||||
|
z.NxTTL = v.(int)
|
||||||
|
}
|
||||||
|
if v, ok := d.GetOk("refresh"); ok {
|
||||||
|
z.Refresh = v.(int)
|
||||||
|
}
|
||||||
|
if v, ok := d.GetOk("retry"); ok {
|
||||||
|
z.Retry = v.(int)
|
||||||
|
}
|
||||||
|
if v, ok := d.GetOk("expiry"); ok {
|
||||||
|
z.Expiry = v.(int)
|
||||||
|
}
|
||||||
|
if v, ok := d.GetOk("primary"); ok {
|
||||||
|
z.MakeSecondary(v.(string))
|
||||||
|
}
|
||||||
|
if v, ok := d.GetOk("link"); ok {
|
||||||
|
z.LinkTo(v.(string))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ZoneCreate creates the given zone in ns1
|
||||||
|
func ZoneCreate(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
client := meta.(*ns1.Client)
|
||||||
|
z := dns.NewZone(d.Get("zone").(string))
|
||||||
|
resourceToZoneData(z, d)
|
||||||
|
if _, err := client.Zones.Create(z); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
zoneToResourceData(d, z)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ZoneRead reads the given zone data from ns1
|
||||||
|
func ZoneRead(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
client := meta.(*ns1.Client)
|
||||||
|
z, _, err := client.Zones.Get(d.Get("zone").(string))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
zoneToResourceData(d, z)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ZoneDelete deteles the given zone from ns1
|
||||||
|
func ZoneDelete(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
client := meta.(*ns1.Client)
|
||||||
|
_, err := client.Zones.Delete(d.Get("zone").(string))
|
||||||
|
d.SetId("")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ZoneUpdate updates the zone with given params in ns1
|
||||||
|
func ZoneUpdate(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
client := meta.(*ns1.Client)
|
||||||
|
z := dns.NewZone(d.Get("zone").(string))
|
||||||
|
resourceToZoneData(z, d)
|
||||||
|
if _, err := client.Zones.Update(z); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
zoneToResourceData(d, z)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ZoneStateFunc(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) {
|
||||||
|
d.Set("zone", d.Id())
|
||||||
|
return []*schema.ResourceData{d}, nil
|
||||||
|
}
|
|
@ -0,0 +1,189 @@
|
||||||
|
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/dns"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAccZone_basic(t *testing.T) {
|
||||||
|
var zone dns.Zone
|
||||||
|
resource.Test(t, resource.TestCase{
|
||||||
|
PreCheck: func() { testAccPreCheck(t) },
|
||||||
|
Providers: testAccProviders,
|
||||||
|
CheckDestroy: testAccCheckZoneDestroy,
|
||||||
|
Steps: []resource.TestStep{
|
||||||
|
resource.TestStep{
|
||||||
|
Config: testAccZoneBasic,
|
||||||
|
Check: resource.ComposeTestCheckFunc(
|
||||||
|
testAccCheckZoneExists("ns1_zone.it", &zone),
|
||||||
|
testAccCheckZoneName(&zone, "terraform-test-zone.io"),
|
||||||
|
testAccCheckZoneTTL(&zone, 3600),
|
||||||
|
testAccCheckZoneRefresh(&zone, 43200),
|
||||||
|
testAccCheckZoneRetry(&zone, 7200),
|
||||||
|
testAccCheckZoneExpiry(&zone, 1209600),
|
||||||
|
testAccCheckZoneNxTTL(&zone, 3600),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAccZone_updated(t *testing.T) {
|
||||||
|
var zone dns.Zone
|
||||||
|
resource.Test(t, resource.TestCase{
|
||||||
|
PreCheck: func() { testAccPreCheck(t) },
|
||||||
|
Providers: testAccProviders,
|
||||||
|
CheckDestroy: testAccCheckZoneDestroy,
|
||||||
|
Steps: []resource.TestStep{
|
||||||
|
resource.TestStep{
|
||||||
|
Config: testAccZoneBasic,
|
||||||
|
Check: resource.ComposeTestCheckFunc(
|
||||||
|
testAccCheckZoneExists("ns1_zone.it", &zone),
|
||||||
|
testAccCheckZoneName(&zone, "terraform-test-zone.io"),
|
||||||
|
testAccCheckZoneTTL(&zone, 3600),
|
||||||
|
testAccCheckZoneRefresh(&zone, 43200),
|
||||||
|
testAccCheckZoneRetry(&zone, 7200),
|
||||||
|
testAccCheckZoneExpiry(&zone, 1209600),
|
||||||
|
testAccCheckZoneNxTTL(&zone, 3600),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
resource.TestStep{
|
||||||
|
Config: testAccZoneUpdated,
|
||||||
|
Check: resource.ComposeTestCheckFunc(
|
||||||
|
testAccCheckZoneExists("ns1_zone.it", &zone),
|
||||||
|
testAccCheckZoneName(&zone, "terraform-test-zone.io"),
|
||||||
|
testAccCheckZoneTTL(&zone, 10800),
|
||||||
|
testAccCheckZoneRefresh(&zone, 3600),
|
||||||
|
testAccCheckZoneRetry(&zone, 300),
|
||||||
|
testAccCheckZoneExpiry(&zone, 2592000),
|
||||||
|
testAccCheckZoneNxTTL(&zone, 3601),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccCheckZoneExists(n string, zone *dns.Zone) resource.TestCheckFunc {
|
||||||
|
return func(s *terraform.State) error {
|
||||||
|
rs, ok := s.RootModule().Resources[n]
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("Not found: %s", n)
|
||||||
|
}
|
||||||
|
|
||||||
|
if rs.Primary.ID == "" {
|
||||||
|
return fmt.Errorf("NoID is set")
|
||||||
|
}
|
||||||
|
|
||||||
|
client := testAccProvider.Meta().(*ns1.Client)
|
||||||
|
|
||||||
|
foundZone, _, err := client.Zones.Get(rs.Primary.Attributes["zone"])
|
||||||
|
|
||||||
|
p := rs.Primary
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if foundZone.ID != p.Attributes["id"] {
|
||||||
|
return fmt.Errorf("Zone not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
*zone = *foundZone
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccCheckZoneDestroy(s *terraform.State) error {
|
||||||
|
client := testAccProvider.Meta().(*ns1.Client)
|
||||||
|
|
||||||
|
for _, rs := range s.RootModule().Resources {
|
||||||
|
if rs.Type != "ns1_zone" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
zone, _, err := client.Zones.Get(rs.Primary.Attributes["zone"])
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
return fmt.Errorf("Zone still exists: %#v: %#v", err, zone)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccCheckZoneName(zone *dns.Zone, expected string) resource.TestCheckFunc {
|
||||||
|
return func(s *terraform.State) error {
|
||||||
|
if zone.Zone != expected {
|
||||||
|
return fmt.Errorf("Zone: got: %s want: %s", zone.Zone, expected)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccCheckZoneTTL(zone *dns.Zone, expected int) resource.TestCheckFunc {
|
||||||
|
return func(s *terraform.State) error {
|
||||||
|
if zone.TTL != expected {
|
||||||
|
return fmt.Errorf("TTL: got: %d want: %d", zone.TTL, expected)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func testAccCheckZoneRefresh(zone *dns.Zone, expected int) resource.TestCheckFunc {
|
||||||
|
return func(s *terraform.State) error {
|
||||||
|
if zone.Refresh != expected {
|
||||||
|
return fmt.Errorf("Refresh: got: %d want: %d", zone.Refresh, expected)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func testAccCheckZoneRetry(zone *dns.Zone, expected int) resource.TestCheckFunc {
|
||||||
|
return func(s *terraform.State) error {
|
||||||
|
if zone.Retry != expected {
|
||||||
|
return fmt.Errorf("Retry: got: %d want: %d", zone.Retry, expected)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func testAccCheckZoneExpiry(zone *dns.Zone, expected int) resource.TestCheckFunc {
|
||||||
|
return func(s *terraform.State) error {
|
||||||
|
if zone.Expiry != expected {
|
||||||
|
return fmt.Errorf("Expiry: got: %d want: %d", zone.Expiry, expected)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func testAccCheckZoneNxTTL(zone *dns.Zone, expected int) resource.TestCheckFunc {
|
||||||
|
return func(s *terraform.State) error {
|
||||||
|
if zone.NxTTL != expected {
|
||||||
|
return fmt.Errorf("NxTTL: got: %d want: %d", zone.NxTTL, expected)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const testAccZoneBasic = `
|
||||||
|
resource "ns1_zone" "it" {
|
||||||
|
zone = "terraform-test-zone.io"
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
const testAccZoneUpdated = `
|
||||||
|
resource "ns1_zone" "it" {
|
||||||
|
zone = "terraform-test-zone.io"
|
||||||
|
ttl = 10800
|
||||||
|
refresh = 3600
|
||||||
|
retry = 300
|
||||||
|
expiry = 2592000
|
||||||
|
nx_ttl = 3601
|
||||||
|
# link = "1.2.3.4.in-addr.arpa" # TODO
|
||||||
|
# primary = "1.2.3.4.in-addr.arpa" # TODO
|
||||||
|
}
|
||||||
|
`
|
|
@ -0,0 +1,47 @@
|
||||||
|
package ns1
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type StringEnum struct {
|
||||||
|
ValueMap map[string]int
|
||||||
|
Expecting string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewStringEnum(values []string) *StringEnum {
|
||||||
|
valueMap := make(map[string]int)
|
||||||
|
quoted := make([]string, len(values), len(values))
|
||||||
|
for i, value := range values {
|
||||||
|
_, present := valueMap[value]
|
||||||
|
if present {
|
||||||
|
panic(fmt.Sprintf("duplicate value %q", value))
|
||||||
|
}
|
||||||
|
valueMap[value] = i
|
||||||
|
|
||||||
|
quoted[i] = fmt.Sprintf("%q", value)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &StringEnum{
|
||||||
|
ValueMap: valueMap,
|
||||||
|
Expecting: strings.Join(quoted, ", "),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (se *StringEnum) Check(v string) (int, error) {
|
||||||
|
i, present := se.ValueMap[v]
|
||||||
|
if present {
|
||||||
|
return i, nil
|
||||||
|
} else {
|
||||||
|
return -1, fmt.Errorf("expecting one of %s; got %q", se.Expecting, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (se *StringEnum) ValidateFunc(v interface{}, k string) (ws []string, es []error) {
|
||||||
|
_, err := se.Check(v.(string))
|
||||||
|
if err != nil {
|
||||||
|
return nil, []error{err}
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
|
}
|
|
@ -40,6 +40,7 @@ import (
|
||||||
mysqlprovider "github.com/hashicorp/terraform/builtin/providers/mysql"
|
mysqlprovider "github.com/hashicorp/terraform/builtin/providers/mysql"
|
||||||
newrelicprovider "github.com/hashicorp/terraform/builtin/providers/newrelic"
|
newrelicprovider "github.com/hashicorp/terraform/builtin/providers/newrelic"
|
||||||
nomadprovider "github.com/hashicorp/terraform/builtin/providers/nomad"
|
nomadprovider "github.com/hashicorp/terraform/builtin/providers/nomad"
|
||||||
|
ns1provider "github.com/hashicorp/terraform/builtin/providers/ns1"
|
||||||
nullprovider "github.com/hashicorp/terraform/builtin/providers/null"
|
nullprovider "github.com/hashicorp/terraform/builtin/providers/null"
|
||||||
openstackprovider "github.com/hashicorp/terraform/builtin/providers/openstack"
|
openstackprovider "github.com/hashicorp/terraform/builtin/providers/openstack"
|
||||||
opsgenieprovider "github.com/hashicorp/terraform/builtin/providers/opsgenie"
|
opsgenieprovider "github.com/hashicorp/terraform/builtin/providers/opsgenie"
|
||||||
|
@ -108,6 +109,7 @@ var InternalProviders = map[string]plugin.ProviderFunc{
|
||||||
"mysql": mysqlprovider.Provider,
|
"mysql": mysqlprovider.Provider,
|
||||||
"newrelic": newrelicprovider.Provider,
|
"newrelic": newrelicprovider.Provider,
|
||||||
"nomad": nomadprovider.Provider,
|
"nomad": nomadprovider.Provider,
|
||||||
|
"ns1": ns1provider.Provider,
|
||||||
"null": nullprovider.Provider,
|
"null": nullprovider.Provider,
|
||||||
"openstack": openstackprovider.Provider,
|
"openstack": openstackprovider.Provider,
|
||||||
"opsgenie": opsgenieprovider.Provider,
|
"opsgenie": opsgenieprovider.Provider,
|
||||||
|
|
|
@ -0,0 +1,678 @@
|
||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright [yyyy] [name of copyright owner]
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
APACHE HTTP SERVER SUBCOMPONENTS:
|
||||||
|
|
||||||
|
The Apache HTTP Server includes a number of subcomponents with
|
||||||
|
separate copyright notices and license terms. Your use of the source
|
||||||
|
code for the these subcomponents is subject to the terms and
|
||||||
|
conditions of the following licenses.
|
||||||
|
|
||||||
|
For the mod_mime_magic component:
|
||||||
|
|
||||||
|
/*
|
||||||
|
* mod_mime_magic: MIME type lookup via file magic numbers
|
||||||
|
* Copyright (c) 1996-1997 Cisco Systems, Inc.
|
||||||
|
*
|
||||||
|
* This software was submitted by Cisco Systems to the Apache Group in July
|
||||||
|
* 1997. Future revisions and derivatives of this source code must
|
||||||
|
* acknowledge Cisco Systems as the original contributor of this module.
|
||||||
|
* All other licensing and usage conditions are those of the Apache Group.
|
||||||
|
*
|
||||||
|
* Some of this code is derived from the free version of the file command
|
||||||
|
* originally posted to comp.sources.unix. Copyright info for that program
|
||||||
|
* is included below as required.
|
||||||
|
* ---------------------------------------------------------------------------
|
||||||
|
* - Copyright (c) Ian F. Darwin, 1987. Written by Ian F. Darwin.
|
||||||
|
*
|
||||||
|
* This software is not subject to any license of the American Telephone and
|
||||||
|
* Telegraph Company or of the Regents of the University of California.
|
||||||
|
*
|
||||||
|
* Permission is granted to anyone to use this software for any purpose on any
|
||||||
|
* computer system, and to alter it and redistribute it freely, subject to
|
||||||
|
* the following restrictions:
|
||||||
|
*
|
||||||
|
* 1. The author is not responsible for the consequences of use of this
|
||||||
|
* software, no matter how awful, even if they arise from flaws in it.
|
||||||
|
*
|
||||||
|
* 2. The origin of this software must not be misrepresented, either by
|
||||||
|
* explicit claim or by omission. Since few users ever read sources, credits
|
||||||
|
* must appear in the documentation.
|
||||||
|
*
|
||||||
|
* 3. Altered versions must be plainly marked as such, and must not be
|
||||||
|
* misrepresented as being the original software. Since few users ever read
|
||||||
|
* sources, credits must appear in the documentation.
|
||||||
|
*
|
||||||
|
* 4. This notice may not be removed or altered.
|
||||||
|
* -------------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
For the modules\mappers\mod_imap.c component:
|
||||||
|
|
||||||
|
"macmartinized" polygon code copyright 1992 by Eric Haines, erich@eye.com
|
||||||
|
|
||||||
|
For the server\util_md5.c component:
|
||||||
|
|
||||||
|
/************************************************************************
|
||||||
|
* NCSA HTTPd Server
|
||||||
|
* Software Development Group
|
||||||
|
* National Center for Supercomputing Applications
|
||||||
|
* University of Illinois at Urbana-Champaign
|
||||||
|
* 605 E. Springfield, Champaign, IL 61820
|
||||||
|
* httpd@ncsa.uiuc.edu
|
||||||
|
*
|
||||||
|
* Copyright (C) 1995, Board of Trustees of the University of Illinois
|
||||||
|
*
|
||||||
|
************************************************************************
|
||||||
|
*
|
||||||
|
* md5.c: NCSA HTTPd code which uses the md5c.c RSA Code
|
||||||
|
*
|
||||||
|
* Original Code Copyright (C) 1994, Jeff Hostetler, Spyglass, Inc.
|
||||||
|
* Portions of Content-MD5 code Copyright (C) 1993, 1994 by Carnegie Mellon
|
||||||
|
* University (see Copyright below).
|
||||||
|
* Portions of Content-MD5 code Copyright (C) 1991 Bell Communications
|
||||||
|
* Research, Inc. (Bellcore) (see Copyright below).
|
||||||
|
* Portions extracted from mpack, John G. Myers - jgm+@cmu.edu
|
||||||
|
* Content-MD5 Code contributed by Martin Hamilton (martin@net.lut.ac.uk)
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
/* these portions extracted from mpack, John G. Myers - jgm+@cmu.edu */
|
||||||
|
/* (C) Copyright 1993,1994 by Carnegie Mellon University
|
||||||
|
* All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Permission to use, copy, modify, distribute, and sell this software
|
||||||
|
* and its documentation for any purpose is hereby granted without
|
||||||
|
* fee, provided that the above copyright notice appear in all copies
|
||||||
|
* and that both that copyright notice and this permission notice
|
||||||
|
* appear in supporting documentation, and that the name of Carnegie
|
||||||
|
* Mellon University not be used in advertising or publicity
|
||||||
|
* pertaining to distribution of the software without specific,
|
||||||
|
* written prior permission. Carnegie Mellon University makes no
|
||||||
|
* representations about the suitability of this software for any
|
||||||
|
* purpose. It is provided "as is" without express or implied
|
||||||
|
* warranty.
|
||||||
|
*
|
||||||
|
* CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO
|
||||||
|
* THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||||
|
* AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE
|
||||||
|
* FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
|
||||||
|
* AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (c) 1991 Bell Communications Research, Inc. (Bellcore)
|
||||||
|
*
|
||||||
|
* Permission to use, copy, modify, and distribute this material
|
||||||
|
* for any purpose and without fee is hereby granted, provided
|
||||||
|
* that the above copyright notice and this permission notice
|
||||||
|
* appear in all copies, and that the name of Bellcore not be
|
||||||
|
* used in advertising or publicity pertaining to this
|
||||||
|
* material without the specific, prior written permission
|
||||||
|
* of an authorized representative of Bellcore. BELLCORE
|
||||||
|
* MAKES NO REPRESENTATIONS ABOUT THE ACCURACY OR SUITABILITY
|
||||||
|
* OF THIS MATERIAL FOR ANY PURPOSE. IT IS PROVIDED "AS IS",
|
||||||
|
* WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES.
|
||||||
|
*/
|
||||||
|
|
||||||
|
For the srclib\apr\include\apr_md5.h component:
|
||||||
|
/*
|
||||||
|
* This is work is derived from material Copyright RSA Data Security, Inc.
|
||||||
|
*
|
||||||
|
* The RSA copyright statement and Licence for that original material is
|
||||||
|
* included below. This is followed by the Apache copyright statement and
|
||||||
|
* licence for the modifications made to that material.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. All
|
||||||
|
rights reserved.
|
||||||
|
|
||||||
|
License to copy and use this software is granted provided that it
|
||||||
|
is identified as the "RSA Data Security, Inc. MD5 Message-Digest
|
||||||
|
Algorithm" in all material mentioning or referencing this software
|
||||||
|
or this function.
|
||||||
|
|
||||||
|
License is also granted to make and use derivative works provided
|
||||||
|
that such works are identified as "derived from the RSA Data
|
||||||
|
Security, Inc. MD5 Message-Digest Algorithm" in all material
|
||||||
|
mentioning or referencing the derived work.
|
||||||
|
|
||||||
|
RSA Data Security, Inc. makes no representations concerning either
|
||||||
|
the merchantability of this software or the suitability of this
|
||||||
|
software for any particular purpose. It is provided "as is"
|
||||||
|
without express or implied warranty of any kind.
|
||||||
|
|
||||||
|
These notices must be retained in any copies of any part of this
|
||||||
|
documentation and/or software.
|
||||||
|
*/
|
||||||
|
|
||||||
|
For the srclib\apr\passwd\apr_md5.c component:
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This is work is derived from material Copyright RSA Data Security, Inc.
|
||||||
|
*
|
||||||
|
* The RSA copyright statement and Licence for that original material is
|
||||||
|
* included below. This is followed by the Apache copyright statement and
|
||||||
|
* licence for the modifications made to that material.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* MD5C.C - RSA Data Security, Inc., MD5 message-digest algorithm
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. All
|
||||||
|
rights reserved.
|
||||||
|
|
||||||
|
License to copy and use this software is granted provided that it
|
||||||
|
is identified as the "RSA Data Security, Inc. MD5 Message-Digest
|
||||||
|
Algorithm" in all material mentioning or referencing this software
|
||||||
|
or this function.
|
||||||
|
|
||||||
|
License is also granted to make and use derivative works provided
|
||||||
|
that such works are identified as "derived from the RSA Data
|
||||||
|
Security, Inc. MD5 Message-Digest Algorithm" in all material
|
||||||
|
mentioning or referencing the derived work.
|
||||||
|
|
||||||
|
RSA Data Security, Inc. makes no representations concerning either
|
||||||
|
the merchantability of this software or the suitability of this
|
||||||
|
software for any particular purpose. It is provided "as is"
|
||||||
|
without express or implied warranty of any kind.
|
||||||
|
|
||||||
|
These notices must be retained in any copies of any part of this
|
||||||
|
documentation and/or software.
|
||||||
|
*/
|
||||||
|
/*
|
||||||
|
* The apr_md5_encode() routine uses much code obtained from the FreeBSD 3.0
|
||||||
|
* MD5 crypt() function, which is licenced as follows:
|
||||||
|
* ----------------------------------------------------------------------------
|
||||||
|
* "THE BEER-WARE LICENSE" (Revision 42):
|
||||||
|
* <phk@login.dknet.dk> wrote this file. As long as you retain this notice you
|
||||||
|
* can do whatever you want with this stuff. If we meet some day, and you think
|
||||||
|
* this stuff is worth it, you can buy me a beer in return. Poul-Henning Kamp
|
||||||
|
* ----------------------------------------------------------------------------
|
||||||
|
*/
|
||||||
|
|
||||||
|
For the srclib\apr-util\crypto\apr_md4.c component:
|
||||||
|
|
||||||
|
* This is derived from material copyright RSA Data Security, Inc.
|
||||||
|
* Their notice is reproduced below in its entirety.
|
||||||
|
*
|
||||||
|
* Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. All
|
||||||
|
* rights reserved.
|
||||||
|
*
|
||||||
|
* License to copy and use this software is granted provided that it
|
||||||
|
* is identified as the "RSA Data Security, Inc. MD4 Message-Digest
|
||||||
|
* Algorithm" in all material mentioning or referencing this software
|
||||||
|
* or this function.
|
||||||
|
*
|
||||||
|
* License is also granted to make and use derivative works provided
|
||||||
|
* that such works are identified as "derived from the RSA Data
|
||||||
|
* Security, Inc. MD4 Message-Digest Algorithm" in all material
|
||||||
|
* mentioning or referencing the derived work.
|
||||||
|
*
|
||||||
|
* RSA Data Security, Inc. makes no representations concerning either
|
||||||
|
* the merchantability of this software or the suitability of this
|
||||||
|
* software for any particular purpose. It is provided "as is"
|
||||||
|
* without express or implied warranty of any kind.
|
||||||
|
*
|
||||||
|
* These notices must be retained in any copies of any part of this
|
||||||
|
* documentation and/or software.
|
||||||
|
*/
|
||||||
|
|
||||||
|
For the srclib\apr-util\include\apr_md4.h component:
|
||||||
|
|
||||||
|
*
|
||||||
|
* This is derived from material copyright RSA Data Security, Inc.
|
||||||
|
* Their notice is reproduced below in its entirety.
|
||||||
|
*
|
||||||
|
* Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. All
|
||||||
|
* rights reserved.
|
||||||
|
*
|
||||||
|
* License to copy and use this software is granted provided that it
|
||||||
|
* is identified as the "RSA Data Security, Inc. MD4 Message-Digest
|
||||||
|
* Algorithm" in all material mentioning or referencing this software
|
||||||
|
* or this function.
|
||||||
|
*
|
||||||
|
* License is also granted to make and use derivative works provided
|
||||||
|
* that such works are identified as "derived from the RSA Data
|
||||||
|
* Security, Inc. MD4 Message-Digest Algorithm" in all material
|
||||||
|
* mentioning or referencing the derived work.
|
||||||
|
*
|
||||||
|
* RSA Data Security, Inc. makes no representations concerning either
|
||||||
|
* the merchantability of this software or the suitability of this
|
||||||
|
* software for any particular purpose. It is provided "as is"
|
||||||
|
* without express or implied warranty of any kind.
|
||||||
|
*
|
||||||
|
* These notices must be retained in any copies of any part of this
|
||||||
|
* documentation and/or software.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
For the srclib\apr-util\test\testdbm.c component:
|
||||||
|
|
||||||
|
/* ====================================================================
|
||||||
|
* The Apache Software License, Version 1.1
|
||||||
|
*
|
||||||
|
* Copyright (c) 2000-2002 The Apache Software Foundation. All rights
|
||||||
|
* reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions
|
||||||
|
* are met:
|
||||||
|
*
|
||||||
|
* 1. Redistributions of source code must retain the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer.
|
||||||
|
*
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer in
|
||||||
|
* the documentation and/or other materials provided with the
|
||||||
|
* distribution.
|
||||||
|
*
|
||||||
|
* 3. The end-user documentation included with the redistribution,
|
||||||
|
* if any, must include the following acknowledgment:
|
||||||
|
* "This product includes software developed by the
|
||||||
|
* Apache Software Foundation (http://www.apache.org/)."
|
||||||
|
* Alternately, this acknowledgment may appear in the software itself,
|
||||||
|
* if and wherever such third-party acknowledgments normally appear.
|
||||||
|
*
|
||||||
|
* 4. The names "Apache" and "Apache Software Foundation" must
|
||||||
|
* not be used to endorse or promote products derived from this
|
||||||
|
* software without prior written permission. For written
|
||||||
|
* permission, please contact apache@apache.org.
|
||||||
|
*
|
||||||
|
* 5. Products derived from this software may not be called "Apache",
|
||||||
|
* nor may "Apache" appear in their name, without prior written
|
||||||
|
* permission of the Apache Software Foundation.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
|
||||||
|
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||||
|
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
* DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
|
||||||
|
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
|
||||||
|
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||||
|
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
|
||||||
|
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||||
|
* SUCH DAMAGE.
|
||||||
|
* ====================================================================
|
||||||
|
*
|
||||||
|
* This software consists of voluntary contributions made by many
|
||||||
|
* individuals on behalf of the Apache Software Foundation. For more
|
||||||
|
* information on the Apache Software Foundation, please see
|
||||||
|
* <http://www.apache.org/>.
|
||||||
|
*
|
||||||
|
* This file came from the SDBM package (written by oz@nexus.yorku.ca).
|
||||||
|
* That package was under public domain. This file has been ported to
|
||||||
|
* APR, updated to ANSI C and other, newer idioms, and added to the Apache
|
||||||
|
* codebase under the above copyright and license.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
For the srclib\apr-util\test\testmd4.c component:
|
||||||
|
|
||||||
|
*
|
||||||
|
* This is derived from material copyright RSA Data Security, Inc.
|
||||||
|
* Their notice is reproduced below in its entirety.
|
||||||
|
*
|
||||||
|
* Copyright (C) 1990-2, RSA Data Security, Inc. Created 1990. All
|
||||||
|
* rights reserved.
|
||||||
|
*
|
||||||
|
* RSA Data Security, Inc. makes no representations concerning either
|
||||||
|
* the merchantability of this software or the suitability of this
|
||||||
|
* software for any particular purpose. It is provided "as is"
|
||||||
|
* without express or implied warranty of any kind.
|
||||||
|
*
|
||||||
|
* These notices must be retained in any copies of any part of this
|
||||||
|
* documentation and/or software.
|
||||||
|
*/
|
||||||
|
|
||||||
|
For the srclib\apr-util\xml\expat\conftools\install-sh component:
|
||||||
|
|
||||||
|
#
|
||||||
|
# install - install a program, script, or datafile
|
||||||
|
# This comes from X11R5 (mit/util/scripts/install.sh).
|
||||||
|
#
|
||||||
|
# Copyright 1991 by the Massachusetts Institute of Technology
|
||||||
|
#
|
||||||
|
# Permission to use, copy, modify, distribute, and sell this software and its
|
||||||
|
# documentation for any purpose is hereby granted without fee, provided that
|
||||||
|
# the above copyright notice appear in all copies and that both that
|
||||||
|
# copyright notice and this permission notice appear in supporting
|
||||||
|
# documentation, and that the name of M.I.T. not be used in advertising or
|
||||||
|
# publicity pertaining to distribution of the software without specific,
|
||||||
|
# written prior permission. M.I.T. makes no representations about the
|
||||||
|
# suitability of this software for any purpose. It is provided "as is"
|
||||||
|
# without express or implied warranty.
|
||||||
|
#
|
||||||
|
|
||||||
|
For the srclib\pcre\install-sh component:
|
||||||
|
|
||||||
|
#
|
||||||
|
# Copyright 1991 by the Massachusetts Institute of Technology
|
||||||
|
#
|
||||||
|
# Permission to use, copy, modify, distribute, and sell this software and its
|
||||||
|
# documentation for any purpose is hereby granted without fee, provided that
|
||||||
|
# the above copyright notice appear in all copies and that both that
|
||||||
|
# copyright notice and this permission notice appear in supporting
|
||||||
|
# documentation, and that the name of M.I.T. not be used in advertising or
|
||||||
|
# publicity pertaining to distribution of the software without specific,
|
||||||
|
# written prior permission. M.I.T. makes no representations about the
|
||||||
|
# suitability of this software for any purpose. It is provided "as is"
|
||||||
|
# without express or implied warranty.
|
||||||
|
|
||||||
|
For the pcre component:
|
||||||
|
|
||||||
|
PCRE LICENCE
|
||||||
|
------------
|
||||||
|
|
||||||
|
PCRE is a library of functions to support regular expressions whose syntax
|
||||||
|
and semantics are as close as possible to those of the Perl 5 language.
|
||||||
|
|
||||||
|
Written by: Philip Hazel <ph10@cam.ac.uk>
|
||||||
|
|
||||||
|
University of Cambridge Computing Service,
|
||||||
|
Cambridge, England. Phone: +44 1223 334714.
|
||||||
|
|
||||||
|
Copyright (c) 1997-2001 University of Cambridge
|
||||||
|
|
||||||
|
Permission is granted to anyone to use this software for any purpose on any
|
||||||
|
computer system, and to redistribute it freely, subject to the following
|
||||||
|
restrictions:
|
||||||
|
|
||||||
|
1. This software is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||||
|
|
||||||
|
2. The origin of this software must not be misrepresented, either by
|
||||||
|
explicit claim or by omission. In practice, this means that if you use
|
||||||
|
PCRE in software which you distribute to others, commercially or
|
||||||
|
otherwise, you must put a sentence like this
|
||||||
|
|
||||||
|
Regular expression support is provided by the PCRE library package,
|
||||||
|
which is open source software, written by Philip Hazel, and copyright
|
||||||
|
by the University of Cambridge, England.
|
||||||
|
|
||||||
|
somewhere reasonably visible in your documentation and in any relevant
|
||||||
|
files or online help data or similar. A reference to the ftp site for
|
||||||
|
the source, that is, to
|
||||||
|
|
||||||
|
ftp://ftp.csx.cam.ac.uk/pub/software/programming/pcre/
|
||||||
|
|
||||||
|
should also be given in the documentation.
|
||||||
|
|
||||||
|
3. Altered versions must be plainly marked as such, and must not be
|
||||||
|
misrepresented as being the original software.
|
||||||
|
|
||||||
|
4. If PCRE is embedded in any software that is released under the GNU
|
||||||
|
General Purpose Licence (GPL), or Lesser General Purpose Licence (LGPL),
|
||||||
|
then the terms of that licence shall supersede any condition above with
|
||||||
|
which it is incompatible.
|
||||||
|
|
||||||
|
The documentation for PCRE, supplied in the "doc" directory, is distributed
|
||||||
|
under the same terms as the software itself.
|
||||||
|
|
||||||
|
End PCRE LICENCE
|
||||||
|
|
||||||
|
|
||||||
|
For the test\zb.c component:
|
||||||
|
|
||||||
|
/* ZeusBench V1.01
|
||||||
|
===============
|
||||||
|
|
||||||
|
This program is Copyright (C) Zeus Technology Limited 1996.
|
||||||
|
|
||||||
|
This program may be used and copied freely providing this copyright notice
|
||||||
|
is not removed.
|
||||||
|
|
||||||
|
This software is provided "as is" and any express or implied waranties,
|
||||||
|
including but not limited to, the implied warranties of merchantability and
|
||||||
|
fitness for a particular purpose are disclaimed. In no event shall
|
||||||
|
Zeus Technology Ltd. be liable for any direct, indirect, incidental, special,
|
||||||
|
exemplary, or consequential damaged (including, but not limited to,
|
||||||
|
procurement of substitute good or services; loss of use, data, or profits;
|
||||||
|
or business interruption) however caused and on theory of liability. Whether
|
||||||
|
in contract, strict liability or tort (including negligence or otherwise)
|
||||||
|
arising in any way out of the use of this software, even if advised of the
|
||||||
|
possibility of such damage.
|
||||||
|
|
||||||
|
Written by Adam Twiss (adam@zeus.co.uk). March 1996
|
||||||
|
|
||||||
|
Thanks to the following people for their input:
|
||||||
|
Mike Belshe (mbelshe@netscape.com)
|
||||||
|
Michael Campanella (campanella@stevms.enet.dec.com)
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
For the expat xml parser component:
|
||||||
|
|
||||||
|
Copyright (c) 1998, 1999, 2000 Thai Open Source Software Center Ltd
|
||||||
|
and Clark Cooper
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
a copy of this software and associated documentation files (the
|
||||||
|
"Software"), to deal in the Software without restriction, including
|
||||||
|
without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
permit persons to whom the Software is furnished to do so, subject to
|
||||||
|
the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included
|
||||||
|
in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||||
|
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||||
|
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||||
|
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||||
|
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
====================================================================
|
|
@ -0,0 +1,11 @@
|
||||||
|
.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
|
||||||
|
|
|
@ -0,0 +1,62 @@
|
||||||
|
[![Build Status](https://travis-ci.org/ns1/ns1-go.svg?branch=v2)](https://travis-ci.org/ns1/ns1-go) [![GoDoc](https://godoc.org/gopkg.in/ns1/ns1-go.v2?status.svg)](https://godoc.org/gopkg.in/ns1/ns1-go.v2)
|
||||||
|
|
||||||
|
# NS1 Golang SDK
|
||||||
|
|
||||||
|
The golang client for the NS1 API: https://api.nsone.net/
|
||||||
|
|
||||||
|
# Installing
|
||||||
|
|
||||||
|
```
|
||||||
|
$ go get gopkg.in/ns1/ns1-go.v2
|
||||||
|
```
|
||||||
|
|
||||||
|
Examples
|
||||||
|
========
|
||||||
|
|
||||||
|
[See more](https://github.com/ns1/ns1-go/tree/v2/rest/_examples)
|
||||||
|
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
api "gopkg.in/ns1/ns1-go.v2/rest"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
k := os.Getenv("NS1_APIKEY")
|
||||||
|
if k == "" {
|
||||||
|
fmt.Println("NS1_APIKEY environment variable is not set, giving up")
|
||||||
|
}
|
||||||
|
|
||||||
|
httpClient := &http.Client{Timeout: time.Second * 10}
|
||||||
|
client := api.NewClient(httpClient, api.SetAPIKey(k))
|
||||||
|
|
||||||
|
zones, _, err := client.Zones.List()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, z := range zones {
|
||||||
|
fmt.Println(z.Zone)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Contributing
|
||||||
|
============
|
||||||
|
|
||||||
|
Contributions, ideas and criticisms are all welcome.
|
||||||
|
|
||||||
|
# LICENSE
|
||||||
|
|
||||||
|
Apache2 - see the included LICENSE file for more information
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
// Package ns1 is the NS1 golang SDK.
|
||||||
|
//
|
||||||
|
// To understand the REST models and terminology,
|
||||||
|
// please visit the ns1 web page:
|
||||||
|
//
|
||||||
|
// https://ns1.com/
|
||||||
|
//
|
||||||
|
package ns1
|
|
@ -0,0 +1,143 @@
|
||||||
|
package rest
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"gopkg.in/ns1/ns1-go.v2/rest/model/account"
|
||||||
|
)
|
||||||
|
|
||||||
|
// APIKeysService handles 'account/apikeys' endpoint.
|
||||||
|
type APIKeysService service
|
||||||
|
|
||||||
|
// List returns all api keys in the account.
|
||||||
|
//
|
||||||
|
// NS1 API docs: https://ns1.com/api/#apikeys-get
|
||||||
|
func (s *APIKeysService) List() ([]*account.APIKey, *http.Response, error) {
|
||||||
|
req, err := s.client.NewRequest("GET", "account/apikeys", nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
kl := []*account.APIKey{}
|
||||||
|
resp, err := s.client.Do(req, &kl)
|
||||||
|
if err != nil {
|
||||||
|
return nil, resp, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return kl, resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get returns details of an api key, including permissions, for a single API Key.
|
||||||
|
// Note: do not use the API Key itself as the keyid in the URL — use the id of the key.
|
||||||
|
//
|
||||||
|
// NS1 API docs: https://ns1.com/api/#apikeys-id-get
|
||||||
|
func (s *APIKeysService) Get(keyID string) (*account.APIKey, *http.Response, error) {
|
||||||
|
path := fmt.Sprintf("account/apikeys/%s", keyID)
|
||||||
|
|
||||||
|
req, err := s.client.NewRequest("GET", path, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var a account.APIKey
|
||||||
|
resp, err := s.client.Do(req, &a)
|
||||||
|
if err != nil {
|
||||||
|
switch err.(type) {
|
||||||
|
case *Error:
|
||||||
|
if err.(*Error).Message == "unknown api key" {
|
||||||
|
return nil, resp, ErrKeyMissing
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return nil, resp, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &a, resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create takes a *APIKey and creates a new account apikey.
|
||||||
|
//
|
||||||
|
// NS1 API docs: https://ns1.com/api/#apikeys-put
|
||||||
|
func (s *APIKeysService) Create(a *account.APIKey) (*http.Response, error) {
|
||||||
|
req, err := s.client.NewRequest("PUT", "account/apikeys", &a)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update account fields with data from api(ensure consistent)
|
||||||
|
resp, err := s.client.Do(req, &a)
|
||||||
|
if err != nil {
|
||||||
|
switch err.(type) {
|
||||||
|
case *Error:
|
||||||
|
if err.(*Error).Message == fmt.Sprintf("api key with name \"%s\" exists", a.Name) {
|
||||||
|
return resp, ErrKeyExists
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return resp, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update changes the name or access rights for an API Key.
|
||||||
|
//
|
||||||
|
// NS1 API docs: https://ns1.com/api/#apikeys-id-post
|
||||||
|
func (s *APIKeysService) Update(a *account.APIKey) (*http.Response, error) {
|
||||||
|
path := fmt.Sprintf("account/apikeys/%s", a.ID)
|
||||||
|
|
||||||
|
req, err := s.client.NewRequest("POST", path, &a)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update apikey fields with data from api(ensure consistent)
|
||||||
|
resp, err := s.client.Do(req, &a)
|
||||||
|
if err != nil {
|
||||||
|
switch err.(type) {
|
||||||
|
case *Error:
|
||||||
|
if err.(*Error).Message == "unknown api key" {
|
||||||
|
return resp, ErrKeyMissing
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return resp, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete deletes an apikey.
|
||||||
|
//
|
||||||
|
// NS1 API docs: https://ns1.com/api/#apikeys-id-delete
|
||||||
|
func (s *APIKeysService) Delete(keyID string) (*http.Response, error) {
|
||||||
|
path := fmt.Sprintf("account/apikeys/%s", keyID)
|
||||||
|
|
||||||
|
req, err := s.client.NewRequest("DELETE", path, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := s.client.Do(req, nil)
|
||||||
|
if err != nil {
|
||||||
|
switch err.(type) {
|
||||||
|
case *Error:
|
||||||
|
if err.(*Error).Message == "unknown api key" {
|
||||||
|
return resp, ErrKeyMissing
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return resp, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ErrKeyExists bundles PUT create error.
|
||||||
|
ErrKeyExists = errors.New("Key already exists.")
|
||||||
|
// ErrKeyMissing bundles GET/POST/DELETE error.
|
||||||
|
ErrKeyMissing = errors.New("Key does not exist.")
|
||||||
|
)
|
|
@ -0,0 +1,46 @@
|
||||||
|
package rest
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"gopkg.in/ns1/ns1-go.v2/rest/model/account"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SettingsService handles 'account/settings' endpoint.
|
||||||
|
type SettingsService service
|
||||||
|
|
||||||
|
// Get returns the basic contact details associated with the account.
|
||||||
|
//
|
||||||
|
// NS1 API docs: https://ns1.com/api/#settings-get
|
||||||
|
func (s *SettingsService) Get() (*account.Setting, *http.Response, error) {
|
||||||
|
req, err := s.client.NewRequest("GET", "account/settings", nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var us account.Setting
|
||||||
|
resp, err := s.client.Do(req, &us)
|
||||||
|
if err != nil {
|
||||||
|
return nil, resp, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &us, resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update changes most of the basic contact details, except customerid.
|
||||||
|
//
|
||||||
|
// NS1 API docs: https://ns1.com/api/#settings-post
|
||||||
|
func (s *SettingsService) Update(us *account.Setting) (*http.Response, error) {
|
||||||
|
req, err := s.client.NewRequest("POST", "account/settings", &us)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update usagewarnings fields with data from api(ensure consistent)
|
||||||
|
resp, err := s.client.Do(req, &us)
|
||||||
|
if err != nil {
|
||||||
|
return resp, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp, nil
|
||||||
|
}
|
|
@ -0,0 +1,142 @@
|
||||||
|
package rest
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"gopkg.in/ns1/ns1-go.v2/rest/model/account"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TeamsService handles 'account/teams' endpoint.
|
||||||
|
type TeamsService service
|
||||||
|
|
||||||
|
// List returns all teams in the account.
|
||||||
|
//
|
||||||
|
// NS1 API docs: https://ns1.com/api/#teams-get
|
||||||
|
func (s *TeamsService) List() ([]*account.Team, *http.Response, error) {
|
||||||
|
req, err := s.client.NewRequest("GET", "account/teams", nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
tl := []*account.Team{}
|
||||||
|
resp, err := s.client.Do(req, &tl)
|
||||||
|
if err != nil {
|
||||||
|
return nil, resp, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return tl, resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get returns details of a single team.
|
||||||
|
//
|
||||||
|
// NS1 API docs: https://ns1.com/api/#teams-id-get
|
||||||
|
func (s *TeamsService) Get(id string) (*account.Team, *http.Response, error) {
|
||||||
|
path := fmt.Sprintf("account/teams/%s", id)
|
||||||
|
|
||||||
|
req, err := s.client.NewRequest("GET", path, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var t account.Team
|
||||||
|
resp, err := s.client.Do(req, &t)
|
||||||
|
if err != nil {
|
||||||
|
switch err.(type) {
|
||||||
|
case *Error:
|
||||||
|
if err.(*Error).Message == "Unknown team id" {
|
||||||
|
return nil, resp, ErrTeamMissing
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return nil, resp, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &t, resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create takes a *Team and creates a new account team.
|
||||||
|
//
|
||||||
|
// NS1 API docs: https://ns1.com/api/#teams-put
|
||||||
|
func (s *TeamsService) Create(t *account.Team) (*http.Response, error) {
|
||||||
|
req, err := s.client.NewRequest("PUT", "account/teams", &t)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update team fields with data from api(ensure consistent)
|
||||||
|
resp, err := s.client.Do(req, &t)
|
||||||
|
if err != nil {
|
||||||
|
switch err.(type) {
|
||||||
|
case *Error:
|
||||||
|
if err.(*Error).Message == fmt.Sprintf("team with name \"%s\" exists", t.Name) {
|
||||||
|
return resp, ErrTeamExists
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return resp, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update changes the name or access rights for a team.
|
||||||
|
//
|
||||||
|
// NS1 API docs: https://ns1.com/api/#teams-id-post
|
||||||
|
func (s *TeamsService) Update(t *account.Team) (*http.Response, error) {
|
||||||
|
path := fmt.Sprintf("account/teams/%s", t.ID)
|
||||||
|
|
||||||
|
req, err := s.client.NewRequest("POST", path, &t)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update team fields with data from api(ensure consistent)
|
||||||
|
resp, err := s.client.Do(req, &t)
|
||||||
|
if err != nil {
|
||||||
|
switch err.(type) {
|
||||||
|
case *Error:
|
||||||
|
if err.(*Error).Message == "unknown team id" {
|
||||||
|
return resp, ErrTeamMissing
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return resp, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete deletes a team.
|
||||||
|
//
|
||||||
|
// NS1 API docs: https://ns1.com/api/#teams-id-delete
|
||||||
|
func (s *TeamsService) Delete(id string) (*http.Response, error) {
|
||||||
|
path := fmt.Sprintf("account/teams/%s", id)
|
||||||
|
|
||||||
|
req, err := s.client.NewRequest("DELETE", path, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := s.client.Do(req, nil)
|
||||||
|
if err != nil {
|
||||||
|
switch err.(type) {
|
||||||
|
case *Error:
|
||||||
|
if err.(*Error).Message == "unknown team id" {
|
||||||
|
return resp, ErrTeamMissing
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return resp, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ErrTeamExists bundles PUT create error.
|
||||||
|
ErrTeamExists = errors.New("Team already exists.")
|
||||||
|
// ErrTeamMissing bundles GET/POST/DELETE error.
|
||||||
|
ErrTeamMissing = errors.New("Team does not exist.")
|
||||||
|
)
|
|
@ -0,0 +1,142 @@
|
||||||
|
package rest
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"gopkg.in/ns1/ns1-go.v2/rest/model/account"
|
||||||
|
)
|
||||||
|
|
||||||
|
// UsersService handles 'account/users' endpoint.
|
||||||
|
type UsersService service
|
||||||
|
|
||||||
|
// List returns all users in the account.
|
||||||
|
//
|
||||||
|
// NS1 API docs: https://ns1.com/api/#users-get
|
||||||
|
func (s *UsersService) List() ([]*account.User, *http.Response, error) {
|
||||||
|
req, err := s.client.NewRequest("GET", "account/users", nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ul := []*account.User{}
|
||||||
|
resp, err := s.client.Do(req, &ul)
|
||||||
|
if err != nil {
|
||||||
|
return nil, resp, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return ul, resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get returns details of a single user.
|
||||||
|
//
|
||||||
|
// NS1 API docs: https://ns1.com/api/#users-user-get
|
||||||
|
func (s *UsersService) Get(username string) (*account.User, *http.Response, error) {
|
||||||
|
path := fmt.Sprintf("account/users/%s", username)
|
||||||
|
|
||||||
|
req, err := s.client.NewRequest("GET", path, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var u account.User
|
||||||
|
resp, err := s.client.Do(req, &u)
|
||||||
|
if err != nil {
|
||||||
|
switch err.(type) {
|
||||||
|
case *Error:
|
||||||
|
if err.(*Error).Message == "Unknown user" {
|
||||||
|
return nil, resp, ErrUserMissing
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return nil, resp, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &u, resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create takes a *User and creates a new account user.
|
||||||
|
//
|
||||||
|
// NS1 API docs: https://ns1.com/api/#users-put
|
||||||
|
func (s *UsersService) Create(u *account.User) (*http.Response, error) {
|
||||||
|
req, err := s.client.NewRequest("PUT", "account/users", &u)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update user fields with data from api(ensure consistent)
|
||||||
|
resp, err := s.client.Do(req, &u)
|
||||||
|
if err != nil {
|
||||||
|
switch err.(type) {
|
||||||
|
case *Error:
|
||||||
|
if err.(*Error).Message == "request failed:Login Name is already in use." {
|
||||||
|
return resp, ErrUserExists
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return resp, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update change contact details, notification settings, or access rights for a user.
|
||||||
|
//
|
||||||
|
// NS1 API docs: https://ns1.com/api/#users-user-post
|
||||||
|
func (s *UsersService) Update(u *account.User) (*http.Response, error) {
|
||||||
|
path := fmt.Sprintf("account/users/%s", u.Username)
|
||||||
|
|
||||||
|
req, err := s.client.NewRequest("POST", path, &u)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update user fields with data from api(ensure consistent)
|
||||||
|
resp, err := s.client.Do(req, &u)
|
||||||
|
if err != nil {
|
||||||
|
switch err.(type) {
|
||||||
|
case *Error:
|
||||||
|
if err.(*Error).Message == "Unknown user" {
|
||||||
|
return resp, ErrUserMissing
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return resp, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete deletes a user.
|
||||||
|
//
|
||||||
|
// NS1 API docs: https://ns1.com/api/#users-user-delete
|
||||||
|
func (s *UsersService) Delete(username string) (*http.Response, error) {
|
||||||
|
path := fmt.Sprintf("account/users/%s", username)
|
||||||
|
|
||||||
|
req, err := s.client.NewRequest("DELETE", path, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := s.client.Do(req, nil)
|
||||||
|
if err != nil {
|
||||||
|
switch err.(type) {
|
||||||
|
case *Error:
|
||||||
|
if err.(*Error).Message == "Unknown user" {
|
||||||
|
return resp, ErrUserMissing
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return resp, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ErrUserExists bundles PUT create error.
|
||||||
|
ErrUserExists = errors.New("User already exists.")
|
||||||
|
// ErrUserMissing bundles GET/POST/DELETE error.
|
||||||
|
ErrUserMissing = errors.New("User does not exist.")
|
||||||
|
)
|
|
@ -0,0 +1,47 @@
|
||||||
|
package rest
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"gopkg.in/ns1/ns1-go.v2/rest/model/account"
|
||||||
|
)
|
||||||
|
|
||||||
|
// WarningsService handles 'account/usagewarnings' endpoint.
|
||||||
|
type WarningsService service
|
||||||
|
|
||||||
|
// Get returns toggles and thresholds used when sending overage warning
|
||||||
|
// alert messages to users with billing notifications enabled.
|
||||||
|
//
|
||||||
|
// NS1 API docs: https://ns1.com/api/#usagewarnings-get
|
||||||
|
func (s *WarningsService) Get() (*account.UsageWarning, *http.Response, error) {
|
||||||
|
req, err := s.client.NewRequest("GET", "account/usagewarnings", nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var uw account.UsageWarning
|
||||||
|
resp, err := s.client.Do(req, &uw)
|
||||||
|
if err != nil {
|
||||||
|
return nil, resp, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &uw, resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update changes alerting toggles and thresholds for overage warning alert messages.
|
||||||
|
//
|
||||||
|
// NS1 API docs: https://ns1.com/api/#usagewarnings-post
|
||||||
|
func (s *WarningsService) Update(uw *account.UsageWarning) (*http.Response, error) {
|
||||||
|
req, err := s.client.NewRequest("POST", "account/usagewarnings", &uw)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update usagewarnings fields with data from api(ensure consistent)
|
||||||
|
resp, err := s.client.Do(req, &uw)
|
||||||
|
if err != nil {
|
||||||
|
return resp, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp, nil
|
||||||
|
}
|
|
@ -0,0 +1,273 @@
|
||||||
|
package rest
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
clientVersion = "2.0.0"
|
||||||
|
defaultEndpoint = "https://api.nsone.net/v1/"
|
||||||
|
defaultUserAgent = "go-ns1/" + clientVersion
|
||||||
|
|
||||||
|
headerAuth = "X-NSONE-Key"
|
||||||
|
headerRateLimit = "X-Ratelimit-Limit"
|
||||||
|
headerRateRemaining = "X-Ratelimit-Remaining"
|
||||||
|
headerRatePeriod = "X-Ratelimit-Period"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Doer is a single method interface that allows a user to extend/augment an http.Client instance.
|
||||||
|
// Note: http.Client satisfies the Doer interface.
|
||||||
|
type Doer interface {
|
||||||
|
Do(*http.Request) (*http.Response, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Client manages communication with the NS1 Rest API.
|
||||||
|
type Client struct {
|
||||||
|
// httpClient handles all rest api communication,
|
||||||
|
// and expects an *http.Client.
|
||||||
|
httpClient Doer
|
||||||
|
|
||||||
|
// NS1 rest endpoint, overrides default if given.
|
||||||
|
Endpoint *url.URL
|
||||||
|
|
||||||
|
// NS1 api key (value for http request header 'X-NSONE-Key').
|
||||||
|
APIKey string
|
||||||
|
|
||||||
|
// NS1 go rest user agent (value for http request header 'User-Agent').
|
||||||
|
UserAgent string
|
||||||
|
|
||||||
|
// Func to call after response is returned in Do
|
||||||
|
RateLimitFunc func(RateLimit)
|
||||||
|
|
||||||
|
// From the excellent github-go client.
|
||||||
|
common service // Reuse a single struct instead of allocating one for each service on the heap.
|
||||||
|
|
||||||
|
// Services used for communicating with different components of the NS1 API.
|
||||||
|
APIKeys *APIKeysService
|
||||||
|
DataFeeds *DataFeedsService
|
||||||
|
DataSources *DataSourcesService
|
||||||
|
Jobs *JobsService
|
||||||
|
Notifications *NotificationsService
|
||||||
|
Records *RecordsService
|
||||||
|
Settings *SettingsService
|
||||||
|
Teams *TeamsService
|
||||||
|
Users *UsersService
|
||||||
|
Warnings *WarningsService
|
||||||
|
Zones *ZonesService
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewClient constructs and returns a reference to an instantiated Client.
|
||||||
|
func NewClient(httpClient Doer, options ...func(*Client)) *Client {
|
||||||
|
endpoint, _ := url.Parse(defaultEndpoint)
|
||||||
|
|
||||||
|
if httpClient == nil {
|
||||||
|
httpClient = http.DefaultClient
|
||||||
|
}
|
||||||
|
|
||||||
|
c := &Client{
|
||||||
|
httpClient: httpClient,
|
||||||
|
Endpoint: endpoint,
|
||||||
|
RateLimitFunc: defaultRateLimitFunc,
|
||||||
|
UserAgent: defaultUserAgent,
|
||||||
|
}
|
||||||
|
|
||||||
|
c.common.client = c
|
||||||
|
c.APIKeys = (*APIKeysService)(&c.common)
|
||||||
|
c.DataFeeds = (*DataFeedsService)(&c.common)
|
||||||
|
c.DataSources = (*DataSourcesService)(&c.common)
|
||||||
|
c.Jobs = (*JobsService)(&c.common)
|
||||||
|
c.Notifications = (*NotificationsService)(&c.common)
|
||||||
|
c.Records = (*RecordsService)(&c.common)
|
||||||
|
c.Settings = (*SettingsService)(&c.common)
|
||||||
|
c.Teams = (*TeamsService)(&c.common)
|
||||||
|
c.Users = (*UsersService)(&c.common)
|
||||||
|
c.Warnings = (*WarningsService)(&c.common)
|
||||||
|
c.Zones = (*ZonesService)(&c.common)
|
||||||
|
|
||||||
|
for _, option := range options {
|
||||||
|
option(c)
|
||||||
|
}
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
type service struct {
|
||||||
|
client *Client
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetHTTPClient sets a Client instances' httpClient.
|
||||||
|
func SetHTTPClient(httpClient Doer) func(*Client) {
|
||||||
|
return func(c *Client) { c.httpClient = httpClient }
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetAPIKey sets a Client instances' APIKey.
|
||||||
|
func SetAPIKey(key string) func(*Client) {
|
||||||
|
return func(c *Client) { c.APIKey = key }
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetEndpoint sets a Client instances' Endpoint.
|
||||||
|
func SetEndpoint(endpoint string) func(*Client) {
|
||||||
|
return func(c *Client) { c.Endpoint, _ = url.Parse(endpoint) }
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetUserAgent sets a Client instances' user agent.
|
||||||
|
func SetUserAgent(ua string) func(*Client) {
|
||||||
|
return func(c *Client) { c.UserAgent = ua }
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetRateLimitFunc sets a Client instances' RateLimitFunc.
|
||||||
|
func SetRateLimitFunc(ratefunc func(rl RateLimit)) func(*Client) {
|
||||||
|
return func(c *Client) { c.RateLimitFunc = ratefunc }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do satisfies the Doer interface.
|
||||||
|
func (c Client) Do(req *http.Request, v interface{}) (*http.Response, error) {
|
||||||
|
resp, err := c.httpClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
err = CheckResponse(resp)
|
||||||
|
if err != nil {
|
||||||
|
return resp, err
|
||||||
|
}
|
||||||
|
|
||||||
|
rl := parseRate(resp)
|
||||||
|
c.RateLimitFunc(rl)
|
||||||
|
|
||||||
|
if v != nil {
|
||||||
|
// Try to unmarshal body into given type using streaming decoder.
|
||||||
|
if err := json.NewDecoder(resp.Body).Decode(&v); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewRequest constructs and returns a http.Request.
|
||||||
|
func (c *Client) NewRequest(method, path string, body interface{}) (*http.Request, error) {
|
||||||
|
rel, err := url.Parse(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
uri := c.Endpoint.ResolveReference(rel)
|
||||||
|
|
||||||
|
// Encode body as json
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
if body != nil {
|
||||||
|
err := json.NewEncoder(buf).Encode(body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := http.NewRequest(method, uri.String(), buf)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Header.Add(headerAuth, c.APIKey)
|
||||||
|
req.Header.Add("User-Agent", c.UserAgent)
|
||||||
|
return req, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Response wraps stdlib http response.
|
||||||
|
type Response struct {
|
||||||
|
*http.Response
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error contains all http responses outside the 2xx range.
|
||||||
|
type Error struct {
|
||||||
|
Resp *http.Response
|
||||||
|
Message string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Satisfy std lib error interface.
|
||||||
|
func (re *Error) Error() string {
|
||||||
|
return fmt.Sprintf("%v %v: %d %v", re.Resp.Request.Method, re.Resp.Request.URL, re.Resp.StatusCode, re.Message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckResponse handles parsing of rest api errors. Returns nil if no error.
|
||||||
|
func CheckResponse(resp *http.Response) error {
|
||||||
|
if c := resp.StatusCode; c >= 200 && c <= 299 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
restErr := &Error{Resp: resp}
|
||||||
|
|
||||||
|
b, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if len(b) == 0 {
|
||||||
|
return restErr
|
||||||
|
}
|
||||||
|
|
||||||
|
err = json.Unmarshal(b, restErr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return restErr
|
||||||
|
}
|
||||||
|
|
||||||
|
// RateLimitFunc is rate limiting strategy for the Client instance.
|
||||||
|
type RateLimitFunc func(RateLimit)
|
||||||
|
|
||||||
|
// RateLimit stores X-Ratelimit-* headers
|
||||||
|
type RateLimit struct {
|
||||||
|
Limit int
|
||||||
|
Remaining int
|
||||||
|
Period int
|
||||||
|
}
|
||||||
|
|
||||||
|
var defaultRateLimitFunc = func(rl RateLimit) {}
|
||||||
|
|
||||||
|
// PercentageLeft returns the ratio of Remaining to Limit as a percentage
|
||||||
|
func (rl RateLimit) PercentageLeft() int {
|
||||||
|
return rl.Remaining * 100 / rl.Limit
|
||||||
|
}
|
||||||
|
|
||||||
|
// WaitTime returns the time.Duration ratio of Period to Limit
|
||||||
|
func (rl RateLimit) WaitTime() time.Duration {
|
||||||
|
return (time.Second * time.Duration(rl.Period)) / time.Duration(rl.Limit)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WaitTimeRemaining returns the time.Duration ratio of Period to Remaining
|
||||||
|
func (rl RateLimit) WaitTimeRemaining() time.Duration {
|
||||||
|
return (time.Second * time.Duration(rl.Period)) / time.Duration(rl.Remaining)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RateLimitStrategySleep sets RateLimitFunc to sleep by WaitTimeRemaining
|
||||||
|
func (c *Client) RateLimitStrategySleep() {
|
||||||
|
c.RateLimitFunc = func(rl RateLimit) {
|
||||||
|
remaining := rl.WaitTimeRemaining()
|
||||||
|
time.Sleep(remaining)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseRate parses rate related headers from http response.
|
||||||
|
func parseRate(resp *http.Response) RateLimit {
|
||||||
|
var rl RateLimit
|
||||||
|
|
||||||
|
if limit := resp.Header.Get(headerRateLimit); limit != "" {
|
||||||
|
rl.Limit, _ = strconv.Atoi(limit)
|
||||||
|
}
|
||||||
|
if remaining := resp.Header.Get(headerRateRemaining); remaining != "" {
|
||||||
|
rl.Remaining, _ = strconv.Atoi(remaining)
|
||||||
|
}
|
||||||
|
if period := resp.Header.Get(headerRatePeriod); period != "" {
|
||||||
|
rl.Period, _ = strconv.Atoi(period)
|
||||||
|
}
|
||||||
|
|
||||||
|
return rl
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
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())
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,116 @@
|
||||||
|
package rest
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"gopkg.in/ns1/ns1-go.v2/rest/model/data"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DataFeedsService handles 'data/feeds' endpoint.
|
||||||
|
type DataFeedsService service
|
||||||
|
|
||||||
|
// List returns all data feeds connected to a given data source.
|
||||||
|
//
|
||||||
|
// NS1 API docs: https://ns1.com/api/#feeds-get
|
||||||
|
func (s *DataFeedsService) List(sourceID string) ([]*data.Feed, *http.Response, error) {
|
||||||
|
path := fmt.Sprintf("data/feeds/%s", sourceID)
|
||||||
|
|
||||||
|
req, err := s.client.NewRequest("GET", path, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
dfl := []*data.Feed{}
|
||||||
|
resp, err := s.client.Do(req, &dfl)
|
||||||
|
if err != nil {
|
||||||
|
return nil, resp, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return dfl, resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get takes a data source ID and a data feed ID and returns the details of a single data feed
|
||||||
|
//
|
||||||
|
// NS1 API docs: https://ns1.com/api/#feeds-feed-get
|
||||||
|
func (s *DataFeedsService) Get(sourceID string, feedID string) (*data.Feed, *http.Response, error) {
|
||||||
|
path := fmt.Sprintf("data/feeds/%s/%s", sourceID, feedID)
|
||||||
|
|
||||||
|
req, err := s.client.NewRequest("GET", path, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var df data.Feed
|
||||||
|
resp, err := s.client.Do(req, &df)
|
||||||
|
if err != nil {
|
||||||
|
return nil, resp, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &df, resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create takes a *DataFeed and connects a new data feed to an existing data source.
|
||||||
|
//
|
||||||
|
// NS1 API docs: https://ns1.com/api/#feeds-put
|
||||||
|
func (s *DataFeedsService) Create(sourceID string, df *data.Feed) (*http.Response, error) {
|
||||||
|
path := fmt.Sprintf("data/feeds/%s", sourceID)
|
||||||
|
|
||||||
|
req, err := s.client.NewRequest("PUT", path, &df)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update datafeeds' fields with data from api(ensure consistent)
|
||||||
|
resp, err := s.client.Do(req, &df)
|
||||||
|
if err != nil {
|
||||||
|
return resp, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update takes a *Feed and modifies and existing data feed.
|
||||||
|
// Note:
|
||||||
|
// - The 'data' portion of a feed does not actually
|
||||||
|
// get updated during a POST. In order to update a feeds'
|
||||||
|
// 'data' attribute, one must use the Publish method.
|
||||||
|
// - Both the 'destinations' and 'networks' attributes are
|
||||||
|
// not updated during a POST.
|
||||||
|
//
|
||||||
|
// NS1 API docs: https://ns1.com/api/#feeds-post
|
||||||
|
func (s *DataFeedsService) Update(sourceID string, df *data.Feed) (*http.Response, error) {
|
||||||
|
path := fmt.Sprintf("data/feeds/%s/%s", sourceID, df.ID)
|
||||||
|
|
||||||
|
req, err := s.client.NewRequest("POST", path, &df)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update df instance fields with data from api(ensure consistent)
|
||||||
|
resp, err := s.client.Do(req, &df)
|
||||||
|
if err != nil {
|
||||||
|
return resp, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete takes a data source ID and a data feed ID and disconnects the feed from the data source and all attached destination metadata tables.
|
||||||
|
//
|
||||||
|
// NS1 API docs: https://ns1.com/api/#feeds-delete
|
||||||
|
func (s *DataFeedsService) Delete(sourceID string, feedID string) (*http.Response, error) {
|
||||||
|
path := fmt.Sprintf("data/feeds/%s/%s", sourceID, feedID)
|
||||||
|
|
||||||
|
req, err := s.client.NewRequest("DELETE", path, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := s.client.Do(req, nil)
|
||||||
|
if err != nil {
|
||||||
|
return resp, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp, nil
|
||||||
|
}
|
|
@ -0,0 +1,126 @@
|
||||||
|
package rest
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"gopkg.in/ns1/ns1-go.v2/rest/model/data"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DataSourcesService handles 'data/sources' endpoint.
|
||||||
|
type DataSourcesService service
|
||||||
|
|
||||||
|
// List returns all connected data sources.
|
||||||
|
//
|
||||||
|
// NS1 API docs: https://ns1.com/api/#sources-get
|
||||||
|
func (s *DataSourcesService) List() ([]*data.Source, *http.Response, error) {
|
||||||
|
req, err := s.client.NewRequest("GET", "data/sources", nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
dsl := []*data.Source{}
|
||||||
|
resp, err := s.client.Do(req, &dsl)
|
||||||
|
if err != nil {
|
||||||
|
return nil, resp, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return dsl, resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get takes an ID returns the details for a single data source.
|
||||||
|
//
|
||||||
|
// NS1 API docs: https://ns1.com/api/#sources-source-get
|
||||||
|
func (s *DataSourcesService) Get(id string) (*data.Source, *http.Response, error) {
|
||||||
|
path := fmt.Sprintf("data/sources/%s", id)
|
||||||
|
|
||||||
|
req, err := s.client.NewRequest("GET", path, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var ds data.Source
|
||||||
|
resp, err := s.client.Do(req, &ds)
|
||||||
|
if err != nil {
|
||||||
|
return nil, resp, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &ds, resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create takes a *DataSource and creates a new data source.
|
||||||
|
//
|
||||||
|
// NS1 API docs: https://ns1.com/api/#sources-put
|
||||||
|
func (s *DataSourcesService) Create(ds *data.Source) (*http.Response, error) {
|
||||||
|
req, err := s.client.NewRequest("PUT", "data/sources", &ds)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update data sources' fields with data from api(ensure consistent)
|
||||||
|
resp, err := s.client.Do(req, &ds)
|
||||||
|
if err != nil {
|
||||||
|
return resp, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update takes a *DataSource modifies basic details of a data source.
|
||||||
|
// NOTE: This does not 'publish' data. See the Publish method.
|
||||||
|
//
|
||||||
|
// NS1 API docs: https://ns1.com/api/#sources-post
|
||||||
|
func (s *DataSourcesService) Update(ds *data.Source) (*http.Response, error) {
|
||||||
|
path := fmt.Sprintf("data/sources/%s", ds.ID)
|
||||||
|
|
||||||
|
req, err := s.client.NewRequest("POST", path, &ds)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update data sources' instance fields with data from api(ensure consistent)
|
||||||
|
resp, err := s.client.Do(req, &ds)
|
||||||
|
if err != nil {
|
||||||
|
return resp, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete takes an ID and removes an existing data source and all connected feeds from the source.
|
||||||
|
//
|
||||||
|
// NS1 API docs: https://ns1.com/api/#sources-delete
|
||||||
|
func (s *DataSourcesService) Delete(id string) (*http.Response, error) {
|
||||||
|
path := fmt.Sprintf("data/sources/%s", id)
|
||||||
|
|
||||||
|
req, err := s.client.NewRequest("DELETE", path, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := s.client.Do(req, nil)
|
||||||
|
if err != nil {
|
||||||
|
return resp, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Publish takes a datasources' id and data to publish.
|
||||||
|
//
|
||||||
|
// NS1 API docs: https://ns1.com/api/#feed-post
|
||||||
|
func (s *DataSourcesService) Publish(dsID string, data interface{}) (*http.Response, error) {
|
||||||
|
path := fmt.Sprintf("feed/%s", dsID)
|
||||||
|
|
||||||
|
req, err := s.client.NewRequest("POST", path, &data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := s.client.Do(req, nil)
|
||||||
|
if err != nil {
|
||||||
|
return resp, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp, nil
|
||||||
|
}
|
|
@ -0,0 +1,2 @@
|
||||||
|
// Package rest defines the api services used to communicate with NS1.
|
||||||
|
package rest
|
|
@ -0,0 +1,13 @@
|
||||||
|
package account
|
||||||
|
|
||||||
|
// APIKey wraps an NS1 /account/apikeys resource
|
||||||
|
type APIKey struct {
|
||||||
|
// Read-only fields
|
||||||
|
ID string `json:"id,omitempty"`
|
||||||
|
Key string `json:"key,omitempty"`
|
||||||
|
LastAccess int `json:"last_access,omitempty"`
|
||||||
|
|
||||||
|
Name string `json:"name"`
|
||||||
|
TeamIDs []string `json:"teams"`
|
||||||
|
Permissions PermissionsMap `json:"permissions"`
|
||||||
|
}
|
|
@ -0,0 +1,2 @@
|
||||||
|
// Package account contains definitions for NS1 apikeys/teams/users/etc.
|
||||||
|
package account
|
|
@ -0,0 +1,44 @@
|
||||||
|
package account
|
||||||
|
|
||||||
|
// PermissionsMap wraps a User's "permissions" attribute
|
||||||
|
type PermissionsMap struct {
|
||||||
|
DNS PermissionsDNS `json:"dns"`
|
||||||
|
Data PermissionsData `json:"data"`
|
||||||
|
Account PermissionsAccount `json:"account"`
|
||||||
|
Monitoring PermissionsMonitoring `json:"monitoring"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// PermissionsDNS wraps a User's "permissions.dns" attribute
|
||||||
|
type PermissionsDNS struct {
|
||||||
|
ViewZones bool `json:"view_zones"`
|
||||||
|
ManageZones bool `json:"manage_zones"`
|
||||||
|
ZonesAllowByDefault bool `json:"zones_allow_by_default"`
|
||||||
|
ZonesDeny []string `json:"zones_deny"`
|
||||||
|
ZonesAllow []string `json:"zones_allow"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// PermissionsData wraps a User's "permissions.data" attribute
|
||||||
|
type PermissionsData struct {
|
||||||
|
PushToDatafeeds bool `json:"push_to_datafeeds"`
|
||||||
|
ManageDatasources bool `json:"manage_datasources"`
|
||||||
|
ManageDatafeeds bool `json:"manage_datafeeds"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// PermissionsAccount wraps a User's "permissions.account" attribute
|
||||||
|
type PermissionsAccount struct {
|
||||||
|
ManageUsers bool `json:"manage_users"`
|
||||||
|
ManagePaymentMethods bool `json:"manage_payment_methods"`
|
||||||
|
ManagePlan bool `json:"manage_plan"`
|
||||||
|
ManageTeams bool `json:"manage_teams"`
|
||||||
|
ManageApikeys bool `json:"manage_apikeys"`
|
||||||
|
ManageAccountSettings bool `json:"manage_account_settings"`
|
||||||
|
ViewActivityLog bool `json:"view_activity_log"`
|
||||||
|
ViewInvoices bool `json:"view_invoices"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// PermissionsMonitoring wraps a User's "permissions.monitoring" attribute
|
||||||
|
type PermissionsMonitoring struct {
|
||||||
|
ManageLists bool `json:"manage_lists"`
|
||||||
|
ManageJobs bool `json:"manage_jobs"`
|
||||||
|
ViewJobs bool `json:"view_jobs"`
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
package account
|
||||||
|
|
||||||
|
// Setting represents an accounts' contact info.
|
||||||
|
type Setting struct {
|
||||||
|
CustomerID int `json:"customerid,omitempty"`
|
||||||
|
FirstName string `json:"firstname,omitempty"`
|
||||||
|
LastName string `json:"lastname,omitempty"`
|
||||||
|
Company string `json:"company,omitempty"`
|
||||||
|
Phone string `json:"phone,omitempty"`
|
||||||
|
Email string `json:"email,omitempty"`
|
||||||
|
Address Address `json:"address,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Address for Setting struct.
|
||||||
|
type Address struct {
|
||||||
|
Country string `json:"country,omitempty"`
|
||||||
|
Street string `json:"street,omitempty"`
|
||||||
|
State string `json:"state,omitempty"`
|
||||||
|
City string `json:"city,omitempty"`
|
||||||
|
Postal string `json:"postalcode,omitempty"`
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
package account
|
||||||
|
|
||||||
|
// Team wraps an NS1 /accounts/teams resource
|
||||||
|
type Team struct {
|
||||||
|
ID string `json:"id,omitempty"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Permissions PermissionsMap `json:"permissions"`
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
package account
|
||||||
|
|
||||||
|
// User wraps an NS1 /account/users resource
|
||||||
|
type User struct {
|
||||||
|
// Read-only fields
|
||||||
|
LastAccess float64 `json:"last_access"`
|
||||||
|
|
||||||
|
Name string `json:"name"`
|
||||||
|
Username string `json:"username"`
|
||||||
|
Email string `json:"email"`
|
||||||
|
TeamIDs []string `json:"teams"`
|
||||||
|
Notify NotificationSettings `json:"notify"`
|
||||||
|
Permissions PermissionsMap `json:"permissions"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NotificationSettings wraps a User's "notify" attribute
|
||||||
|
type NotificationSettings struct {
|
||||||
|
Billing bool `json:"billing"`
|
||||||
|
}
|
|
@ -0,0 +1,117 @@
|
||||||
|
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")
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
package account
|
||||||
|
|
||||||
|
// UsageWarning wraps an NS1 /account/usagewarnings resource
|
||||||
|
type UsageWarning struct {
|
||||||
|
Records Warning `json:"records"`
|
||||||
|
Queries Warning `json:"queries"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warning contains alerting toggles and thresholds for overage warning alert messages.
|
||||||
|
// First thresholds must be smaller than Second ones and all thresholds
|
||||||
|
// must be percentages between 0 and 100.
|
||||||
|
type Warning struct {
|
||||||
|
Send bool `json:"send_warnings"`
|
||||||
|
|
||||||
|
First int `json:"warning_1"`
|
||||||
|
Second int `json:"warning_2"`
|
||||||
|
}
|
|
@ -0,0 +1,2 @@
|
||||||
|
// Package data contains definitions for NS1 metadata/sources/feeds/etc.
|
||||||
|
package data
|
|
@ -0,0 +1,70 @@
|
||||||
|
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>
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
package data
|
||||||
|
|
||||||
|
// Destination is the target resource the receives data from a feed/source.
|
||||||
|
type Destination struct {
|
||||||
|
ID string `json:"destid"`
|
||||||
|
|
||||||
|
// All destinations must point to a record.
|
||||||
|
RecordID string `json:"record"`
|
||||||
|
|
||||||
|
// Type is the 'level' at which to apply the filters(on the targeted record).
|
||||||
|
// Options:
|
||||||
|
// - answer (highest precedence)
|
||||||
|
// - region
|
||||||
|
// - record (lowest precendence)
|
||||||
|
Type string `json:"desttype"`
|
||||||
|
|
||||||
|
SourceID string `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDestination returns an empty feed destination.
|
||||||
|
func NewDestination() *Destination {
|
||||||
|
return &Destination{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Feed wraps an NS1 /data/feeds resource
|
||||||
|
type Feed struct {
|
||||||
|
ID string `json:"id,omitempty"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Config Config `json:"config,omitempty"`
|
||||||
|
Data Meta `json:"data,omitempty"`
|
||||||
|
|
||||||
|
SourceID string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewFeed returns a data feed with given name and config.
|
||||||
|
func NewFeed(name string, cfg Config) *Feed {
|
||||||
|
return &Feed{Name: name, Config: cfg}
|
||||||
|
}
|
|
@ -0,0 +1,127 @@
|
||||||
|
package data
|
||||||
|
|
||||||
|
// FeedPtr represents the dynamic metadata value in which a feed is providing the value.
|
||||||
|
type FeedPtr struct {
|
||||||
|
FeedID string `json:"feed,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Meta contains information on an entities metadata table. Metadata key/value
|
||||||
|
// pairs are used by a records' filter pipeline during a dns query.
|
||||||
|
// All values can be a feed id as well, indicating real-time updates of these values.
|
||||||
|
// Structure/Precendence of metadata tables:
|
||||||
|
// - Record
|
||||||
|
// - Meta <- lowest precendence in filter
|
||||||
|
// - Region(s)
|
||||||
|
// - Meta <- middle precedence in filter chain
|
||||||
|
// - ...
|
||||||
|
// - Answer(s)
|
||||||
|
// - Meta <- highest precedence in filter chain
|
||||||
|
// - ...
|
||||||
|
// - ...
|
||||||
|
type Meta struct {
|
||||||
|
// STATUS
|
||||||
|
|
||||||
|
// Indicates whether or not entity is considered 'up'
|
||||||
|
// bool or FeedPtr.
|
||||||
|
Up interface{} `json:"up,omitempty"`
|
||||||
|
|
||||||
|
// Indicates the number of active connections.
|
||||||
|
// Values must be positive.
|
||||||
|
// int or FeedPtr.
|
||||||
|
Connections interface{} `json:"connections,omitempty"`
|
||||||
|
|
||||||
|
// Indicates the number of active requests (HTTP or otherwise).
|
||||||
|
// Values must be positive.
|
||||||
|
// int or FeedPtr.
|
||||||
|
Requests interface{} `json:"requests,omitempty"`
|
||||||
|
|
||||||
|
// Indicates the "load average".
|
||||||
|
// Values must be positive, and will be rounded to the nearest tenth.
|
||||||
|
// float64 or FeedPtr.
|
||||||
|
LoadAvg interface{} `json:",loadavg,omitempty"`
|
||||||
|
|
||||||
|
// The Job ID of a Pulsar telemetry gathering job and routing granularities
|
||||||
|
// to associate with.
|
||||||
|
// string or FeedPtr.
|
||||||
|
Pulsar interface{} `json:"pulsar,omitempty"`
|
||||||
|
|
||||||
|
// GEOGRAPHICAL
|
||||||
|
|
||||||
|
// Must be between -180.0 and +180.0 where negative
|
||||||
|
// indicates South and positive indicates North.
|
||||||
|
// e.g., the longitude of the datacenter where a server resides.
|
||||||
|
// float64 or FeedPtr.
|
||||||
|
Latitude interface{} `json:"latitude,omitempty"`
|
||||||
|
|
||||||
|
// Must be between -180.0 and +180.0 where negative
|
||||||
|
// indicates West and positive indicates East.
|
||||||
|
// e.g., the longitude of the datacenter where a server resides.
|
||||||
|
// float64 or FeedPtr.
|
||||||
|
Longitude interface{} `json:"longitude,omitempty"`
|
||||||
|
|
||||||
|
// Valid geographic regions are: 'US-EAST', 'US-CENTRAL', 'US-WEST',
|
||||||
|
// 'EUROPE', 'ASIAPAC', 'SOUTH-AMERICA', 'AFRICA'.
|
||||||
|
// e.g., the rough geographic location of the Datacenter where a server resides.
|
||||||
|
// []string or FeedPtr.
|
||||||
|
Georegion interface{} `json:"georegion,omitempty"`
|
||||||
|
|
||||||
|
// Countr(ies) must be specified as ISO3166 2-character country code(s).
|
||||||
|
// []string or FeedPtr.
|
||||||
|
Country interface{} `json:"country,omitempty"`
|
||||||
|
|
||||||
|
// State(s) must be specified as standard 2-character state code(s).
|
||||||
|
// []string or FeedPtr.
|
||||||
|
USState interface{} `json:"us_state,omitempty"`
|
||||||
|
|
||||||
|
// Canadian Province(s) must be specified as standard 2-character province
|
||||||
|
// code(s).
|
||||||
|
// []string or FeedPtr.
|
||||||
|
CAProvince interface{} `json:"ca_province,omitempty"`
|
||||||
|
|
||||||
|
// INFORMATIONAL
|
||||||
|
|
||||||
|
// Notes to indicate any necessary details for operators.
|
||||||
|
// Up to 256 characters in length.
|
||||||
|
// string or FeedPtr.
|
||||||
|
Note interface{} `json:"note,omitempty"`
|
||||||
|
|
||||||
|
// NETWORK
|
||||||
|
|
||||||
|
// IP (v4 and v6) prefixes in CIDR format ("a.b.c.d/mask").
|
||||||
|
// May include up to 1000 prefixes.
|
||||||
|
// e.g., "1.2.3.4/24"
|
||||||
|
// []string or FeedPtr.
|
||||||
|
IPPrefixes interface{} `json:"ip_prefixes,omitempty"`
|
||||||
|
|
||||||
|
// Autonomous System (AS) number(s).
|
||||||
|
// May include up to 1000 AS numbers.
|
||||||
|
// []string or FeedPtr.
|
||||||
|
ASN interface{} `json:"asn,omitempty"`
|
||||||
|
|
||||||
|
// TRAFFIC
|
||||||
|
|
||||||
|
// Indicates the "priority tier".
|
||||||
|
// Lower values indicate higher priority.
|
||||||
|
// Values must be positive.
|
||||||
|
// int or FeedPtr.
|
||||||
|
Priority interface{} `json:"priority,omitempty"`
|
||||||
|
|
||||||
|
// Indicates a weight.
|
||||||
|
// Filters that use weights normalize them.
|
||||||
|
// Any positive values are allowed.
|
||||||
|
// Values between 0 and 100 are recommended for simplicity's sake.
|
||||||
|
// float64 or FeedPtr.
|
||||||
|
Weight interface{} `json:"weight,omitempty"`
|
||||||
|
|
||||||
|
// Indicates a "low watermark" to use for load shedding.
|
||||||
|
// The value should depend on the metric used to determine
|
||||||
|
// load (e.g., loadavg, connections, etc).
|
||||||
|
// int or FeedPtr.
|
||||||
|
LowWatermark interface{} `json:"low_watermark,omitempty"`
|
||||||
|
|
||||||
|
// Indicates a "high watermark" to use for load shedding.
|
||||||
|
// The value should depend on the metric used to determine
|
||||||
|
// load (e.g., loadavg, connections, etc).
|
||||||
|
// int or FeedPtr.
|
||||||
|
HighWatermark interface{} `json:"high_watermark,omitempty"`
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
package data
|
||||||
|
|
||||||
|
// Region is a metadata table with a name/key.
|
||||||
|
// Can be thought of as metadata groupings.
|
||||||
|
type Region struct {
|
||||||
|
Meta Meta `json:"meta,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Regions is simply a mapping of Regions inside a record.
|
||||||
|
type Regions map[string]Region
|
|
@ -0,0 +1,28 @@
|
||||||
|
package data
|
||||||
|
|
||||||
|
// Config is a flat mapping where values are simple (no slices/maps).
|
||||||
|
type Config map[string]interface{}
|
||||||
|
|
||||||
|
// Source wraps an NS1 /data/sources resource
|
||||||
|
type Source struct {
|
||||||
|
ID string `json:"id,omitempty"`
|
||||||
|
|
||||||
|
// Human readable name of the source.
|
||||||
|
Name string `json:"name"`
|
||||||
|
|
||||||
|
Type string `json:"sourcetype"`
|
||||||
|
Config Config `json:"config,omitempty"`
|
||||||
|
Status string `json:"status,omitempty"`
|
||||||
|
|
||||||
|
Feeds []*Feed `json:"feeds,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSource takes a name and type t.
|
||||||
|
func NewSource(name string, t string) *Source {
|
||||||
|
return &Source{
|
||||||
|
Name: name,
|
||||||
|
Type: t,
|
||||||
|
Config: Config{},
|
||||||
|
Feeds: []*Feed{},
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,101 @@
|
||||||
|
package dns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"gopkg.in/ns1/ns1-go.v2/rest/model/data"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Answer wraps the values of a Record's "filters" attribute
|
||||||
|
type Answer struct {
|
||||||
|
Meta *data.Meta `json:"meta,omitempty"`
|
||||||
|
|
||||||
|
// Answer response data. eg:
|
||||||
|
// Av4: ["1.1.1.1"]
|
||||||
|
// Av6: ["2001:db8:85a3::8a2e:370:7334"]
|
||||||
|
// MX: [10, "2.2.2.2"]
|
||||||
|
Rdata []string `json:"answer"`
|
||||||
|
|
||||||
|
// Region(grouping) that answer belongs to.
|
||||||
|
RegionName string `json:"region,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a Answer) String() string {
|
||||||
|
return strings.Trim(fmt.Sprint(a.Rdata), "[]")
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetRegion associates a region with this answer.
|
||||||
|
func (a *Answer) SetRegion(name string) {
|
||||||
|
a.RegionName = name
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewAnswer creates a generic Answer with given rdata.
|
||||||
|
func NewAnswer(rdata []string) *Answer {
|
||||||
|
return &Answer{
|
||||||
|
Meta: &data.Meta{},
|
||||||
|
Rdata: rdata,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewAv4Answer creates an Answer for A record.
|
||||||
|
func NewAv4Answer(host string) *Answer {
|
||||||
|
return &Answer{
|
||||||
|
Meta: &data.Meta{},
|
||||||
|
Rdata: []string{host},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewAv6Answer creates an Answer for AAAA record.
|
||||||
|
func NewAv6Answer(host string) *Answer {
|
||||||
|
return &Answer{
|
||||||
|
Meta: &data.Meta{},
|
||||||
|
Rdata: []string{host},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewALIASAnswer creates an Answer for ALIAS record.
|
||||||
|
func NewALIASAnswer(host string) *Answer {
|
||||||
|
return &Answer{
|
||||||
|
Meta: &data.Meta{},
|
||||||
|
Rdata: []string{host},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewCNAMEAnswer creates an Answer for CNAME record.
|
||||||
|
func NewCNAMEAnswer(name string) *Answer {
|
||||||
|
return &Answer{
|
||||||
|
Meta: &data.Meta{},
|
||||||
|
Rdata: []string{name},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewTXTAnswer creates an Answer for TXT record.
|
||||||
|
func NewTXTAnswer(text string) *Answer {
|
||||||
|
return &Answer{
|
||||||
|
Meta: &data.Meta{},
|
||||||
|
Rdata: []string{text},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMXAnswer creates an Answer for MX record.
|
||||||
|
func NewMXAnswer(pri int, host string) *Answer {
|
||||||
|
return &Answer{
|
||||||
|
Meta: &data.Meta{},
|
||||||
|
Rdata: []string{strconv.Itoa(pri), host},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSRVAnswer creates an Answer for SRV record.
|
||||||
|
func NewSRVAnswer(priority, weight, port int, target string) *Answer {
|
||||||
|
return &Answer{
|
||||||
|
Meta: &data.Meta{},
|
||||||
|
Rdata: []string{
|
||||||
|
strconv.Itoa(priority),
|
||||||
|
strconv.Itoa(weight),
|
||||||
|
strconv.Itoa(port),
|
||||||
|
target,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,2 @@
|
||||||
|
// Package dns contains definitions for NS1 zones/records/answers/etc.
|
||||||
|
package dns
|
|
@ -0,0 +1,141 @@
|
||||||
|
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>
|
||||||
|
// []
|
||||||
|
}
|
|
@ -0,0 +1,76 @@
|
||||||
|
package dns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"gopkg.in/ns1/ns1-go.v2/rest/model/data"
|
||||||
|
"gopkg.in/ns1/ns1-go.v2/rest/model/filter"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Record wraps an NS1 /zone/{zone}/{domain}/{type} resource
|
||||||
|
type Record struct {
|
||||||
|
Meta *data.Meta `json:"meta,omitempty"`
|
||||||
|
|
||||||
|
ID string `json:"id,omitempty"`
|
||||||
|
Zone string `json:"zone"`
|
||||||
|
Domain string `json:"domain"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
Link string `json:"link,omitempty"`
|
||||||
|
TTL int `json:"ttl,omitempty"`
|
||||||
|
UseClientSubnet *bool `json:"use_client_subnet,omitempty"`
|
||||||
|
|
||||||
|
// Answers must all be of the same type as the record.
|
||||||
|
Answers []*Answer `json:"answers"`
|
||||||
|
// The records' filter chain.
|
||||||
|
Filters []*filter.Filter `json:"filters,omitempty"`
|
||||||
|
// The records' regions.
|
||||||
|
Regions data.Regions `json:"regions,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r Record) String() string {
|
||||||
|
return fmt.Sprintf("%s %s", r.Domain, r.Type)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewRecord takes a zone, domain and record type t and creates a *Record with
|
||||||
|
// UseClientSubnet: true & empty Answers.
|
||||||
|
func NewRecord(zone string, domain string, t string) *Record {
|
||||||
|
if !strings.HasSuffix(domain, zone) {
|
||||||
|
domain = fmt.Sprintf("%s.%s", domain, zone)
|
||||||
|
}
|
||||||
|
return &Record{
|
||||||
|
Meta: &data.Meta{},
|
||||||
|
Zone: zone,
|
||||||
|
Domain: domain,
|
||||||
|
Type: t,
|
||||||
|
Answers: []*Answer{},
|
||||||
|
Regions: data.Regions{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// LinkTo sets a Record Link to an FQDN.
|
||||||
|
// to is the FQDN of the target record whose config should be used. Does
|
||||||
|
// not have to be in the same zone.
|
||||||
|
func (r *Record) LinkTo(to string) {
|
||||||
|
r.Meta = nil
|
||||||
|
r.Answers = []*Answer{}
|
||||||
|
r.Link = to
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddAnswer adds an answer to the record.
|
||||||
|
func (r *Record) AddAnswer(ans *Answer) {
|
||||||
|
if r.Answers == nil {
|
||||||
|
r.Answers = []*Answer{}
|
||||||
|
}
|
||||||
|
|
||||||
|
r.Answers = append(r.Answers, ans)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddFilter adds a filter to the records' filter chain(ordering of filters matters).
|
||||||
|
func (r *Record) AddFilter(fil *filter.Filter) {
|
||||||
|
if r.Filters == nil {
|
||||||
|
r.Filters = []*filter.Filter{}
|
||||||
|
}
|
||||||
|
|
||||||
|
r.Filters = append(r.Filters, fil)
|
||||||
|
}
|
|
@ -0,0 +1,157 @@
|
||||||
|
package dns
|
||||||
|
|
||||||
|
import "gopkg.in/ns1/ns1-go.v2/rest/model/data"
|
||||||
|
|
||||||
|
// Zone wraps an NS1 /zone resource
|
||||||
|
type Zone struct {
|
||||||
|
// Zones have metadata tables, but no filters act on 'zone-level' meta.
|
||||||
|
Meta *data.Meta `json:"meta,omitempty"`
|
||||||
|
|
||||||
|
// Read-only fields
|
||||||
|
DNSServers []string `json:"dns_servers,omitempty"`
|
||||||
|
NetworkPools []string `json:"network_pools,omitempty"`
|
||||||
|
Pool string `json:"pool,omitempty"` // Deprecated
|
||||||
|
|
||||||
|
ID string `json:"id,omitempty"`
|
||||||
|
Zone string `json:"zone,omitempty"`
|
||||||
|
|
||||||
|
TTL int `json:"ttl,omitempty"`
|
||||||
|
NxTTL int `json:"nx_ttl,omitempty"`
|
||||||
|
Retry int `json:"retry,omitempty"`
|
||||||
|
Serial int `json:"serial,omitempty"`
|
||||||
|
Refresh int `json:"refresh,omitempty"`
|
||||||
|
Expiry int `json:"expiry,omitempty"`
|
||||||
|
Hostmaster string `json:"hostmaster,omitempty"`
|
||||||
|
|
||||||
|
// If this is a linked zone, Link points to an existing standard zone,
|
||||||
|
// reusing its configuration and records. Link is a zones' domain name.
|
||||||
|
Link *string `json:"link,omitempty"`
|
||||||
|
|
||||||
|
// Networks contains the network ids the zone is available. Most zones
|
||||||
|
// will be in the NSONE Global Network(which is id 0).
|
||||||
|
NetworkIDs []int `json:"networks,omitempty"`
|
||||||
|
Records []*ZoneRecord `json:"records,omitempty"`
|
||||||
|
|
||||||
|
// Primary contains info to enable slaving of the zone by third party dns servers.
|
||||||
|
Primary *ZonePrimary `json:"primary,omitempty"`
|
||||||
|
// Secondary contains info for slaving the zone to a primary dns server.
|
||||||
|
Secondary *ZoneSecondary `json:"secondary,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (z Zone) String() string {
|
||||||
|
return z.Zone
|
||||||
|
}
|
||||||
|
|
||||||
|
// ZoneRecord wraps Zone's "records" attribute
|
||||||
|
type ZoneRecord struct {
|
||||||
|
Domain string `json:"Domain,omitempty"`
|
||||||
|
ID string `json:"id,omitempty"`
|
||||||
|
Link string `json:"link,omitempty"`
|
||||||
|
ShortAns []string `json:"short_answers,omitempty"`
|
||||||
|
Tier int `json:"tier,omitempty"`
|
||||||
|
TTL int `json:"ttl,omitempty"`
|
||||||
|
Type string `json:"type,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ZonePrimary wraps a Zone's "primary" attribute
|
||||||
|
type ZonePrimary struct {
|
||||||
|
// Enabled determines whether AXFR queries (and optionally NOTIFY messages)
|
||||||
|
// will be enabled for the zone.
|
||||||
|
Enabled bool `json:"enabled"`
|
||||||
|
Secondaries []ZoneSecondaryServer `json:"secondaries"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ZoneSecondaryServer wraps elements of a Zone's "primary.secondary" attribute
|
||||||
|
type ZoneSecondaryServer struct {
|
||||||
|
// Read-Only
|
||||||
|
NetworkIDs []int `json:"networks,omitempty"`
|
||||||
|
|
||||||
|
IP string `json:"ip"`
|
||||||
|
Port int `json:"port,omitempty"`
|
||||||
|
Notify bool `json:"notify"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ZoneSecondary wraps a Zone's "secondary" attribute
|
||||||
|
type ZoneSecondary struct {
|
||||||
|
// Read-Only fields
|
||||||
|
Expired bool `json:"expired,omitempty"`
|
||||||
|
LastXfr int `json:"last_xfr,omitempty"`
|
||||||
|
Status string `json:"status,omitempty"`
|
||||||
|
Error *string `json:"error"`
|
||||||
|
|
||||||
|
PrimaryIP string `json:"primary_ip,omitempty"`
|
||||||
|
PrimaryPort int `json:"primary_port,omitempty"`
|
||||||
|
Enabled bool `json:"enabled"`
|
||||||
|
|
||||||
|
TSIG *TSIG `json:"tsig"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TSIG is a zones transaction signature.
|
||||||
|
type TSIG struct {
|
||||||
|
// Key is the encrypted TSIG key(read-only)
|
||||||
|
Key string `json:"key,omitempty"`
|
||||||
|
|
||||||
|
// Whether TSIG is enabled for a secondary zone.
|
||||||
|
Enabled bool `json:"enabled,omitempty"`
|
||||||
|
// Which hashing algorithm
|
||||||
|
Hash string `json:"hash,omitempty"`
|
||||||
|
// Name of the TSIG key
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewZone takes a zone domain name and creates a new zone.
|
||||||
|
func NewZone(zone string) *Zone {
|
||||||
|
z := Zone{
|
||||||
|
Zone: zone,
|
||||||
|
}
|
||||||
|
return &z
|
||||||
|
}
|
||||||
|
|
||||||
|
// MakePrimary enables Primary, disables Secondary, and sets primary's
|
||||||
|
// Secondaries to all provided ZoneSecondaryServers
|
||||||
|
func (z *Zone) MakePrimary(secondaries ...ZoneSecondaryServer) {
|
||||||
|
z.Secondary = nil
|
||||||
|
z.Primary = &ZonePrimary{
|
||||||
|
Enabled: true,
|
||||||
|
Secondaries: secondaries,
|
||||||
|
}
|
||||||
|
if z.Primary.Secondaries == nil {
|
||||||
|
z.Primary.Secondaries = make([]ZoneSecondaryServer, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MakeSecondary enables Secondary, disables Primary, and sets secondary's
|
||||||
|
// Primary_ip to provided ip.
|
||||||
|
func (z *Zone) MakeSecondary(ip string) {
|
||||||
|
z.Secondary = &ZoneSecondary{
|
||||||
|
Enabled: true,
|
||||||
|
PrimaryIP: ip,
|
||||||
|
PrimaryPort: 53,
|
||||||
|
}
|
||||||
|
z.Primary = &ZonePrimary{
|
||||||
|
Enabled: false,
|
||||||
|
Secondaries: make([]ZoneSecondaryServer, 0),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// LinkTo sets Link to a target zone domain name and unsets all other configuration properties.
|
||||||
|
// No other zone configuration properties (such as refresh, retry, etc) may be specified,
|
||||||
|
// since they are all pulled from the target zone. Linked zones, once created, cannot be
|
||||||
|
// configured at all and cannot have records added to them. They may only be deleted, which
|
||||||
|
// does not affect the target zone at all.
|
||||||
|
func (z *Zone) LinkTo(to string) {
|
||||||
|
z.Meta = nil
|
||||||
|
z.TTL = 0
|
||||||
|
z.NxTTL = 0
|
||||||
|
z.Retry = 0
|
||||||
|
z.Refresh = 0
|
||||||
|
z.Expiry = 0
|
||||||
|
z.Primary = nil
|
||||||
|
z.DNSServers = nil
|
||||||
|
z.NetworkIDs = nil
|
||||||
|
z.NetworkPools = nil
|
||||||
|
z.Hostmaster = ""
|
||||||
|
z.Pool = ""
|
||||||
|
z.Secondary = nil
|
||||||
|
z.Link = &to
|
||||||
|
}
|
|
@ -0,0 +1,2 @@
|
||||||
|
// Package model defines structures for interacting with the NS1 API.
|
||||||
|
package model
|
|
@ -0,0 +1,2 @@
|
||||||
|
// Package filter contains definitions for NS1 filter chains.
|
||||||
|
package filter
|
|
@ -0,0 +1,182 @@
|
||||||
|
package filter
|
||||||
|
|
||||||
|
// Filter wraps the values of a Record's "filters" attribute
|
||||||
|
type Filter struct {
|
||||||
|
Type string `json:"filter"`
|
||||||
|
Disabled bool `json:"disabled,omitempty"`
|
||||||
|
Config Config `json:"config"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enable a filter.
|
||||||
|
func (f *Filter) Enable() {
|
||||||
|
f.Disabled = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disable a filter.
|
||||||
|
func (f *Filter) Disable() {
|
||||||
|
f.Disabled = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Config is a flat mapping where values are simple (no slices/maps).
|
||||||
|
type Config map[string]interface{}
|
||||||
|
|
||||||
|
// NewSelFirstN returns a filter that eliminates all but the
|
||||||
|
// first N answers from the list.
|
||||||
|
func NewSelFirstN(n int) *Filter {
|
||||||
|
return &Filter{
|
||||||
|
Type: "select_first_n",
|
||||||
|
Config: Config{"N": n},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewShuffle returns a filter that randomly sorts the answers.
|
||||||
|
func NewShuffle() *Filter {
|
||||||
|
return &Filter{Type: "shuffle", Config: Config{}}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GEOGRAPHICAL FILTERS
|
||||||
|
|
||||||
|
// NewSelFirstRegion returns a filter that keeps only the answers
|
||||||
|
// that are in the same region as the first answer.
|
||||||
|
func NewSelFirstRegion() *Filter {
|
||||||
|
return &Filter{Type: "select_first_n", Config: Config{}}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewStickyRegion first sorts regions uniquely depending on the IP
|
||||||
|
// address of the requester, and then groups all answers together by
|
||||||
|
// region. The same requester always gets the same ordering of regions,
|
||||||
|
// but answers within each region may be in any order. byNetwork indicates
|
||||||
|
// whether to apply the 'stickyness' by subnet(not individual IP).
|
||||||
|
func NewStickyRegion(byNetwork bool) *Filter {
|
||||||
|
return &Filter{
|
||||||
|
Type: "sticky_region",
|
||||||
|
Config: Config{"sticky_by_network": byNetwork},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewGeofenceCountry returns a filter that fences using "country",
|
||||||
|
// "us_state", and "ca_province" metadata fields in answers. Only
|
||||||
|
// answers in the same country/state/province as the user (or
|
||||||
|
// answers with no specified location) are returned. rmNoLoc determines
|
||||||
|
// whether to remove answers without location on any match.
|
||||||
|
func NewGeofenceCountry(rmNoLoc bool) *Filter {
|
||||||
|
return &Filter{
|
||||||
|
Type: "geofence_country",
|
||||||
|
Config: Config{"remove_no_location": rmNoLoc},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewGeofenceRegional returns a filter that restricts to answers in
|
||||||
|
// same geographical region as requester. rmNoGeo determines whether
|
||||||
|
// to remove answers without georegion on any match.
|
||||||
|
func NewGeofenceRegional(rmNoGeo bool) *Filter {
|
||||||
|
return &Filter{
|
||||||
|
Type: "geofence_regional",
|
||||||
|
Config: Config{"remove_no_georegion": rmNoGeo},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewGeotargetCountry returns a filter that sorts answers by distance
|
||||||
|
// to requester by country, US state, and/or Canadian province.
|
||||||
|
func NewGeotargetCountry() *Filter {
|
||||||
|
return &Filter{Type: "geofence_country", Config: Config{}}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewGeotargetLatLong returns a filter that sorts answers by distance
|
||||||
|
// to user using lat/long.
|
||||||
|
func NewGeotargetLatLong() *Filter {
|
||||||
|
return &Filter{Type: "geotarget_latlong", Config: Config{}}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewGeotargetRegional returns a filter that sorts answers by distance
|
||||||
|
// to user by geographical region.
|
||||||
|
func NewGeotargetRegional() *Filter {
|
||||||
|
return &Filter{Type: "geotarget_regional", Config: Config{}}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NETWORK FILTERS
|
||||||
|
|
||||||
|
// NewSticky returns a filter that sorts answers uniquely depending
|
||||||
|
// on the IP address of the requester. The same requester always
|
||||||
|
// gets the same ordering of answers. byNetwork indicates whether
|
||||||
|
// to apply the 'stickyness' by subnet(not individual IP).
|
||||||
|
func NewSticky(byNetwork bool) *Filter {
|
||||||
|
return &Filter{
|
||||||
|
Type: "sticky",
|
||||||
|
Config: Config{"sticky_by_network": byNetwork},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewWeightedSticky returns a filter that shuffles answers randomly
|
||||||
|
// per-requester based on weight. byNetwork indicates whether to
|
||||||
|
// apply the 'stickyness' by subnet(not individual IP).
|
||||||
|
func NewWeightedSticky(byNetwork bool) *Filter {
|
||||||
|
return &Filter{
|
||||||
|
Type: "weighted_sticky",
|
||||||
|
Config: Config{"sticky_by_network": byNetwork},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewIPv4PrefixShuffle returns a filter that randomly selects
|
||||||
|
// IPv4 addresses from prefix list. This filter can only be used
|
||||||
|
// A records. n is the number of IPs to randomly select per answer.
|
||||||
|
func NewIPv4PrefixShuffle(n int) *Filter {
|
||||||
|
return &Filter{
|
||||||
|
Type: "ipv4_prefix_shuffle",
|
||||||
|
Config: Config{"N": n},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewNetfenceASN returns a filter that restricts to answers where
|
||||||
|
// the ASN of requester IP matches ASN list. rmNoASN determines
|
||||||
|
// whether to remove answers without asn list on any match.
|
||||||
|
func NewNetfenceASN(rmNoASN bool) *Filter {
|
||||||
|
return &Filter{
|
||||||
|
Type: "netfence_asn",
|
||||||
|
Config: Config{"remove_no_asn": rmNoASN},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewNetfencePrefix returns a filter that restricts to answers where
|
||||||
|
// requester IP matches prefix list. rmNoIPPrefix determines
|
||||||
|
// whether to remove answers without ip prefixes on any match.
|
||||||
|
func NewNetfencePrefix(rmNoIPPrefix bool) *Filter {
|
||||||
|
return &Filter{
|
||||||
|
Type: "netfence_prefix",
|
||||||
|
Config: Config{"remove_no_ip_prefixes": rmNoIPPrefix},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// STATUS FILTERS
|
||||||
|
|
||||||
|
// NewUp returns a filter that eliminates all answers where
|
||||||
|
// the 'up' metadata field is not true.
|
||||||
|
func NewUp() *Filter {
|
||||||
|
return &Filter{Type: "up", Config: Config{}}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewPriority returns a filter that fails over according to
|
||||||
|
// prioritized answer tiers.
|
||||||
|
func NewPriority() *Filter {
|
||||||
|
return &Filter{Type: "priority", Config: Config{}}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewShedLoad returns a filter that "sheds" traffic to answers
|
||||||
|
// based on load, using one of several load metrics. You must set
|
||||||
|
// values for low_watermark, high_watermark, and the configured
|
||||||
|
// load metric, for each answer you intend to subject to load
|
||||||
|
// shedding.
|
||||||
|
func NewShedLoad(metric string) *Filter {
|
||||||
|
return &Filter{
|
||||||
|
Type: "shed_load",
|
||||||
|
Config: Config{"metric": metric},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TRAFFIC FILTERS
|
||||||
|
|
||||||
|
// NewWeightedShuffle returns a filter that shuffles answers
|
||||||
|
// randomly based on their weight.
|
||||||
|
func NewWeightedShuffle() *Filter {
|
||||||
|
return &Filter{Type: "weighted_shuffle", Config: Config{}}
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
package monitor
|
||||||
|
|
||||||
|
// Config is a flat mapping where values are simple (no slices/maps).
|
||||||
|
type Config map[string]interface{}
|
|
@ -0,0 +1,2 @@
|
||||||
|
// Package monitor contains definitions for NS1 monitoring jobs.
|
||||||
|
package monitor
|
|
@ -0,0 +1,172 @@
|
||||||
|
package monitor
|
||||||
|
|
||||||
|
// Job wraps an NS1 /monitoring/jobs resource
|
||||||
|
type Job struct {
|
||||||
|
ID string `json:"id,omitempty"`
|
||||||
|
|
||||||
|
// The id of the notification list to send notifications to.
|
||||||
|
NotifyListID string `json:"notify_list"`
|
||||||
|
|
||||||
|
// Type of monitor to be run.
|
||||||
|
// Available job types:
|
||||||
|
// - http: Do an HTTP request against a webserver
|
||||||
|
// - dns: Do a DNS lookup against a nameserver
|
||||||
|
// - tcp: Connect to a TCP port on a host
|
||||||
|
// - ping: Ping a host using ICMP packets
|
||||||
|
Type string `json:"job_type"`
|
||||||
|
|
||||||
|
// Configuration dictionary(key/vals depend on the jobs' type).
|
||||||
|
Config Config `json:"config"`
|
||||||
|
|
||||||
|
// The current status of the monitor.
|
||||||
|
Status map[string]Status `json:"status,omitempty"`
|
||||||
|
|
||||||
|
// Rules for determining failure conditions.
|
||||||
|
Rules []*Rule `json:"rules"`
|
||||||
|
|
||||||
|
// List of regions in which to run the monitor.
|
||||||
|
// eg, ["dal", "sin", "sjc", "lga", "ams"]
|
||||||
|
Regions []string `json:"regions"`
|
||||||
|
|
||||||
|
// Indicates if the job is active or temporarily disabled.
|
||||||
|
Active bool `json:"active"`
|
||||||
|
|
||||||
|
// Frequency(in seconds), at which to run the monitor.
|
||||||
|
Frequency int `json:"frequency"`
|
||||||
|
|
||||||
|
// The policy for determining the monitor's global status based
|
||||||
|
// on the status of the job in all regions.
|
||||||
|
// Available policies:
|
||||||
|
// - quorum: Status change when majority status
|
||||||
|
// - all: Status change only when all regions are in agreement
|
||||||
|
// - one: Status change if any region changes
|
||||||
|
Policy string `json:"policy"`
|
||||||
|
|
||||||
|
// Controls behavior of how the job is assigned to monitoring regions.
|
||||||
|
// Currently this must be fixed — indicating monitoring regions are explicitly chosen.
|
||||||
|
RegionScope string `json:"region_scope"`
|
||||||
|
|
||||||
|
// Freeform notes to be included in any notifications about this job,
|
||||||
|
// e.g., instructions for operators who will receive the notifications.
|
||||||
|
Notes string `json:"notes,omitempty"`
|
||||||
|
|
||||||
|
// A free-form display name for the monitoring job.
|
||||||
|
Name string `json:"name"`
|
||||||
|
|
||||||
|
// Time(in seconds) between repeat notifications of a failed job.
|
||||||
|
// Set to 0 to disable repeating notifications.
|
||||||
|
NotifyRepeat int `json:"notify_repeat"`
|
||||||
|
|
||||||
|
// If true, on any apparent state change, the job is quickly re-run after
|
||||||
|
// one second to confirm the state change before notification.
|
||||||
|
RapidRecheck bool `json:"rapid_recheck"`
|
||||||
|
|
||||||
|
// Time(in seconds) after a failure to wait before sending a notification.
|
||||||
|
NotifyDelay int `json:"notify_delay"`
|
||||||
|
|
||||||
|
// If true, notifications are sent for any regional failure (and failback if desired),
|
||||||
|
// in addition to global state notifications.
|
||||||
|
NotifyRegional bool `json:"notidy_regional"`
|
||||||
|
|
||||||
|
// If true, a notification is sent when a job returns to an "up" state.
|
||||||
|
NotifyFailback bool `json:"notify_failback"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Activate a monitoring job.
|
||||||
|
func (j *Job) Activate() {
|
||||||
|
j.Active = true
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deactivate a monitoring job.
|
||||||
|
func (j *Job) Deactivate() {
|
||||||
|
j.Active = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Result wraps an element of a JobType's "results" attribute
|
||||||
|
type Result struct {
|
||||||
|
Comparators []string `json:"comparators"`
|
||||||
|
Metric bool `json:"metric"`
|
||||||
|
Validator string `json:"validator"`
|
||||||
|
ShortDesc string `json:"shortdesc"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
Desc string `json:"desc"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Status wraps an value of a Job's "status" attribute
|
||||||
|
type Status struct {
|
||||||
|
Since int `json:"since"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rule wraps an element of a Job's "rules" attribute
|
||||||
|
type Rule struct {
|
||||||
|
Key string `json:"key"`
|
||||||
|
Value interface{} `json:"value"`
|
||||||
|
Comparison string `json:"comparison"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewHTTPConfig constructs/returns a job configuration for HTTP type jobs.
|
||||||
|
// url is the URL to query. (Required)
|
||||||
|
// method is the HTTP method(valid methods are HEAD, GET, and POST).
|
||||||
|
// ua is the user agent text in the request header.
|
||||||
|
// auth is the authorization header to use in request.
|
||||||
|
// connTimeout is the timeout(in sec) to wait for query output.
|
||||||
|
func NewHTTPConfig(url, method, ua, auth string, connTimeout int) *Config {
|
||||||
|
return &Config{
|
||||||
|
"url": url, // Required
|
||||||
|
"method": method,
|
||||||
|
"user_agent": ua,
|
||||||
|
"auth": auth,
|
||||||
|
"connection_timeout": connTimeout,
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDNSConfig constructs/returns a job configuration for DNS type jobs.
|
||||||
|
// host is the IP address or hostname of the nameserver to query. (Required)
|
||||||
|
// domain name to query. (Required)
|
||||||
|
// port is the dns port to query on host.
|
||||||
|
// t is the type of the DNS record type to query.
|
||||||
|
// respTimeout is the timeout(in ms) after sending query to wait for the output.
|
||||||
|
func NewDNSConfig(host, domain string, port int, t string, respTimeout int) *Config {
|
||||||
|
return &Config{
|
||||||
|
"host": host, // Required
|
||||||
|
"domain": domain, // Required
|
||||||
|
"port": port,
|
||||||
|
"type": t,
|
||||||
|
"response_timeout": respTimeout,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewTCPConfig constructs/returns a job configuration for TCP type jobs.
|
||||||
|
// host is the IP address or hostname to connect to. (Required)
|
||||||
|
// port is the tcp port to connect to on host. (Required)
|
||||||
|
// connTimeout is the timeout(in ms) before giving up on trying to connect.
|
||||||
|
// respTimeout is the timeout(in sec) after connecting to wait for output.
|
||||||
|
// send is the string to send to the host upon connecting.
|
||||||
|
// ssl determines whether to attempt negotiating an SSL connection.
|
||||||
|
func NewTCPConfig(host string, port, connTimeout, respTimeout int, send string, ssl bool) *Config {
|
||||||
|
return &Config{
|
||||||
|
"host": host, // Required
|
||||||
|
"port": port, // Required
|
||||||
|
"connection_timeout": connTimeout,
|
||||||
|
"response_timeout": respTimeout,
|
||||||
|
"send": send,
|
||||||
|
"ssl": ssl,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewPINGConfig constructs/returns a job configuration for PING type jobs.
|
||||||
|
// host is the IP address or hostname to ping. (Required)
|
||||||
|
// timeout is the timeout(in ms) before marking the host as failed.
|
||||||
|
// count is the number of packets to send.
|
||||||
|
// interval is the minimum time(in ms) to wait between sending each packet.
|
||||||
|
func NewPINGConfig(host string, timeout, count, interval int) *Config {
|
||||||
|
return &Config{
|
||||||
|
"host": host, // Required
|
||||||
|
"timeout": timeout,
|
||||||
|
"count": count,
|
||||||
|
"interval": interval,
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,97 @@
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,72 @@
|
||||||
|
package monitor
|
||||||
|
|
||||||
|
// NotifyList wraps notifications.
|
||||||
|
type NotifyList struct {
|
||||||
|
ID string `json:"id,omitempty"`
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
|
Notifications []*Notification `json:"notify_list,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Notification represents endpoint to alert to.
|
||||||
|
type Notification struct {
|
||||||
|
Type string `json:"type,omitempty"`
|
||||||
|
Config Config `json:"config,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewNotifyList returns a notify list that alerts via the given notifications.
|
||||||
|
func NewNotifyList(name string, nl ...*Notification) *NotifyList {
|
||||||
|
if nl == nil {
|
||||||
|
nl = []*Notification{}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &NotifyList{Name: name, Notifications: nl}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewUserNotification returns a notification that alerts via user.
|
||||||
|
func NewUserNotification(username string) *Notification {
|
||||||
|
return &Notification{
|
||||||
|
Type: "user",
|
||||||
|
Config: Config{"user": username}}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewEmailNotification returns a notification that alerts via email.
|
||||||
|
func NewEmailNotification(email string) *Notification {
|
||||||
|
return &Notification{
|
||||||
|
Type: "email",
|
||||||
|
Config: Config{"email": email}}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewFeedNotification returns a notification that alerts via datafeed.
|
||||||
|
func NewFeedNotification(sourceID string) *Notification {
|
||||||
|
return &Notification{
|
||||||
|
Type: "datafeed",
|
||||||
|
Config: Config{"sourceid": sourceID}}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewWebNotification returns a notification that alerts via webhook.
|
||||||
|
func NewWebNotification(url string) *Notification {
|
||||||
|
return &Notification{
|
||||||
|
Type: "webhook",
|
||||||
|
Config: Config{"url": url}}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewPagerDutyNotification returns a notification that alerts via pagerduty.
|
||||||
|
func NewPagerDutyNotification(key string) *Notification {
|
||||||
|
return &Notification{
|
||||||
|
Type: "pagerduty",
|
||||||
|
Config: Config{"service_key": key}}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewHipChatNotification returns a notification that alerts via hipchat.
|
||||||
|
func NewHipChatNotification(token, room string) *Notification {
|
||||||
|
return &Notification{
|
||||||
|
Type: "hipchat",
|
||||||
|
Config: Config{"token": token, "room": room}}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSlackNotification returns a notification that alerts via slack.
|
||||||
|
func NewSlackNotification(url, username, channel string) *Notification {
|
||||||
|
return &Notification{
|
||||||
|
Type: "slack",
|
||||||
|
Config: Config{"url": url, "username": username, "channel": channel}}
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
package model
|
||||||
|
|
||||||
|
// // GetQPSStats returns current queries per second (QPS) for the account
|
||||||
|
// func (c APIClient) GetQPSStats() (v float64, err error) {
|
||||||
|
// var s map[string]float64
|
||||||
|
// _, err = c.doHTTPUnmarshal("GET", "https://api.nsone.net/v1/stats/qps", nil, &s)
|
||||||
|
// if err != nil {
|
||||||
|
// return v, err
|
||||||
|
// }
|
||||||
|
// v, found := s["qps"]
|
||||||
|
// if !found {
|
||||||
|
// return v, errors.New("Could not find 'qps' key in returned data")
|
||||||
|
// }
|
||||||
|
// return v, nil
|
||||||
|
// }
|
|
@ -0,0 +1,108 @@
|
||||||
|
package rest
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"gopkg.in/ns1/ns1-go.v2/rest/model/monitor"
|
||||||
|
)
|
||||||
|
|
||||||
|
// JobsService handles 'monitoring/jobs' endpoint.
|
||||||
|
type JobsService service
|
||||||
|
|
||||||
|
// List returns all monitoring jobs for the account.
|
||||||
|
//
|
||||||
|
// NS1 API docs: https://ns1.com/api/#jobs-get
|
||||||
|
func (s *JobsService) List() ([]*monitor.Job, *http.Response, error) {
|
||||||
|
req, err := s.client.NewRequest("GET", "monitoring/jobs", nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
mjl := []*monitor.Job{}
|
||||||
|
resp, err := s.client.Do(req, &mjl)
|
||||||
|
if err != nil {
|
||||||
|
return nil, resp, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return mjl, resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get takes an ID and returns details for a specific monitoring job.
|
||||||
|
//
|
||||||
|
// NS1 API docs: https://ns1.com/api/#jobs-jobid-get
|
||||||
|
func (s *JobsService) Get(id string) (*monitor.Job, *http.Response, error) {
|
||||||
|
path := fmt.Sprintf("%s/%s", "monitoring/jobs", id)
|
||||||
|
|
||||||
|
req, err := s.client.NewRequest("GET", path, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var mj monitor.Job
|
||||||
|
resp, err := s.client.Do(req, &mj)
|
||||||
|
if err != nil {
|
||||||
|
return nil, resp, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &mj, resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create takes a *MonitoringJob and creates a new monitoring job.
|
||||||
|
//
|
||||||
|
// NS1 API docs: https://ns1.com/api/#jobs-put
|
||||||
|
func (s *JobsService) Create(mj *monitor.Job) (*http.Response, error) {
|
||||||
|
path := fmt.Sprintf("%s/%s", "monitoring/jobs", mj.ID)
|
||||||
|
|
||||||
|
req, err := s.client.NewRequest("PUT", path, &mj)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update mon jobs' fields with data from api(ensure consistent)
|
||||||
|
resp, err := s.client.Do(req, &mj)
|
||||||
|
if err != nil {
|
||||||
|
return resp, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update takes a *MonitoringJob and change the configuration details of an existing monitoring job.
|
||||||
|
//
|
||||||
|
// NS1 API docs: https://ns1.com/api/#jobs-jobid-post
|
||||||
|
func (s *JobsService) Update(mj *monitor.Job) (*http.Response, error) {
|
||||||
|
path := fmt.Sprintf("%s/%s", "monitoring/jobs", mj.ID)
|
||||||
|
|
||||||
|
req, err := s.client.NewRequest("POST", path, &mj)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update mon jobs' fields with data from api(ensure consistent)
|
||||||
|
resp, err := s.client.Do(req, &mj)
|
||||||
|
if err != nil {
|
||||||
|
return resp, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete takes an ID and immediately terminates and deletes and existing monitoring job.
|
||||||
|
//
|
||||||
|
// NS1 API docs: https://ns1.com/api/#jobs-jobid-delete
|
||||||
|
func (s *JobsService) Delete(id string) (*http.Response, error) {
|
||||||
|
path := fmt.Sprintf("%s/%s", "monitoring/jobs", id)
|
||||||
|
|
||||||
|
req, err := s.client.NewRequest("DELETE", path, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := s.client.Do(req, nil)
|
||||||
|
if err != nil {
|
||||||
|
return resp, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp, nil
|
||||||
|
}
|
|
@ -0,0 +1,128 @@
|
||||||
|
package rest
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"gopkg.in/ns1/ns1-go.v2/rest/model/monitor"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NotificationsService handles 'monitoring/lists' endpoint.
|
||||||
|
type NotificationsService service
|
||||||
|
|
||||||
|
// List returns all configured notification lists.
|
||||||
|
//
|
||||||
|
// NS1 API docs: https://ns1.com/api/#lists-get
|
||||||
|
func (s *NotificationsService) List() ([]*monitor.NotifyList, *http.Response, error) {
|
||||||
|
req, err := s.client.NewRequest("GET", "lists", nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
nl := []*monitor.NotifyList{}
|
||||||
|
resp, err := s.client.Do(req, &nl)
|
||||||
|
if err != nil {
|
||||||
|
return nil, resp, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nl, resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get returns the details and notifiers associated with a specific notification list.
|
||||||
|
//
|
||||||
|
// NS1 API docs: https://ns1.com/api/#lists-listid-get
|
||||||
|
func (s *NotificationsService) Get(listID string) (*monitor.NotifyList, *http.Response, error) {
|
||||||
|
path := fmt.Sprintf("%s/%s", "lists", listID)
|
||||||
|
|
||||||
|
req, err := s.client.NewRequest("GET", path, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var nl monitor.NotifyList
|
||||||
|
resp, err := s.client.Do(req, &nl)
|
||||||
|
if err != nil {
|
||||||
|
switch err.(type) {
|
||||||
|
case *Error:
|
||||||
|
if err.(*Error).Message == "unknown notification list" {
|
||||||
|
return nil, resp, ErrListMissing
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return nil, resp, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &nl, resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create takes a *NotifyList and creates a new notify list.
|
||||||
|
//
|
||||||
|
// NS1 API docs: https://ns1.com/api/#lists-put
|
||||||
|
func (s *NotificationsService) Create(nl *monitor.NotifyList) (*http.Response, error) {
|
||||||
|
req, err := s.client.NewRequest("PUT", "lists", &nl)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update notify list fields with data from api(ensure consistent)
|
||||||
|
resp, err := s.client.Do(req, &nl)
|
||||||
|
if err != nil {
|
||||||
|
switch err.(type) {
|
||||||
|
case *Error:
|
||||||
|
if err.(*Error).Message == fmt.Sprintf("notification list with name \"%s\" exists", nl.Name) {
|
||||||
|
return resp, ErrListExists
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return resp, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update adds or removes entries or otherwise update a notification list.
|
||||||
|
//
|
||||||
|
// NS1 API docs: https://ns1.com/api/#list-listid-post
|
||||||
|
func (s *NotificationsService) Update(nl *monitor.NotifyList) (*http.Response, error) {
|
||||||
|
path := fmt.Sprintf("%s/%s", "lists", nl.ID)
|
||||||
|
|
||||||
|
req, err := s.client.NewRequest("POST", path, &nl)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update mon lists' fields with data from api(ensure consistent)
|
||||||
|
resp, err := s.client.Do(req, &nl)
|
||||||
|
if err != nil {
|
||||||
|
return resp, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete immediately deletes an existing notification list.
|
||||||
|
//
|
||||||
|
// NS1 API docs: https://ns1.com/api/#lists-listid-delete
|
||||||
|
func (s *NotificationsService) Delete(listID string) (*http.Response, error) {
|
||||||
|
path := fmt.Sprintf("%s/%s", "lists", listID)
|
||||||
|
|
||||||
|
req, err := s.client.NewRequest("DELETE", path, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := s.client.Do(req, nil)
|
||||||
|
if err != nil {
|
||||||
|
return resp, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ErrListExists bundles PUT create error.
|
||||||
|
ErrListExists = errors.New("Notify List already exists.")
|
||||||
|
// ErrListMissing bundles GET/POST/DELETE error.
|
||||||
|
ErrListMissing = errors.New("Notify List does not exist.")
|
||||||
|
)
|
|
@ -0,0 +1,134 @@
|
||||||
|
package rest
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"gopkg.in/ns1/ns1-go.v2/rest/model/dns"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RecordsService handles 'zones/ZONE/DOMAIN/TYPE' endpoint.
|
||||||
|
type RecordsService service
|
||||||
|
|
||||||
|
// Get takes a zone, domain and record type t and returns full configuration for a DNS record.
|
||||||
|
//
|
||||||
|
// NS1 API docs: https://ns1.com/api/#record-get
|
||||||
|
func (s *RecordsService) Get(zone, domain, t string) (*dns.Record, *http.Response, error) {
|
||||||
|
path := fmt.Sprintf("zones/%s/%s/%s", zone, domain, t)
|
||||||
|
|
||||||
|
req, err := s.client.NewRequest("GET", path, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var r dns.Record
|
||||||
|
resp, err := s.client.Do(req, &r)
|
||||||
|
if err != nil {
|
||||||
|
switch err.(type) {
|
||||||
|
case *Error:
|
||||||
|
if err.(*Error).Message == "record not found" {
|
||||||
|
return nil, resp, ErrRecordMissing
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return nil, resp, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &r, resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create takes a *Record and creates a new DNS record in the specified zone, for the specified domain, of the given record type.
|
||||||
|
//
|
||||||
|
// The given record must have at least one answer.
|
||||||
|
// NS1 API docs: https://ns1.com/api/#record-put
|
||||||
|
func (s *RecordsService) Create(r *dns.Record) (*http.Response, error) {
|
||||||
|
path := fmt.Sprintf("zones/%s/%s/%s", r.Zone, r.Domain, r.Type)
|
||||||
|
|
||||||
|
req, err := s.client.NewRequest("PUT", path, &r)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update record fields with data from api(ensure consistent)
|
||||||
|
resp, err := s.client.Do(req, &r)
|
||||||
|
if err != nil {
|
||||||
|
switch err.(type) {
|
||||||
|
case *Error:
|
||||||
|
switch err.(*Error).Message {
|
||||||
|
case "zone not found":
|
||||||
|
return resp, ErrZoneMissing
|
||||||
|
case "record already exists":
|
||||||
|
return resp, ErrRecordExists
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return resp, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update takes a *Record and modifies configuration details for an existing DNS record.
|
||||||
|
//
|
||||||
|
// Only the fields to be updated are required in the given record.
|
||||||
|
// NS1 API docs: https://ns1.com/api/#record-post
|
||||||
|
func (s *RecordsService) Update(r *dns.Record) (*http.Response, error) {
|
||||||
|
path := fmt.Sprintf("zones/%s/%s/%s", r.Zone, r.Domain, r.Type)
|
||||||
|
|
||||||
|
req, err := s.client.NewRequest("POST", path, &r)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update records fields with data from api(ensure consistent)
|
||||||
|
resp, err := s.client.Do(req, &r)
|
||||||
|
if err != nil {
|
||||||
|
switch err.(type) {
|
||||||
|
case *Error:
|
||||||
|
switch err.(*Error).Message {
|
||||||
|
case "zone not found":
|
||||||
|
return resp, ErrZoneMissing
|
||||||
|
case "record already exists":
|
||||||
|
return resp, ErrRecordExists
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return resp, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete takes a zone, domain and record type t and removes an existing record and all associated answers and configuration details.
|
||||||
|
//
|
||||||
|
// NS1 API docs: https://ns1.com/api/#record-delete
|
||||||
|
func (s *RecordsService) Delete(zone string, domain string, t string) (*http.Response, error) {
|
||||||
|
path := fmt.Sprintf("zones/%s/%s/%s", zone, domain, t)
|
||||||
|
|
||||||
|
req, err := s.client.NewRequest("DELETE", path, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := s.client.Do(req, nil)
|
||||||
|
if err != nil {
|
||||||
|
switch err.(type) {
|
||||||
|
case *Error:
|
||||||
|
if err.(*Error).Message == "record not found" {
|
||||||
|
return resp, ErrRecordMissing
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return resp, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ErrRecordExists bundles PUT create error.
|
||||||
|
ErrRecordExists = errors.New("Record already exists.")
|
||||||
|
// ErrRecordMissing bundles GET/POST/DELETE error.
|
||||||
|
ErrRecordMissing = errors.New("Record does not exist.")
|
||||||
|
)
|
|
@ -0,0 +1,15 @@
|
||||||
|
package rest
|
||||||
|
|
||||||
|
// // GetQPSStats returns current queries per second (QPS) for the account
|
||||||
|
// func (c APIClient) GetQPSStats() (v float64, err error) {
|
||||||
|
// var s map[string]float64
|
||||||
|
// _, err = c.doHTTPUnmarshal("GET", "https://api.nsone.net/v1/stats/qps", nil, &s)
|
||||||
|
// if err != nil {
|
||||||
|
// return v, err
|
||||||
|
// }
|
||||||
|
// v, found := s["qps"]
|
||||||
|
// if !found {
|
||||||
|
// return v, errors.New("Could not find 'qps' key in returned data")
|
||||||
|
// }
|
||||||
|
// return v, nil
|
||||||
|
// }
|
|
@ -0,0 +1,42 @@
|
||||||
|
package rest
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DoerFunc satisfies Interface. DoerFuncs are useful for adding
|
||||||
|
// logging/instrumentation to the http.Client that is used
|
||||||
|
// within the rest.APIClient.
|
||||||
|
type DoerFunc func(*http.Request) (*http.Response, error)
|
||||||
|
|
||||||
|
// Do is implementation of rest.Doer interface. Calls itself on the
|
||||||
|
// given http.Request.
|
||||||
|
func (f DoerFunc) Do(r *http.Request) (*http.Response, error) {
|
||||||
|
return f(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// A Decorator wraps a Doer with extra behavior, and doesnt
|
||||||
|
// affect the behavior of other instances of the same type.
|
||||||
|
type Decorator func(Doer) Doer
|
||||||
|
|
||||||
|
// Decorate decorates a Doer c with all the given Decorators, in order.
|
||||||
|
// Core object(Doer instance) that we want to apply layers(Decorator slice) to.
|
||||||
|
func Decorate(d Doer, ds ...Decorator) Doer {
|
||||||
|
decorated := d
|
||||||
|
for _, decorate := range ds {
|
||||||
|
decorated = decorate(decorated)
|
||||||
|
}
|
||||||
|
return decorated
|
||||||
|
}
|
||||||
|
|
||||||
|
// Logging returns a Decorator that logs a Doer's requests.
|
||||||
|
// Dependency injection for the logger instance(inside the closures environment).
|
||||||
|
func Logging(l *log.Logger) Decorator {
|
||||||
|
return func(d Doer) Doer {
|
||||||
|
return DoerFunc(func(r *http.Request) (*http.Response, error) {
|
||||||
|
l.Printf("%s: %s %s", r.UserAgent(), r.Method, r.URL)
|
||||||
|
return d.Do(r)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,144 @@
|
||||||
|
package rest
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"gopkg.in/ns1/ns1-go.v2/rest/model/dns"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ZonesService handles 'zones' endpoint.
|
||||||
|
type ZonesService service
|
||||||
|
|
||||||
|
// List returns all active zones and basic zone configuration details for each.
|
||||||
|
//
|
||||||
|
// NS1 API docs: https://ns1.com/api/#zones-get
|
||||||
|
func (s *ZonesService) List() ([]*dns.Zone, *http.Response, error) {
|
||||||
|
req, err := s.client.NewRequest("GET", "zones", nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
zl := []*dns.Zone{}
|
||||||
|
resp, err := s.client.Do(req, &zl)
|
||||||
|
if err != nil {
|
||||||
|
return nil, resp, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return zl, resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get takes a zone name and returns a single active zone and its basic configuration details.
|
||||||
|
//
|
||||||
|
// NS1 API docs: https://ns1.com/api/#zones-zone-get
|
||||||
|
func (s *ZonesService) Get(zone string) (*dns.Zone, *http.Response, error) {
|
||||||
|
path := fmt.Sprintf("zones/%s", zone)
|
||||||
|
|
||||||
|
req, err := s.client.NewRequest("GET", path, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var z dns.Zone
|
||||||
|
resp, err := s.client.Do(req, &z)
|
||||||
|
if err != nil {
|
||||||
|
switch err.(type) {
|
||||||
|
case *Error:
|
||||||
|
if err.(*Error).Message == "zone not found" {
|
||||||
|
return nil, resp, ErrZoneMissing
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return nil, resp, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &z, resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create takes a *Zone and creates a new DNS zone.
|
||||||
|
//
|
||||||
|
// NS1 API docs: https://ns1.com/api/#zones-put
|
||||||
|
func (s *ZonesService) Create(z *dns.Zone) (*http.Response, error) {
|
||||||
|
path := fmt.Sprintf("zones/%s", z.Zone)
|
||||||
|
|
||||||
|
req, err := s.client.NewRequest("PUT", path, &z)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update zones fields with data from api(ensure consistent)
|
||||||
|
resp, err := s.client.Do(req, &z)
|
||||||
|
if err != nil {
|
||||||
|
switch err.(type) {
|
||||||
|
case *Error:
|
||||||
|
if err.(*Error).Message == "zone already exists" {
|
||||||
|
return resp, ErrZoneExists
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return resp, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update takes a *Zone and modifies basic details of a DNS zone.
|
||||||
|
//
|
||||||
|
// NS1 API docs: https://ns1.com/api/#zones-post
|
||||||
|
func (s *ZonesService) Update(z *dns.Zone) (*http.Response, error) {
|
||||||
|
path := fmt.Sprintf("zones/%s", z.Zone)
|
||||||
|
|
||||||
|
req, err := s.client.NewRequest("POST", path, &z)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update zones fields with data from api(ensure consistent)
|
||||||
|
resp, err := s.client.Do(req, &z)
|
||||||
|
if err != nil {
|
||||||
|
switch err.(type) {
|
||||||
|
case *Error:
|
||||||
|
if err.(*Error).Message == "zone not found" {
|
||||||
|
return resp, ErrZoneMissing
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return resp, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete takes a zone and destroys an existing DNS zone and all records in the zone.
|
||||||
|
//
|
||||||
|
// NS1 API docs: https://ns1.com/api/#zones-delete
|
||||||
|
func (s *ZonesService) Delete(zone string) (*http.Response, error) {
|
||||||
|
path := fmt.Sprintf("zones/%s", zone)
|
||||||
|
|
||||||
|
req, err := s.client.NewRequest("DELETE", path, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := s.client.Do(req, nil)
|
||||||
|
if err != nil {
|
||||||
|
switch err.(type) {
|
||||||
|
case *Error:
|
||||||
|
if err.(*Error).Message == "zone not found" {
|
||||||
|
return resp, ErrZoneMissing
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return resp, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ErrZoneExists bundles PUT create error.
|
||||||
|
ErrZoneExists = errors.New("Zone already exists.")
|
||||||
|
// ErrZoneMissing bundles GET/POST/DELETE error.
|
||||||
|
ErrZoneMissing = errors.New("Zone does not exist.")
|
||||||
|
)
|
|
@ -2959,6 +2959,12 @@
|
||||||
"revision": "766e555c68dc8bda90d197ee8946c37519c19409",
|
"revision": "766e555c68dc8bda90d197ee8946c37519c19409",
|
||||||
"revisionTime": "2017-01-17T13:00:17Z"
|
"revisionTime": "2017-01-17T13:00:17Z"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"checksumSHA1": "U0s6Vq/AxUhEEsnORw2DnZ4cw1c=",
|
||||||
|
"path": "gopkg.in/ns1/ns1-go.v2",
|
||||||
|
"revision": "d8d10b7f448291ddbdce48d4594fb1b667014c8b",
|
||||||
|
"revisionTime": "2016-11-05T01:14:08Z"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"checksumSHA1": "mkLQOQwQwoUc9Kr9+PaVGrKUzI4=",
|
"checksumSHA1": "mkLQOQwQwoUc9Kr9+PaVGrKUzI4=",
|
||||||
"path": "gopkg.in/resty.v0",
|
"path": "gopkg.in/resty.v0",
|
||||||
|
|
|
@ -41,6 +41,7 @@ body.layout-mailgun,
|
||||||
body.layout-mysql,
|
body.layout-mysql,
|
||||||
body.layout-newrelic,
|
body.layout-newrelic,
|
||||||
body.layout-nomad,
|
body.layout-nomad,
|
||||||
|
body.layout-ns1,
|
||||||
body.layout-openstack,
|
body.layout-openstack,
|
||||||
body.layout-opsgenie,
|
body.layout-opsgenie,
|
||||||
body.layout-packet,
|
body.layout-packet,
|
||||||
|
|
|
@ -0,0 +1,36 @@
|
||||||
|
---
|
||||||
|
layout: "ns1"
|
||||||
|
page_title: "Provider: NS1"
|
||||||
|
sidebar_current: "docs-ns1-index"
|
||||||
|
description: |-
|
||||||
|
The [NS1](https://ns1.com/) provider is used to interact with the resources supported by NS1.
|
||||||
|
---
|
||||||
|
|
||||||
|
# NS1 Provider
|
||||||
|
|
||||||
|
The NS1 provider exposes resources to interact with the NS1 REST API. The provider needs to be configured
|
||||||
|
with the proper credentials before it can be used.
|
||||||
|
|
||||||
|
Use the navigation to the left to read about the available resources.
|
||||||
|
|
||||||
|
## Example Usage
|
||||||
|
|
||||||
|
```
|
||||||
|
# Configure the NS1 provider
|
||||||
|
provider "ns1" {
|
||||||
|
apikey = "${var.ns1_apikey}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Create a new zone
|
||||||
|
resource "ns1_zone" "foobar" {
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Argument Reference
|
||||||
|
|
||||||
|
The following arguments are supported:
|
||||||
|
|
||||||
|
* `apikey` - (Required) NS1 API token. It must be provided, but it can also
|
||||||
|
be sourced from the `NS1_API_KEY` environment variable.
|
||||||
|
|
|
@ -0,0 +1,60 @@
|
||||||
|
---
|
||||||
|
layout: "ns1"
|
||||||
|
page_title: "NS1: ns1_apikey"
|
||||||
|
sidebar_current: "docs-ns1-resource-apikey"
|
||||||
|
description: |-
|
||||||
|
Provides a NS1 Api Key resource.
|
||||||
|
---
|
||||||
|
|
||||||
|
# ns1\_apikey
|
||||||
|
|
||||||
|
Provides a NS1 Api Key resource. This can be used to create, modify, and delete api keys.
|
||||||
|
|
||||||
|
## Example Usage
|
||||||
|
|
||||||
|
```
|
||||||
|
resource "ns1_team" "example" {
|
||||||
|
name = "Example team"
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "ns1_apikey" "example" {
|
||||||
|
name = "Example key"
|
||||||
|
teams = ["${ns1_team.example.id}"]
|
||||||
|
permissions = {
|
||||||
|
dns_view_zones = false
|
||||||
|
account_manage_users = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Argument Reference
|
||||||
|
|
||||||
|
The following arguments are supported:
|
||||||
|
|
||||||
|
* `name` - (Required) The free form name of the apikey.
|
||||||
|
* `key` - (Required) The apikeys authentication token.
|
||||||
|
* `teams` - (Required) The teams that the apikey belongs to.
|
||||||
|
* `permissions` - (Optional) The allowed permissions of the apikey. Permissions documented below.
|
||||||
|
|
||||||
|
Permissions (`permissions`) support the following:
|
||||||
|
|
||||||
|
* `dns_view_zones` - (Optional) Whether the apikey can view the accounts zones.
|
||||||
|
* `dns_manage_zones` - (Optional) Whether the apikey can modify the accounts zones.
|
||||||
|
* `dns_zones_allow_by_default` - (Optional) If true, enable the `dns_zones_allow` list, otherwise enable the `dns_zones_deny` list.
|
||||||
|
* `dns_zones_allow` - (Optional) List of zones that the apikey may access.
|
||||||
|
* `dns_zones_deny` - (Optional) List of zones that the apikey may not access.
|
||||||
|
* `data_push_to_datafeeds` - (Optional) Whether the apikey can publish to data feeds.
|
||||||
|
* `data_manage_datasources` - (Optional) Whether the apikey can modify data sources.
|
||||||
|
* `data_manage_datafeeds` - (Optional) Whether the apikey can modify data feeds.
|
||||||
|
* `account_manage_users` - (Optional) Whether the apikey can modify account users.
|
||||||
|
* `account_manage_payment_methods` - (Optional) Whether the apikey can modify account payment methods.
|
||||||
|
* `account_manage_plan` - (Optional) Whether the apikey can modify the account plan.
|
||||||
|
* `account_manage_teams` - (Optional) Whether the apikey can modify other teams in the account.
|
||||||
|
* `account_manage_apikeys` - (Optional) Whether the apikey can modify account apikeys.
|
||||||
|
* `account_manage_account_settings` - (Optional) Whether the apikey can modify account settings.
|
||||||
|
* `account_view_activity_log` - (Optional) Whether the apikey can view activity logs.
|
||||||
|
* `account_view_invoices` - (Optional) Whether the apikey can view invoices.
|
||||||
|
* `monitoring_manage_lists` - (Optional) Whether the apikey can modify notification lists.
|
||||||
|
* `monitoring_manage_jobs` - (Optional) Whether the apikey can modify monitoring jobs.
|
||||||
|
* `monitoring_view_jobs` - (Optional) Whether the apikey can view monitoring jobs.
|
||||||
|
|
|
@ -0,0 +1,45 @@
|
||||||
|
---
|
||||||
|
layout: "ns1"
|
||||||
|
page_title: "NS1: ns1_datafeed"
|
||||||
|
sidebar_current: "docs-ns1-resource-datafeed"
|
||||||
|
description: |-
|
||||||
|
Provides a NS1 Data Feed resource.
|
||||||
|
---
|
||||||
|
|
||||||
|
# ns1\_datafeed
|
||||||
|
|
||||||
|
Provides a NS1 Data Feed resource. This can be used to create, modify, and delete data feeds.
|
||||||
|
|
||||||
|
## Example Usage
|
||||||
|
|
||||||
|
```
|
||||||
|
resource "ns1_datasource" "example" {
|
||||||
|
name = "example"
|
||||||
|
sourcetype = "nsone_v1"
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "ns1_datafeed" "uswest_feed" {
|
||||||
|
name = "uswest_feed"
|
||||||
|
source_id = "${ns1_datasource.example.id}"
|
||||||
|
config = {
|
||||||
|
label = "uswest"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "ns1_datafeed" "useast_feed" {
|
||||||
|
name = "useast_feed"
|
||||||
|
source_id = "${ns1_datasource.example.id}"
|
||||||
|
config = {
|
||||||
|
label = "useast"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Argument Reference
|
||||||
|
|
||||||
|
The following arguments are supported:
|
||||||
|
|
||||||
|
* `source_id` - (Required) The data source id that this feed is connected to.
|
||||||
|
* `name` - (Required) The free form name of the data feed.
|
||||||
|
* `config` - (Optional) The feeds configuration matching the specification in 'feed\_config' from /data/sourcetypes.
|
||||||
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
---
|
||||||
|
layout: "ns1"
|
||||||
|
page_title: "NS1: ns1_datasource"
|
||||||
|
sidebar_current: "docs-ns1-resource-datasource"
|
||||||
|
description: |-
|
||||||
|
Provides a NS1 Data Source resource.
|
||||||
|
---
|
||||||
|
|
||||||
|
# ns1\_datasource
|
||||||
|
|
||||||
|
Provides a NS1 Data Source resource. This can be used to create, modify, and delete data sources.
|
||||||
|
|
||||||
|
## Example Usage
|
||||||
|
|
||||||
|
```
|
||||||
|
resource "ns1_datasource" "example" {
|
||||||
|
name = "example"
|
||||||
|
sourcetype = "nsone_v1"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Argument Reference
|
||||||
|
|
||||||
|
The following arguments are supported:
|
||||||
|
|
||||||
|
* `name` - (Required) The free form name of the data source.
|
||||||
|
* `sourcetype` - (Required) The data sources type, listed in API endpoint https://api.nsone.net/v1/data/sourcetypes.
|
||||||
|
* `config` - (Optional) The data source configuration, determined by its type.
|
||||||
|
|
|
@ -0,0 +1,62 @@
|
||||||
|
---
|
||||||
|
layout: "ns1"
|
||||||
|
page_title: "NS1: ns1_monitoringjob"
|
||||||
|
sidebar_current: "docs-ns1-resource-monitoringjob"
|
||||||
|
description: |-
|
||||||
|
Provides a NS1 Monitoring Job resource.
|
||||||
|
---
|
||||||
|
|
||||||
|
# ns1\_monitoringjob
|
||||||
|
|
||||||
|
Provides a NS1 Monitoring Job resource. This can be used to create, modify, and delete monitoring jobs.
|
||||||
|
|
||||||
|
## Example Usage
|
||||||
|
|
||||||
|
```
|
||||||
|
resource "ns1_monitoringjob" "uswest_monitor" {
|
||||||
|
name = "uswest"
|
||||||
|
active = true
|
||||||
|
regions = ["sjc", "sin", "lga"]
|
||||||
|
job_type = "tcp"
|
||||||
|
frequency = 60
|
||||||
|
rapid_recheck = true
|
||||||
|
policy = "quorum"
|
||||||
|
config = {
|
||||||
|
send = "HEAD / HTTP/1.0\r\n\r\n"
|
||||||
|
port = 80
|
||||||
|
host = "example-elb-uswest.aws.amazon.com"
|
||||||
|
}
|
||||||
|
rules = {
|
||||||
|
value = "200 OK"
|
||||||
|
comparison = "contains"
|
||||||
|
key = "output"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Argument Reference
|
||||||
|
|
||||||
|
The following arguments are supported:
|
||||||
|
|
||||||
|
* `name` - (Required) The free-form display name for the monitoring job.
|
||||||
|
* `job_type` - (Required) The type of monitoring job to be run.
|
||||||
|
* `active` - (Required) Indicates if the job is active or temporaril.y disabled.
|
||||||
|
* `regions` - (Required) The list of region codes in which to run the monitoring job.
|
||||||
|
* `frequency` - (Required) The frequency, in seconds, at which to run the monitoring job in each region.
|
||||||
|
* `rapid_recheck` - (Required) If true, on any apparent state change, the job is quickly re-run after one second to confirm the state change before notification.
|
||||||
|
* `policy` - (Required) The policy for determining the monitor's global status based on the status of the job in all regions.
|
||||||
|
* `config` - (Required) A configuration dictionary with keys and values depending on the jobs' type.
|
||||||
|
* `notify_delay` - (Optional) The time in seconds after a failure to wait before sending a notification.
|
||||||
|
* `notify_repeat` - (Optional) The time in seconds between repeat notifications of a failed job.
|
||||||
|
* `notify_failback` - (Optional) If true, a notification is sent when a job returns to an "up" state.
|
||||||
|
* `notify_regional` - (Optional) If true, notifications are sent for any regional failure (and failback if desired), in addition to global state notifications.
|
||||||
|
* `notify_list` - (Optional) The id of the notification list to send notifications to.
|
||||||
|
* `notes` - (Optional) Freeform notes to be included in any notifications about this job.
|
||||||
|
* `rules` - (Optional) A list of rules for determining failure conditions. Job Rules are documented below.
|
||||||
|
|
||||||
|
Monitoring Job Rules (`rules`) support the following:
|
||||||
|
|
||||||
|
* `key` - (Required) The output key.
|
||||||
|
* `comparison` - (Required) The comparison to perform on the the output.
|
||||||
|
* `value` - (Required) The value to compare to.
|
||||||
|
|
|
@ -0,0 +1,62 @@
|
||||||
|
---
|
||||||
|
layout: "ns1"
|
||||||
|
page_title: "NS1: ns1_record"
|
||||||
|
sidebar_current: "docs-ns1-resource-record"
|
||||||
|
description: |-
|
||||||
|
Provides a NS1 Record resource.
|
||||||
|
---
|
||||||
|
|
||||||
|
# ns1\_record
|
||||||
|
|
||||||
|
Provides a NS1 Record resource. This can be used to create, modify, and delete records.
|
||||||
|
|
||||||
|
## Example Usage
|
||||||
|
|
||||||
|
```
|
||||||
|
resource "ns1_zone" "tld" {
|
||||||
|
zone = "terraform.example"
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "ns1_record" "www" {
|
||||||
|
zone = "${ns1_zone.tld.id}"
|
||||||
|
domain = "www.${ns1_zone.tld.zone}"
|
||||||
|
type = "CNAME"
|
||||||
|
ttl = 60
|
||||||
|
|
||||||
|
answers = {
|
||||||
|
answer = ["sub1.${ns1_zone.tld.zone}"]
|
||||||
|
}
|
||||||
|
answer = {
|
||||||
|
answer = ["sub2.${ns1_zone.tld.zone}"]
|
||||||
|
}
|
||||||
|
|
||||||
|
filters = {
|
||||||
|
filter = "select_first_n"
|
||||||
|
config = {N=1}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Argument Reference
|
||||||
|
|
||||||
|
The following arguments are supported:
|
||||||
|
|
||||||
|
* `zone` - (Required) The zone the record belongs to.
|
||||||
|
* `domain` - (Required) The records' domain.
|
||||||
|
* `type` - (Required) The records' RR type.
|
||||||
|
* `ttl` - (Optional) The records' time to live.
|
||||||
|
* `link` - (Optional) The target record to link to. This means this record is a 'linked' record, and it inherits all properties from its target.
|
||||||
|
* `use_client_subnet` - (Optional) Whether to use EDNS client subnet data when available(in filter chain).
|
||||||
|
* `answers` - (Optional) The list of the RDATA fields for the records' specified type. Answers are documented below.
|
||||||
|
* `filters` - (Optional) The list of NS1 filters for the record(order matters). Filters are documented below.
|
||||||
|
|
||||||
|
Answers (`answers`) support the following:
|
||||||
|
|
||||||
|
* `answer` - (Required) List of RDATA fields.
|
||||||
|
* `region` - (Required) The region this answer belongs to.
|
||||||
|
|
||||||
|
Filters (`filters`) support the following:
|
||||||
|
|
||||||
|
* `filter` - (Required) The type of filter.
|
||||||
|
* `disabled` - (Required) Determines whether the filter is applied in the filter chain.
|
||||||
|
* `config` - (Required) The filters' configuration. Simple key/value pairs determined by the filter type.
|
|
@ -0,0 +1,54 @@
|
||||||
|
---
|
||||||
|
layout: "ns1"
|
||||||
|
page_title: "NS1: ns1_team"
|
||||||
|
sidebar_current: "docs-ns1-resource-team"
|
||||||
|
description: |-
|
||||||
|
Provides a NS1 Team resource.
|
||||||
|
---
|
||||||
|
|
||||||
|
# ns1\_team
|
||||||
|
|
||||||
|
Provides a NS1 Team resource. This can be used to create, modify, and delete teams.
|
||||||
|
|
||||||
|
## Example Usage
|
||||||
|
|
||||||
|
```
|
||||||
|
# Create a new NS1 Team
|
||||||
|
resource "ns1_team" "example" {
|
||||||
|
name = "Example team"
|
||||||
|
permissions = {
|
||||||
|
dns_view_zones = false
|
||||||
|
account_manage_users = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Argument Reference
|
||||||
|
|
||||||
|
The following arguments are supported:
|
||||||
|
|
||||||
|
* `name` - (Required) The free form name of the team.
|
||||||
|
* `permissions` - (Optional) The allowed permissions of the team. Permissions documented below.
|
||||||
|
|
||||||
|
Permissions (`permissions`) support the following:
|
||||||
|
|
||||||
|
* `dns_view_zones` - (Optional) Whether the team can view the accounts zones.
|
||||||
|
* `dns_manage_zones` - (Optional) Whether the team can modify the accounts zones.
|
||||||
|
* `dns_zones_allow_by_default` - (Optional) If true, enable the `dns_zones_allow` list, otherwise enable the `dns_zones_deny` list.
|
||||||
|
* `dns_zones_allow` - (Optional) List of zones that the team may access.
|
||||||
|
* `dns_zones_deny` - (Optional) List of zones that the team may not access.
|
||||||
|
* `data_push_to_datafeeds` - (Optional) Whether the team can publish to data feeds.
|
||||||
|
* `data_manage_datasources` - (Optional) Whether the team can modify data sources.
|
||||||
|
* `data_manage_datafeeds` - (Optional) Whether the team can modify data feeds.
|
||||||
|
* `account_manage_users` - (Optional) Whether the team can modify account users.
|
||||||
|
* `account_manage_payment_methods` - (Optional) Whether the team can modify account payment methods.
|
||||||
|
* `account_manage_plan` - (Optional) Whether the team can modify the account plan.
|
||||||
|
* `account_manage_teams` - (Optional) Whether the team can modify other teams in the account.
|
||||||
|
* `account_manage_apikeys` - (Optional) Whether the team can modify account apikeys.
|
||||||
|
* `account_manage_account_settings` - (Optional) Whether the team can modify account settings.
|
||||||
|
* `account_view_activity_log` - (Optional) Whether the team can view activity logs.
|
||||||
|
* `account_view_invoices` - (Optional) Whether the team can view invoices.
|
||||||
|
* `monitoring_manage_lists` - (Optional) Whether the team can modify notification lists.
|
||||||
|
* `monitoring_manage_jobs` - (Optional) Whether the team can modify monitoring jobs.
|
||||||
|
* `monitoring_view_jobs` - (Optional) Whether the team can view monitoring jobs.
|
||||||
|
|
|
@ -0,0 +1,64 @@
|
||||||
|
---
|
||||||
|
layout: "ns1"
|
||||||
|
page_title: "NS1: ns1_user"
|
||||||
|
sidebar_current: "docs-ns1-resource-user"
|
||||||
|
description: |-
|
||||||
|
Provides a NS1 User resource.
|
||||||
|
---
|
||||||
|
|
||||||
|
# ns1\_user
|
||||||
|
|
||||||
|
Provides a NS1 User resource. Creating a user sends an invitation email to the user's email address. This can be used to create, modify, and delete users.
|
||||||
|
|
||||||
|
## Example Usage
|
||||||
|
|
||||||
|
```
|
||||||
|
resource "ns1_team" "example" {
|
||||||
|
name = "Example team"
|
||||||
|
permissions = {
|
||||||
|
dns_view_zones = false
|
||||||
|
account_manage_users = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "ns1_user" "example" {
|
||||||
|
name = "Example User"
|
||||||
|
username = "example_user"
|
||||||
|
email = "user@example.com"
|
||||||
|
teams = ["${ns1_team.example.id}"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Argument Reference
|
||||||
|
|
||||||
|
The following arguments are supported:
|
||||||
|
|
||||||
|
* `name` - (Required) The free form name of the user.
|
||||||
|
* `username` - (Required) The users login name.
|
||||||
|
* `email` - (Required) The email address of the user.
|
||||||
|
* `notify` - (Required) The Whether or not to notify the user of specified events. Only `billing` is available currently.
|
||||||
|
* `teams` - (Required) The teams that the user belongs to.
|
||||||
|
* `permissions` - (Optional) The allowed permissions of the user. Permissions documented below.
|
||||||
|
|
||||||
|
Permissions (`permissions`) support the following:
|
||||||
|
|
||||||
|
* `dns_view_zones` - (Optional) Whether the user can view the accounts zones.
|
||||||
|
* `dns_manage_zones` - (Optional) Whether the user can modify the accounts zones.
|
||||||
|
* `dns_zones_allow_by_default` - (Optional) If true, enable the `dns_zones_allow` list, otherwise enable the `dns_zones_deny` list.
|
||||||
|
* `dns_zones_allow` - (Optional) List of zones that the user may access.
|
||||||
|
* `dns_zones_deny` - (Optional) List of zones that the user may not access.
|
||||||
|
* `data_push_to_datafeeds` - (Optional) Whether the user can publish to data feeds.
|
||||||
|
* `data_manage_datasources` - (Optional) Whether the user can modify data sources.
|
||||||
|
* `data_manage_datafeeds` - (Optional) Whether the user can modify data feeds.
|
||||||
|
* `account_manage_users` - (Optional) Whether the user can modify account users.
|
||||||
|
* `account_manage_payment_methods` - (Optional) Whether the user can modify account payment methods.
|
||||||
|
* `account_manage_plan` - (Optional) Whether the user can modify the account plan.
|
||||||
|
* `account_manage_teams` - (Optional) Whether the user can modify other teams in the account.
|
||||||
|
* `account_manage_apikeys` - (Optional) Whether the user can modify account apikeys.
|
||||||
|
* `account_manage_account_settings` - (Optional) Whether the user can modify account settings.
|
||||||
|
* `account_view_activity_log` - (Optional) Whether the user can view activity logs.
|
||||||
|
* `account_view_invoices` - (Optional) Whether the user can view invoices.
|
||||||
|
* `monitoring_manage_lists` - (Optional) Whether the user can modify notification lists.
|
||||||
|
* `monitoring_manage_jobs` - (Optional) Whether the user can modify monitoring jobs.
|
||||||
|
* `monitoring_view_jobs` - (Optional) Whether the user can view monitoring jobs.
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
---
|
||||||
|
layout: "ns1"
|
||||||
|
page_title: "NS1: ns1_zone"
|
||||||
|
sidebar_current: "docs-ns1-resource-zone"
|
||||||
|
description: |-
|
||||||
|
Provides a NS1 Zone resource.
|
||||||
|
---
|
||||||
|
|
||||||
|
# ns1\_zone
|
||||||
|
|
||||||
|
Provides a NS1 DNS Zone resource. This can be used to create, modify, and delete zones.
|
||||||
|
|
||||||
|
## Example Usage
|
||||||
|
|
||||||
|
```
|
||||||
|
# Create a new DNS zone
|
||||||
|
resource "ns1_zone" "example" {
|
||||||
|
zone = "terraform.example.io"
|
||||||
|
ttl = 600
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Argument Reference
|
||||||
|
|
||||||
|
The following arguments are supported:
|
||||||
|
|
||||||
|
* `zone` - (Required) The domain name of the zone.
|
||||||
|
* `link` - (Optional) The target zone(domain name) to link to.
|
||||||
|
* `ttl` - (Optional) The SOA TTL.
|
||||||
|
* `refresh` - (Optional) The SOA Refresh.
|
||||||
|
* `retry` - (Optional) The SOA Retry.
|
||||||
|
* `expiry` - (Optional) The SOA Expiry.
|
||||||
|
* `nx_ttl` - (Optional) The SOA NX TTL.
|
||||||
|
* `primary` - (Optional) The primary zones' ip. This makes the zone a secondary.
|
|
@ -295,6 +295,14 @@
|
||||||
<a href="/docs/providers/newrelic/index.html">New Relic</a>
|
<a href="/docs/providers/newrelic/index.html">New Relic</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
|
<li<%= sidebar_current("docs-providers-nomad") %>>
|
||||||
|
<a href="/docs/providers/nomad/index.html">Nomad</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li<%= sidebar_current("docs-providers-ns1") %>>
|
||||||
|
<a href="/docs/providers/ns1/index.html">NS1</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
<li<%= sidebar_current("docs-providers-azurerm") %>>
|
<li<%= sidebar_current("docs-providers-azurerm") %>>
|
||||||
<a href="/docs/providers/azurerm/index.html">Microsoft Azure</a>
|
<a href="/docs/providers/azurerm/index.html">Microsoft Azure</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
|
@ -0,0 +1,47 @@
|
||||||
|
<% wrap_layout :inner do %>
|
||||||
|
<% content_for :sidebar do %>
|
||||||
|
<div class="docs-sidebar hidden-print affix-top" role="complementary">
|
||||||
|
<ul class="nav docs-sidenav">
|
||||||
|
<li<%= sidebar_current("docs-home") %>>
|
||||||
|
<a href="/docs/providers/index.html">« Documentation Home</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li<%= sidebar_current("docs-ns1-index") %>>
|
||||||
|
<a href="/docs/providers/ns1/index.html">NS1 Provider</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li<%= sidebar_current(/^docs-ns1-resource/) %>>
|
||||||
|
<a href="#">Resources</a>
|
||||||
|
<ul class="nav nav-visible">
|
||||||
|
<li<%= sidebar_current("docs-ns1-resource-zone") %>>
|
||||||
|
<a href="/docs/providers/ns1/r/zone.html">ns1_zone</a>
|
||||||
|
</li>
|
||||||
|
<li<%= sidebar_current("docs-ns1-resource-record") %>>
|
||||||
|
<a href="/docs/providers/ns1/r/record.html">ns1_record</a>
|
||||||
|
</li>
|
||||||
|
<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-datasource") %>>
|
||||||
|
<a href="/docs/providers/ns1/r/datasource.html">ns1_datasource</a>
|
||||||
|
</li>
|
||||||
|
<li<%= sidebar_current("docs-ns1-resource-datafeed") %>>
|
||||||
|
<a href="/docs/providers/ns1/r/datafeed.html">ns1_datafeed</a>
|
||||||
|
</li>
|
||||||
|
<li<%= sidebar_current("docs-ns1-resource-apikey") %>>
|
||||||
|
<a href="/docs/providers/ns1/r/apikey.html">ns1_apikey</a>
|
||||||
|
</li>
|
||||||
|
<li<%= sidebar_current("docs-ns1-resource-team") %>>
|
||||||
|
<a href="/docs/providers/ns1/r/team.html">ns1_team</a>
|
||||||
|
</li>
|
||||||
|
<li<%= sidebar_current("docs-ns1-resource-user") %>>
|
||||||
|
<a href="/docs/providers/ns1/r/user.html">ns1_user</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
<%= yield %>
|
||||||
|
<% end %>
|
Loading…
Reference in New Issue