forked from kubernetes/kops
-
Notifications
You must be signed in to change notification settings - Fork 0
/
context.go
150 lines (130 loc) · 4 KB
/
context.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
package vfs
import (
"fmt"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/s3"
"github.com/golang/glog"
"io/ioutil"
"net/http"
"net/url"
"os"
"strings"
)
// VFSContext is a 'context' for VFS, that is normally a singleton
// but allows us to configure S3 credentials, for example
type VFSContext struct {
}
var Context VFSContext
// ReadLocation reads a file from a vfs URL
// It supports additional schemes which don't (yet) have full VFS implementations:
// metadata: reads from instance metadata on GCE/AWS
// http / https: reads from HTTP
func (c *VFSContext) ReadFile(location string) ([]byte, error) {
if strings.Contains(location, "://") {
// Handle our special case schemas
u, err := url.Parse(location)
if err != nil {
return nil, fmt.Errorf("error parsing location %q - not a valid URI", location)
}
switch u.Scheme {
case "metadata":
switch u.Host {
case "gce":
httpURL := "http://169.254.169.254/computeMetadata/v1/instance/attributes/" + u.Path
httpHeaders := make(map[string]string)
httpHeaders["Metadata-Flavor"] = "Google"
return c.readHttpLocation(httpURL, httpHeaders)
case "aws":
httpURL := "http://169.254.169.254/latest/" + u.Path
return c.readHttpLocation(httpURL, nil)
default:
return nil, fmt.Errorf("unknown metadata type: %q in %q", u.Host, location)
}
case "http", "https":
return c.readHttpLocation(location, nil)
}
}
p, err := c.BuildVfsPath(location)
if err != nil {
return nil, err
}
return p.ReadFile()
}
func (c *VFSContext) BuildVfsPath(p string) (Path, error) {
if !strings.Contains(p, "://") {
return NewFSPath(p), nil
}
if strings.HasPrefix(p, "s3://") {
return c.buildS3Path(p)
}
return nil, fmt.Errorf("unknown / unhandled path type: %q", p)
}
// readHttpLocation reads an http (or https) url.
// It returns the contents, or an error on any non-200 response. On a 404, it will return os.ErrNotExist
func (c *VFSContext) readHttpLocation(httpURL string, httpHeaders map[string]string) ([]byte, error) {
req, err := http.NewRequest("GET", httpURL, nil)
if err != nil {
return nil, err
}
for k, v := range httpHeaders {
req.Header.Add(k, v)
}
response, err := http.DefaultClient.Do(req)
if response != nil {
defer response.Body.Close()
}
if err != nil {
return nil, fmt.Errorf("error fetching %q: %v", httpURL, err)
}
body, err := ioutil.ReadAll(response.Body)
if err != nil {
return nil, fmt.Errorf("error reading response for %q: %v", httpURL, err)
}
if response.StatusCode == 404 {
return nil, os.ErrNotExist
}
if response.StatusCode != 200 {
return nil, fmt.Errorf("unexpected response code %q for %q: %v", response.Status, httpURL, string(body))
}
return body, nil
}
func (c *VFSContext) buildS3Path(p string) (*S3Path, error) {
u, err := url.Parse(p)
if err != nil {
return nil, fmt.Errorf("invalid s3 path: %q", err)
}
bucket := strings.TrimSuffix(u.Host, "/")
var region string
{
// Probe to find correct region for bucket
// TODO: Caching (both of the client & of the bucket location)
config := aws.NewConfig().WithRegion("us-east-1")
session := session.New()
s3Client := s3.New(session, config)
request := &s3.GetBucketLocationInput{}
request.Bucket = aws.String(bucket)
response, err := s3Client.GetBucketLocation(request)
if err != nil {
// TODO: Auto-create bucket?
return nil, fmt.Errorf("error getting location for S3 bucket %q: %v", bucket, err)
}
if response.LocationConstraint == nil {
// US Classic does not return a region
region = "us-east-1"
} else {
region = *response.LocationConstraint
// Another special case: "EU" can mean eu-west-1
if region == "EU" {
region = "eu-west-1"
}
}
glog.V(2).Infof("Found bucket %q in region %q", bucket, region)
}
// TODO: Caching (of the S3 client)
config := aws.NewConfig().WithRegion(region)
session := session.New()
s3Client := s3.New(session, config)
s3path := NewS3Path(s3Client, bucket, u.Path)
return s3path, nil
}