Signed-off-by: Akihiro Suda <suda.akihiro@lab.ntt.co.jp>
| ... | ... |
@@ -88,9 +88,12 @@ github.com/jmespath/go-jmespath 0b12d6b521d83fc7f755e7cfc1b1fbdd35a01a74 |
| 88 | 88 |
github.com/bsphere/le_go d3308aafe090956bc89a65f0769f58251a1b4f03 |
| 89 | 89 |
|
| 90 | 90 |
# gcplogs deps |
| 91 |
-golang.org/x/oauth2 2baa8a1b9338cf13d9eeb27696d761155fa480be |
|
| 91 |
+golang.org/x/oauth2 96382aa079b72d8c014eb0c50f6c223d1e6a2de0 |
|
| 92 | 92 |
google.golang.org/api dc6d2353af16e2a2b0ff6986af051d473a4ed468 |
| 93 |
+# TODO: remove google.golang.org/cloud, which has been replaced by cloud.google.com/go |
|
| 93 | 94 |
google.golang.org/cloud dae7e3d993bc3812a2185af60552bb6b847e52a0 |
| 95 |
+cloud.google.com/go 9d965e63e8cceb1b5d7977a202f0fcb8866d6525 |
|
| 96 |
+github.com/googleapis/gax-go da06d194a00e19ce00d9011a13931c3f6f6887c7 |
|
| 94 | 97 |
|
| 95 | 98 |
# native credentials |
| 96 | 99 |
github.com/docker/docker-credential-helpers f72c04f1d8e71959a6d103f808c50ccbad79b9fd |
| 97 | 100 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,202 @@ |
| 0 |
+ |
|
| 1 |
+ Apache License |
|
| 2 |
+ Version 2.0, January 2004 |
|
| 3 |
+ http://www.apache.org/licenses/ |
|
| 4 |
+ |
|
| 5 |
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION |
|
| 6 |
+ |
|
| 7 |
+ 1. Definitions. |
|
| 8 |
+ |
|
| 9 |
+ "License" shall mean the terms and conditions for use, reproduction, |
|
| 10 |
+ and distribution as defined by Sections 1 through 9 of this document. |
|
| 11 |
+ |
|
| 12 |
+ "Licensor" shall mean the copyright owner or entity authorized by |
|
| 13 |
+ the copyright owner that is granting the License. |
|
| 14 |
+ |
|
| 15 |
+ "Legal Entity" shall mean the union of the acting entity and all |
|
| 16 |
+ other entities that control, are controlled by, or are under common |
|
| 17 |
+ control with that entity. For the purposes of this definition, |
|
| 18 |
+ "control" means (i) the power, direct or indirect, to cause the |
|
| 19 |
+ direction or management of such entity, whether by contract or |
|
| 20 |
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the |
|
| 21 |
+ outstanding shares, or (iii) beneficial ownership of such entity. |
|
| 22 |
+ |
|
| 23 |
+ "You" (or "Your") shall mean an individual or Legal Entity |
|
| 24 |
+ exercising permissions granted by this License. |
|
| 25 |
+ |
|
| 26 |
+ "Source" form shall mean the preferred form for making modifications, |
|
| 27 |
+ including but not limited to software source code, documentation |
|
| 28 |
+ source, and configuration files. |
|
| 29 |
+ |
|
| 30 |
+ "Object" form shall mean any form resulting from mechanical |
|
| 31 |
+ transformation or translation of a Source form, including but |
|
| 32 |
+ not limited to compiled object code, generated documentation, |
|
| 33 |
+ and conversions to other media types. |
|
| 34 |
+ |
|
| 35 |
+ "Work" shall mean the work of authorship, whether in Source or |
|
| 36 |
+ Object form, made available under the License, as indicated by a |
|
| 37 |
+ copyright notice that is included in or attached to the work |
|
| 38 |
+ (an example is provided in the Appendix below). |
|
| 39 |
+ |
|
| 40 |
+ "Derivative Works" shall mean any work, whether in Source or Object |
|
| 41 |
+ form, that is based on (or derived from) the Work and for which the |
|
| 42 |
+ editorial revisions, annotations, elaborations, or other modifications |
|
| 43 |
+ represent, as a whole, an original work of authorship. For the purposes |
|
| 44 |
+ of this License, Derivative Works shall not include works that remain |
|
| 45 |
+ separable from, or merely link (or bind by name) to the interfaces of, |
|
| 46 |
+ the Work and Derivative Works thereof. |
|
| 47 |
+ |
|
| 48 |
+ "Contribution" shall mean any work of authorship, including |
|
| 49 |
+ the original version of the Work and any modifications or additions |
|
| 50 |
+ to that Work or Derivative Works thereof, that is intentionally |
|
| 51 |
+ submitted to Licensor for inclusion in the Work by the copyright owner |
|
| 52 |
+ or by an individual or Legal Entity authorized to submit on behalf of |
|
| 53 |
+ the copyright owner. For the purposes of this definition, "submitted" |
|
| 54 |
+ means any form of electronic, verbal, or written communication sent |
|
| 55 |
+ to the Licensor or its representatives, including but not limited to |
|
| 56 |
+ communication on electronic mailing lists, source code control systems, |
|
| 57 |
+ and issue tracking systems that are managed by, or on behalf of, the |
|
| 58 |
+ Licensor for the purpose of discussing and improving the Work, but |
|
| 59 |
+ excluding communication that is conspicuously marked or otherwise |
|
| 60 |
+ designated in writing by the copyright owner as "Not a Contribution." |
|
| 61 |
+ |
|
| 62 |
+ "Contributor" shall mean Licensor and any individual or Legal Entity |
|
| 63 |
+ on behalf of whom a Contribution has been received by Licensor and |
|
| 64 |
+ subsequently incorporated within the Work. |
|
| 65 |
+ |
|
| 66 |
+ 2. Grant of Copyright License. Subject to the terms and conditions of |
|
| 67 |
+ this License, each Contributor hereby grants to You a perpetual, |
|
| 68 |
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable |
|
| 69 |
+ copyright license to reproduce, prepare Derivative Works of, |
|
| 70 |
+ publicly display, publicly perform, sublicense, and distribute the |
|
| 71 |
+ Work and such Derivative Works in Source or Object form. |
|
| 72 |
+ |
|
| 73 |
+ 3. Grant of Patent License. Subject to the terms and conditions of |
|
| 74 |
+ this License, each Contributor hereby grants to You a perpetual, |
|
| 75 |
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable |
|
| 76 |
+ (except as stated in this section) patent license to make, have made, |
|
| 77 |
+ use, offer to sell, sell, import, and otherwise transfer the Work, |
|
| 78 |
+ where such license applies only to those patent claims licensable |
|
| 79 |
+ by such Contributor that are necessarily infringed by their |
|
| 80 |
+ Contribution(s) alone or by combination of their Contribution(s) |
|
| 81 |
+ with the Work to which such Contribution(s) was submitted. If You |
|
| 82 |
+ institute patent litigation against any entity (including a |
|
| 83 |
+ cross-claim or counterclaim in a lawsuit) alleging that the Work |
|
| 84 |
+ or a Contribution incorporated within the Work constitutes direct |
|
| 85 |
+ or contributory patent infringement, then any patent licenses |
|
| 86 |
+ granted to You under this License for that Work shall terminate |
|
| 87 |
+ as of the date such litigation is filed. |
|
| 88 |
+ |
|
| 89 |
+ 4. Redistribution. You may reproduce and distribute copies of the |
|
| 90 |
+ Work or Derivative Works thereof in any medium, with or without |
|
| 91 |
+ modifications, and in Source or Object form, provided that You |
|
| 92 |
+ meet the following conditions: |
|
| 93 |
+ |
|
| 94 |
+ (a) You must give any other recipients of the Work or |
|
| 95 |
+ Derivative Works a copy of this License; and |
|
| 96 |
+ |
|
| 97 |
+ (b) You must cause any modified files to carry prominent notices |
|
| 98 |
+ stating that You changed the files; and |
|
| 99 |
+ |
|
| 100 |
+ (c) You must retain, in the Source form of any Derivative Works |
|
| 101 |
+ that You distribute, all copyright, patent, trademark, and |
|
| 102 |
+ attribution notices from the Source form of the Work, |
|
| 103 |
+ excluding those notices that do not pertain to any part of |
|
| 104 |
+ the Derivative Works; and |
|
| 105 |
+ |
|
| 106 |
+ (d) If the Work includes a "NOTICE" text file as part of its |
|
| 107 |
+ distribution, then any Derivative Works that You distribute must |
|
| 108 |
+ include a readable copy of the attribution notices contained |
|
| 109 |
+ within such NOTICE file, excluding those notices that do not |
|
| 110 |
+ pertain to any part of the Derivative Works, in at least one |
|
| 111 |
+ of the following places: within a NOTICE text file distributed |
|
| 112 |
+ as part of the Derivative Works; within the Source form or |
|
| 113 |
+ documentation, if provided along with the Derivative Works; or, |
|
| 114 |
+ within a display generated by the Derivative Works, if and |
|
| 115 |
+ wherever such third-party notices normally appear. The contents |
|
| 116 |
+ of the NOTICE file are for informational purposes only and |
|
| 117 |
+ do not modify the License. You may add Your own attribution |
|
| 118 |
+ notices within Derivative Works that You distribute, alongside |
|
| 119 |
+ or as an addendum to the NOTICE text from the Work, provided |
|
| 120 |
+ that such additional attribution notices cannot be construed |
|
| 121 |
+ as modifying the License. |
|
| 122 |
+ |
|
| 123 |
+ You may add Your own copyright statement to Your modifications and |
|
| 124 |
+ may provide additional or different license terms and conditions |
|
| 125 |
+ for use, reproduction, or distribution of Your modifications, or |
|
| 126 |
+ for any such Derivative Works as a whole, provided Your use, |
|
| 127 |
+ reproduction, and distribution of the Work otherwise complies with |
|
| 128 |
+ the conditions stated in this License. |
|
| 129 |
+ |
|
| 130 |
+ 5. Submission of Contributions. Unless You explicitly state otherwise, |
|
| 131 |
+ any Contribution intentionally submitted for inclusion in the Work |
|
| 132 |
+ by You to the Licensor shall be under the terms and conditions of |
|
| 133 |
+ this License, without any additional terms or conditions. |
|
| 134 |
+ Notwithstanding the above, nothing herein shall supersede or modify |
|
| 135 |
+ the terms of any separate license agreement you may have executed |
|
| 136 |
+ with Licensor regarding such Contributions. |
|
| 137 |
+ |
|
| 138 |
+ 6. Trademarks. This License does not grant permission to use the trade |
|
| 139 |
+ names, trademarks, service marks, or product names of the Licensor, |
|
| 140 |
+ except as required for reasonable and customary use in describing the |
|
| 141 |
+ origin of the Work and reproducing the content of the NOTICE file. |
|
| 142 |
+ |
|
| 143 |
+ 7. Disclaimer of Warranty. Unless required by applicable law or |
|
| 144 |
+ agreed to in writing, Licensor provides the Work (and each |
|
| 145 |
+ Contributor provides its Contributions) on an "AS IS" BASIS, |
|
| 146 |
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or |
|
| 147 |
+ implied, including, without limitation, any warranties or conditions |
|
| 148 |
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A |
|
| 149 |
+ PARTICULAR PURPOSE. You are solely responsible for determining the |
|
| 150 |
+ appropriateness of using or redistributing the Work and assume any |
|
| 151 |
+ risks associated with Your exercise of permissions under this License. |
|
| 152 |
+ |
|
| 153 |
+ 8. Limitation of Liability. In no event and under no legal theory, |
|
| 154 |
+ whether in tort (including negligence), contract, or otherwise, |
|
| 155 |
+ unless required by applicable law (such as deliberate and grossly |
|
| 156 |
+ negligent acts) or agreed to in writing, shall any Contributor be |
|
| 157 |
+ liable to You for damages, including any direct, indirect, special, |
|
| 158 |
+ incidental, or consequential damages of any character arising as a |
|
| 159 |
+ result of this License or out of the use or inability to use the |
|
| 160 |
+ Work (including but not limited to damages for loss of goodwill, |
|
| 161 |
+ work stoppage, computer failure or malfunction, or any and all |
|
| 162 |
+ other commercial damages or losses), even if such Contributor |
|
| 163 |
+ has been advised of the possibility of such damages. |
|
| 164 |
+ |
|
| 165 |
+ 9. Accepting Warranty or Additional Liability. While redistributing |
|
| 166 |
+ the Work or Derivative Works thereof, You may choose to offer, |
|
| 167 |
+ and charge a fee for, acceptance of support, warranty, indemnity, |
|
| 168 |
+ or other liability obligations and/or rights consistent with this |
|
| 169 |
+ License. However, in accepting such obligations, You may act only |
|
| 170 |
+ on Your own behalf and on Your sole responsibility, not on behalf |
|
| 171 |
+ of any other Contributor, and only if You agree to indemnify, |
|
| 172 |
+ defend, and hold each Contributor harmless for any liability |
|
| 173 |
+ incurred by, or claims asserted against, such Contributor by reason |
|
| 174 |
+ of your accepting any such warranty or additional liability. |
|
| 175 |
+ |
|
| 176 |
+ END OF TERMS AND CONDITIONS |
|
| 177 |
+ |
|
| 178 |
+ APPENDIX: How to apply the Apache License to your work. |
|
| 179 |
+ |
|
| 180 |
+ To apply the Apache License to your work, attach the following |
|
| 181 |
+ boilerplate notice, with the fields enclosed by brackets "[]" |
|
| 182 |
+ replaced with your own identifying information. (Don't include |
|
| 183 |
+ the brackets!) The text should be enclosed in the appropriate |
|
| 184 |
+ comment syntax for the file format. We also recommend that a |
|
| 185 |
+ file or class name and description of purpose be included on the |
|
| 186 |
+ same "printed page" as the copyright notice for easier |
|
| 187 |
+ identification within third-party archives. |
|
| 188 |
+ |
|
| 189 |
+ Copyright 2014 Google Inc. |
|
| 190 |
+ |
|
| 191 |
+ Licensed under the Apache License, Version 2.0 (the "License"); |
|
| 192 |
+ you may not use this file except in compliance with the License. |
|
| 193 |
+ You may obtain a copy of the License at |
|
| 194 |
+ |
|
| 195 |
+ http://www.apache.org/licenses/LICENSE-2.0 |
|
| 196 |
+ |
|
| 197 |
+ Unless required by applicable law or agreed to in writing, software |
|
| 198 |
+ distributed under the License is distributed on an "AS IS" BASIS, |
|
| 199 |
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
| 200 |
+ See the License for the specific language governing permissions and |
|
| 201 |
+ limitations under the License. |
| 0 | 202 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,438 @@ |
| 0 |
+// Copyright 2014 Google Inc. All Rights Reserved. |
|
| 1 |
+// |
|
| 2 |
+// Licensed under the Apache License, Version 2.0 (the "License"); |
|
| 3 |
+// you may not use this file except in compliance with the License. |
|
| 4 |
+// You may obtain a copy of the License at |
|
| 5 |
+// |
|
| 6 |
+// http://www.apache.org/licenses/LICENSE-2.0 |
|
| 7 |
+// |
|
| 8 |
+// Unless required by applicable law or agreed to in writing, software |
|
| 9 |
+// distributed under the License is distributed on an "AS IS" BASIS, |
|
| 10 |
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
| 11 |
+// See the License for the specific language governing permissions and |
|
| 12 |
+// limitations under the License. |
|
| 13 |
+ |
|
| 14 |
+// Package metadata provides access to Google Compute Engine (GCE) |
|
| 15 |
+// metadata and API service accounts. |
|
| 16 |
+// |
|
| 17 |
+// This package is a wrapper around the GCE metadata service, |
|
| 18 |
+// as documented at https://developers.google.com/compute/docs/metadata. |
|
| 19 |
+package metadata // import "cloud.google.com/go/compute/metadata" |
|
| 20 |
+ |
|
| 21 |
+import ( |
|
| 22 |
+ "encoding/json" |
|
| 23 |
+ "fmt" |
|
| 24 |
+ "io/ioutil" |
|
| 25 |
+ "net" |
|
| 26 |
+ "net/http" |
|
| 27 |
+ "net/url" |
|
| 28 |
+ "os" |
|
| 29 |
+ "runtime" |
|
| 30 |
+ "strings" |
|
| 31 |
+ "sync" |
|
| 32 |
+ "time" |
|
| 33 |
+ |
|
| 34 |
+ "golang.org/x/net/context" |
|
| 35 |
+ "golang.org/x/net/context/ctxhttp" |
|
| 36 |
+ |
|
| 37 |
+ "cloud.google.com/go/internal" |
|
| 38 |
+) |
|
| 39 |
+ |
|
| 40 |
+const ( |
|
| 41 |
+ // metadataIP is the documented metadata server IP address. |
|
| 42 |
+ metadataIP = "169.254.169.254" |
|
| 43 |
+ |
|
| 44 |
+ // metadataHostEnv is the environment variable specifying the |
|
| 45 |
+ // GCE metadata hostname. If empty, the default value of |
|
| 46 |
+ // metadataIP ("169.254.169.254") is used instead.
|
|
| 47 |
+ // This is variable name is not defined by any spec, as far as |
|
| 48 |
+ // I know; it was made up for the Go package. |
|
| 49 |
+ metadataHostEnv = "GCE_METADATA_HOST" |
|
| 50 |
+) |
|
| 51 |
+ |
|
| 52 |
+type cachedValue struct {
|
|
| 53 |
+ k string |
|
| 54 |
+ trim bool |
|
| 55 |
+ mu sync.Mutex |
|
| 56 |
+ v string |
|
| 57 |
+} |
|
| 58 |
+ |
|
| 59 |
+var ( |
|
| 60 |
+ projID = &cachedValue{k: "project/project-id", trim: true}
|
|
| 61 |
+ projNum = &cachedValue{k: "project/numeric-project-id", trim: true}
|
|
| 62 |
+ instID = &cachedValue{k: "instance/id", trim: true}
|
|
| 63 |
+) |
|
| 64 |
+ |
|
| 65 |
+var ( |
|
| 66 |
+ metaClient = &http.Client{
|
|
| 67 |
+ Transport: &internal.Transport{
|
|
| 68 |
+ Base: &http.Transport{
|
|
| 69 |
+ Dial: (&net.Dialer{
|
|
| 70 |
+ Timeout: 2 * time.Second, |
|
| 71 |
+ KeepAlive: 30 * time.Second, |
|
| 72 |
+ }).Dial, |
|
| 73 |
+ ResponseHeaderTimeout: 2 * time.Second, |
|
| 74 |
+ }, |
|
| 75 |
+ }, |
|
| 76 |
+ } |
|
| 77 |
+ subscribeClient = &http.Client{
|
|
| 78 |
+ Transport: &internal.Transport{
|
|
| 79 |
+ Base: &http.Transport{
|
|
| 80 |
+ Dial: (&net.Dialer{
|
|
| 81 |
+ Timeout: 2 * time.Second, |
|
| 82 |
+ KeepAlive: 30 * time.Second, |
|
| 83 |
+ }).Dial, |
|
| 84 |
+ }, |
|
| 85 |
+ }, |
|
| 86 |
+ } |
|
| 87 |
+) |
|
| 88 |
+ |
|
| 89 |
+// NotDefinedError is returned when requested metadata is not defined. |
|
| 90 |
+// |
|
| 91 |
+// The underlying string is the suffix after "/computeMetadata/v1/". |
|
| 92 |
+// |
|
| 93 |
+// This error is not returned if the value is defined to be the empty |
|
| 94 |
+// string. |
|
| 95 |
+type NotDefinedError string |
|
| 96 |
+ |
|
| 97 |
+func (suffix NotDefinedError) Error() string {
|
|
| 98 |
+ return fmt.Sprintf("metadata: GCE metadata %q not defined", string(suffix))
|
|
| 99 |
+} |
|
| 100 |
+ |
|
| 101 |
+// Get returns a value from the metadata service. |
|
| 102 |
+// The suffix is appended to "http://${GCE_METADATA_HOST}/computeMetadata/v1/".
|
|
| 103 |
+// |
|
| 104 |
+// If the GCE_METADATA_HOST environment variable is not defined, a default of |
|
| 105 |
+// 169.254.169.254 will be used instead. |
|
| 106 |
+// |
|
| 107 |
+// If the requested metadata is not defined, the returned error will |
|
| 108 |
+// be of type NotDefinedError. |
|
| 109 |
+func Get(suffix string) (string, error) {
|
|
| 110 |
+ val, _, err := getETag(metaClient, suffix) |
|
| 111 |
+ return val, err |
|
| 112 |
+} |
|
| 113 |
+ |
|
| 114 |
+// getETag returns a value from the metadata service as well as the associated |
|
| 115 |
+// ETag using the provided client. This func is otherwise equivalent to Get. |
|
| 116 |
+func getETag(client *http.Client, suffix string) (value, etag string, err error) {
|
|
| 117 |
+ // Using a fixed IP makes it very difficult to spoof the metadata service in |
|
| 118 |
+ // a container, which is an important use-case for local testing of cloud |
|
| 119 |
+ // deployments. To enable spoofing of the metadata service, the environment |
|
| 120 |
+ // variable GCE_METADATA_HOST is first inspected to decide where metadata |
|
| 121 |
+ // requests shall go. |
|
| 122 |
+ host := os.Getenv(metadataHostEnv) |
|
| 123 |
+ if host == "" {
|
|
| 124 |
+ // Using 169.254.169.254 instead of "metadata" here because Go |
|
| 125 |
+ // binaries built with the "netgo" tag and without cgo won't |
|
| 126 |
+ // know the search suffix for "metadata" is |
|
| 127 |
+ // ".google.internal", and this IP address is documented as |
|
| 128 |
+ // being stable anyway. |
|
| 129 |
+ host = metadataIP |
|
| 130 |
+ } |
|
| 131 |
+ url := "http://" + host + "/computeMetadata/v1/" + suffix |
|
| 132 |
+ req, _ := http.NewRequest("GET", url, nil)
|
|
| 133 |
+ req.Header.Set("Metadata-Flavor", "Google")
|
|
| 134 |
+ res, err := client.Do(req) |
|
| 135 |
+ if err != nil {
|
|
| 136 |
+ return "", "", err |
|
| 137 |
+ } |
|
| 138 |
+ defer res.Body.Close() |
|
| 139 |
+ if res.StatusCode == http.StatusNotFound {
|
|
| 140 |
+ return "", "", NotDefinedError(suffix) |
|
| 141 |
+ } |
|
| 142 |
+ if res.StatusCode != 200 {
|
|
| 143 |
+ return "", "", fmt.Errorf("status code %d trying to fetch %s", res.StatusCode, url)
|
|
| 144 |
+ } |
|
| 145 |
+ all, err := ioutil.ReadAll(res.Body) |
|
| 146 |
+ if err != nil {
|
|
| 147 |
+ return "", "", err |
|
| 148 |
+ } |
|
| 149 |
+ return string(all), res.Header.Get("Etag"), nil
|
|
| 150 |
+} |
|
| 151 |
+ |
|
| 152 |
+func getTrimmed(suffix string) (s string, err error) {
|
|
| 153 |
+ s, err = Get(suffix) |
|
| 154 |
+ s = strings.TrimSpace(s) |
|
| 155 |
+ return |
|
| 156 |
+} |
|
| 157 |
+ |
|
| 158 |
+func (c *cachedValue) get() (v string, err error) {
|
|
| 159 |
+ defer c.mu.Unlock() |
|
| 160 |
+ c.mu.Lock() |
|
| 161 |
+ if c.v != "" {
|
|
| 162 |
+ return c.v, nil |
|
| 163 |
+ } |
|
| 164 |
+ if c.trim {
|
|
| 165 |
+ v, err = getTrimmed(c.k) |
|
| 166 |
+ } else {
|
|
| 167 |
+ v, err = Get(c.k) |
|
| 168 |
+ } |
|
| 169 |
+ if err == nil {
|
|
| 170 |
+ c.v = v |
|
| 171 |
+ } |
|
| 172 |
+ return |
|
| 173 |
+} |
|
| 174 |
+ |
|
| 175 |
+var ( |
|
| 176 |
+ onGCEOnce sync.Once |
|
| 177 |
+ onGCE bool |
|
| 178 |
+) |
|
| 179 |
+ |
|
| 180 |
+// OnGCE reports whether this process is running on Google Compute Engine. |
|
| 181 |
+func OnGCE() bool {
|
|
| 182 |
+ onGCEOnce.Do(initOnGCE) |
|
| 183 |
+ return onGCE |
|
| 184 |
+} |
|
| 185 |
+ |
|
| 186 |
+func initOnGCE() {
|
|
| 187 |
+ onGCE = testOnGCE() |
|
| 188 |
+} |
|
| 189 |
+ |
|
| 190 |
+func testOnGCE() bool {
|
|
| 191 |
+ // The user explicitly said they're on GCE, so trust them. |
|
| 192 |
+ if os.Getenv(metadataHostEnv) != "" {
|
|
| 193 |
+ return true |
|
| 194 |
+ } |
|
| 195 |
+ |
|
| 196 |
+ ctx, cancel := context.WithCancel(context.Background()) |
|
| 197 |
+ defer cancel() |
|
| 198 |
+ |
|
| 199 |
+ resc := make(chan bool, 2) |
|
| 200 |
+ |
|
| 201 |
+ // Try two strategies in parallel. |
|
| 202 |
+ // See https://github.com/GoogleCloudPlatform/google-cloud-go/issues/194 |
|
| 203 |
+ go func() {
|
|
| 204 |
+ res, err := ctxhttp.Get(ctx, metaClient, "http://"+metadataIP) |
|
| 205 |
+ if err != nil {
|
|
| 206 |
+ resc <- false |
|
| 207 |
+ return |
|
| 208 |
+ } |
|
| 209 |
+ defer res.Body.Close() |
|
| 210 |
+ resc <- res.Header.Get("Metadata-Flavor") == "Google"
|
|
| 211 |
+ }() |
|
| 212 |
+ |
|
| 213 |
+ go func() {
|
|
| 214 |
+ addrs, err := net.LookupHost("metadata.google.internal")
|
|
| 215 |
+ if err != nil || len(addrs) == 0 {
|
|
| 216 |
+ resc <- false |
|
| 217 |
+ return |
|
| 218 |
+ } |
|
| 219 |
+ resc <- strsContains(addrs, metadataIP) |
|
| 220 |
+ }() |
|
| 221 |
+ |
|
| 222 |
+ tryHarder := systemInfoSuggestsGCE() |
|
| 223 |
+ if tryHarder {
|
|
| 224 |
+ res := <-resc |
|
| 225 |
+ if res {
|
|
| 226 |
+ // The first strategy succeeded, so let's use it. |
|
| 227 |
+ return true |
|
| 228 |
+ } |
|
| 229 |
+ // Wait for either the DNS or metadata server probe to |
|
| 230 |
+ // contradict the other one and say we are running on |
|
| 231 |
+ // GCE. Give it a lot of time to do so, since the system |
|
| 232 |
+ // info already suggests we're running on a GCE BIOS. |
|
| 233 |
+ timer := time.NewTimer(5 * time.Second) |
|
| 234 |
+ defer timer.Stop() |
|
| 235 |
+ select {
|
|
| 236 |
+ case res = <-resc: |
|
| 237 |
+ return res |
|
| 238 |
+ case <-timer.C: |
|
| 239 |
+ // Too slow. Who knows what this system is. |
|
| 240 |
+ return false |
|
| 241 |
+ } |
|
| 242 |
+ } |
|
| 243 |
+ |
|
| 244 |
+ // There's no hint from the system info that we're running on |
|
| 245 |
+ // GCE, so use the first probe's result as truth, whether it's |
|
| 246 |
+ // true or false. The goal here is to optimize for speed for |
|
| 247 |
+ // users who are NOT running on GCE. We can't assume that |
|
| 248 |
+ // either a DNS lookup or an HTTP request to a blackholed IP |
|
| 249 |
+ // address is fast. Worst case this should return when the |
|
| 250 |
+ // metaClient's Transport.ResponseHeaderTimeout or |
|
| 251 |
+ // Transport.Dial.Timeout fires (in two seconds). |
|
| 252 |
+ return <-resc |
|
| 253 |
+} |
|
| 254 |
+ |
|
| 255 |
+// systemInfoSuggestsGCE reports whether the local system (without |
|
| 256 |
+// doing network requests) suggests that we're running on GCE. If this |
|
| 257 |
+// returns true, testOnGCE tries a bit harder to reach its metadata |
|
| 258 |
+// server. |
|
| 259 |
+func systemInfoSuggestsGCE() bool {
|
|
| 260 |
+ if runtime.GOOS != "linux" {
|
|
| 261 |
+ // We don't have any non-Linux clues available, at least yet. |
|
| 262 |
+ return false |
|
| 263 |
+ } |
|
| 264 |
+ slurp, _ := ioutil.ReadFile("/sys/class/dmi/id/product_name")
|
|
| 265 |
+ name := strings.TrimSpace(string(slurp)) |
|
| 266 |
+ return name == "Google" || name == "Google Compute Engine" |
|
| 267 |
+} |
|
| 268 |
+ |
|
| 269 |
+// Subscribe subscribes to a value from the metadata service. |
|
| 270 |
+// The suffix is appended to "http://${GCE_METADATA_HOST}/computeMetadata/v1/".
|
|
| 271 |
+// The suffix may contain query parameters. |
|
| 272 |
+// |
|
| 273 |
+// Subscribe calls fn with the latest metadata value indicated by the provided |
|
| 274 |
+// suffix. If the metadata value is deleted, fn is called with the empty string |
|
| 275 |
+// and ok false. Subscribe blocks until fn returns a non-nil error or the value |
|
| 276 |
+// is deleted. Subscribe returns the error value returned from the last call to |
|
| 277 |
+// fn, which may be nil when ok == false. |
|
| 278 |
+func Subscribe(suffix string, fn func(v string, ok bool) error) error {
|
|
| 279 |
+ const failedSubscribeSleep = time.Second * 5 |
|
| 280 |
+ |
|
| 281 |
+ // First check to see if the metadata value exists at all. |
|
| 282 |
+ val, lastETag, err := getETag(subscribeClient, suffix) |
|
| 283 |
+ if err != nil {
|
|
| 284 |
+ return err |
|
| 285 |
+ } |
|
| 286 |
+ |
|
| 287 |
+ if err := fn(val, true); err != nil {
|
|
| 288 |
+ return err |
|
| 289 |
+ } |
|
| 290 |
+ |
|
| 291 |
+ ok := true |
|
| 292 |
+ if strings.ContainsRune(suffix, '?') {
|
|
| 293 |
+ suffix += "&wait_for_change=true&last_etag=" |
|
| 294 |
+ } else {
|
|
| 295 |
+ suffix += "?wait_for_change=true&last_etag=" |
|
| 296 |
+ } |
|
| 297 |
+ for {
|
|
| 298 |
+ val, etag, err := getETag(subscribeClient, suffix+url.QueryEscape(lastETag)) |
|
| 299 |
+ if err != nil {
|
|
| 300 |
+ if _, deleted := err.(NotDefinedError); !deleted {
|
|
| 301 |
+ time.Sleep(failedSubscribeSleep) |
|
| 302 |
+ continue // Retry on other errors. |
|
| 303 |
+ } |
|
| 304 |
+ ok = false |
|
| 305 |
+ } |
|
| 306 |
+ lastETag = etag |
|
| 307 |
+ |
|
| 308 |
+ if err := fn(val, ok); err != nil || !ok {
|
|
| 309 |
+ return err |
|
| 310 |
+ } |
|
| 311 |
+ } |
|
| 312 |
+} |
|
| 313 |
+ |
|
| 314 |
+// ProjectID returns the current instance's project ID string. |
|
| 315 |
+func ProjectID() (string, error) { return projID.get() }
|
|
| 316 |
+ |
|
| 317 |
+// NumericProjectID returns the current instance's numeric project ID. |
|
| 318 |
+func NumericProjectID() (string, error) { return projNum.get() }
|
|
| 319 |
+ |
|
| 320 |
+// InternalIP returns the instance's primary internal IP address. |
|
| 321 |
+func InternalIP() (string, error) {
|
|
| 322 |
+ return getTrimmed("instance/network-interfaces/0/ip")
|
|
| 323 |
+} |
|
| 324 |
+ |
|
| 325 |
+// ExternalIP returns the instance's primary external (public) IP address. |
|
| 326 |
+func ExternalIP() (string, error) {
|
|
| 327 |
+ return getTrimmed("instance/network-interfaces/0/access-configs/0/external-ip")
|
|
| 328 |
+} |
|
| 329 |
+ |
|
| 330 |
+// Hostname returns the instance's hostname. This will be of the form |
|
| 331 |
+// "<instanceID>.c.<projID>.internal". |
|
| 332 |
+func Hostname() (string, error) {
|
|
| 333 |
+ return getTrimmed("instance/hostname")
|
|
| 334 |
+} |
|
| 335 |
+ |
|
| 336 |
+// InstanceTags returns the list of user-defined instance tags, |
|
| 337 |
+// assigned when initially creating a GCE instance. |
|
| 338 |
+func InstanceTags() ([]string, error) {
|
|
| 339 |
+ var s []string |
|
| 340 |
+ j, err := Get("instance/tags")
|
|
| 341 |
+ if err != nil {
|
|
| 342 |
+ return nil, err |
|
| 343 |
+ } |
|
| 344 |
+ if err := json.NewDecoder(strings.NewReader(j)).Decode(&s); err != nil {
|
|
| 345 |
+ return nil, err |
|
| 346 |
+ } |
|
| 347 |
+ return s, nil |
|
| 348 |
+} |
|
| 349 |
+ |
|
| 350 |
+// InstanceID returns the current VM's numeric instance ID. |
|
| 351 |
+func InstanceID() (string, error) {
|
|
| 352 |
+ return instID.get() |
|
| 353 |
+} |
|
| 354 |
+ |
|
| 355 |
+// InstanceName returns the current VM's instance ID string. |
|
| 356 |
+func InstanceName() (string, error) {
|
|
| 357 |
+ host, err := Hostname() |
|
| 358 |
+ if err != nil {
|
|
| 359 |
+ return "", err |
|
| 360 |
+ } |
|
| 361 |
+ return strings.Split(host, ".")[0], nil |
|
| 362 |
+} |
|
| 363 |
+ |
|
| 364 |
+// Zone returns the current VM's zone, such as "us-central1-b". |
|
| 365 |
+func Zone() (string, error) {
|
|
| 366 |
+ zone, err := getTrimmed("instance/zone")
|
|
| 367 |
+ // zone is of the form "projects/<projNum>/zones/<zoneName>". |
|
| 368 |
+ if err != nil {
|
|
| 369 |
+ return "", err |
|
| 370 |
+ } |
|
| 371 |
+ return zone[strings.LastIndex(zone, "/")+1:], nil |
|
| 372 |
+} |
|
| 373 |
+ |
|
| 374 |
+// InstanceAttributes returns the list of user-defined attributes, |
|
| 375 |
+// assigned when initially creating a GCE VM instance. The value of an |
|
| 376 |
+// attribute can be obtained with InstanceAttributeValue. |
|
| 377 |
+func InstanceAttributes() ([]string, error) { return lines("instance/attributes/") }
|
|
| 378 |
+ |
|
| 379 |
+// ProjectAttributes returns the list of user-defined attributes |
|
| 380 |
+// applying to the project as a whole, not just this VM. The value of |
|
| 381 |
+// an attribute can be obtained with ProjectAttributeValue. |
|
| 382 |
+func ProjectAttributes() ([]string, error) { return lines("project/attributes/") }
|
|
| 383 |
+ |
|
| 384 |
+func lines(suffix string) ([]string, error) {
|
|
| 385 |
+ j, err := Get(suffix) |
|
| 386 |
+ if err != nil {
|
|
| 387 |
+ return nil, err |
|
| 388 |
+ } |
|
| 389 |
+ s := strings.Split(strings.TrimSpace(j), "\n") |
|
| 390 |
+ for i := range s {
|
|
| 391 |
+ s[i] = strings.TrimSpace(s[i]) |
|
| 392 |
+ } |
|
| 393 |
+ return s, nil |
|
| 394 |
+} |
|
| 395 |
+ |
|
| 396 |
+// InstanceAttributeValue returns the value of the provided VM |
|
| 397 |
+// instance attribute. |
|
| 398 |
+// |
|
| 399 |
+// If the requested attribute is not defined, the returned error will |
|
| 400 |
+// be of type NotDefinedError. |
|
| 401 |
+// |
|
| 402 |
+// InstanceAttributeValue may return ("", nil) if the attribute was
|
|
| 403 |
+// defined to be the empty string. |
|
| 404 |
+func InstanceAttributeValue(attr string) (string, error) {
|
|
| 405 |
+ return Get("instance/attributes/" + attr)
|
|
| 406 |
+} |
|
| 407 |
+ |
|
| 408 |
+// ProjectAttributeValue returns the value of the provided |
|
| 409 |
+// project attribute. |
|
| 410 |
+// |
|
| 411 |
+// If the requested attribute is not defined, the returned error will |
|
| 412 |
+// be of type NotDefinedError. |
|
| 413 |
+// |
|
| 414 |
+// ProjectAttributeValue may return ("", nil) if the attribute was
|
|
| 415 |
+// defined to be the empty string. |
|
| 416 |
+func ProjectAttributeValue(attr string) (string, error) {
|
|
| 417 |
+ return Get("project/attributes/" + attr)
|
|
| 418 |
+} |
|
| 419 |
+ |
|
| 420 |
+// Scopes returns the service account scopes for the given account. |
|
| 421 |
+// The account may be empty or the string "default" to use the instance's |
|
| 422 |
+// main account. |
|
| 423 |
+func Scopes(serviceAccount string) ([]string, error) {
|
|
| 424 |
+ if serviceAccount == "" {
|
|
| 425 |
+ serviceAccount = "default" |
|
| 426 |
+ } |
|
| 427 |
+ return lines("instance/service-accounts/" + serviceAccount + "/scopes")
|
|
| 428 |
+} |
|
| 429 |
+ |
|
| 430 |
+func strsContains(ss []string, s string) bool {
|
|
| 431 |
+ for _, v := range ss {
|
|
| 432 |
+ if v == s {
|
|
| 433 |
+ return true |
|
| 434 |
+ } |
|
| 435 |
+ } |
|
| 436 |
+ return false |
|
| 437 |
+} |
| 0 | 438 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,64 @@ |
| 0 |
+// Copyright 2014 Google Inc. All Rights Reserved. |
|
| 1 |
+// |
|
| 2 |
+// Licensed under the Apache License, Version 2.0 (the "License"); |
|
| 3 |
+// you may not use this file except in compliance with the License. |
|
| 4 |
+// You may obtain a copy of the License at |
|
| 5 |
+// |
|
| 6 |
+// http://www.apache.org/licenses/LICENSE-2.0 |
|
| 7 |
+// |
|
| 8 |
+// Unless required by applicable law or agreed to in writing, software |
|
| 9 |
+// distributed under the License is distributed on an "AS IS" BASIS, |
|
| 10 |
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
| 11 |
+// See the License for the specific language governing permissions and |
|
| 12 |
+// limitations under the License. |
|
| 13 |
+ |
|
| 14 |
+// Package internal provides support for the cloud packages. |
|
| 15 |
+// |
|
| 16 |
+// Users should not import this package directly. |
|
| 17 |
+package internal |
|
| 18 |
+ |
|
| 19 |
+import ( |
|
| 20 |
+ "fmt" |
|
| 21 |
+ "net/http" |
|
| 22 |
+) |
|
| 23 |
+ |
|
| 24 |
+const userAgent = "gcloud-golang/0.1" |
|
| 25 |
+ |
|
| 26 |
+// Transport is an http.RoundTripper that appends Google Cloud client's |
|
| 27 |
+// user-agent to the original request's user-agent header. |
|
| 28 |
+type Transport struct {
|
|
| 29 |
+ // TODO(bradfitz): delete internal.Transport. It's too wrappy for what it does. |
|
| 30 |
+ // Do User-Agent some other way. |
|
| 31 |
+ |
|
| 32 |
+ // Base is the actual http.RoundTripper |
|
| 33 |
+ // requests will use. It must not be nil. |
|
| 34 |
+ Base http.RoundTripper |
|
| 35 |
+} |
|
| 36 |
+ |
|
| 37 |
+// RoundTrip appends a user-agent to the existing user-agent |
|
| 38 |
+// header and delegates the request to the base http.RoundTripper. |
|
| 39 |
+func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error) {
|
|
| 40 |
+ req = cloneRequest(req) |
|
| 41 |
+ ua := req.Header.Get("User-Agent")
|
|
| 42 |
+ if ua == "" {
|
|
| 43 |
+ ua = userAgent |
|
| 44 |
+ } else {
|
|
| 45 |
+ ua = fmt.Sprintf("%s %s", ua, userAgent)
|
|
| 46 |
+ } |
|
| 47 |
+ req.Header.Set("User-Agent", ua)
|
|
| 48 |
+ return t.Base.RoundTrip(req) |
|
| 49 |
+} |
|
| 50 |
+ |
|
| 51 |
+// cloneRequest returns a clone of the provided *http.Request. |
|
| 52 |
+// The clone is a shallow copy of the struct and its Header map. |
|
| 53 |
+func cloneRequest(r *http.Request) *http.Request {
|
|
| 54 |
+ // shallow copy of the struct |
|
| 55 |
+ r2 := new(http.Request) |
|
| 56 |
+ *r2 = *r |
|
| 57 |
+ // deep copy of the Header |
|
| 58 |
+ r2.Header = make(http.Header) |
|
| 59 |
+ for k, s := range r.Header {
|
|
| 60 |
+ r2.Header[k] = s |
|
| 61 |
+ } |
|
| 62 |
+ return r2 |
|
| 63 |
+} |
| 0 | 64 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,55 @@ |
| 0 |
+// Copyright 2016 Google Inc. All Rights Reserved. |
|
| 1 |
+// |
|
| 2 |
+// Licensed under the Apache License, Version 2.0 (the "License"); |
|
| 3 |
+// you may not use this file except in compliance with the License. |
|
| 4 |
+// You may obtain a copy of the License at |
|
| 5 |
+// |
|
| 6 |
+// http://www.apache.org/licenses/LICENSE-2.0 |
|
| 7 |
+// |
|
| 8 |
+// Unless required by applicable law or agreed to in writing, software |
|
| 9 |
+// distributed under the License is distributed on an "AS IS" BASIS, |
|
| 10 |
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
| 11 |
+// See the License for the specific language governing permissions and |
|
| 12 |
+// limitations under the License. |
|
| 13 |
+ |
|
| 14 |
+package internal |
|
| 15 |
+ |
|
| 16 |
+import ( |
|
| 17 |
+ "fmt" |
|
| 18 |
+ "time" |
|
| 19 |
+ |
|
| 20 |
+ gax "github.com/googleapis/gax-go" |
|
| 21 |
+ |
|
| 22 |
+ "golang.org/x/net/context" |
|
| 23 |
+) |
|
| 24 |
+ |
|
| 25 |
+// Retry calls the supplied function f repeatedly according to the provided |
|
| 26 |
+// backoff parameters. It returns when one of the following occurs: |
|
| 27 |
+// When f's first return value is true, Retry immediately returns with f's second |
|
| 28 |
+// return value. |
|
| 29 |
+// When the provided context is done, Retry returns with ctx.Err(). |
|
| 30 |
+func Retry(ctx context.Context, bo gax.Backoff, f func() (stop bool, err error)) error {
|
|
| 31 |
+ return retry(ctx, bo, f, gax.Sleep) |
|
| 32 |
+} |
|
| 33 |
+ |
|
| 34 |
+func retry(ctx context.Context, bo gax.Backoff, f func() (stop bool, err error), |
|
| 35 |
+ sleep func(context.Context, time.Duration) error) error {
|
|
| 36 |
+ var lastErr error |
|
| 37 |
+ for {
|
|
| 38 |
+ stop, err := f() |
|
| 39 |
+ if stop {
|
|
| 40 |
+ return err |
|
| 41 |
+ } |
|
| 42 |
+ // Remember the last "real" error from f. |
|
| 43 |
+ if err != nil && err != context.Canceled && err != context.DeadlineExceeded {
|
|
| 44 |
+ lastErr = err |
|
| 45 |
+ } |
|
| 46 |
+ p := bo.Pause() |
|
| 47 |
+ if cerr := sleep(ctx, p); cerr != nil {
|
|
| 48 |
+ if lastErr != nil {
|
|
| 49 |
+ return fmt.Errorf("%v; last function err: %v", cerr, lastErr)
|
|
| 50 |
+ } |
|
| 51 |
+ return cerr |
|
| 52 |
+ } |
|
| 53 |
+ } |
|
| 54 |
+} |
| 0 | 55 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,27 @@ |
| 0 |
+Copyright 2016, Google Inc. |
|
| 1 |
+All rights reserved. |
|
| 2 |
+Redistribution and use in source and binary forms, with or without |
|
| 3 |
+modification, are permitted provided that the following conditions are |
|
| 4 |
+met: |
|
| 5 |
+ |
|
| 6 |
+ * Redistributions of source code must retain the above copyright |
|
| 7 |
+notice, this list of conditions and the following disclaimer. |
|
| 8 |
+ * Redistributions in binary form must reproduce the above |
|
| 9 |
+copyright notice, this list of conditions and the following disclaimer |
|
| 10 |
+in the documentation and/or other materials provided with the |
|
| 11 |
+distribution. |
|
| 12 |
+ * Neither the name of Google Inc. nor the names of its |
|
| 13 |
+contributors may be used to endorse or promote products derived from |
|
| 14 |
+this software without specific prior written permission. |
|
| 15 |
+ |
|
| 16 |
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
|
| 17 |
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
|
| 18 |
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
|
| 19 |
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
|
| 20 |
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
|
| 21 |
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
|
| 22 |
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
|
| 23 |
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
|
| 24 |
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
|
| 25 |
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
|
| 26 |
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| 0 | 27 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,136 @@ |
| 0 |
+// Copyright 2016, Google Inc. |
|
| 1 |
+// All rights reserved. |
|
| 2 |
+// |
|
| 3 |
+// Redistribution and use in source and binary forms, with or without |
|
| 4 |
+// modification, are permitted provided that the following conditions are |
|
| 5 |
+// met: |
|
| 6 |
+// |
|
| 7 |
+// * Redistributions of source code must retain the above copyright |
|
| 8 |
+// notice, this list of conditions and the following disclaimer. |
|
| 9 |
+// * Redistributions in binary form must reproduce the above |
|
| 10 |
+// copyright notice, this list of conditions and the following disclaimer |
|
| 11 |
+// in the documentation and/or other materials provided with the |
|
| 12 |
+// distribution. |
|
| 13 |
+// * Neither the name of Google Inc. nor the names of its |
|
| 14 |
+// contributors may be used to endorse or promote products derived from |
|
| 15 |
+// this software without specific prior written permission. |
|
| 16 |
+// |
|
| 17 |
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
|
| 18 |
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
|
| 19 |
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
|
| 20 |
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
|
| 21 |
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
|
| 22 |
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
|
| 23 |
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
|
| 24 |
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
|
| 25 |
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
|
| 26 |
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
|
| 27 |
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
|
| 28 |
+ |
|
| 29 |
+package gax |
|
| 30 |
+ |
|
| 31 |
+import ( |
|
| 32 |
+ "math/rand" |
|
| 33 |
+ "time" |
|
| 34 |
+ |
|
| 35 |
+ "google.golang.org/grpc" |
|
| 36 |
+ "google.golang.org/grpc/codes" |
|
| 37 |
+) |
|
| 38 |
+ |
|
| 39 |
+// CallOption is an option used by Invoke to control behaviors of RPC calls. |
|
| 40 |
+// CallOption works by modifying relevant fields of CallSettings. |
|
| 41 |
+type CallOption interface {
|
|
| 42 |
+ // Resolve applies the option by modifying cs. |
|
| 43 |
+ Resolve(cs *CallSettings) |
|
| 44 |
+} |
|
| 45 |
+ |
|
| 46 |
+// Retryer is used by Invoke to determine retry behavior. |
|
| 47 |
+type Retryer interface {
|
|
| 48 |
+ // Retry reports whether a request should be retriedand how long to pause before retrying |
|
| 49 |
+ // if the previous attempt returned with err. Invoke never calls Retry with nil error. |
|
| 50 |
+ Retry(err error) (pause time.Duration, shouldRetry bool) |
|
| 51 |
+} |
|
| 52 |
+ |
|
| 53 |
+type retryerOption func() Retryer |
|
| 54 |
+ |
|
| 55 |
+func (o retryerOption) Resolve(s *CallSettings) {
|
|
| 56 |
+ s.Retry = o |
|
| 57 |
+} |
|
| 58 |
+ |
|
| 59 |
+// WithRetry sets CallSettings.Retry to fn. |
|
| 60 |
+func WithRetry(fn func() Retryer) CallOption {
|
|
| 61 |
+ return retryerOption(fn) |
|
| 62 |
+} |
|
| 63 |
+ |
|
| 64 |
+// OnCodes returns a Retryer that retries if and only if |
|
| 65 |
+// the previous attempt returns a GRPC error whose error code is stored in cc. |
|
| 66 |
+// Pause times between retries are specified by bo. |
|
| 67 |
+// |
|
| 68 |
+// bo is only used for its parameters; each Retryer has its own copy. |
|
| 69 |
+func OnCodes(cc []codes.Code, bo Backoff) Retryer {
|
|
| 70 |
+ return &boRetryer{
|
|
| 71 |
+ backoff: bo, |
|
| 72 |
+ codes: append([]codes.Code(nil), cc...), |
|
| 73 |
+ } |
|
| 74 |
+} |
|
| 75 |
+ |
|
| 76 |
+type boRetryer struct {
|
|
| 77 |
+ backoff Backoff |
|
| 78 |
+ codes []codes.Code |
|
| 79 |
+} |
|
| 80 |
+ |
|
| 81 |
+func (r *boRetryer) Retry(err error) (time.Duration, bool) {
|
|
| 82 |
+ c := grpc.Code(err) |
|
| 83 |
+ for _, rc := range r.codes {
|
|
| 84 |
+ if c == rc {
|
|
| 85 |
+ return r.backoff.Pause(), true |
|
| 86 |
+ } |
|
| 87 |
+ } |
|
| 88 |
+ return 0, false |
|
| 89 |
+} |
|
| 90 |
+ |
|
| 91 |
+// Backoff implements exponential backoff. |
|
| 92 |
+// The wait time between retries is a random value between 0 and the "retry envelope". |
|
| 93 |
+// The envelope starts at Initial and increases by the factor of Multiplier every retry, |
|
| 94 |
+// but is capped at Max. |
|
| 95 |
+type Backoff struct {
|
|
| 96 |
+ // Initial is the initial value of the retry envelope, defaults to 1 second. |
|
| 97 |
+ Initial time.Duration |
|
| 98 |
+ |
|
| 99 |
+ // Max is the maximum value of the retry envelope, defaults to 30 seconds. |
|
| 100 |
+ Max time.Duration |
|
| 101 |
+ |
|
| 102 |
+ // Multiplier is the factor by which the retry envelope increases. |
|
| 103 |
+ // It should be greater than 1 and defaults to 2. |
|
| 104 |
+ Multiplier float64 |
|
| 105 |
+ |
|
| 106 |
+ // cur is the current retry envelope |
|
| 107 |
+ cur time.Duration |
|
| 108 |
+} |
|
| 109 |
+ |
|
| 110 |
+func (bo *Backoff) Pause() time.Duration {
|
|
| 111 |
+ if bo.Initial == 0 {
|
|
| 112 |
+ bo.Initial = time.Second |
|
| 113 |
+ } |
|
| 114 |
+ if bo.cur == 0 {
|
|
| 115 |
+ bo.cur = bo.Initial |
|
| 116 |
+ } |
|
| 117 |
+ if bo.Max == 0 {
|
|
| 118 |
+ bo.Max = 30 * time.Second |
|
| 119 |
+ } |
|
| 120 |
+ if bo.Multiplier < 1 {
|
|
| 121 |
+ bo.Multiplier = 2 |
|
| 122 |
+ } |
|
| 123 |
+ d := time.Duration(rand.Int63n(int64(bo.cur))) |
|
| 124 |
+ bo.cur = time.Duration(float64(bo.cur) * bo.Multiplier) |
|
| 125 |
+ if bo.cur > bo.Max {
|
|
| 126 |
+ bo.cur = bo.Max |
|
| 127 |
+ } |
|
| 128 |
+ return d |
|
| 129 |
+} |
|
| 130 |
+ |
|
| 131 |
+type CallSettings struct {
|
|
| 132 |
+ // Retry returns a Retryer to be used to control retry logic of a method call. |
|
| 133 |
+ // If Retry is nil or the returned Retryer is nil, the call will not be retried. |
|
| 134 |
+ Retry func() Retryer |
|
| 135 |
+} |
| 0 | 136 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,40 @@ |
| 0 |
+// Copyright 2016, Google Inc. |
|
| 1 |
+// All rights reserved. |
|
| 2 |
+// |
|
| 3 |
+// Redistribution and use in source and binary forms, with or without |
|
| 4 |
+// modification, are permitted provided that the following conditions are |
|
| 5 |
+// met: |
|
| 6 |
+// |
|
| 7 |
+// * Redistributions of source code must retain the above copyright |
|
| 8 |
+// notice, this list of conditions and the following disclaimer. |
|
| 9 |
+// * Redistributions in binary form must reproduce the above |
|
| 10 |
+// copyright notice, this list of conditions and the following disclaimer |
|
| 11 |
+// in the documentation and/or other materials provided with the |
|
| 12 |
+// distribution. |
|
| 13 |
+// * Neither the name of Google Inc. nor the names of its |
|
| 14 |
+// contributors may be used to endorse or promote products derived from |
|
| 15 |
+// this software without specific prior written permission. |
|
| 16 |
+// |
|
| 17 |
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
|
| 18 |
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
|
| 19 |
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
|
| 20 |
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
|
| 21 |
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
|
| 22 |
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
|
| 23 |
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
|
| 24 |
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
|
| 25 |
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
|
| 26 |
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
|
| 27 |
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
|
| 28 |
+ |
|
| 29 |
+// Package gax contains a set of modules which aid the development of APIs |
|
| 30 |
+// for clients and servers based on gRPC and Google API conventions. |
|
| 31 |
+// |
|
| 32 |
+// Application code will rarely need to use this library directly. |
|
| 33 |
+// However, code generated automatically from API definition files can use it |
|
| 34 |
+// to simplify code generation and to provide more convenient and idiomatic API surfaces. |
|
| 35 |
+// |
|
| 36 |
+// This project is currently experimental and not supported. |
|
| 37 |
+package gax |
|
| 38 |
+ |
|
| 39 |
+const Version = "0.1.0" |
| 0 | 40 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,90 @@ |
| 0 |
+// Copyright 2016, Google Inc. |
|
| 1 |
+// All rights reserved. |
|
| 2 |
+// |
|
| 3 |
+// Redistribution and use in source and binary forms, with or without |
|
| 4 |
+// modification, are permitted provided that the following conditions are |
|
| 5 |
+// met: |
|
| 6 |
+// |
|
| 7 |
+// * Redistributions of source code must retain the above copyright |
|
| 8 |
+// notice, this list of conditions and the following disclaimer. |
|
| 9 |
+// * Redistributions in binary form must reproduce the above |
|
| 10 |
+// copyright notice, this list of conditions and the following disclaimer |
|
| 11 |
+// in the documentation and/or other materials provided with the |
|
| 12 |
+// distribution. |
|
| 13 |
+// * Neither the name of Google Inc. nor the names of its |
|
| 14 |
+// contributors may be used to endorse or promote products derived from |
|
| 15 |
+// this software without specific prior written permission. |
|
| 16 |
+// |
|
| 17 |
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
|
| 18 |
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
|
| 19 |
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
|
| 20 |
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
|
| 21 |
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
|
| 22 |
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
|
| 23 |
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
|
| 24 |
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
|
| 25 |
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
|
| 26 |
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
|
| 27 |
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
|
| 28 |
+ |
|
| 29 |
+package gax |
|
| 30 |
+ |
|
| 31 |
+import ( |
|
| 32 |
+ "time" |
|
| 33 |
+ |
|
| 34 |
+ "golang.org/x/net/context" |
|
| 35 |
+) |
|
| 36 |
+ |
|
| 37 |
+// A user defined call stub. |
|
| 38 |
+type APICall func(context.Context) error |
|
| 39 |
+ |
|
| 40 |
+// Invoke calls the given APICall, |
|
| 41 |
+// performing retries as specified by opts, if any. |
|
| 42 |
+func Invoke(ctx context.Context, call APICall, opts ...CallOption) error {
|
|
| 43 |
+ var settings CallSettings |
|
| 44 |
+ for _, opt := range opts {
|
|
| 45 |
+ opt.Resolve(&settings) |
|
| 46 |
+ } |
|
| 47 |
+ return invoke(ctx, call, settings, Sleep) |
|
| 48 |
+} |
|
| 49 |
+ |
|
| 50 |
+// Sleep is similar to time.Sleep, but it can be interrupted by ctx.Done() closing. |
|
| 51 |
+// If interrupted, Sleep returns ctx.Err(). |
|
| 52 |
+func Sleep(ctx context.Context, d time.Duration) error {
|
|
| 53 |
+ t := time.NewTimer(d) |
|
| 54 |
+ select {
|
|
| 55 |
+ case <-ctx.Done(): |
|
| 56 |
+ t.Stop() |
|
| 57 |
+ return ctx.Err() |
|
| 58 |
+ case <-t.C: |
|
| 59 |
+ return nil |
|
| 60 |
+ } |
|
| 61 |
+} |
|
| 62 |
+ |
|
| 63 |
+type sleeper func(ctx context.Context, d time.Duration) error |
|
| 64 |
+ |
|
| 65 |
+// invoke implements Invoke, taking an additional sleeper argument for testing. |
|
| 66 |
+func invoke(ctx context.Context, call APICall, settings CallSettings, sp sleeper) error {
|
|
| 67 |
+ var retryer Retryer |
|
| 68 |
+ for {
|
|
| 69 |
+ err := call(ctx) |
|
| 70 |
+ if err == nil {
|
|
| 71 |
+ return nil |
|
| 72 |
+ } |
|
| 73 |
+ if settings.Retry == nil {
|
|
| 74 |
+ return err |
|
| 75 |
+ } |
|
| 76 |
+ if retryer == nil {
|
|
| 77 |
+ if r := settings.Retry(); r != nil {
|
|
| 78 |
+ retryer = r |
|
| 79 |
+ } else {
|
|
| 80 |
+ return err |
|
| 81 |
+ } |
|
| 82 |
+ } |
|
| 83 |
+ if d, ok := retryer.Retry(err); !ok {
|
|
| 84 |
+ return err |
|
| 85 |
+ } else if err = sp(ctx, d); err != nil {
|
|
| 86 |
+ return err |
|
| 87 |
+ } |
|
| 88 |
+ } |
|
| 89 |
+} |
| 0 | 90 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,176 @@ |
| 0 |
+// Copyright 2016, Google Inc. |
|
| 1 |
+// All rights reserved. |
|
| 2 |
+// |
|
| 3 |
+// Redistribution and use in source and binary forms, with or without |
|
| 4 |
+// modification, are permitted provided that the following conditions are |
|
| 5 |
+// met: |
|
| 6 |
+// |
|
| 7 |
+// * Redistributions of source code must retain the above copyright |
|
| 8 |
+// notice, this list of conditions and the following disclaimer. |
|
| 9 |
+// * Redistributions in binary form must reproduce the above |
|
| 10 |
+// copyright notice, this list of conditions and the following disclaimer |
|
| 11 |
+// in the documentation and/or other materials provided with the |
|
| 12 |
+// distribution. |
|
| 13 |
+// * Neither the name of Google Inc. nor the names of its |
|
| 14 |
+// contributors may be used to endorse or promote products derived from |
|
| 15 |
+// this software without specific prior written permission. |
|
| 16 |
+// |
|
| 17 |
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
|
| 18 |
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
|
| 19 |
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
|
| 20 |
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
|
| 21 |
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
|
| 22 |
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
|
| 23 |
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
|
| 24 |
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
|
| 25 |
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
|
| 26 |
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
|
| 27 |
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
|
| 28 |
+ |
|
| 29 |
+package gax |
|
| 30 |
+ |
|
| 31 |
+import ( |
|
| 32 |
+ "errors" |
|
| 33 |
+ "fmt" |
|
| 34 |
+ "strings" |
|
| 35 |
+) |
|
| 36 |
+ |
|
| 37 |
+type matcher interface {
|
|
| 38 |
+ match([]string) (int, error) |
|
| 39 |
+ String() string |
|
| 40 |
+} |
|
| 41 |
+ |
|
| 42 |
+type segment struct {
|
|
| 43 |
+ matcher |
|
| 44 |
+ name string |
|
| 45 |
+} |
|
| 46 |
+ |
|
| 47 |
+type labelMatcher string |
|
| 48 |
+ |
|
| 49 |
+func (ls labelMatcher) match(segments []string) (int, error) {
|
|
| 50 |
+ if len(segments) == 0 {
|
|
| 51 |
+ return 0, fmt.Errorf("expected %s but no more segments found", ls)
|
|
| 52 |
+ } |
|
| 53 |
+ if segments[0] != string(ls) {
|
|
| 54 |
+ return 0, fmt.Errorf("expected %s but got %s", ls, segments[0])
|
|
| 55 |
+ } |
|
| 56 |
+ return 1, nil |
|
| 57 |
+} |
|
| 58 |
+ |
|
| 59 |
+func (ls labelMatcher) String() string {
|
|
| 60 |
+ return string(ls) |
|
| 61 |
+} |
|
| 62 |
+ |
|
| 63 |
+type wildcardMatcher int |
|
| 64 |
+ |
|
| 65 |
+func (wm wildcardMatcher) match(segments []string) (int, error) {
|
|
| 66 |
+ if len(segments) == 0 {
|
|
| 67 |
+ return 0, errors.New("no more segments found")
|
|
| 68 |
+ } |
|
| 69 |
+ return 1, nil |
|
| 70 |
+} |
|
| 71 |
+ |
|
| 72 |
+func (wm wildcardMatcher) String() string {
|
|
| 73 |
+ return "*" |
|
| 74 |
+} |
|
| 75 |
+ |
|
| 76 |
+type pathWildcardMatcher int |
|
| 77 |
+ |
|
| 78 |
+func (pwm pathWildcardMatcher) match(segments []string) (int, error) {
|
|
| 79 |
+ length := len(segments) - int(pwm) |
|
| 80 |
+ if length <= 0 {
|
|
| 81 |
+ return 0, errors.New("not sufficient segments are supplied for path wildcard")
|
|
| 82 |
+ } |
|
| 83 |
+ return length, nil |
|
| 84 |
+} |
|
| 85 |
+ |
|
| 86 |
+func (pwm pathWildcardMatcher) String() string {
|
|
| 87 |
+ return "**" |
|
| 88 |
+} |
|
| 89 |
+ |
|
| 90 |
+type ParseError struct {
|
|
| 91 |
+ Pos int |
|
| 92 |
+ Template string |
|
| 93 |
+ Message string |
|
| 94 |
+} |
|
| 95 |
+ |
|
| 96 |
+func (pe ParseError) Error() string {
|
|
| 97 |
+ return fmt.Sprintf("at %d of template '%s', %s", pe.Pos, pe.Template, pe.Message)
|
|
| 98 |
+} |
|
| 99 |
+ |
|
| 100 |
+// PathTemplate manages the template to build and match with paths used |
|
| 101 |
+// by API services. It holds a template and variable names in it, and |
|
| 102 |
+// it can extract matched patterns from a path string or build a path |
|
| 103 |
+// string from a binding. |
|
| 104 |
+// |
|
| 105 |
+// See http.proto in github.com/googleapis/googleapis/ for the details of |
|
| 106 |
+// the template syntax. |
|
| 107 |
+type PathTemplate struct {
|
|
| 108 |
+ segments []segment |
|
| 109 |
+} |
|
| 110 |
+ |
|
| 111 |
+// NewPathTemplate parses a path template, and returns a PathTemplate |
|
| 112 |
+// instance if successful. |
|
| 113 |
+func NewPathTemplate(template string) (*PathTemplate, error) {
|
|
| 114 |
+ return parsePathTemplate(template) |
|
| 115 |
+} |
|
| 116 |
+ |
|
| 117 |
+// MustCompilePathTemplate is like NewPathTemplate but panics if the |
|
| 118 |
+// expression cannot be parsed. It simplifies safe initialization of |
|
| 119 |
+// global variables holding compiled regular expressions. |
|
| 120 |
+func MustCompilePathTemplate(template string) *PathTemplate {
|
|
| 121 |
+ pt, err := NewPathTemplate(template) |
|
| 122 |
+ if err != nil {
|
|
| 123 |
+ panic(err) |
|
| 124 |
+ } |
|
| 125 |
+ return pt |
|
| 126 |
+} |
|
| 127 |
+ |
|
| 128 |
+// Match attempts to match the given path with the template, and returns |
|
| 129 |
+// the mapping of the variable name to the matched pattern string. |
|
| 130 |
+func (pt *PathTemplate) Match(path string) (map[string]string, error) {
|
|
| 131 |
+ paths := strings.Split(path, "/") |
|
| 132 |
+ values := map[string]string{}
|
|
| 133 |
+ for _, segment := range pt.segments {
|
|
| 134 |
+ length, err := segment.match(paths) |
|
| 135 |
+ if err != nil {
|
|
| 136 |
+ return nil, err |
|
| 137 |
+ } |
|
| 138 |
+ if segment.name != "" {
|
|
| 139 |
+ value := strings.Join(paths[:length], "/") |
|
| 140 |
+ if oldValue, ok := values[segment.name]; ok {
|
|
| 141 |
+ values[segment.name] = oldValue + "/" + value |
|
| 142 |
+ } else {
|
|
| 143 |
+ values[segment.name] = value |
|
| 144 |
+ } |
|
| 145 |
+ } |
|
| 146 |
+ paths = paths[length:] |
|
| 147 |
+ } |
|
| 148 |
+ if len(paths) != 0 {
|
|
| 149 |
+ return nil, fmt.Errorf("Trailing path %s remains after the matching", strings.Join(paths, "/"))
|
|
| 150 |
+ } |
|
| 151 |
+ return values, nil |
|
| 152 |
+} |
|
| 153 |
+ |
|
| 154 |
+// Render creates a path string from its template and the binding from |
|
| 155 |
+// the variable name to the value. |
|
| 156 |
+func (pt *PathTemplate) Render(binding map[string]string) (string, error) {
|
|
| 157 |
+ result := make([]string, 0, len(pt.segments)) |
|
| 158 |
+ var lastVariableName string |
|
| 159 |
+ for _, segment := range pt.segments {
|
|
| 160 |
+ name := segment.name |
|
| 161 |
+ if lastVariableName != "" && name == lastVariableName {
|
|
| 162 |
+ continue |
|
| 163 |
+ } |
|
| 164 |
+ lastVariableName = name |
|
| 165 |
+ if name == "" {
|
|
| 166 |
+ result = append(result, segment.String()) |
|
| 167 |
+ } else if value, ok := binding[name]; ok {
|
|
| 168 |
+ result = append(result, value) |
|
| 169 |
+ } else {
|
|
| 170 |
+ return "", fmt.Errorf("%s is not found", name)
|
|
| 171 |
+ } |
|
| 172 |
+ } |
|
| 173 |
+ built := strings.Join(result, "/") |
|
| 174 |
+ return built, nil |
|
| 175 |
+} |
| 0 | 176 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,227 @@ |
| 0 |
+// Copyright 2016, Google Inc. |
|
| 1 |
+// All rights reserved. |
|
| 2 |
+// |
|
| 3 |
+// Redistribution and use in source and binary forms, with or without |
|
| 4 |
+// modification, are permitted provided that the following conditions are |
|
| 5 |
+// met: |
|
| 6 |
+// |
|
| 7 |
+// * Redistributions of source code must retain the above copyright |
|
| 8 |
+// notice, this list of conditions and the following disclaimer. |
|
| 9 |
+// * Redistributions in binary form must reproduce the above |
|
| 10 |
+// copyright notice, this list of conditions and the following disclaimer |
|
| 11 |
+// in the documentation and/or other materials provided with the |
|
| 12 |
+// distribution. |
|
| 13 |
+// * Neither the name of Google Inc. nor the names of its |
|
| 14 |
+// contributors may be used to endorse or promote products derived from |
|
| 15 |
+// this software without specific prior written permission. |
|
| 16 |
+// |
|
| 17 |
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
|
| 18 |
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
|
| 19 |
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
|
| 20 |
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
|
| 21 |
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
|
| 22 |
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
|
| 23 |
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
|
| 24 |
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
|
| 25 |
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
|
| 26 |
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
|
| 27 |
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
|
| 28 |
+ |
|
| 29 |
+package gax |
|
| 30 |
+ |
|
| 31 |
+import ( |
|
| 32 |
+ "fmt" |
|
| 33 |
+ "io" |
|
| 34 |
+ "strings" |
|
| 35 |
+) |
|
| 36 |
+ |
|
| 37 |
+// This parser follows the syntax of path templates, from |
|
| 38 |
+// https://github.com/googleapis/googleapis/blob/master/google/api/http.proto. |
|
| 39 |
+// The differences are that there is no custom verb, we allow the initial slash |
|
| 40 |
+// to be absent, and that we are not strict as |
|
| 41 |
+// https://tools.ietf.org/html/rfc6570 about the characters in identifiers and |
|
| 42 |
+// literals. |
|
| 43 |
+ |
|
| 44 |
+type pathTemplateParser struct {
|
|
| 45 |
+ r *strings.Reader |
|
| 46 |
+ runeCount int // the number of the current rune in the original string |
|
| 47 |
+ nextVar int // the number to use for the next unnamed variable |
|
| 48 |
+ seenName map[string]bool // names we've seen already |
|
| 49 |
+ seenPathWildcard bool // have we seen "**" already? |
|
| 50 |
+} |
|
| 51 |
+ |
|
| 52 |
+func parsePathTemplate(template string) (pt *PathTemplate, err error) {
|
|
| 53 |
+ p := &pathTemplateParser{
|
|
| 54 |
+ r: strings.NewReader(template), |
|
| 55 |
+ seenName: map[string]bool{},
|
|
| 56 |
+ } |
|
| 57 |
+ |
|
| 58 |
+ // Handle panics with strings like errors. |
|
| 59 |
+ // See pathTemplateParser.error, below. |
|
| 60 |
+ defer func() {
|
|
| 61 |
+ if x := recover(); x != nil {
|
|
| 62 |
+ errmsg, ok := x.(errString) |
|
| 63 |
+ if !ok {
|
|
| 64 |
+ panic(x) |
|
| 65 |
+ } |
|
| 66 |
+ pt = nil |
|
| 67 |
+ err = ParseError{p.runeCount, template, string(errmsg)}
|
|
| 68 |
+ } |
|
| 69 |
+ }() |
|
| 70 |
+ |
|
| 71 |
+ segs := p.template() |
|
| 72 |
+ // If there is a path wildcard, set its length. We can't do this |
|
| 73 |
+ // until we know how many segments we've got all together. |
|
| 74 |
+ for i, seg := range segs {
|
|
| 75 |
+ if _, ok := seg.matcher.(pathWildcardMatcher); ok {
|
|
| 76 |
+ segs[i].matcher = pathWildcardMatcher(len(segs) - i - 1) |
|
| 77 |
+ break |
|
| 78 |
+ } |
|
| 79 |
+ } |
|
| 80 |
+ return &PathTemplate{segments: segs}, nil
|
|
| 81 |
+ |
|
| 82 |
+} |
|
| 83 |
+ |
|
| 84 |
+// Used to indicate errors "thrown" by this parser. We don't use string because |
|
| 85 |
+// many parts of the standard library panic with strings. |
|
| 86 |
+type errString string |
|
| 87 |
+ |
|
| 88 |
+// Terminates parsing immediately with an error. |
|
| 89 |
+func (p *pathTemplateParser) error(msg string) {
|
|
| 90 |
+ panic(errString(msg)) |
|
| 91 |
+} |
|
| 92 |
+ |
|
| 93 |
+// Template = [ "/" ] Segments |
|
| 94 |
+func (p *pathTemplateParser) template() []segment {
|
|
| 95 |
+ var segs []segment |
|
| 96 |
+ if p.consume('/') {
|
|
| 97 |
+ // Initial '/' needs an initial empty matcher. |
|
| 98 |
+ segs = append(segs, segment{matcher: labelMatcher("")})
|
|
| 99 |
+ } |
|
| 100 |
+ return append(segs, p.segments("")...)
|
|
| 101 |
+} |
|
| 102 |
+ |
|
| 103 |
+// Segments = Segment { "/" Segment }
|
|
| 104 |
+func (p *pathTemplateParser) segments(name string) []segment {
|
|
| 105 |
+ var segs []segment |
|
| 106 |
+ for {
|
|
| 107 |
+ subsegs := p.segment(name) |
|
| 108 |
+ segs = append(segs, subsegs...) |
|
| 109 |
+ if !p.consume('/') {
|
|
| 110 |
+ break |
|
| 111 |
+ } |
|
| 112 |
+ } |
|
| 113 |
+ return segs |
|
| 114 |
+} |
|
| 115 |
+ |
|
| 116 |
+// Segment = "*" | "**" | LITERAL | Variable |
|
| 117 |
+func (p *pathTemplateParser) segment(name string) []segment {
|
|
| 118 |
+ if p.consume('*') {
|
|
| 119 |
+ if name == "" {
|
|
| 120 |
+ name = fmt.Sprintf("$%d", p.nextVar)
|
|
| 121 |
+ p.nextVar++ |
|
| 122 |
+ } |
|
| 123 |
+ if p.consume('*') {
|
|
| 124 |
+ if p.seenPathWildcard {
|
|
| 125 |
+ p.error("multiple '**' disallowed")
|
|
| 126 |
+ } |
|
| 127 |
+ p.seenPathWildcard = true |
|
| 128 |
+ // We'll change 0 to the right number at the end. |
|
| 129 |
+ return []segment{{name: name, matcher: pathWildcardMatcher(0)}}
|
|
| 130 |
+ } |
|
| 131 |
+ return []segment{{name: name, matcher: wildcardMatcher(0)}}
|
|
| 132 |
+ } |
|
| 133 |
+ if p.consume('{') {
|
|
| 134 |
+ if name != "" {
|
|
| 135 |
+ p.error("recursive named bindings are not allowed")
|
|
| 136 |
+ } |
|
| 137 |
+ return p.variable() |
|
| 138 |
+ } |
|
| 139 |
+ return []segment{{name: name, matcher: labelMatcher(p.literal())}}
|
|
| 140 |
+} |
|
| 141 |
+ |
|
| 142 |
+// Variable = "{" FieldPath [ "=" Segments ] "}"
|
|
| 143 |
+// "{" is already consumed.
|
|
| 144 |
+func (p *pathTemplateParser) variable() []segment {
|
|
| 145 |
+ // Simplification: treat FieldPath as LITERAL, instead of IDENT { '.' IDENT }
|
|
| 146 |
+ name := p.literal() |
|
| 147 |
+ if p.seenName[name] {
|
|
| 148 |
+ p.error(name + " appears multiple times") |
|
| 149 |
+ } |
|
| 150 |
+ p.seenName[name] = true |
|
| 151 |
+ var segs []segment |
|
| 152 |
+ if p.consume('=') {
|
|
| 153 |
+ segs = p.segments(name) |
|
| 154 |
+ } else {
|
|
| 155 |
+ // "{var}" is equivalent to "{var=*}"
|
|
| 156 |
+ segs = []segment{{name: name, matcher: wildcardMatcher(0)}}
|
|
| 157 |
+ } |
|
| 158 |
+ if !p.consume('}') {
|
|
| 159 |
+ p.error("expected '}'")
|
|
| 160 |
+ } |
|
| 161 |
+ return segs |
|
| 162 |
+} |
|
| 163 |
+ |
|
| 164 |
+// A literal is any sequence of characters other than a few special ones. |
|
| 165 |
+// The list of stop characters is not quite the same as in the template RFC. |
|
| 166 |
+func (p *pathTemplateParser) literal() string {
|
|
| 167 |
+ lit := p.consumeUntil("/*}{=")
|
|
| 168 |
+ if lit == "" {
|
|
| 169 |
+ p.error("empty literal")
|
|
| 170 |
+ } |
|
| 171 |
+ return lit |
|
| 172 |
+} |
|
| 173 |
+ |
|
| 174 |
+// Read runes until EOF or one of the runes in stopRunes is encountered. |
|
| 175 |
+// If the latter, unread the stop rune. Return the accumulated runes as a string. |
|
| 176 |
+func (p *pathTemplateParser) consumeUntil(stopRunes string) string {
|
|
| 177 |
+ var runes []rune |
|
| 178 |
+ for {
|
|
| 179 |
+ r, ok := p.readRune() |
|
| 180 |
+ if !ok {
|
|
| 181 |
+ break |
|
| 182 |
+ } |
|
| 183 |
+ if strings.IndexRune(stopRunes, r) >= 0 {
|
|
| 184 |
+ p.unreadRune() |
|
| 185 |
+ break |
|
| 186 |
+ } |
|
| 187 |
+ runes = append(runes, r) |
|
| 188 |
+ } |
|
| 189 |
+ return string(runes) |
|
| 190 |
+} |
|
| 191 |
+ |
|
| 192 |
+// If the next rune is r, consume it and return true. |
|
| 193 |
+// Otherwise, leave the input unchanged and return false. |
|
| 194 |
+func (p *pathTemplateParser) consume(r rune) bool {
|
|
| 195 |
+ rr, ok := p.readRune() |
|
| 196 |
+ if !ok {
|
|
| 197 |
+ return false |
|
| 198 |
+ } |
|
| 199 |
+ if r == rr {
|
|
| 200 |
+ return true |
|
| 201 |
+ } |
|
| 202 |
+ p.unreadRune() |
|
| 203 |
+ return false |
|
| 204 |
+} |
|
| 205 |
+ |
|
| 206 |
+// Read the next rune from the input. Return it. |
|
| 207 |
+// The second return value is false at EOF. |
|
| 208 |
+func (p *pathTemplateParser) readRune() (rune, bool) {
|
|
| 209 |
+ r, _, err := p.r.ReadRune() |
|
| 210 |
+ if err == io.EOF {
|
|
| 211 |
+ return r, false |
|
| 212 |
+ } |
|
| 213 |
+ if err != nil {
|
|
| 214 |
+ p.error(err.Error()) |
|
| 215 |
+ } |
|
| 216 |
+ p.runeCount++ |
|
| 217 |
+ return r, true |
|
| 218 |
+} |
|
| 219 |
+ |
|
| 220 |
+// Put the last rune that was read back on the input. |
|
| 221 |
+func (p *pathTemplateParser) unreadRune() {
|
|
| 222 |
+ if err := p.r.UnreadRune(); err != nil {
|
|
| 223 |
+ p.error(err.Error()) |
|
| 224 |
+ } |
|
| 225 |
+ p.runeCount-- |
|
| 226 |
+} |
| ... | ... |
@@ -20,6 +20,9 @@ var appengineVM bool |
| 20 | 20 |
// Set at init time by appengine_hook.go. If nil, we're not on App Engine. |
| 21 | 21 |
var appengineTokenFunc func(c context.Context, scopes ...string) (token string, expiry time.Time, err error) |
| 22 | 22 |
|
| 23 |
+// Set at init time by appengine_hook.go. If nil, we're not on App Engine. |
|
| 24 |
+var appengineAppIDFunc func(c context.Context) string |
|
| 25 |
+ |
|
| 23 | 26 |
// AppEngineTokenSource returns a token source that fetches tokens |
| 24 | 27 |
// issued to the current App Engine application's service account. |
| 25 | 28 |
// If you are implementing a 3-legged OAuth 2.0 flow on App Engine |
| ... | ... |
@@ -6,7 +6,6 @@ package google |
| 6 | 6 |
|
| 7 | 7 |
import ( |
| 8 | 8 |
"encoding/json" |
| 9 |
- "errors" |
|
| 10 | 9 |
"fmt" |
| 11 | 10 |
"io/ioutil" |
| 12 | 11 |
"net/http" |
| ... | ... |
@@ -14,22 +13,21 @@ import ( |
| 14 | 14 |
"path/filepath" |
| 15 | 15 |
"runtime" |
| 16 | 16 |
|
| 17 |
+ "cloud.google.com/go/compute/metadata" |
|
| 17 | 18 |
"golang.org/x/net/context" |
| 18 | 19 |
"golang.org/x/oauth2" |
| 19 |
- "golang.org/x/oauth2/jwt" |
|
| 20 |
- "google.golang.org/cloud/compute/metadata" |
|
| 21 | 20 |
) |
| 22 | 21 |
|
| 23 |
-// DefaultClient returns an HTTP Client that uses the |
|
| 24 |
-// DefaultTokenSource to obtain authentication credentials. |
|
| 25 |
-// |
|
| 26 |
-// This client should be used when developing services |
|
| 27 |
-// that run on Google App Engine or Google Compute Engine |
|
| 28 |
-// and use "Application Default Credentials." |
|
| 29 |
-// |
|
| 22 |
+// DefaultCredentials holds "Application Default Credentials". |
|
| 30 | 23 |
// For more details, see: |
| 31 | 24 |
// https://developers.google.com/accounts/docs/application-default-credentials |
| 32 |
-// |
|
| 25 |
+type DefaultCredentials struct {
|
|
| 26 |
+ ProjectID string // may be empty |
|
| 27 |
+ TokenSource oauth2.TokenSource |
|
| 28 |
+} |
|
| 29 |
+ |
|
| 30 |
+// DefaultClient returns an HTTP Client that uses the |
|
| 31 |
+// DefaultTokenSource to obtain authentication credentials. |
|
| 33 | 32 |
func DefaultClient(ctx context.Context, scope ...string) (*http.Client, error) {
|
| 34 | 33 |
ts, err := DefaultTokenSource(ctx, scope...) |
| 35 | 34 |
if err != nil {
|
| ... | ... |
@@ -38,8 +36,18 @@ func DefaultClient(ctx context.Context, scope ...string) (*http.Client, error) {
|
| 38 | 38 |
return oauth2.NewClient(ctx, ts), nil |
| 39 | 39 |
} |
| 40 | 40 |
|
| 41 |
-// DefaultTokenSource is a token source that uses |
|
| 41 |
+// DefaultTokenSource returns the token source for |
|
| 42 | 42 |
// "Application Default Credentials". |
| 43 |
+// It is a shortcut for FindDefaultCredentials(ctx, scope).TokenSource. |
|
| 44 |
+func DefaultTokenSource(ctx context.Context, scope ...string) (oauth2.TokenSource, error) {
|
|
| 45 |
+ creds, err := FindDefaultCredentials(ctx, scope...) |
|
| 46 |
+ if err != nil {
|
|
| 47 |
+ return nil, err |
|
| 48 |
+ } |
|
| 49 |
+ return creds.TokenSource, nil |
|
| 50 |
+} |
|
| 51 |
+ |
|
| 52 |
+// FindDefaultCredentials searches for "Application Default Credentials". |
|
| 43 | 53 |
// |
| 44 | 54 |
// It looks for credentials in the following places, |
| 45 | 55 |
// preferring the first location found: |
| ... | ... |
@@ -53,45 +61,40 @@ func DefaultClient(ctx context.Context, scope ...string) (*http.Client, error) {
|
| 53 | 53 |
// 4. On Google Compute Engine and Google App Engine Managed VMs, it fetches |
| 54 | 54 |
// credentials from the metadata server. |
| 55 | 55 |
// (In this final case any provided scopes are ignored.) |
| 56 |
-// |
|
| 57 |
-// For more details, see: |
|
| 58 |
-// https://developers.google.com/accounts/docs/application-default-credentials |
|
| 59 |
-// |
|
| 60 |
-func DefaultTokenSource(ctx context.Context, scope ...string) (oauth2.TokenSource, error) {
|
|
| 56 |
+func FindDefaultCredentials(ctx context.Context, scope ...string) (*DefaultCredentials, error) {
|
|
| 61 | 57 |
// First, try the environment variable. |
| 62 | 58 |
const envVar = "GOOGLE_APPLICATION_CREDENTIALS" |
| 63 | 59 |
if filename := os.Getenv(envVar); filename != "" {
|
| 64 |
- ts, err := tokenSourceFromFile(ctx, filename, scope) |
|
| 60 |
+ creds, err := readCredentialsFile(ctx, filename, scope) |
|
| 65 | 61 |
if err != nil {
|
| 66 | 62 |
return nil, fmt.Errorf("google: error getting credentials using %v environment variable: %v", envVar, err)
|
| 67 | 63 |
} |
| 68 |
- return ts, nil |
|
| 64 |
+ return creds, nil |
|
| 69 | 65 |
} |
| 70 | 66 |
|
| 71 | 67 |
// Second, try a well-known file. |
| 72 | 68 |
filename := wellKnownFile() |
| 73 |
- _, err := os.Stat(filename) |
|
| 74 |
- if err == nil {
|
|
| 75 |
- ts, err2 := tokenSourceFromFile(ctx, filename, scope) |
|
| 76 |
- if err2 == nil {
|
|
| 77 |
- return ts, nil |
|
| 78 |
- } |
|
| 79 |
- err = err2 |
|
| 80 |
- } else if os.IsNotExist(err) {
|
|
| 81 |
- err = nil // ignore this error |
|
| 82 |
- } |
|
| 83 |
- if err != nil {
|
|
| 69 |
+ if creds, err := readCredentialsFile(ctx, filename, scope); err == nil {
|
|
| 70 |
+ return creds, nil |
|
| 71 |
+ } else if !os.IsNotExist(err) {
|
|
| 84 | 72 |
return nil, fmt.Errorf("google: error getting credentials using well-known file (%v): %v", filename, err)
|
| 85 | 73 |
} |
| 86 | 74 |
|
| 87 | 75 |
// Third, if we're on Google App Engine use those credentials. |
| 88 | 76 |
if appengineTokenFunc != nil && !appengineVM {
|
| 89 |
- return AppEngineTokenSource(ctx, scope...), nil |
|
| 77 |
+ return &DefaultCredentials{
|
|
| 78 |
+ ProjectID: appengineAppIDFunc(ctx), |
|
| 79 |
+ TokenSource: AppEngineTokenSource(ctx, scope...), |
|
| 80 |
+ }, nil |
|
| 90 | 81 |
} |
| 91 | 82 |
|
| 92 | 83 |
// Fourth, if we're on Google Compute Engine use the metadata server. |
| 93 | 84 |
if metadata.OnGCE() {
|
| 94 |
- return ComputeTokenSource(""), nil
|
|
| 85 |
+ id, _ := metadata.ProjectID() |
|
| 86 |
+ return &DefaultCredentials{
|
|
| 87 |
+ ProjectID: id, |
|
| 88 |
+ TokenSource: ComputeTokenSource(""),
|
|
| 89 |
+ }, nil |
|
| 95 | 90 |
} |
| 96 | 91 |
|
| 97 | 92 |
// None are found; return helpful error. |
| ... | ... |
@@ -107,49 +110,21 @@ func wellKnownFile() string {
|
| 107 | 107 |
return filepath.Join(guessUnixHomeDir(), ".config", "gcloud", f) |
| 108 | 108 |
} |
| 109 | 109 |
|
| 110 |
-func tokenSourceFromFile(ctx context.Context, filename string, scopes []string) (oauth2.TokenSource, error) {
|
|
| 110 |
+func readCredentialsFile(ctx context.Context, filename string, scopes []string) (*DefaultCredentials, error) {
|
|
| 111 | 111 |
b, err := ioutil.ReadFile(filename) |
| 112 | 112 |
if err != nil {
|
| 113 | 113 |
return nil, err |
| 114 | 114 |
} |
| 115 |
- var d struct {
|
|
| 116 |
- // Common fields |
|
| 117 |
- Type string |
|
| 118 |
- ClientID string `json:"client_id"` |
|
| 119 |
- |
|
| 120 |
- // User Credential fields |
|
| 121 |
- ClientSecret string `json:"client_secret"` |
|
| 122 |
- RefreshToken string `json:"refresh_token"` |
|
| 123 |
- |
|
| 124 |
- // Service Account fields |
|
| 125 |
- ClientEmail string `json:"client_email"` |
|
| 126 |
- PrivateKeyID string `json:"private_key_id"` |
|
| 127 |
- PrivateKey string `json:"private_key"` |
|
| 128 |
- } |
|
| 129 |
- if err := json.Unmarshal(b, &d); err != nil {
|
|
| 115 |
+ var f credentialsFile |
|
| 116 |
+ if err := json.Unmarshal(b, &f); err != nil {
|
|
| 130 | 117 |
return nil, err |
| 131 | 118 |
} |
| 132 |
- switch d.Type {
|
|
| 133 |
- case "authorized_user": |
|
| 134 |
- cfg := &oauth2.Config{
|
|
| 135 |
- ClientID: d.ClientID, |
|
| 136 |
- ClientSecret: d.ClientSecret, |
|
| 137 |
- Scopes: append([]string{}, scopes...), // copy
|
|
| 138 |
- Endpoint: Endpoint, |
|
| 139 |
- } |
|
| 140 |
- tok := &oauth2.Token{RefreshToken: d.RefreshToken}
|
|
| 141 |
- return cfg.TokenSource(ctx, tok), nil |
|
| 142 |
- case "service_account": |
|
| 143 |
- cfg := &jwt.Config{
|
|
| 144 |
- Email: d.ClientEmail, |
|
| 145 |
- PrivateKey: []byte(d.PrivateKey), |
|
| 146 |
- Scopes: append([]string{}, scopes...), // copy
|
|
| 147 |
- TokenURL: JWTTokenURL, |
|
| 148 |
- } |
|
| 149 |
- return cfg.TokenSource(ctx), nil |
|
| 150 |
- case "": |
|
| 151 |
- return nil, errors.New("missing 'type' field in credentials")
|
|
| 152 |
- default: |
|
| 153 |
- return nil, fmt.Errorf("unknown credential type: %q", d.Type)
|
|
| 119 |
+ ts, err := f.tokenSource(ctx, append([]string(nil), scopes...)) |
|
| 120 |
+ if err != nil {
|
|
| 121 |
+ return nil, err |
|
| 154 | 122 |
} |
| 123 |
+ return &DefaultCredentials{
|
|
| 124 |
+ ProjectID: f.ProjectID, |
|
| 125 |
+ TokenSource: ts, |
|
| 126 |
+ }, nil |
|
| 155 | 127 |
} |
| ... | ... |
@@ -21,9 +21,10 @@ import ( |
| 21 | 21 |
"strings" |
| 22 | 22 |
"time" |
| 23 | 23 |
|
| 24 |
+ "cloud.google.com/go/compute/metadata" |
|
| 25 |
+ "golang.org/x/net/context" |
|
| 24 | 26 |
"golang.org/x/oauth2" |
| 25 | 27 |
"golang.org/x/oauth2/jwt" |
| 26 |
- "google.golang.org/cloud/compute/metadata" |
|
| 27 | 28 |
) |
| 28 | 29 |
|
| 29 | 30 |
// Endpoint is Google's OAuth 2.0 endpoint. |
| ... | ... |
@@ -37,9 +38,10 @@ const JWTTokenURL = "https://accounts.google.com/o/oauth2/token" |
| 37 | 37 |
|
| 38 | 38 |
// ConfigFromJSON uses a Google Developers Console client_credentials.json |
| 39 | 39 |
// file to construct a config. |
| 40 |
-// client_credentials.json can be downloadable from https://console.developers.google.com, |
|
| 41 |
-// under "APIs & Auth" > "Credentials". Download the Web application credentials in the |
|
| 42 |
-// JSON format and provide the contents of the file as jsonKey. |
|
| 40 |
+// client_credentials.json can be downloaded from |
|
| 41 |
+// https://console.developers.google.com, under "Credentials". Download the Web |
|
| 42 |
+// application credentials in the JSON format and provide the contents of the |
|
| 43 |
+// file as jsonKey. |
|
| 43 | 44 |
func ConfigFromJSON(jsonKey []byte, scope ...string) (*oauth2.Config, error) {
|
| 44 | 45 |
type cred struct {
|
| 45 | 46 |
ClientID string `json:"client_id"` |
| ... | ... |
@@ -81,22 +83,77 @@ func ConfigFromJSON(jsonKey []byte, scope ...string) (*oauth2.Config, error) {
|
| 81 | 81 |
|
| 82 | 82 |
// JWTConfigFromJSON uses a Google Developers service account JSON key file to read |
| 83 | 83 |
// the credentials that authorize and authenticate the requests. |
| 84 |
-// Create a service account on "Credentials" page under "APIs & Auth" for your |
|
| 85 |
-// project at https://console.developers.google.com to download a JSON key file. |
|
| 84 |
+// Create a service account on "Credentials" for your project at |
|
| 85 |
+// https://console.developers.google.com to download a JSON key file. |
|
| 86 | 86 |
func JWTConfigFromJSON(jsonKey []byte, scope ...string) (*jwt.Config, error) {
|
| 87 |
- var key struct {
|
|
| 88 |
- Email string `json:"client_email"` |
|
| 89 |
- PrivateKey string `json:"private_key"` |
|
| 90 |
- } |
|
| 91 |
- if err := json.Unmarshal(jsonKey, &key); err != nil {
|
|
| 87 |
+ var f credentialsFile |
|
| 88 |
+ if err := json.Unmarshal(jsonKey, &f); err != nil {
|
|
| 92 | 89 |
return nil, err |
| 93 | 90 |
} |
| 94 |
- return &jwt.Config{
|
|
| 95 |
- Email: key.Email, |
|
| 96 |
- PrivateKey: []byte(key.PrivateKey), |
|
| 97 |
- Scopes: scope, |
|
| 98 |
- TokenURL: JWTTokenURL, |
|
| 99 |
- }, nil |
|
| 91 |
+ if f.Type != serviceAccountKey {
|
|
| 92 |
+ return nil, fmt.Errorf("google: read JWT from JSON credentials: 'type' field is %q (expected %q)", f.Type, serviceAccountKey)
|
|
| 93 |
+ } |
|
| 94 |
+ scope = append([]string(nil), scope...) // copy |
|
| 95 |
+ return f.jwtConfig(scope), nil |
|
| 96 |
+} |
|
| 97 |
+ |
|
| 98 |
+// JSON key file types. |
|
| 99 |
+const ( |
|
| 100 |
+ serviceAccountKey = "service_account" |
|
| 101 |
+ userCredentialsKey = "authorized_user" |
|
| 102 |
+) |
|
| 103 |
+ |
|
| 104 |
+// credentialsFile is the unmarshalled representation of a credentials file. |
|
| 105 |
+type credentialsFile struct {
|
|
| 106 |
+ Type string `json:"type"` // serviceAccountKey or userCredentialsKey |
|
| 107 |
+ |
|
| 108 |
+ // Service Account fields |
|
| 109 |
+ ClientEmail string `json:"client_email"` |
|
| 110 |
+ PrivateKeyID string `json:"private_key_id"` |
|
| 111 |
+ PrivateKey string `json:"private_key"` |
|
| 112 |
+ TokenURL string `json:"token_uri"` |
|
| 113 |
+ ProjectID string `json:"project_id"` |
|
| 114 |
+ |
|
| 115 |
+ // User Credential fields |
|
| 116 |
+ // (These typically come from gcloud auth.) |
|
| 117 |
+ ClientSecret string `json:"client_secret"` |
|
| 118 |
+ ClientID string `json:"client_id"` |
|
| 119 |
+ RefreshToken string `json:"refresh_token"` |
|
| 120 |
+} |
|
| 121 |
+ |
|
| 122 |
+func (f *credentialsFile) jwtConfig(scopes []string) *jwt.Config {
|
|
| 123 |
+ cfg := &jwt.Config{
|
|
| 124 |
+ Email: f.ClientEmail, |
|
| 125 |
+ PrivateKey: []byte(f.PrivateKey), |
|
| 126 |
+ PrivateKeyID: f.PrivateKeyID, |
|
| 127 |
+ Scopes: scopes, |
|
| 128 |
+ TokenURL: f.TokenURL, |
|
| 129 |
+ } |
|
| 130 |
+ if cfg.TokenURL == "" {
|
|
| 131 |
+ cfg.TokenURL = JWTTokenURL |
|
| 132 |
+ } |
|
| 133 |
+ return cfg |
|
| 134 |
+} |
|
| 135 |
+ |
|
| 136 |
+func (f *credentialsFile) tokenSource(ctx context.Context, scopes []string) (oauth2.TokenSource, error) {
|
|
| 137 |
+ switch f.Type {
|
|
| 138 |
+ case serviceAccountKey: |
|
| 139 |
+ cfg := f.jwtConfig(scopes) |
|
| 140 |
+ return cfg.TokenSource(ctx), nil |
|
| 141 |
+ case userCredentialsKey: |
|
| 142 |
+ cfg := &oauth2.Config{
|
|
| 143 |
+ ClientID: f.ClientID, |
|
| 144 |
+ ClientSecret: f.ClientSecret, |
|
| 145 |
+ Scopes: scopes, |
|
| 146 |
+ Endpoint: Endpoint, |
|
| 147 |
+ } |
|
| 148 |
+ tok := &oauth2.Token{RefreshToken: f.RefreshToken}
|
|
| 149 |
+ return cfg.TokenSource(ctx, tok), nil |
|
| 150 |
+ case "": |
|
| 151 |
+ return nil, errors.New("missing 'type' field in credentials")
|
|
| 152 |
+ default: |
|
| 153 |
+ return nil, fmt.Errorf("unknown credential type: %q", f.Type)
|
|
| 154 |
+ } |
|
| 100 | 155 |
} |
| 101 | 156 |
|
| 102 | 157 |
// ComputeTokenSource returns a token source that fetches access tokens |
| ... | ... |
@@ -36,6 +36,7 @@ func JWTAccessTokenSourceFromJSON(jsonKey []byte, audience string) (oauth2.Token |
| 36 | 36 |
email: cfg.Email, |
| 37 | 37 |
audience: audience, |
| 38 | 38 |
pk: pk, |
| 39 |
+ pkID: cfg.PrivateKeyID, |
|
| 39 | 40 |
} |
| 40 | 41 |
tok, err := ts.Token() |
| 41 | 42 |
if err != nil {
|
| ... | ... |
@@ -47,6 +48,7 @@ func JWTAccessTokenSourceFromJSON(jsonKey []byte, audience string) (oauth2.Token |
| 47 | 47 |
type jwtAccessTokenSource struct {
|
| 48 | 48 |
email, audience string |
| 49 | 49 |
pk *rsa.PrivateKey |
| 50 |
+ pkID string |
|
| 50 | 51 |
} |
| 51 | 52 |
|
| 52 | 53 |
func (ts *jwtAccessTokenSource) Token() (*oauth2.Token, error) {
|
| ... | ... |
@@ -62,6 +64,7 @@ func (ts *jwtAccessTokenSource) Token() (*oauth2.Token, error) {
|
| 62 | 62 |
hdr := &jws.Header{
|
| 63 | 63 |
Algorithm: "RS256", |
| 64 | 64 |
Typ: "JWT", |
| 65 |
+ KeyID: string(ts.pkID), |
|
| 65 | 66 |
} |
| 66 | 67 |
msg, err := jws.Encode(hdr, cs, ts.pk) |
| 67 | 68 |
if err != nil {
|
| ... | ... |
@@ -160,9 +160,13 @@ var sdkConfigPath = func() (string, error) {
|
| 160 | 160 |
} |
| 161 | 161 |
|
| 162 | 162 |
func guessUnixHomeDir() string {
|
| 163 |
- usr, err := user.Current() |
|
| 164 |
- if err == nil {
|
|
| 165 |
- return usr.HomeDir |
|
| 163 |
+ // Prefer $HOME over user.Current due to glibc bug: golang.org/issue/13470 |
|
| 164 |
+ if v := os.Getenv("HOME"); v != "" {
|
|
| 165 |
+ return v |
|
| 166 | 166 |
} |
| 167 |
- return os.Getenv("HOME")
|
|
| 167 |
+ // Else, fall back to user.Current: |
|
| 168 |
+ if u, err := user.Current(); err == nil {
|
|
| 169 |
+ return u.HomeDir |
|
| 170 |
+ } |
|
| 171 |
+ return "" |
|
| 168 | 172 |
} |
| ... | ... |
@@ -92,6 +92,7 @@ func (e *expirationTime) UnmarshalJSON(b []byte) error {
|
| 92 | 92 |
var brokenAuthHeaderProviders = []string{
|
| 93 | 93 |
"https://accounts.google.com/", |
| 94 | 94 |
"https://api.dropbox.com/", |
| 95 |
+ "https://api.dropboxapi.com/", |
|
| 95 | 96 |
"https://api.instagram.com/", |
| 96 | 97 |
"https://api.netatmo.net/", |
| 97 | 98 |
"https://api.odnoklassniki.ru/", |
| ... | ... |
@@ -105,6 +106,7 @@ var brokenAuthHeaderProviders = []string{
|
| 105 | 105 |
"https://oauth.sandbox.trainingpeaks.com/", |
| 106 | 106 |
"https://oauth.trainingpeaks.com/", |
| 107 | 107 |
"https://oauth.vk.com/", |
| 108 |
+ "https://openapi.baidu.com/", |
|
| 108 | 109 |
"https://slack.com/", |
| 109 | 110 |
"https://test-sandbox.auth.corp.google.com", |
| 110 | 111 |
"https://test.salesforce.com/", |
| ... | ... |
@@ -113,6 +115,10 @@ var brokenAuthHeaderProviders = []string{
|
| 113 | 113 |
"https://www.googleapis.com/", |
| 114 | 114 |
"https://www.linkedin.com/", |
| 115 | 115 |
"https://www.strava.com/oauth/", |
| 116 |
+ "https://www.wunderlist.com/oauth/", |
|
| 117 |
+ "https://api.patreon.com/", |
|
| 118 |
+ "https://sandbox.codeswholesale.com/oauth/token", |
|
| 119 |
+ "https://api.codeswholesale.com/oauth/token", |
|
| 116 | 120 |
} |
| 117 | 121 |
|
| 118 | 122 |
func RegisterBrokenAuthHeaderProvider(tokenURL string) {
|
| ... | ... |
@@ -142,23 +148,23 @@ func providerAuthHeaderWorks(tokenURL string) bool {
|
| 142 | 142 |
return true |
| 143 | 143 |
} |
| 144 | 144 |
|
| 145 |
-func RetrieveToken(ctx context.Context, ClientID, ClientSecret, TokenURL string, v url.Values) (*Token, error) {
|
|
| 145 |
+func RetrieveToken(ctx context.Context, clientID, clientSecret, tokenURL string, v url.Values) (*Token, error) {
|
|
| 146 | 146 |
hc, err := ContextClient(ctx) |
| 147 | 147 |
if err != nil {
|
| 148 | 148 |
return nil, err |
| 149 | 149 |
} |
| 150 |
- v.Set("client_id", ClientID)
|
|
| 151 |
- bustedAuth := !providerAuthHeaderWorks(TokenURL) |
|
| 152 |
- if bustedAuth && ClientSecret != "" {
|
|
| 153 |
- v.Set("client_secret", ClientSecret)
|
|
| 150 |
+ v.Set("client_id", clientID)
|
|
| 151 |
+ bustedAuth := !providerAuthHeaderWorks(tokenURL) |
|
| 152 |
+ if bustedAuth && clientSecret != "" {
|
|
| 153 |
+ v.Set("client_secret", clientSecret)
|
|
| 154 | 154 |
} |
| 155 |
- req, err := http.NewRequest("POST", TokenURL, strings.NewReader(v.Encode()))
|
|
| 155 |
+ req, err := http.NewRequest("POST", tokenURL, strings.NewReader(v.Encode()))
|
|
| 156 | 156 |
if err != nil {
|
| 157 | 157 |
return nil, err |
| 158 | 158 |
} |
| 159 | 159 |
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
| 160 | 160 |
if !bustedAuth {
|
| 161 |
- req.SetBasicAuth(ClientID, ClientSecret) |
|
| 161 |
+ req.SetBasicAuth(clientID, clientSecret) |
|
| 162 | 162 |
} |
| 163 | 163 |
r, err := hc.Do(req) |
| 164 | 164 |
if err != nil {
|
| ... | ... |
@@ -2,8 +2,16 @@ |
| 2 | 2 |
// Use of this source code is governed by a BSD-style |
| 3 | 3 |
// license that can be found in the LICENSE file. |
| 4 | 4 |
|
| 5 |
-// Package jws provides encoding and decoding utilities for |
|
| 6 |
-// signed JWS messages. |
|
| 5 |
+// Package jws provides a partial implementation |
|
| 6 |
+// of JSON Web Signature encoding and decoding. |
|
| 7 |
+// It exists to support the golang.org/x/oauth2 package. |
|
| 8 |
+// |
|
| 9 |
+// See RFC 7515. |
|
| 10 |
+// |
|
| 11 |
+// Deprecated: this package is not intended for public use and might be |
|
| 12 |
+// removed in the future. It exists for internal use only. |
|
| 13 |
+// Please switch to another JWS package or copy this package into your own |
|
| 14 |
+// source tree. |
|
| 7 | 15 |
package jws // import "golang.org/x/oauth2/jws" |
| 8 | 16 |
|
| 9 | 17 |
import ( |
| ... | ... |
@@ -64,7 +72,7 @@ func (c *ClaimSet) encode() (string, error) {
|
| 64 | 64 |
} |
| 65 | 65 |
|
| 66 | 66 |
if len(c.PrivateClaims) == 0 {
|
| 67 |
- return base64Encode(b), nil |
|
| 67 |
+ return base64.RawURLEncoding.EncodeToString(b), nil |
|
| 68 | 68 |
} |
| 69 | 69 |
|
| 70 | 70 |
// Marshal private claim set and then append it to b. |
| ... | ... |
@@ -82,7 +90,7 @@ func (c *ClaimSet) encode() (string, error) {
|
| 82 | 82 |
} |
| 83 | 83 |
b[len(b)-1] = ',' // Replace closing curly brace with a comma. |
| 84 | 84 |
b = append(b, prv[1:]...) // Append private claims. |
| 85 |
- return base64Encode(b), nil |
|
| 85 |
+ return base64.RawURLEncoding.EncodeToString(b), nil |
|
| 86 | 86 |
} |
| 87 | 87 |
|
| 88 | 88 |
// Header represents the header for the signed JWS payloads. |
| ... | ... |
@@ -92,6 +100,9 @@ type Header struct {
|
| 92 | 92 |
|
| 93 | 93 |
// Represents the token type. |
| 94 | 94 |
Typ string `json:"typ"` |
| 95 |
+ |
|
| 96 |
+ // The optional hint of which key is being used. |
|
| 97 |
+ KeyID string `json:"kid,omitempty"` |
|
| 95 | 98 |
} |
| 96 | 99 |
|
| 97 | 100 |
func (h *Header) encode() (string, error) {
|
| ... | ... |
@@ -99,7 +110,7 @@ func (h *Header) encode() (string, error) {
|
| 99 | 99 |
if err != nil {
|
| 100 | 100 |
return "", err |
| 101 | 101 |
} |
| 102 |
- return base64Encode(b), nil |
|
| 102 |
+ return base64.RawURLEncoding.EncodeToString(b), nil |
|
| 103 | 103 |
} |
| 104 | 104 |
|
| 105 | 105 |
// Decode decodes a claim set from a JWS payload. |
| ... | ... |
@@ -110,7 +121,7 @@ func Decode(payload string) (*ClaimSet, error) {
|
| 110 | 110 |
// TODO(jbd): Provide more context about the error. |
| 111 | 111 |
return nil, errors.New("jws: invalid token received")
|
| 112 | 112 |
} |
| 113 |
- decoded, err := base64Decode(s[1]) |
|
| 113 |
+ decoded, err := base64.RawURLEncoding.DecodeString(s[1]) |
|
| 114 | 114 |
if err != nil {
|
| 115 | 115 |
return nil, err |
| 116 | 116 |
} |
| ... | ... |
@@ -137,7 +148,7 @@ func EncodeWithSigner(header *Header, c *ClaimSet, sg Signer) (string, error) {
|
| 137 | 137 |
if err != nil {
|
| 138 | 138 |
return "", err |
| 139 | 139 |
} |
| 140 |
- return fmt.Sprintf("%s.%s", ss, base64Encode(sig)), nil
|
|
| 140 |
+ return fmt.Sprintf("%s.%s", ss, base64.RawURLEncoding.EncodeToString(sig)), nil
|
|
| 141 | 141 |
} |
| 142 | 142 |
|
| 143 | 143 |
// Encode encodes a signed JWS with provided header and claim set. |
| ... | ... |
@@ -145,28 +156,27 @@ func EncodeWithSigner(header *Header, c *ClaimSet, sg Signer) (string, error) {
|
| 145 | 145 |
func Encode(header *Header, c *ClaimSet, key *rsa.PrivateKey) (string, error) {
|
| 146 | 146 |
sg := func(data []byte) (sig []byte, err error) {
|
| 147 | 147 |
h := sha256.New() |
| 148 |
- h.Write([]byte(data)) |
|
| 148 |
+ h.Write(data) |
|
| 149 | 149 |
return rsa.SignPKCS1v15(rand.Reader, key, crypto.SHA256, h.Sum(nil)) |
| 150 | 150 |
} |
| 151 | 151 |
return EncodeWithSigner(header, c, sg) |
| 152 | 152 |
} |
| 153 | 153 |
|
| 154 |
-// base64Encode returns and Base64url encoded version of the input string with any |
|
| 155 |
-// trailing "=" stripped. |
|
| 156 |
-func base64Encode(b []byte) string {
|
|
| 157 |
- return strings.TrimRight(base64.URLEncoding.EncodeToString(b), "=") |
|
| 158 |
-} |
|
| 154 |
+// Verify tests whether the provided JWT token's signature was produced by the private key |
|
| 155 |
+// associated with the supplied public key. |
|
| 156 |
+func Verify(token string, key *rsa.PublicKey) error {
|
|
| 157 |
+ parts := strings.Split(token, ".") |
|
| 158 |
+ if len(parts) != 3 {
|
|
| 159 |
+ return errors.New("jws: invalid token received, token must have 3 parts")
|
|
| 160 |
+ } |
|
| 161 |
+ |
|
| 162 |
+ signedContent := parts[0] + "." + parts[1] |
|
| 163 |
+ signatureString, err := base64.RawURLEncoding.DecodeString(parts[2]) |
|
| 164 |
+ if err != nil {
|
|
| 165 |
+ return err |
|
| 166 |
+ } |
|
| 159 | 167 |
|
| 160 |
-// base64Decode decodes the Base64url encoded string |
|
| 161 |
-func base64Decode(s string) ([]byte, error) {
|
|
| 162 |
- // add back missing padding |
|
| 163 |
- switch len(s) % 4 {
|
|
| 164 |
- case 1: |
|
| 165 |
- s += "===" |
|
| 166 |
- case 2: |
|
| 167 |
- s += "==" |
|
| 168 |
- case 3: |
|
| 169 |
- s += "=" |
|
| 170 |
- } |
|
| 171 |
- return base64.URLEncoding.DecodeString(s) |
|
| 168 |
+ h := sha256.New() |
|
| 169 |
+ h.Write([]byte(signedContent)) |
|
| 170 |
+ return rsa.VerifyPKCS1v15(key, crypto.SHA256, h.Sum(nil), []byte(signatureString)) |
|
| 172 | 171 |
} |
| ... | ... |
@@ -21,6 +21,8 @@ import ( |
| 21 | 21 |
|
| 22 | 22 |
// NoContext is the default context you should supply if not using |
| 23 | 23 |
// your own context.Context (see https://golang.org/x/net/context). |
| 24 |
+// |
|
| 25 |
+// Deprecated: Use context.Background() or context.TODO() instead. |
|
| 24 | 26 |
var NoContext = context.TODO() |
| 25 | 27 |
|
| 26 | 28 |
// RegisterBrokenAuthHeaderProvider registers an OAuth2 server |
| ... | ... |
@@ -37,6 +39,8 @@ func RegisterBrokenAuthHeaderProvider(tokenURL string) {
|
| 37 | 37 |
|
| 38 | 38 |
// Config describes a typical 3-legged OAuth2 flow, with both the |
| 39 | 39 |
// client application information and the server's endpoint URLs. |
| 40 |
+// For the client credentials 2-legged OAuth2 flow, see the clientcredentials |
|
| 41 |
+// package (https://golang.org/x/oauth2/clientcredentials). |
|
| 40 | 42 |
type Config struct {
|
| 41 | 43 |
// ClientID is the application's ID. |
| 42 | 44 |
ClientID string |
| ... | ... |
@@ -295,7 +299,7 @@ func NewClient(ctx context.Context, src TokenSource) *http.Client {
|
| 295 | 295 |
if src == nil {
|
| 296 | 296 |
c, err := internal.ContextClient(ctx) |
| 297 | 297 |
if err != nil {
|
| 298 |
- return &http.Client{Transport: internal.ErrorTransport{err}}
|
|
| 298 |
+ return &http.Client{Transport: internal.ErrorTransport{Err: err}}
|
|
| 299 | 299 |
} |
| 300 | 300 |
return c |
| 301 | 301 |
} |