mirror of
https://github.com/StackExchange/dnscontrol.git
synced 2025-09-10 15:14:25 +08:00
NEW REGISTRAR: OpenSRS (#275)
* Initial commit for OpenSRS registrar support #272 * sort existing name servers before comparing. * vendor philhug/opensrs-go * Update docs for OpenSRS #272 * Cache OpenSRS client to prevent http connection leak * run go fmt
This commit is contained in:
parent
20f0c984e4
commit
dfd015e5cd
10 changed files with 795 additions and 0 deletions
|
@ -19,6 +19,7 @@
|
||||||
<th class="rotate"><div><span>NAMEDOTCOM</span></div></th>
|
<th class="rotate"><div><span>NAMEDOTCOM</span></div></th>
|
||||||
<th class="rotate"><div><span>NS1</span></div></th>
|
<th class="rotate"><div><span>NS1</span></div></th>
|
||||||
<th class="rotate"><div><span>OCTODNS</span></div></th>
|
<th class="rotate"><div><span>OCTODNS</span></div></th>
|
||||||
|
<th class="rotate"><div><span>OPENSRS</span></div></th>
|
||||||
<th class="rotate"><div><span>OVH</span></div></th>
|
<th class="rotate"><div><span>OVH</span></div></th>
|
||||||
<th class="rotate"><div><span>ROUTE53</span></div></th>
|
<th class="rotate"><div><span>ROUTE53</span></div></th>
|
||||||
<th class="rotate"><div><span>SOFTLAYER</span></div></th>
|
<th class="rotate"><div><span>SOFTLAYER</span></div></th>
|
||||||
|
@ -70,6 +71,9 @@
|
||||||
<td class="danger">
|
<td class="danger">
|
||||||
<i class="fa fa-times text-danger" aria-hidden="true"></i>
|
<i class="fa fa-times text-danger" aria-hidden="true"></i>
|
||||||
</td>
|
</td>
|
||||||
|
<td class="danger">
|
||||||
|
<i class="fa fa-times text-danger" aria-hidden="true"></i>
|
||||||
|
</td>
|
||||||
<td class="success">
|
<td class="success">
|
||||||
<i class="fa fa-check text-success" aria-hidden="true"></i>
|
<i class="fa fa-check text-success" aria-hidden="true"></i>
|
||||||
</td>
|
</td>
|
||||||
|
@ -121,6 +125,9 @@
|
||||||
<td class="success">
|
<td class="success">
|
||||||
<i class="fa fa-check text-success" aria-hidden="true"></i>
|
<i class="fa fa-check text-success" aria-hidden="true"></i>
|
||||||
</td>
|
</td>
|
||||||
|
<td class="danger">
|
||||||
|
<i class="fa fa-times text-danger" aria-hidden="true"></i>
|
||||||
|
</td>
|
||||||
<td class="success">
|
<td class="success">
|
||||||
<i class="fa fa-check text-success" aria-hidden="true"></i>
|
<i class="fa fa-check text-success" aria-hidden="true"></i>
|
||||||
</td>
|
</td>
|
||||||
|
@ -181,6 +188,9 @@
|
||||||
<td class="success">
|
<td class="success">
|
||||||
<i class="fa fa-check text-success" aria-hidden="true"></i>
|
<i class="fa fa-check text-success" aria-hidden="true"></i>
|
||||||
</td>
|
</td>
|
||||||
|
<td class="success">
|
||||||
|
<i class="fa fa-check text-success" aria-hidden="true"></i>
|
||||||
|
</td>
|
||||||
<td class="danger">
|
<td class="danger">
|
||||||
<i class="fa fa-times text-danger" aria-hidden="true"></i>
|
<i class="fa fa-times text-danger" aria-hidden="true"></i>
|
||||||
</td>
|
</td>
|
||||||
|
@ -213,6 +223,7 @@
|
||||||
</td>
|
</td>
|
||||||
<td><i class="fa fa-minus dim"></i></td>
|
<td><i class="fa fa-minus dim"></i></td>
|
||||||
<td><i class="fa fa-minus dim"></i></td>
|
<td><i class="fa fa-minus dim"></i></td>
|
||||||
|
<td><i class="fa fa-minus dim"></i></td>
|
||||||
<td class="danger">
|
<td class="danger">
|
||||||
<i class="fa fa-times text-danger" aria-hidden="true"></i>
|
<i class="fa fa-times text-danger" aria-hidden="true"></i>
|
||||||
</td>
|
</td>
|
||||||
|
@ -255,6 +266,7 @@
|
||||||
<td><i class="fa fa-minus dim"></i></td>
|
<td><i class="fa fa-minus dim"></i></td>
|
||||||
<td><i class="fa fa-minus dim"></i></td>
|
<td><i class="fa fa-minus dim"></i></td>
|
||||||
<td><i class="fa fa-minus dim"></i></td>
|
<td><i class="fa fa-minus dim"></i></td>
|
||||||
|
<td><i class="fa fa-minus dim"></i></td>
|
||||||
<td class="danger">
|
<td class="danger">
|
||||||
<i class="fa fa-times text-danger" aria-hidden="true"></i>
|
<i class="fa fa-times text-danger" aria-hidden="true"></i>
|
||||||
</td>
|
</td>
|
||||||
|
@ -299,6 +311,7 @@
|
||||||
<td class="success">
|
<td class="success">
|
||||||
<i class="fa fa-check text-success" aria-hidden="true"></i>
|
<i class="fa fa-check text-success" aria-hidden="true"></i>
|
||||||
</td>
|
</td>
|
||||||
|
<td><i class="fa fa-minus dim"></i></td>
|
||||||
<td class="danger">
|
<td class="danger">
|
||||||
<i class="fa fa-times text-danger" aria-hidden="true"></i>
|
<i class="fa fa-times text-danger" aria-hidden="true"></i>
|
||||||
</td>
|
</td>
|
||||||
|
@ -349,6 +362,7 @@
|
||||||
<td class="success">
|
<td class="success">
|
||||||
<i class="fa fa-check text-success" aria-hidden="true"></i>
|
<i class="fa fa-check text-success" aria-hidden="true"></i>
|
||||||
</td>
|
</td>
|
||||||
|
<td><i class="fa fa-minus dim"></i></td>
|
||||||
<td class="success">
|
<td class="success">
|
||||||
<i class="fa fa-check text-success" aria-hidden="true"></i>
|
<i class="fa fa-check text-success" aria-hidden="true"></i>
|
||||||
</td>
|
</td>
|
||||||
|
@ -383,6 +397,7 @@
|
||||||
<td><i class="fa fa-minus dim"></i></td>
|
<td><i class="fa fa-minus dim"></i></td>
|
||||||
<td><i class="fa fa-minus dim"></i></td>
|
<td><i class="fa fa-minus dim"></i></td>
|
||||||
<td><i class="fa fa-minus dim"></i></td>
|
<td><i class="fa fa-minus dim"></i></td>
|
||||||
|
<td><i class="fa fa-minus dim"></i></td>
|
||||||
<td class="success">
|
<td class="success">
|
||||||
<i class="fa fa-check text-success" aria-hidden="true"></i>
|
<i class="fa fa-check text-success" aria-hidden="true"></i>
|
||||||
</td>
|
</td>
|
||||||
|
@ -412,6 +427,7 @@
|
||||||
<td><i class="fa fa-minus dim"></i></td>
|
<td><i class="fa fa-minus dim"></i></td>
|
||||||
<td><i class="fa fa-minus dim"></i></td>
|
<td><i class="fa fa-minus dim"></i></td>
|
||||||
<td><i class="fa fa-minus dim"></i></td>
|
<td><i class="fa fa-minus dim"></i></td>
|
||||||
|
<td><i class="fa fa-minus dim"></i></td>
|
||||||
<td class="success">
|
<td class="success">
|
||||||
<i class="fa fa-check text-success" aria-hidden="true"></i>
|
<i class="fa fa-check text-success" aria-hidden="true"></i>
|
||||||
</td>
|
</td>
|
||||||
|
@ -434,6 +450,7 @@
|
||||||
<td><i class="fa fa-minus dim"></i></td>
|
<td><i class="fa fa-minus dim"></i></td>
|
||||||
<td><i class="fa fa-minus dim"></i></td>
|
<td><i class="fa fa-minus dim"></i></td>
|
||||||
<td><i class="fa fa-minus dim"></i></td>
|
<td><i class="fa fa-minus dim"></i></td>
|
||||||
|
<td><i class="fa fa-minus dim"></i></td>
|
||||||
<td class="success">
|
<td class="success">
|
||||||
<i class="fa fa-check text-success" aria-hidden="true"></i>
|
<i class="fa fa-check text-success" aria-hidden="true"></i>
|
||||||
</td>
|
</td>
|
||||||
|
@ -475,6 +492,7 @@
|
||||||
<td class="danger" data-toggle="tooltip" data-container="body" data-placement="top" title="Research is needed.">
|
<td class="danger" data-toggle="tooltip" data-container="body" data-placement="top" title="Research is needed.">
|
||||||
<i class="fa has-tooltip fa-times text-danger" aria-hidden="true"></i>
|
<i class="fa has-tooltip fa-times text-danger" aria-hidden="true"></i>
|
||||||
</td>
|
</td>
|
||||||
|
<td><i class="fa fa-minus dim"></i></td>
|
||||||
<td class="success">
|
<td class="success">
|
||||||
<i class="fa fa-check text-success" aria-hidden="true"></i>
|
<i class="fa fa-check text-success" aria-hidden="true"></i>
|
||||||
</td>
|
</td>
|
||||||
|
@ -525,6 +543,9 @@
|
||||||
<td class="danger" data-toggle="tooltip" data-container="body" data-placement="top" title="Driver just maintains list of OctoDNS config files. You must manually create the master config files that refer these.">
|
<td class="danger" data-toggle="tooltip" data-container="body" data-placement="top" title="Driver just maintains list of OctoDNS config files. You must manually create the master config files that refer these.">
|
||||||
<i class="fa has-tooltip fa-times text-danger" aria-hidden="true"></i>
|
<i class="fa has-tooltip fa-times text-danger" aria-hidden="true"></i>
|
||||||
</td>
|
</td>
|
||||||
|
<td class="danger">
|
||||||
|
<i class="fa fa-times text-danger" aria-hidden="true"></i>
|
||||||
|
</td>
|
||||||
<td class="danger" data-toggle="tooltip" data-container="body" data-placement="top" title="New domains require registration">
|
<td class="danger" data-toggle="tooltip" data-container="body" data-placement="top" title="New domains require registration">
|
||||||
<i class="fa has-tooltip fa-times text-danger" aria-hidden="true"></i>
|
<i class="fa has-tooltip fa-times text-danger" aria-hidden="true"></i>
|
||||||
</td>
|
</td>
|
||||||
|
@ -591,6 +612,9 @@
|
||||||
<td class="success">
|
<td class="success">
|
||||||
<i class="fa fa-check text-success" aria-hidden="true"></i>
|
<i class="fa fa-check text-success" aria-hidden="true"></i>
|
||||||
</td>
|
</td>
|
||||||
|
<td class="success">
|
||||||
|
<i class="fa fa-check text-success" aria-hidden="true"></i>
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
|
@ -15,6 +15,7 @@ import (
|
||||||
_ "github.com/StackExchange/dnscontrol/providers/namedotcom"
|
_ "github.com/StackExchange/dnscontrol/providers/namedotcom"
|
||||||
_ "github.com/StackExchange/dnscontrol/providers/ns1"
|
_ "github.com/StackExchange/dnscontrol/providers/ns1"
|
||||||
_ "github.com/StackExchange/dnscontrol/providers/octodns"
|
_ "github.com/StackExchange/dnscontrol/providers/octodns"
|
||||||
|
_ "github.com/StackExchange/dnscontrol/providers/opensrs"
|
||||||
_ "github.com/StackExchange/dnscontrol/providers/ovh"
|
_ "github.com/StackExchange/dnscontrol/providers/ovh"
|
||||||
_ "github.com/StackExchange/dnscontrol/providers/route53"
|
_ "github.com/StackExchange/dnscontrol/providers/route53"
|
||||||
_ "github.com/StackExchange/dnscontrol/providers/softlayer"
|
_ "github.com/StackExchange/dnscontrol/providers/softlayer"
|
||||||
|
|
144
providers/opensrs/opensrsProvider.go
Normal file
144
providers/opensrs/opensrsProvider.go
Normal file
|
@ -0,0 +1,144 @@
|
||||||
|
package opensrs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/StackExchange/dnscontrol/models"
|
||||||
|
"github.com/StackExchange/dnscontrol/providers"
|
||||||
|
|
||||||
|
opensrs "github.com/philhug/opensrs-go/opensrs"
|
||||||
|
)
|
||||||
|
|
||||||
|
var docNotes = providers.DocumentationNotes{
|
||||||
|
providers.DocCreateDomains: providers.Cannot(),
|
||||||
|
providers.DocOfficiallySupported: providers.Cannot(),
|
||||||
|
providers.CanUseTLSA: providers.Cannot(),
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
providers.RegisterRegistrarType("OPENSRS", newReg)
|
||||||
|
}
|
||||||
|
|
||||||
|
var defaultNameServerNames = []string{
|
||||||
|
"ns1.systemdns.com",
|
||||||
|
"ns2.systemdns.com",
|
||||||
|
"ns3.systemdns.com",
|
||||||
|
}
|
||||||
|
|
||||||
|
type OpenSRSApi struct {
|
||||||
|
UserName string // reseller user name
|
||||||
|
ApiKey string // API Key
|
||||||
|
|
||||||
|
BaseURL string // An alternate base URI
|
||||||
|
client *opensrs.Client // Client
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *OpenSRSApi) GetNameservers(domainName string) ([]*models.Nameserver, error) {
|
||||||
|
return models.StringsToNameservers(defaultNameServerNames), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *OpenSRSApi) GetRegistrarCorrections(dc *models.DomainConfig) ([]*models.Correction, error) {
|
||||||
|
corrections := []*models.Correction{}
|
||||||
|
|
||||||
|
nameServers, err := c.getNameservers(dc.Name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Strings(nameServers)
|
||||||
|
actual := strings.Join(nameServers, ",")
|
||||||
|
|
||||||
|
expectedSet := []string{}
|
||||||
|
for _, ns := range dc.Nameservers {
|
||||||
|
expectedSet = append(expectedSet, ns.Name)
|
||||||
|
}
|
||||||
|
sort.Strings(expectedSet)
|
||||||
|
expected := strings.Join(expectedSet, ",")
|
||||||
|
|
||||||
|
if actual != expected {
|
||||||
|
return []*models.Correction{
|
||||||
|
{
|
||||||
|
Msg: fmt.Sprintf("Update nameservers %s -> %s", actual, expected),
|
||||||
|
F: c.updateNameserversFunc(expectedSet, dc.Name),
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return corrections, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// OpenSRS calls
|
||||||
|
|
||||||
|
func (c *OpenSRSApi) getClient() *opensrs.Client {
|
||||||
|
return c.client
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the name server names that should be used. If the domain is registered
|
||||||
|
// then this method will return the delegation name servers. If this domain
|
||||||
|
// is hosted only, then it will return the default OpenSRS name servers.
|
||||||
|
func (c *OpenSRSApi) getNameservers(domainName string) ([]string, error) {
|
||||||
|
client := c.getClient()
|
||||||
|
|
||||||
|
status, err := client.Domains.GetDomain(domainName, "status", 1)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if status.Attributes.LockState == "0" {
|
||||||
|
dom, err := client.Domains.GetDomain(domainName, "nameservers", 1)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return dom.Attributes.NameserverList.ToString(), nil
|
||||||
|
} else {
|
||||||
|
return nil, errors.New("Domain is locked")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns a function that can be invoked to change the delegation of the domain to the given name server names.
|
||||||
|
func (c *OpenSRSApi) updateNameserversFunc(nameServerNames []string, domainName string) func() error {
|
||||||
|
return func() error {
|
||||||
|
client := c.getClient()
|
||||||
|
|
||||||
|
_, err := client.Domains.UpdateDomainNameservers(domainName, nameServerNames)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// constructors
|
||||||
|
|
||||||
|
func newReg(conf map[string]string) (providers.Registrar, error) {
|
||||||
|
return newProvider(conf, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newProvider(m map[string]string, metadata json.RawMessage) (*OpenSRSApi, error) {
|
||||||
|
api := &OpenSRSApi{}
|
||||||
|
api.ApiKey = m["apikey"]
|
||||||
|
|
||||||
|
if api.ApiKey == "" {
|
||||||
|
return nil, fmt.Errorf("OpenSRS apikey must be provided.")
|
||||||
|
}
|
||||||
|
|
||||||
|
api.UserName = m["username"]
|
||||||
|
if api.UserName == "" {
|
||||||
|
return nil, fmt.Errorf("OpenSRS username key must be provided.")
|
||||||
|
}
|
||||||
|
|
||||||
|
if m["baseurl"] != "" {
|
||||||
|
api.BaseURL = m["baseurl"]
|
||||||
|
}
|
||||||
|
|
||||||
|
api.client = opensrs.NewClient(opensrs.NewApiKeyMD5Credentials(api.UserName, api.ApiKey))
|
||||||
|
if api.BaseURL != "" {
|
||||||
|
api.client.BaseURL = api.BaseURL
|
||||||
|
}
|
||||||
|
|
||||||
|
return api, nil
|
||||||
|
}
|
21
vendor/github.com/philhug/opensrs-go/LICENSE
generated
vendored
Normal file
21
vendor/github.com/philhug/opensrs-go/LICENSE
generated
vendored
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2017 Philipp Hug
|
||||||
|
|
||||||
|
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.
|
44
vendor/github.com/philhug/opensrs-go/opensrs/authentication.go
generated
vendored
Normal file
44
vendor/github.com/philhug/opensrs-go/opensrs/authentication.go
generated
vendored
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
package opensrs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/md5"
|
||||||
|
"encoding/hex"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
httpHeaderUserName = "X-UserName"
|
||||||
|
httpHeaderSignature = "X-Signature"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Provides credentials that can be used for authenticating with OpenSRS.
|
||||||
|
//
|
||||||
|
type Credentials interface {
|
||||||
|
// Returns the HTTP headers that should be set
|
||||||
|
// to authenticate the HTTP Request.
|
||||||
|
Headers(xml []byte) map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
|
// API key MD5 authentication
|
||||||
|
type apiKeyMD5Credentials struct {
|
||||||
|
userName string
|
||||||
|
apiKey string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewApiKeyMD5Credentials construct Credentials using the OpenSRS MD5 Api Key method.
|
||||||
|
func NewApiKeyMD5Credentials(userName string, apiKey string) Credentials {
|
||||||
|
return &apiKeyMD5Credentials{userName: userName, apiKey: apiKey}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *apiKeyMD5Credentials) Headers(xml []byte) map[string]string {
|
||||||
|
h := md5.New()
|
||||||
|
h.Write(xml)
|
||||||
|
h.Write([]byte(c.apiKey))
|
||||||
|
m := hex.EncodeToString(h.Sum(nil))
|
||||||
|
|
||||||
|
h = md5.New()
|
||||||
|
h.Write([]byte(m))
|
||||||
|
h.Write([]byte(c.apiKey))
|
||||||
|
m = hex.EncodeToString(h.Sum(nil))
|
||||||
|
|
||||||
|
return map[string]string{httpHeaderUserName: c.userName, httpHeaderSignature: m}
|
||||||
|
}
|
56
vendor/github.com/philhug/opensrs-go/opensrs/domains.go
generated
vendored
Normal file
56
vendor/github.com/philhug/opensrs-go/opensrs/domains.go
generated
vendored
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
package opensrs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DomainsService handles communication with the domain related
|
||||||
|
// methods of the OpenSRS API.
|
||||||
|
//
|
||||||
|
type DomainsService struct {
|
||||||
|
client *Client
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDomain fetches a domain.
|
||||||
|
//
|
||||||
|
func (s *DomainsService) GetDomain(domainIdentifier string, domainType string, limit int) (*OpsResponse, error) {
|
||||||
|
opsResponse := OpsResponse{}
|
||||||
|
opsRequestAttributes := OpsRequestAttributes{Domain: domainIdentifier, Limit: strconv.Itoa(limit), Type: domainType}
|
||||||
|
|
||||||
|
resp, err := s.client.post("GET", "DOMAIN", opsRequestAttributes, &opsResponse)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
_ = resp
|
||||||
|
return &opsResponse, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateDomainNameservers changes domain servers on a domain.
|
||||||
|
//
|
||||||
|
func (s *DomainsService) UpdateDomainNameservers(domainIdentifier string, newDs []string) (*OpsResponse, error) {
|
||||||
|
opsResponse := OpsResponse{}
|
||||||
|
|
||||||
|
opsRequestAttributes := OpsRequestAttributes{Domain: domainIdentifier, AssignNs: newDs, OpType: "assign"}
|
||||||
|
|
||||||
|
resp, err := s.client.post("ADVANCED_UPDATE_NAMESERVERS", "DOMAIN", opsRequestAttributes, &opsResponse)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
_ = resp
|
||||||
|
return &opsResponse, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDNSZone fetches zone info for a domain.
|
||||||
|
//
|
||||||
|
func (s *DomainsService) GetDNSZone(domainIdentifier string) (*OpsResponse, error) {
|
||||||
|
opsResponse := OpsResponse{}
|
||||||
|
opsRequestAttributes := OpsRequestAttributes{Domain: domainIdentifier}
|
||||||
|
|
||||||
|
resp, err := s.client.post("GET_DNS_ZONE", "DOMAIN", opsRequestAttributes, &opsResponse)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
_ = resp
|
||||||
|
return &opsResponse, nil
|
||||||
|
}
|
||||||
|
|
231
vendor/github.com/philhug/opensrs-go/opensrs/opensrs.go
generated
vendored
Normal file
231
vendor/github.com/philhug/opensrs-go/opensrs/opensrs.go
generated
vendored
Normal file
|
@ -0,0 +1,231 @@
|
||||||
|
// Package opensrs provides a client for the OpenSRS API.
|
||||||
|
// In order to use this package you will need a OpenSRS account.
|
||||||
|
package opensrs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/tls"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Version identifies the current library version.
|
||||||
|
// This is a pro-forma convention given that Go dependencies
|
||||||
|
// tends to be fetched directly from the repo.
|
||||||
|
// It is also used in the user-agent identify the client.
|
||||||
|
Version = "0.0.1"
|
||||||
|
|
||||||
|
// defaultBaseURL to the OpenSRS production API.
|
||||||
|
//defaultBaseURL = "https://rr-n1-tor.opensrs.net:55443"
|
||||||
|
defaultBaseURL = "https://horizon.opensrs.net:55443"
|
||||||
|
|
||||||
|
// userAgent represents the default user agent used
|
||||||
|
// when no other user agent is set.
|
||||||
|
defaultUserAgent = "opensrs-go/" + Version
|
||||||
|
)
|
||||||
|
|
||||||
|
// Client represents a client to the OpenSRS API.
|
||||||
|
type Client struct {
|
||||||
|
// HttpClient is the underlying HTTP client
|
||||||
|
// used to communicate with the API.
|
||||||
|
HttpClient *http.Client
|
||||||
|
|
||||||
|
// Credentials used for accessing the OpenSRS API
|
||||||
|
Credentials Credentials
|
||||||
|
|
||||||
|
// BaseURL for API requests.
|
||||||
|
// Defaults to the public OpenSRS API, but can be set to a different endpoint (e.g. the sandbox).
|
||||||
|
BaseURL string
|
||||||
|
|
||||||
|
// UserAgent used when communicating with the OpenSRS API.
|
||||||
|
UserAgent string
|
||||||
|
|
||||||
|
// Services used for talking to different parts of the OpenSRS API.
|
||||||
|
Domains *DomainsService
|
||||||
|
|
||||||
|
// Set to true to output debugging logs during API calls
|
||||||
|
Debug bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewClient returns a new OpenSRS API client using the given credentials.
|
||||||
|
func NewClient(credentials Credentials) *Client {
|
||||||
|
proxyUrl, _ := url.Parse("http://127.0.0.1:8080")
|
||||||
|
|
||||||
|
tr := &http.Transport{
|
||||||
|
TLSClientConfig: &tls.Config{InsecureSkipVerify : true},
|
||||||
|
Proxy: http.ProxyURL(proxyUrl),
|
||||||
|
}
|
||||||
|
c := &Client{Credentials: credentials, HttpClient: &http.Client{Transport: tr}, BaseURL: defaultBaseURL}
|
||||||
|
c.Domains = &DomainsService{client: c}
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewRequest creates an API request.
|
||||||
|
// The path is expected to be a relative path and will be resolved
|
||||||
|
// according to the BaseURL of the Client. Paths should always be specified without a preceding slash.
|
||||||
|
func (c *Client) NewRequest(method, path string, payload interface{}) (*http.Request, error) {
|
||||||
|
url := c.BaseURL + path
|
||||||
|
|
||||||
|
body := new(bytes.Buffer)
|
||||||
|
if payload != nil {
|
||||||
|
xml, err := ToXml(payload)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
body = bytes.NewBuffer(xml)
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := http.NewRequest(method, url, body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Header.Set("Content-Type", "text/xml")
|
||||||
|
req.Header.Add("Accept", "text/xml")
|
||||||
|
req.Header.Add("User-Agent", formatUserAgent(c.UserAgent))
|
||||||
|
for key, value := range c.Credentials.Headers(body.Bytes()) {
|
||||||
|
req.Header.Add(key, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
return req, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// formatUserAgent builds the final user agent to use for HTTP requests.
|
||||||
|
//
|
||||||
|
// If no custom user agent is provided, the default user agent is used.
|
||||||
|
//
|
||||||
|
// opensrs-go/1.0
|
||||||
|
//
|
||||||
|
// If a custom user agent is provided, the final user agent is the combination of the custom user agent
|
||||||
|
// prepended by the default user agent.
|
||||||
|
//
|
||||||
|
// opensrs-go/1.0 customAgentFlag
|
||||||
|
//
|
||||||
|
func formatUserAgent(customUserAgent string) string {
|
||||||
|
if customUserAgent == "" {
|
||||||
|
return defaultUserAgent
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf("%s %s", defaultUserAgent, customUserAgent)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) post(action string, object string, attributes OpsRequestAttributes, obj *OpsResponse) (*http.Response, error) {
|
||||||
|
payload := OpsRequest{Action: action, Object: object, Protocol: "XCP", Attributes: attributes}
|
||||||
|
req, err := c.NewRequest("POST", "", payload)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Do(req, obj)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do sends an API request and returns the API response.
|
||||||
|
//
|
||||||
|
// The API response is JSON decoded and stored in the value pointed by obj,
|
||||||
|
// or returned as an error if an API error has occurred.
|
||||||
|
// If obj implements the io.Writer interface, the raw response body will be written to obj,
|
||||||
|
// without attempting to decode it.
|
||||||
|
func (c *Client) Do(req *http.Request, obj *OpsResponse) (*http.Response, error) {
|
||||||
|
if c.Debug {
|
||||||
|
log.Printf("Executing request (%v): %#v", req.URL, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := c.HttpClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if c.Debug {
|
||||||
|
log.Printf("Response received: %#v", resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = CheckResponse(resp)
|
||||||
|
if err != nil {
|
||||||
|
return resp, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// If obj implements the io.Writer,
|
||||||
|
// the response body is decoded into v.
|
||||||
|
if obj != nil {
|
||||||
|
b, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = FromXml(b, obj)
|
||||||
|
if err != nil {
|
||||||
|
return resp, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = CheckOpsResponse(resp, obj)
|
||||||
|
if err != nil {
|
||||||
|
return resp, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// An ErrorResponse represents an API response that generated an error.
|
||||||
|
type ErrorResponse struct {
|
||||||
|
HttpResponse *http.Response
|
||||||
|
OpsResponse *OpsResponse
|
||||||
|
|
||||||
|
// human-readable message
|
||||||
|
Message string `json:"message"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error implements the error interface.
|
||||||
|
func (r *ErrorResponse) Error() string {
|
||||||
|
s := fmt.Sprintf("%v %v: ",
|
||||||
|
r.HttpResponse.Request.Method, r.HttpResponse.Request.URL)
|
||||||
|
if r.OpsResponse != nil {
|
||||||
|
s = s + fmt.Sprintf("%v %v",
|
||||||
|
r.OpsResponse.ResponseCode,
|
||||||
|
r.OpsResponse.ResponseText)
|
||||||
|
} else {
|
||||||
|
s = s + fmt.Sprintf("%v %v",
|
||||||
|
r.HttpResponse.StatusCode, r.Message)
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckResponse checks the API response for errors, and returns them if present.
|
||||||
|
// A response is considered an error if the status code is different than 2xx. Specific requests
|
||||||
|
// may have additional requirements, but this is sufficient in most of the cases.
|
||||||
|
func CheckResponse(resp *http.Response) error {
|
||||||
|
if code := resp.StatusCode; 200 <= code && code <= 299 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
errorResponse := &ErrorResponse{}
|
||||||
|
errorResponse.HttpResponse = resp
|
||||||
|
|
||||||
|
b, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = FromXml(b, errorResponse)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return errorResponse
|
||||||
|
}
|
||||||
|
|
||||||
|
func CheckOpsResponse(resp *http.Response, or *OpsResponse) error {
|
||||||
|
if or.IsSuccess == "1" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
errorResponse := &ErrorResponse{}
|
||||||
|
errorResponse.HttpResponse = resp
|
||||||
|
errorResponse.OpsResponse = or
|
||||||
|
|
||||||
|
return errorResponse
|
||||||
|
}
|
92
vendor/github.com/philhug/opensrs-go/opensrs/structs.go
generated
vendored
Normal file
92
vendor/github.com/philhug/opensrs-go/opensrs/structs.go
generated
vendored
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
package opensrs
|
||||||
|
|
||||||
|
type NameserverList []struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
IpAddress string `json:"ipaddress,omitempty"`
|
||||||
|
Ipv6 string `json:"ipv6,omitempty"`
|
||||||
|
SortOrder string `json:"sortorder,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ARecord struct {
|
||||||
|
IpAddress string `json:"ipaddress,omitempty"`
|
||||||
|
SubDomain string `json:"subdomain,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type AAAARecord struct {
|
||||||
|
Ipv6Address string `json:"ipv6_address,omitempty"`
|
||||||
|
SubDomain string `json:"subdomain,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CNAMERecord struct {
|
||||||
|
HostName string `json:"hostname,omitempty"`
|
||||||
|
SubDomain string `json:"subdomain,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type MXRecord struct {
|
||||||
|
Priority string `json:"priority,omitempty"`
|
||||||
|
SubDomain string `json:"subdomain,omitempty"`
|
||||||
|
HostName string `json:"hostname,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SRVRecord struct {
|
||||||
|
Priority string `json:"priority,omitempty"`
|
||||||
|
Weight string `json:"weight,omitempty"`
|
||||||
|
SubDomain string `json:"subdomain,omitempty"`
|
||||||
|
HostName string `json:"hostname,omitempty"`
|
||||||
|
Port string `json:"port,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type TXTRecord struct {
|
||||||
|
SubDomain string `json:"subdomain,omitempty"`
|
||||||
|
Text string `json:"text,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type DnsRecords struct {
|
||||||
|
A []ARecord `json:"A,omitempty"`
|
||||||
|
AAAA []AAAARecord `json:"AAAA,omitempty"`
|
||||||
|
CNAME []CNAMERecord `json:"CNAME,omitempty"`
|
||||||
|
MX []MXRecord `json:"MX,omitempty"`
|
||||||
|
SRV []SRVRecord `json:"SRV,omitempty"`
|
||||||
|
TXT []TXTRecord `json:"TXT,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n NameserverList) ToString() []string {
|
||||||
|
domains := make([]string, len(n))
|
||||||
|
for i, ns := range n {
|
||||||
|
domains[i] = ns.Name
|
||||||
|
}
|
||||||
|
return domains
|
||||||
|
}
|
||||||
|
|
||||||
|
type OpsRequestAttributes struct {
|
||||||
|
Domain string `json:"domain"`
|
||||||
|
Limit string `json:"limit,omitempty"`
|
||||||
|
Type string `json:"type,omitempty"`
|
||||||
|
Data string `json:"data,omitempty"`
|
||||||
|
AffectDomains string `json:"affect_domains,omitempty"`
|
||||||
|
NameserverList NameserverList `json:"nameserver_list,omitempty"`
|
||||||
|
OpType string `json:"op_type,omitempty"`
|
||||||
|
AssignNs []string `json:"assign_ns,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type OpsResponse struct {
|
||||||
|
Action string `json:"action"`
|
||||||
|
Object string `json:"object"`
|
||||||
|
Protocol string `json:"protocol"`
|
||||||
|
IsSuccess string `json:"is_success"`
|
||||||
|
ResponseCode string `json:"response_code"`
|
||||||
|
ResponseText string `json:"response_text"`
|
||||||
|
Attributes struct {
|
||||||
|
NameserverList NameserverList `json:"nameserver_list,omitempty"`
|
||||||
|
Type string `json:"type,omitempty"`
|
||||||
|
LockState string `json:"lock_state,omitempty"`
|
||||||
|
Records DnsRecords `json:"records,omitempty"`
|
||||||
|
} `json:"attributes"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type OpsRequest struct {
|
||||||
|
Action string `json:"action"`
|
||||||
|
Object string `json:"object"`
|
||||||
|
Protocol string `json:"protocol"`
|
||||||
|
Attributes OpsRequestAttributes `json:"attributes"`
|
||||||
|
}
|
176
vendor/github.com/philhug/opensrs-go/opensrs/xml.go
generated
vendored
Normal file
176
vendor/github.com/philhug/opensrs-go/opensrs/xml.go
generated
vendored
Normal file
|
@ -0,0 +1,176 @@
|
||||||
|
package opensrs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"encoding/xml"
|
||||||
|
"errors"
|
||||||
|
"log"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Header struct {
|
||||||
|
XMLName xml.Name `xml:"header"`
|
||||||
|
Version string `xml:"version"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Item struct {
|
||||||
|
XMLName xml.Name `xml:"item"`
|
||||||
|
Key string `xml:"key,attr"`
|
||||||
|
DtArray *DtArray `xml:"dt_array,omitempty"`
|
||||||
|
DtAssoc *DtAssoc `xml:"dt_assoc,omitempty"`
|
||||||
|
Value string `xml:",chardata"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Item) decode() interface{} {
|
||||||
|
if i.DtAssoc != nil {
|
||||||
|
return i.DtAssoc.decode()
|
||||||
|
}
|
||||||
|
if i.DtArray != nil {
|
||||||
|
return i.DtArray.decode()
|
||||||
|
}
|
||||||
|
return i.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
type DtArray struct {
|
||||||
|
XMLName xml.Name `xml:"dt_array"`
|
||||||
|
ItemList []Item `xml:"item,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DtArray) decode() []interface{} {
|
||||||
|
m := make([]interface{}, 0)
|
||||||
|
for _, element := range d.ItemList {
|
||||||
|
m = append(m, element.decode())
|
||||||
|
}
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
type DtAssoc struct {
|
||||||
|
XMLName xml.Name `xml:"dt_assoc"`
|
||||||
|
ItemList []Item `xml:"item,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DtAssoc) decode() Map {
|
||||||
|
m := make(Map)
|
||||||
|
for _, element := range d.ItemList {
|
||||||
|
m[element.Key] = element.decode()
|
||||||
|
}
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
type DataBlock struct {
|
||||||
|
XMLName xml.Name `xml:"data_block"`
|
||||||
|
DtAssoc *DtAssoc `xml:"dt_assoc,omitempty"`
|
||||||
|
//DtArray DtArray `xml:"dt_array,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Map map[string]interface{}
|
||||||
|
|
||||||
|
func (d *DataBlock) decode() Map {
|
||||||
|
m := make(Map)
|
||||||
|
if d.DtAssoc != nil {
|
||||||
|
return d.DtAssoc.decode()
|
||||||
|
}
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
func encodeItem(key string, value reflect.Value) Item {
|
||||||
|
item := Item{}
|
||||||
|
item.Key = key
|
||||||
|
v := internalEncode(value)
|
||||||
|
s, ok := v.(string)
|
||||||
|
if ok {
|
||||||
|
item.Value = s
|
||||||
|
}
|
||||||
|
dtass, ok := v.(DtAssoc)
|
||||||
|
if ok {
|
||||||
|
item.DtAssoc = &dtass
|
||||||
|
}
|
||||||
|
dtarr, ok := v.(DtArray)
|
||||||
|
if ok {
|
||||||
|
item.DtArray = &dtarr
|
||||||
|
}
|
||||||
|
return item
|
||||||
|
}
|
||||||
|
|
||||||
|
func internalEncode(v reflect.Value) (p interface{}) {
|
||||||
|
t := v.Type()
|
||||||
|
switch t.Kind() {
|
||||||
|
case reflect.Interface:
|
||||||
|
return internalEncode(v.Elem())
|
||||||
|
case reflect.String:
|
||||||
|
return v.Interface().(string)
|
||||||
|
case reflect.Struct:
|
||||||
|
dt := DtAssoc{}
|
||||||
|
|
||||||
|
for i := 0; i < t.NumField(); i++ {
|
||||||
|
key := strings.ToLower(t.Field(i).Name)
|
||||||
|
value := v.Field(i)
|
||||||
|
item := encodeItem(key, value)
|
||||||
|
dt.ItemList = append(dt.ItemList, item)
|
||||||
|
}
|
||||||
|
return dt
|
||||||
|
case reflect.Map: // DtAssoc
|
||||||
|
dt := DtAssoc{}
|
||||||
|
|
||||||
|
for _, k := range v.MapKeys() {
|
||||||
|
v := v.MapIndex(k)
|
||||||
|
key := k.String()
|
||||||
|
item := encodeItem(key, v)
|
||||||
|
dt.ItemList = append(dt.ItemList, item)
|
||||||
|
}
|
||||||
|
return dt
|
||||||
|
case reflect.Slice: //DtArray
|
||||||
|
dt := DtArray{}
|
||||||
|
for i := 0; i < v.Len(); i++ {
|
||||||
|
key := strconv.Itoa(i)
|
||||||
|
value := v.Index(i)
|
||||||
|
item := encodeItem(key, value)
|
||||||
|
dt.ItemList = append(dt.ItemList, item)
|
||||||
|
}
|
||||||
|
return dt
|
||||||
|
default:
|
||||||
|
log.Println("FAIL, unknown type", t.Kind())
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type Body struct {
|
||||||
|
XMLName xml.Name `xml:"body"`
|
||||||
|
DataBlock DataBlock `xml:"data_block"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type OPSEnvelope struct {
|
||||||
|
XMLName xml.Name `xml:"OPS_envelope"`
|
||||||
|
Header Header `xml:"header"`
|
||||||
|
Body Body `xml:"body"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func FromXml(b []byte, v interface{}) error {
|
||||||
|
var q OPSEnvelope
|
||||||
|
err := xml.Unmarshal(b, &q)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
m := q.Body.DataBlock.decode()
|
||||||
|
jsonString, _ := json.Marshal(m)
|
||||||
|
log.Println(string(jsonString))
|
||||||
|
return json.Unmarshal(jsonString, &v)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func ToXml(v interface{}) (b []byte, err error) {
|
||||||
|
jsonString, _ := json.Marshal(v)
|
||||||
|
var m interface{}
|
||||||
|
json.Unmarshal(jsonString, &m)
|
||||||
|
|
||||||
|
q := OPSEnvelope{Header: Header{Version: "0.9"}, Body: Body{}}
|
||||||
|
dtass, ok := internalEncode(reflect.ValueOf(m)).(DtAssoc)
|
||||||
|
if ok {
|
||||||
|
q.Body.DataBlock.DtAssoc = &dtass
|
||||||
|
} else {
|
||||||
|
return nil, errors.New("Encoding failed")
|
||||||
|
}
|
||||||
|
return xml.MarshalIndent(q, "", " ")
|
||||||
|
}
|
6
vendor/vendor.json
vendored
6
vendor/vendor.json
vendored
|
@ -330,6 +330,12 @@
|
||||||
"revision": "8e652d1a756f2480f1ad28dc211c77a25264e02d",
|
"revision": "8e652d1a756f2480f1ad28dc211c77a25264e02d",
|
||||||
"revisionTime": "2018-01-09T18:53:19Z"
|
"revisionTime": "2018-01-09T18:53:19Z"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"checksumSHA1": "ln/74dXVPX/i7aDah00ZKqbdpw8=",
|
||||||
|
"path": "github.com/philhug/opensrs-go/opensrs",
|
||||||
|
"revision": "58112e74137c104eb77bdad4b53c51da22b7b6d5",
|
||||||
|
"revisionTime": "2017-11-26T23:10:42Z"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"checksumSHA1": "ynJSWoF6v+3zMnh9R0QmmG6iGV8=",
|
"checksumSHA1": "ynJSWoF6v+3zMnh9R0QmmG6iGV8=",
|
||||||
"path": "github.com/pkg/errors",
|
"path": "github.com/pkg/errors",
|
||||||
|
|
Loading…
Add table
Reference in a new issue