@ -0,0 +1,202 @@ | |||
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. |
@ -0,0 +1,12 @@ | |||
{ | |||
"name": "metadata", | |||
"name_pretty": "Google Compute Engine Metadata API", | |||
"product_documentation": "https://cloud.google.com/compute/docs/storing-retrieving-metadata", | |||
"client_documentation": "https://godoc.org/cloud.google.com/go/compute/metadata", | |||
"release_level": "ga", | |||
"language": "go", | |||
"repo": "googleapis/google-cloud-go", | |||
"distribution_name": "cloud.google.com/go/compute/metadata", | |||
"api_id": "compute:metadata", | |||
"requires_billing": false | |||
} |
@ -0,0 +1,525 @@ | |||
// Copyright 2014 Google LLC | |||
// | |||
// 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. | |||
// Package metadata provides access to Google Compute Engine (GCE) | |||
// metadata and API service accounts. | |||
// | |||
// This package is a wrapper around the GCE metadata service, | |||
// as documented at https://developers.google.com/compute/docs/metadata. | |||
package metadata // import "cloud.google.com/go/compute/metadata" | |||
import ( | |||
"context" | |||
"encoding/json" | |||
"fmt" | |||
"io/ioutil" | |||
"net" | |||
"net/http" | |||
"net/url" | |||
"os" | |||
"runtime" | |||
"strings" | |||
"sync" | |||
"time" | |||
) | |||
const ( | |||
// metadataIP is the documented metadata server IP address. | |||
metadataIP = "169.254.169.254" | |||
// metadataHostEnv is the environment variable specifying the | |||
// GCE metadata hostname. If empty, the default value of | |||
// metadataIP ("169.254.169.254") is used instead. | |||
// This is variable name is not defined by any spec, as far as | |||
// I know; it was made up for the Go package. | |||
metadataHostEnv = "GCE_METADATA_HOST" | |||
userAgent = "gcloud-golang/0.1" | |||
) | |||
type cachedValue struct { | |||
k string | |||
trim bool | |||
mu sync.Mutex | |||
v string | |||
} | |||
var ( | |||
projID = &cachedValue{k: "project/project-id", trim: true} | |||
projNum = &cachedValue{k: "project/numeric-project-id", trim: true} | |||
instID = &cachedValue{k: "instance/id", trim: true} | |||
) | |||
var ( | |||
defaultClient = &Client{hc: &http.Client{ | |||
Transport: &http.Transport{ | |||
Dial: (&net.Dialer{ | |||
Timeout: 2 * time.Second, | |||
KeepAlive: 30 * time.Second, | |||
}).Dial, | |||
ResponseHeaderTimeout: 2 * time.Second, | |||
}, | |||
}} | |||
subscribeClient = &Client{hc: &http.Client{ | |||
Transport: &http.Transport{ | |||
Dial: (&net.Dialer{ | |||
Timeout: 2 * time.Second, | |||
KeepAlive: 30 * time.Second, | |||
}).Dial, | |||
}, | |||
}} | |||
) | |||
// NotDefinedError is returned when requested metadata is not defined. | |||
// | |||
// The underlying string is the suffix after "/computeMetadata/v1/". | |||
// | |||
// This error is not returned if the value is defined to be the empty | |||
// string. | |||
type NotDefinedError string | |||
func (suffix NotDefinedError) Error() string { | |||
return fmt.Sprintf("metadata: GCE metadata %q not defined", string(suffix)) | |||
} | |||
func (c *cachedValue) get(cl *Client) (v string, err error) { | |||
defer c.mu.Unlock() | |||
c.mu.Lock() | |||
if c.v != "" { | |||
return c.v, nil | |||
} | |||
if c.trim { | |||
v, err = cl.getTrimmed(c.k) | |||
} else { | |||
v, err = cl.Get(c.k) | |||
} | |||
if err == nil { | |||
c.v = v | |||
} | |||
return | |||
} | |||
var ( | |||
onGCEOnce sync.Once | |||
onGCE bool | |||
) | |||
// OnGCE reports whether this process is running on Google Compute Engine. | |||
func OnGCE() bool { | |||
onGCEOnce.Do(initOnGCE) | |||
return onGCE | |||
} | |||
func initOnGCE() { | |||
onGCE = testOnGCE() | |||
} | |||
func testOnGCE() bool { | |||
// The user explicitly said they're on GCE, so trust them. | |||
if os.Getenv(metadataHostEnv) != "" { | |||
return true | |||
} | |||
ctx, cancel := context.WithCancel(context.Background()) | |||
defer cancel() | |||
resc := make(chan bool, 2) | |||
// Try two strategies in parallel. | |||
// See https://github.com/googleapis/google-cloud-go/issues/194 | |||
go func() { | |||
req, _ := http.NewRequest("GET", "http://"+metadataIP, nil) | |||
req.Header.Set("User-Agent", userAgent) | |||
res, err := defaultClient.hc.Do(req.WithContext(ctx)) | |||
if err != nil { | |||
resc <- false | |||
return | |||
} | |||
defer res.Body.Close() | |||
resc <- res.Header.Get("Metadata-Flavor") == "Google" | |||
}() | |||
go func() { | |||
addrs, err := net.LookupHost("metadata.google.internal") | |||
if err != nil || len(addrs) == 0 { | |||
resc <- false | |||
return | |||
} | |||
resc <- strsContains(addrs, metadataIP) | |||
}() | |||
tryHarder := systemInfoSuggestsGCE() | |||
if tryHarder { | |||
res := <-resc | |||
if res { | |||
// The first strategy succeeded, so let's use it. | |||
return true | |||
} | |||
// Wait for either the DNS or metadata server probe to | |||
// contradict the other one and say we are running on | |||
// GCE. Give it a lot of time to do so, since the system | |||
// info already suggests we're running on a GCE BIOS. | |||
timer := time.NewTimer(5 * time.Second) | |||
defer timer.Stop() | |||
select { | |||
case res = <-resc: | |||
return res | |||
case <-timer.C: | |||
// Too slow. Who knows what this system is. | |||
return false | |||
} | |||
} | |||
// There's no hint from the system info that we're running on | |||
// GCE, so use the first probe's result as truth, whether it's | |||
// true or false. The goal here is to optimize for speed for | |||
// users who are NOT running on GCE. We can't assume that | |||
// either a DNS lookup or an HTTP request to a blackholed IP | |||
// address is fast. Worst case this should return when the | |||
// metaClient's Transport.ResponseHeaderTimeout or | |||
// Transport.Dial.Timeout fires (in two seconds). | |||
return <-resc | |||
} | |||
// systemInfoSuggestsGCE reports whether the local system (without | |||
// doing network requests) suggests that we're running on GCE. If this | |||
// returns true, testOnGCE tries a bit harder to reach its metadata | |||
// server. | |||
func systemInfoSuggestsGCE() bool { | |||
if runtime.GOOS != "linux" { | |||
// We don't have any non-Linux clues available, at least yet. | |||
return false | |||
} | |||
slurp, _ := ioutil.ReadFile("/sys/class/dmi/id/product_name") | |||
name := strings.TrimSpace(string(slurp)) | |||
return name == "Google" || name == "Google Compute Engine" | |||
} | |||
// Subscribe calls Client.Subscribe on a client designed for subscribing (one with no | |||
// ResponseHeaderTimeout). | |||
func Subscribe(suffix string, fn func(v string, ok bool) error) error { | |||
return subscribeClient.Subscribe(suffix, fn) | |||
} | |||
// Get calls Client.Get on the default client. | |||
func Get(suffix string) (string, error) { return defaultClient.Get(suffix) } | |||
// ProjectID returns the current instance's project ID string. | |||
func ProjectID() (string, error) { return defaultClient.ProjectID() } | |||
// NumericProjectID returns the current instance's numeric project ID. | |||
func NumericProjectID() (string, error) { return defaultClient.NumericProjectID() } | |||
// InternalIP returns the instance's primary internal IP address. | |||
func InternalIP() (string, error) { return defaultClient.InternalIP() } | |||
// ExternalIP returns the instance's primary external (public) IP address. | |||
func ExternalIP() (string, error) { return defaultClient.ExternalIP() } | |||
// Email calls Client.Email on the default client. | |||
func Email(serviceAccount string) (string, error) { return defaultClient.Email(serviceAccount) } | |||
// Hostname returns the instance's hostname. This will be of the form | |||
// "<instanceID>.c.<projID>.internal". | |||
func Hostname() (string, error) { return defaultClient.Hostname() } | |||
// InstanceTags returns the list of user-defined instance tags, | |||
// assigned when initially creating a GCE instance. | |||
func InstanceTags() ([]string, error) { return defaultClient.InstanceTags() } | |||
// InstanceID returns the current VM's numeric instance ID. | |||
func InstanceID() (string, error) { return defaultClient.InstanceID() } | |||
// InstanceName returns the current VM's instance ID string. | |||
func InstanceName() (string, error) { return defaultClient.InstanceName() } | |||
// Zone returns the current VM's zone, such as "us-central1-b". | |||
func Zone() (string, error) { return defaultClient.Zone() } | |||
// InstanceAttributes calls Client.InstanceAttributes on the default client. | |||
func InstanceAttributes() ([]string, error) { return defaultClient.InstanceAttributes() } | |||
// ProjectAttributes calls Client.ProjectAttributes on the default client. | |||
func ProjectAttributes() ([]string, error) { return defaultClient.ProjectAttributes() } | |||
// InstanceAttributeValue calls Client.InstanceAttributeValue on the default client. | |||
func InstanceAttributeValue(attr string) (string, error) { | |||
return defaultClient.InstanceAttributeValue(attr) | |||
} | |||
// ProjectAttributeValue calls Client.ProjectAttributeValue on the default client. | |||
func ProjectAttributeValue(attr string) (string, error) { | |||
return defaultClient.ProjectAttributeValue(attr) | |||
} | |||
// Scopes calls Client.Scopes on the default client. | |||
func Scopes(serviceAccount string) ([]string, error) { return defaultClient.Scopes(serviceAccount) } | |||
func strsContains(ss []string, s string) bool { | |||
for _, v := range ss { | |||
if v == s { | |||
return true | |||
} | |||
} | |||
return false | |||
} | |||
// A Client provides metadata. | |||
type Client struct { | |||
hc *http.Client | |||
} | |||
// NewClient returns a Client that can be used to fetch metadata. All HTTP requests | |||
// will use the given http.Client instead of the default client. | |||
func NewClient(c *http.Client) *Client { | |||
return &Client{hc: c} | |||
} | |||
// getETag returns a value from the metadata service as well as the associated ETag. | |||
// This func is otherwise equivalent to Get. | |||
func (c *Client) getETag(suffix string) (value, etag string, err error) { | |||
// Using a fixed IP makes it very difficult to spoof the metadata service in | |||
// a container, which is an important use-case for local testing of cloud | |||
// deployments. To enable spoofing of the metadata service, the environment | |||
// variable GCE_METADATA_HOST is first inspected to decide where metadata | |||
// requests shall go. | |||
host := os.Getenv(metadataHostEnv) | |||
if host == "" { | |||
// Using 169.254.169.254 instead of "metadata" here because Go | |||
// binaries built with the "netgo" tag and without cgo won't | |||
// know the search suffix for "metadata" is | |||
// ".google.internal", and this IP address is documented as | |||
// being stable anyway. | |||
host = metadataIP | |||
} | |||
u := "http://" + host + "/computeMetadata/v1/" + suffix | |||
req, err := http.NewRequest("GET", u, nil) | |||
if err != nil { | |||
return "", "", err | |||
} | |||
req.Header.Set("Metadata-Flavor", "Google") | |||
req.Header.Set("User-Agent", userAgent) | |||
res, err := c.hc.Do(req) | |||
if err != nil { | |||
return "", "", err | |||
} | |||
defer res.Body.Close() | |||
if res.StatusCode == http.StatusNotFound { | |||
return "", "", NotDefinedError(suffix) | |||
} | |||
all, err := ioutil.ReadAll(res.Body) | |||
if err != nil { | |||
return "", "", err | |||
} | |||
if res.StatusCode != 200 { | |||
return "", "", &Error{Code: res.StatusCode, Message: string(all)} | |||
} | |||
return string(all), res.Header.Get("Etag"), nil | |||
} | |||
// Get returns a value from the metadata service. | |||
// The suffix is appended to "http://${GCE_METADATA_HOST}/computeMetadata/v1/". | |||
// | |||
// If the GCE_METADATA_HOST environment variable is not defined, a default of | |||
// 169.254.169.254 will be used instead. | |||
// | |||
// If the requested metadata is not defined, the returned error will | |||
// be of type NotDefinedError. | |||
func (c *Client) Get(suffix string) (string, error) { | |||
val, _, err := c.getETag(suffix) | |||
return val, err | |||
} | |||
func (c *Client) getTrimmed(suffix string) (s string, err error) { | |||
s, err = c.Get(suffix) | |||
s = strings.TrimSpace(s) | |||
return | |||
} | |||
func (c *Client) lines(suffix string) ([]string, error) { | |||
j, err := c.Get(suffix) | |||
if err != nil { | |||
return nil, err | |||
} | |||
s := strings.Split(strings.TrimSpace(j), "\n") | |||
for i := range s { | |||
s[i] = strings.TrimSpace(s[i]) | |||
} | |||
return s, nil | |||
} | |||
// ProjectID returns the current instance's project ID string. | |||
func (c *Client) ProjectID() (string, error) { return projID.get(c) } | |||
// NumericProjectID returns the current instance's numeric project ID. | |||
func (c *Client) NumericProjectID() (string, error) { return projNum.get(c) } | |||
// InstanceID returns the current VM's numeric instance ID. | |||
func (c *Client) InstanceID() (string, error) { return instID.get(c) } | |||
// InternalIP returns the instance's primary internal IP address. | |||
func (c *Client) InternalIP() (string, error) { | |||
return c.getTrimmed("instance/network-interfaces/0/ip") | |||
} | |||
// Email returns the email address associated with the service account. | |||
// The account may be empty or the string "default" to use the instance's | |||
// main account. | |||
func (c *Client) Email(serviceAccount string) (string, error) { | |||
if serviceAccount == "" { | |||
serviceAccount = "default" | |||
} | |||
return c.getTrimmed("instance/service-accounts/" + serviceAccount + "/email") | |||
} | |||
// ExternalIP returns the instance's primary external (public) IP address. | |||
func (c *Client) ExternalIP() (string, error) { | |||
return c.getTrimmed("instance/network-interfaces/0/access-configs/0/external-ip") | |||
} | |||
// Hostname returns the instance's hostname. This will be of the form | |||
// "<instanceID>.c.<projID>.internal". | |||
func (c *Client) Hostname() (string, error) { | |||
return c.getTrimmed("instance/hostname") | |||
} | |||
// InstanceTags returns the list of user-defined instance tags, | |||
// assigned when initially creating a GCE instance. | |||
func (c *Client) InstanceTags() ([]string, error) { | |||
var s []string | |||
j, err := c.Get("instance/tags") | |||
if err != nil { | |||
return nil, err | |||
} | |||
if err := json.NewDecoder(strings.NewReader(j)).Decode(&s); err != nil { | |||
return nil, err | |||
} | |||
return s, nil | |||
} | |||
// InstanceName returns the current VM's instance ID string. | |||
func (c *Client) InstanceName() (string, error) { | |||
return c.getTrimmed("instance/name") | |||
} | |||
// Zone returns the current VM's zone, such as "us-central1-b". | |||
func (c *Client) Zone() (string, error) { | |||
zone, err := c.getTrimmed("instance/zone") | |||
// zone is of the form "projects/<projNum>/zones/<zoneName>". | |||
if err != nil { | |||
return "", err | |||
} | |||
return zone[strings.LastIndex(zone, "/")+1:], nil | |||
} | |||
// InstanceAttributes returns the list of user-defined attributes, | |||
// assigned when initially creating a GCE VM instance. The value of an | |||
// attribute can be obtained with InstanceAttributeValue. | |||
func (c *Client) InstanceAttributes() ([]string, error) { return c.lines("instance/attributes/") } | |||
// ProjectAttributes returns the list of user-defined attributes | |||
// applying to the project as a whole, not just this VM. The value of | |||
// an attribute can be obtained with ProjectAttributeValue. | |||
func (c *Client) ProjectAttributes() ([]string, error) { return c.lines("project/attributes/") } | |||
// InstanceAttributeValue returns the value of the provided VM | |||
// instance attribute. | |||
// | |||
// If the requested attribute is not defined, the returned error will | |||
// be of type NotDefinedError. | |||
// | |||
// InstanceAttributeValue may return ("", nil) if the attribute was | |||
// defined to be the empty string. | |||
func (c *Client) InstanceAttributeValue(attr string) (string, error) { | |||
return c.Get("instance/attributes/" + attr) | |||
} | |||
// ProjectAttributeValue returns the value of the provided | |||
// project attribute. | |||
// | |||
// If the requested attribute is not defined, the returned error will | |||
// be of type NotDefinedError. | |||
// | |||
// ProjectAttributeValue may return ("", nil) if the attribute was | |||
// defined to be the empty string. | |||
func (c *Client) ProjectAttributeValue(attr string) (string, error) { | |||
return c.Get("project/attributes/" + attr) | |||
} | |||
// Scopes returns the service account scopes for the given account. | |||
// The account may be empty or the string "default" to use the instance's | |||
// main account. | |||
func (c *Client) Scopes(serviceAccount string) ([]string, error) { | |||
if serviceAccount == "" { | |||
serviceAccount = "default" | |||
} | |||
return c.lines("instance/service-accounts/" + serviceAccount + "/scopes") | |||
} | |||
// Subscribe subscribes to a value from the metadata service. | |||
// The suffix is appended to "http://${GCE_METADATA_HOST}/computeMetadata/v1/". | |||
// The suffix may contain query parameters. | |||
// | |||
// Subscribe calls fn with the latest metadata value indicated by the provided | |||
// suffix. If the metadata value is deleted, fn is called with the empty string | |||
// and ok false. Subscribe blocks until fn returns a non-nil error or the value | |||
// is deleted. Subscribe returns the error value returned from the last call to | |||
// fn, which may be nil when ok == false. | |||
func (c *Client) Subscribe(suffix string, fn func(v string, ok bool) error) error { | |||
const failedSubscribeSleep = time.Second * 5 | |||
// First check to see if the metadata value exists at all. | |||
val, lastETag, err := c.getETag(suffix) | |||
if err != nil { | |||
return err | |||
} | |||
if err := fn(val, true); err != nil { | |||
return err | |||
} | |||
ok := true | |||
if strings.ContainsRune(suffix, '?') { | |||
suffix += "&wait_for_change=true&last_etag=" | |||
} else { | |||
suffix += "?wait_for_change=true&last_etag=" | |||
} | |||
for { | |||
val, etag, err := c.getETag(suffix + url.QueryEscape(lastETag)) | |||
if err != nil { | |||
if _, deleted := err.(NotDefinedError); !deleted { | |||
time.Sleep(failedSubscribeSleep) | |||
continue // Retry on other errors. | |||
} | |||
ok = false | |||
} | |||
lastETag = etag | |||
if err := fn(val, ok); err != nil || !ok { | |||
return err | |||
} | |||
} | |||
} | |||
// Error contains an error response from the server. | |||
type Error struct { | |||
// Code is the HTTP response status code. | |||
Code int | |||
// Message is the server response message. | |||
Message string | |||
} | |||
func (e *Error) Error() string { | |||
return fmt.Sprintf("compute: Received %d `%s`", e.Code, e.Message) | |||
} |
@ -0,0 +1,202 @@ | |||
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. |
@ -0,0 +1,11 @@ | |||
Copyright (c) 2015-Present CloudFoundry.org Foundation, Inc. All Rights Reserved. | |||
This project contains software that is Copyright (c) 2014-2015 Pivotal Software, Inc. | |||
This project is licensed to you under the Apache License, Version 2.0 (the "License"). | |||
You may not use this project except in compliance with the License. | |||
This project may include a number of subcomponents with separate copyright notices | |||
and license terms. Your use of these subcomponents is subject to the terms and | |||
conditions of the subcomponent's license, as noted in the LICENSE file. |
@ -0,0 +1,20 @@ | |||
package fileutils | |||
import ( | |||
"os" | |||
) | |||
func IsDirEmpty(dir string) (isEmpty bool, err error) { | |||
dirFile, err := os.Open(dir) | |||
if err != nil { | |||
return | |||
} | |||
_, readErr := dirFile.Readdirnames(1) | |||
if readErr != nil { | |||
isEmpty = true | |||
} else { | |||
isEmpty = false | |||
} | |||
return | |||
} |
@ -0,0 +1,74 @@ | |||
package fileutils | |||
import ( | |||
"io" | |||
"io/ioutil" | |||
"os" | |||
"path" | |||
"path/filepath" | |||
) | |||
func Open(path string) (file *os.File, err error) { | |||
err = os.MkdirAll(filepath.Dir(path), os.ModeDir|os.ModePerm) | |||
if err != nil { | |||
return | |||
} | |||
return os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) | |||
} | |||
func Create(path string) (file *os.File, err error) { | |||
err = os.MkdirAll(filepath.Dir(path), os.ModeDir|os.ModePerm) | |||
if err != nil { | |||
return | |||
} | |||
return os.Create(path) | |||
} | |||
func CopyPathToPath(fromPath, toPath string) (err error) { | |||
srcFileInfo, err := os.Stat(fromPath) | |||
if err != nil { | |||
return err | |||
} | |||
if srcFileInfo.IsDir() { | |||
err = os.MkdirAll(toPath, srcFileInfo.Mode()) | |||
if err != nil { | |||
return err | |||
} | |||
files, err := ioutil.ReadDir(fromPath) | |||
if err != nil { | |||
return err | |||
} | |||
for _, file := range files { | |||
err = CopyPathToPath(path.Join(fromPath, file.Name()), path.Join(toPath, file.Name())) | |||
if err != nil { | |||
return err | |||
} | |||
} | |||
} else { | |||
var dst *os.File | |||
dst, err = Create(toPath) | |||
if err != nil { | |||
return err | |||
} | |||
defer dst.Close() | |||
dst.Chmod(srcFileInfo.Mode()) | |||
src, err := os.Open(fromPath) | |||
if err != nil { | |||
return err | |||
} | |||
defer src.Close() | |||
_, err = io.Copy(dst, src) | |||
if err != nil { | |||
return err | |||
} | |||
} | |||
return err | |||
} |
@ -0,0 +1,27 @@ | |||
package fileutils | |||
import ( | |||
"io/ioutil" | |||
"os" | |||
) | |||
func TempDir(namePrefix string, cb func(tmpDir string, err error)) { | |||
tmpDir, err := ioutil.TempDir("", namePrefix) | |||
defer func() { | |||
os.RemoveAll(tmpDir) | |||
}() | |||
cb(tmpDir, err) | |||
} | |||
func TempFile(namePrefix string, cb func(tmpFile *os.File, err error)) { | |||
tmpFile, err := ioutil.TempFile("", namePrefix) | |||
defer func() { | |||
tmpFile.Close() | |||
os.Remove(tmpFile.Name()) | |||
}() | |||
cb(tmpFile, err) | |||
} |
@ -0,0 +1,392 @@ | |||
# hclouddns | |||
-- | |||
import "git.blindage.org/21h/hcloud-dns" | |||
## Usage | |||
#### type HCloudAnswerCreateRecords | |||
```go | |||
type HCloudAnswerCreateRecords struct { | |||
Records []HCloudRecord `json:"records,omitempty"` | |||
ValidRecords []HCloudRecord `json:"valid_records,omitempty"` | |||
InvalidRecords []HCloudRecord `json:"invalid_records,omitempty"` | |||
Error HCloudError | |||
} | |||
``` | |||
#### type HCloudAnswerDeleteRecord | |||
```go | |||
type HCloudAnswerDeleteRecord struct { | |||
Error HCloudError | |||
} | |||
``` | |||
#### type HCloudAnswerDeleteZone | |||
```go | |||
type HCloudAnswerDeleteZone struct { | |||
Error HCloudError | |||
} | |||
``` | |||
#### type HCloudAnswerError | |||
```go | |||
type HCloudAnswerError struct { | |||
Error HCloudError `json:"error,omitempty"` | |||
} | |||
``` | |||
sometime can be returned HCloudError | |||
#### type HCloudAnswerErrorString | |||
```go | |||
type HCloudAnswerErrorString struct { | |||
Error string `json:"error,omitempty"` | |||
} | |||
``` | |||
or plain string | |||
#### type HCloudAnswerGetRecord | |||
```go | |||
type HCloudAnswerGetRecord struct { | |||
Record HCloudRecord `json:"record,omitempty"` | |||
Error HCloudError | |||
} | |||
``` | |||
#### type HCloudAnswerGetRecords | |||
```go | |||
type HCloudAnswerGetRecords struct { | |||
Records []HCloudRecord `json:"records,omitempty"` | |||
Meta HCloudMeta `json:"meta,omitempty"` | |||
Error HCloudError | |||
} | |||
``` | |||
#### type HCloudAnswerGetZone | |||
```go | |||
type HCloudAnswerGetZone struct { | |||
Zone HCloudZone `json:"zone,omitempty"` | |||
Error HCloudError | |||
} | |||
``` | |||
#### type HCloudAnswerGetZonePlainText | |||
```go | |||
type HCloudAnswerGetZonePlainText struct { | |||
ZonePlainText string `json:"zone,omitempty"` | |||
Error HCloudError | |||
} | |||
``` | |||
#### type HCloudAnswerGetZones | |||
```go | |||
type HCloudAnswerGetZones struct { | |||
Zones []HCloudZone `json:"zones,omitempty"` | |||
Meta HCloudMeta `json:"meta,omitempty"` | |||
Error HCloudError | |||
} | |||
``` | |||
#### type HCloudAnswerUpdateRecords | |||
```go | |||
type HCloudAnswerUpdateRecords struct { | |||
Records []HCloudRecord `json:"records,omitempty"` | |||
InvalidRecords []HCloudRecord `json:"failed_records,omitempty"` | |||
Error HCloudError | |||
} | |||
``` | |||
#### type HCloudAnswerZoneValidate | |||
```go | |||
type HCloudAnswerZoneValidate struct { | |||
ParsedRecords int `json:"parsed_records,omitempty"` | |||
ValidRecords []HCloudZone `json:"valid_records,omitempty"` | |||
Error HCloudError | |||
} | |||
``` | |||
#### type HCloudDNS | |||
```go | |||
type HCloudDNS struct { | |||
} | |||
``` | |||
Base types | |||
#### func New | |||
```go | |||
func New(t string) *HCloudDNS | |||
``` | |||
New instance | |||
#### func (*HCloudDNS) CreateRecord | |||
```go | |||
func (d *HCloudDNS) CreateRecord(record HCloudRecord) (HCloudAnswerGetRecord, error) | |||
``` | |||
CreateRecord creates new single record. Accepts HCloudRecord with record to | |||
create, of cource no ID. Returns HCloudAnswerGetRecord with HCloudRecord and | |||
error. | |||
#### func (*HCloudDNS) CreateRecordBulk | |||
```go | |||
func (d *HCloudDNS) CreateRecordBulk(record []HCloudRecord) (HCloudAnswerCreateRecords, error) | |||
``` | |||
CreateRecordBulk creates many records at once. Accepts array of HCloudRecord, | |||
converts it to json and makes POST to Hetzner. Returns HCloudAnswerCreateRecords | |||
with arrays of HCloudRecord with whole list, valid and invalid, error. | |||
#### func (*HCloudDNS) CreateZone | |||
```go | |||
func (d *HCloudDNS) CreateZone(zone HCloudZone) (HCloudAnswerGetZone, error) | |||
``` | |||
CreateZone creates new single zone. Accepts HCloudZone with record to create, of | |||
cource no ID. Returns HCloudAnswerGetZone with HCloudZone and error. | |||
#### func (*HCloudDNS) DeleteRecord | |||
```go | |||
func (d *HCloudDNS) DeleteRecord(ID string) (HCloudAnswerDeleteRecord, error) | |||
``` | |||
DeleteRecord remove record by ID. Accepts single ID string. Returns | |||
HCloudAnswerDeleteRecord and error. | |||
#### func (*HCloudDNS) DeleteZone | |||
```go | |||
func (d *HCloudDNS) DeleteZone(ID string) (HCloudAnswerDeleteZone, error) | |||
``` | |||
DeleteZone remove zone by ID. Accepts single ID string. Returns | |||
HCloudAnswerDeleteZone with error. | |||
#### func (*HCloudDNS) ExportZoneToString | |||
```go | |||
func (d *HCloudDNS) ExportZoneToString(zoneID string) (HCloudAnswerGetZonePlainText, error) | |||
``` | |||
ExportZoneToString exports single zone from imported text. Accepts ID and | |||
zonePlainText strings. Returns HCloudAnswerGetZonePlainText with HCloudZone and | |||
error. | |||
#### func (*HCloudDNS) GetRecord | |||
```go | |||
func (d *HCloudDNS) GetRecord(ID string) (HCloudAnswerGetRecord, error) | |||
``` | |||
GetRecord retrieve one single record by ID. Accepts single ID of record. Returns | |||
HCloudAnswerGetRecord with HCloudRecord and error. | |||
#### func (*HCloudDNS) GetRecords | |||
```go | |||
func (d *HCloudDNS) GetRecords(params HCloudGetRecordsParams) (HCloudAnswerGetRecords, error) | |||
``` | |||
GetRecords retrieve all records of user. Accepts HCloudGetRecordsParams struct | |||
Returns HCloudAnswerGetRecords with array of HCloudRecord, Meta and error. | |||
#### func (*HCloudDNS) GetZone | |||
```go | |||
func (d *HCloudDNS) GetZone(ID string) (HCloudAnswerGetZone, error) | |||
``` | |||
GetZone retrieve one single zone by ID. Accepts zone ID string. Returns | |||
HCloudAnswerGetZone with HCloudZone and error | |||
#### func (*HCloudDNS) GetZones | |||
```go | |||
func (d *HCloudDNS) GetZones(params HCloudGetZonesParams) (HCloudAnswerGetZones, error) | |||
``` | |||
GetZones retrieve all zones of user. Accepts exact name as string, search name | |||
with partial name. Returns HCloudAnswerGetZones with array of HCloudZone, Meta | |||
and error. | |||
#### func (*HCloudDNS) ImportZoneString | |||
```go | |||
func (d *HCloudDNS) ImportZoneString(zoneID string, zonePlainText string) (HCloudAnswerGetZone, error) | |||
``` | |||
ImportZoneString imports single zone from imported text. Accepts ID and | |||
zonePlainText strings. Returns HCloudAnswerGetZone with HCloudZone and error. | |||
#### func (*HCloudDNS) UpdateRecord | |||
```go | |||
func (d *HCloudDNS) UpdateRecord(record HCloudRecord) (HCloudAnswerGetRecord, error) | |||
``` | |||
UpdateRecord makes update of single record by ID. Accepts HCloudRecord with | |||
fullfilled fields. Returns HCloudAnswerGetRecord with HCloudRecord and error. | |||
#### func (*HCloudDNS) UpdateRecordBulk | |||
```go | |||
func (d *HCloudDNS) UpdateRecordBulk(record []HCloudRecord) (HCloudAnswerUpdateRecords, error) | |||
``` | |||
UpdateRecordBulk updates many records at once. Accepts array of HCloudRecord, | |||
converting to json and makes PUT to Hetzner. Returns HCloudAnswerUpdateRecords | |||
with arrays of HCloudRecord with updated and failed, error. | |||
#### func (*HCloudDNS) UpdateZone | |||
```go | |||
func (d *HCloudDNS) UpdateZone(zone HCloudZone) (HCloudAnswerGetZone, error) | |||
``` | |||
UpdateZone makes update of single zone by ID. Accepts HCloudZone with fullfilled | |||
fields. Returns HCloudAnswerGetZone with HCloudZone and error. | |||
#### func (*HCloudDNS) ValidateZoneString | |||
```go | |||
func (d *HCloudDNS) ValidateZoneString(zonePlainText string) (HCloudAnswerZoneValidate, error) | |||
``` | |||
ValidateZoneString validate single zone from imported text. Accepts ID and | |||
zonePlainText strings. Returns HCloudAnswerZoneValidate with HCloudZone and | |||
error. | |||
#### type HCloudError | |||
```go | |||
type HCloudError struct { | |||
Code int `json:"code,omitempty"` | |||
Message string `json:"message,omitempty"` | |||
} | |||
``` | |||
Hetzner errors roundabout. Fuck you Hetzner. | |||
#### type HCloudGetRecordsParams | |||
```go | |||
type HCloudGetRecordsParams struct { | |||
ZoneID string | |||
Page string | |||
PerPage string | |||
} | |||
``` | |||
#### type HCloudGetZonesParams | |||
```go | |||
type HCloudGetZonesParams struct { | |||
Name string | |||
SearchName string | |||
Page string | |||
PerPage string | |||
} | |||
``` | |||
#### type HCloudMeta | |||
```go | |||
type HCloudMeta struct { | |||
Pagination struct { | |||
Page int `json:"page"` | |||
PerPage int `json:"per_page"` | |||
LastPage int `json:"last_page"` | |||
TotalEntries int `json:"total_entries"` | |||
} `json:"pagination,omitempty"` | |||
} | |||
``` | |||
#### type HCloudRecord | |||
```go | |||
type HCloudRecord struct { | |||
RecordType RecordType `json:"type"` | |||
ID string `json:"id"` | |||
Created string `json:"created"` | |||
Modified string `json:"modified"` | |||
ZoneID string `json:"zone_id"` | |||
Name string `json:"name"` | |||
Value string `json:"value"` | |||
TTL int `json:"ttl"` | |||
} | |||
``` | |||
#### type HCloudZone | |||
```go | |||
type HCloudZone struct { | |||
ID string `json:"id,omitempty"` | |||
Created string `json:"created,omitempty"` | |||
Modified string `json:"modified,omitempty"` | |||
LegacyDNSHost string `json:"legacy_dns_host,omitempty"` | |||
LegacyNS []string `json:"legacy_ns,omitempty"` | |||
Name string `json:"name,omitempty"` | |||
NS []string `json:"ns,omitempty"` | |||
Owner string `json:"owner,omitempty"` | |||
Paused bool `json:"paused,omitempty"` | |||
Permission string `json:"permission,omitempty"` | |||
Project string `json:"project,omitempty"` | |||
Registrar string `json:"registrar,omitempty"` | |||
Status string `json:"status,omitempty"` | |||
TTL int `json:"ttl,omitempty"` | |||
Verified string `json:"verified,omitempty"` | |||
RecordsCount int `json:"records_count,omitempty"` | |||
IsSecondaryDNS bool `json:"is_secondary_dns,omitempty"` | |||
TXTverification struct { | |||
Name string `json:"name,omitempty"` | |||
Token string `json:"token,omitempty"` | |||
} `json:"txt_verification,omitempty"` | |||
} | |||
``` | |||
#### type RecordType | |||
```go | |||
type RecordType string | |||
``` | |||
RecordType supported by Hetzner | |||
```go | |||
const ( | |||
A RecordType = "A" | |||
AAAA RecordType = "AAAA" | |||
CNAME RecordType = "CNAME" | |||
MX RecordType = "MX" | |||
NS RecordType = "NS" | |||
TXT RecordType = "TXT" | |||
RP RecordType = "RP" | |||
SOA RecordType = "SOA" | |||
HINFO RecordType = "HINFO" | |||
SRV RecordType = "SRV" | |||
DANE RecordType = "DANE" | |||
TLSA RecordType = "TLSA" | |||
DS RecordType = "DS" | |||
CAA RecordType = "CAA" | |||
) | |||
``` |
@ -0,0 +1,3 @@ | |||
module git.blindage.org/21h/hcloud-dns | |||
go 1.14 |
@ -0,0 +1,17 @@ | |||
/* | |||
Created by Vladimir Smagin, 2020 | |||
http://blindage.org 21h@blindage.org | |||
Project page: https://git.blindage.org/21h/hcloud-dns | |||
*/ | |||
package hclouddns | |||
// New instance | |||
func New(t string) HCloudClientAdapter { | |||
return &HCloudClient{ | |||
Token: t, | |||
} | |||
} |
@ -0,0 +1,30 @@ | |||
# Hetzner DNS golang library | |||
I made this library to interact with Hetzner DNS API in most easy way. Hopefully in future it will be used for Hetzner external-dns provider. Check out [example](example) directory and [API_help.md](API_help.md). | |||
Get your own token on Hetzner DNS and place it to `token` variable and run code | |||
```go | |||
token := "jcB2UywP9XtZGhvhSHpH5m" | |||
zone := "vhSHpH5mjcB2UywP9XtZGh" | |||
log.Println("Create new instance") | |||
hdns := hclouddns.New(token) | |||
log.Println("Get zone", zone) | |||
allRecords, err := hdns.GetRecords(zone) | |||
if err != nil { | |||
log.Fatalln(err) | |||
} | |||
log.Println(allRecords.Records) | |||
log.Println(allRecords.Error) | |||
``` | |||
At this moment library supports all API requests. If you found some bug mail me or register here and create issue. | |||
--- | |||
Copyright by Vladimir Smagin (21h) 2020 | |||
http://blindage.org email: 21h@blindage.org | |||
Project page: https://git.blindage.org/21h/hcloud-dns |
@ -0,0 +1,385 @@ | |||
package hclouddns | |||
import ( | |||
"bytes" | |||
"encoding/json" | |||
"fmt" | |||
"io/ioutil" | |||
"net/http" | |||
"net/url" | |||
) | |||
// GetRecord retrieve one single record by ID. | |||
// Accepts single ID of record. | |||
// Returns HCloudAnswerGetRecord with HCloudRecord and error. | |||
func (d *HCloudClient) GetRecord(ID string) (HCloudAnswerGetRecord, error) { | |||
client := &http.Client{} | |||
req, err := http.NewRequest("GET", fmt.Sprintf("https://dns.hetzner.com/api/v1/records/%v", ID), nil) | |||
if err != nil { | |||
return HCloudAnswerGetRecord{}, err | |||
} | |||
req.Header.Add("Auth-API-Token", d.Token) | |||
resp, err := client.Do(req) | |||
if err != nil { | |||
return HCloudAnswerGetRecord{}, err | |||
} | |||
respBody, err := ioutil.ReadAll(resp.Body) | |||
if err != nil { | |||
return HCloudAnswerGetRecord{}, err | |||
} | |||
answer := HCloudAnswerGetRecord{} | |||
err = json.Unmarshal([]byte(respBody), &answer) | |||
if err != nil { | |||
return HCloudAnswerGetRecord{}, err | |||
} | |||
// parse error | |||
errorResult := HCloudAnswerError{} | |||
err = json.Unmarshal([]byte(respBody), &errorResult) | |||
if err != nil { | |||
//ok, non-standard error, try another form | |||
errorResultString := HCloudAnswerErrorString{} | |||
err = json.Unmarshal([]byte(respBody), &errorResultString) | |||
if err != nil { | |||
return HCloudAnswerGetRecord{}, err | |||
} | |||
errorResult.Error.Message = errorResultString.Error | |||
errorResult.Error.Code = resp.StatusCode | |||
} | |||
answer.Error = errorResult.Error | |||
return answer, nil | |||
} | |||
// GetRecords retrieve all records of user. | |||
// Accepts HCloudGetRecordsParams struct | |||
// Returns HCloudAnswerGetRecords with array of HCloudRecord, Meta and error. | |||
func (d *HCloudClient) GetRecords(params HCloudGetRecordsParams) (HCloudAnswerGetRecords, error) { | |||
v := url.Values{} | |||
if params.Page != "" { | |||
v.Add("page", params.Page) | |||
} | |||
if params.PerPage != "" { | |||
v.Add("per_page", params.PerPage) | |||
} | |||
if params.ZoneID != "" { | |||
v.Add("zone_id", params.ZoneID) | |||
} | |||
client := &http.Client{} | |||
req, err := http.NewRequest("GET", fmt.Sprintf("https://dns.hetzner.com/api/v1/records?%v", v.Encode()), nil) | |||
if err != nil { | |||
return HCloudAnswerGetRecords{}, err | |||
} | |||
req.Header.Add("Auth-API-Token", d.Token) | |||
parseFormErr := req.ParseForm() | |||
if parseFormErr != nil { | |||
return HCloudAnswerGetRecords{}, parseFormErr | |||
} | |||
resp, err := client.Do(req) | |||
if err != nil { | |||
return HCloudAnswerGetRecords{}, err | |||
} | |||
respBody, err := ioutil.ReadAll(resp.Body) | |||
if err != nil { | |||
return HCloudAnswerGetRecords{}, err | |||
} | |||
answer := HCloudAnswerGetRecords{} | |||
err = json.Unmarshal([]byte(respBody), &answer) | |||
if err != nil { | |||
return HCloudAnswerGetRecords{}, err | |||
} | |||
// parse error | |||
errorResult := HCloudAnswerError{} | |||
err = json.Unmarshal([]byte(respBody), &errorResult) | |||
if err != nil { | |||
//ok, non-standard error, try another form | |||
errorResultString := HCloudAnswerErrorString{} | |||
err = json.Unmarshal([]byte(respBody), &errorResultString) | |||
if err != nil { | |||
return HCloudAnswerGetRecords{}, err | |||
} | |||
errorResult.Error.Message = errorResultString.Error | |||
errorResult.Error.Code = resp.StatusCode | |||
} | |||
answer.Error = errorResult.Error | |||
return answer, nil | |||
} | |||
// UpdateRecord makes update of single record by ID. | |||
// Accepts HCloudRecord with fullfilled fields. | |||
// Returns HCloudAnswerGetRecord with HCloudRecord and error. | |||
func (d *HCloudClient) UpdateRecord(record HCloudRecord) (HCloudAnswerGetRecord, error) { | |||
jsonRecordString, err := json.Marshal(record) | |||
if err != nil { | |||
return HCloudAnswerGetRecord{}, err | |||
} | |||
body := bytes.NewBuffer(jsonRecordString) | |||
client := &http.Client{} | |||
req, err := http.NewRequest("PUT", fmt.Sprintf("https://dns.hetzner.com/api/v1/records/%v", record.ID), body) | |||
if err != nil { | |||
return HCloudAnswerGetRecord{}, err | |||
} | |||
req.Header.Add("Content-Type", "application/json") | |||
req.Header.Add("Auth-API-Token", d.Token) | |||
resp, err := client.Do(req) | |||
if err != nil { | |||
return HCloudAnswerGetRecord{}, err | |||
} | |||
respBody, err := ioutil.ReadAll(resp.Body) | |||
if err != nil { | |||
return HCloudAnswerGetRecord{}, err | |||
} | |||
answer := HCloudAnswerGetRecord{} | |||
err = json.Unmarshal([]byte(respBody), &answer) | |||
if err != nil { | |||
return HCloudAnswerGetRecord{}, err | |||
} | |||
// parse error | |||
errorResult := HCloudAnswerError{} | |||
err = json.Unmarshal([]byte(respBody), &errorResult) | |||
if err != nil { | |||
//ok, non-standard error, try another form | |||
errorResultString := HCloudAnswerErrorString{} | |||
err = json.Unmarshal([]byte(respBody), &errorResultString) | |||
if err != nil { | |||
return HCloudAnswerGetRecord{}, err | |||
} | |||
errorResult.Error.Message = errorResultString.Error | |||
errorResult.Error.Code = resp.StatusCode | |||
} | |||
answer.Error = errorResult.Error | |||
return answer, nil | |||
} | |||
// DeleteRecord remove record by ID. | |||
// Accepts single ID string. | |||
// Returns HCloudAnswerDeleteRecord and error. | |||
func (d *HCloudClient) DeleteRecord(ID string) (HCloudAnswerDeleteRecord, error) { | |||
client := &http.Client{} | |||
req, err := http.NewRequest("DELETE", fmt.Sprintf("https://dns.hetzner.com/api/v1/records/%v", ID), nil) | |||
if err != nil { | |||
return HCloudAnswerDeleteRecord{}, err | |||
} | |||
req.Header.Add("Auth-API-Token", d.Token) | |||
resp, err := client.Do(req) | |||
if err != nil { | |||
return HCloudAnswerDeleteRecord{}, err | |||
} | |||
respBody, err := ioutil.ReadAll(resp.Body) | |||
if err != nil { | |||
return HCloudAnswerDeleteRecord{}, err | |||
} | |||
answer := HCloudAnswerDeleteRecord{} | |||
// parse error | |||
errorResult := HCloudAnswerError{} | |||
err = json.Unmarshal([]byte(respBody), &errorResult) | |||
if err != nil { | |||
//ok, non-standard error, try another form | |||
errorResultString := HCloudAnswerErrorStrin |