Skip to content

Commit 18b64c0

Browse files
author
Keerthan Mala
committed
Add support for azure
1 parent 8ee6f76 commit 18b64c0

6 files changed

Lines changed: 353 additions & 6 deletions

File tree

boot.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"github.com/deis/builder/pkg/gitreceive"
1414
"github.com/deis/builder/pkg/healthsrv"
1515
"github.com/deis/builder/pkg/sshd"
16+
_ "github.com/deis/builder/pkg/storage/driver/azure"
1617
"github.com/deis/builder/pkg/storage/driver/factory"
1718
"github.com/deis/builder/pkg/sys"
1819
pkglog "github.com/deis/pkg/log"

glide.lock

Lines changed: 6 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

glide.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,3 +59,7 @@ import:
5959
version: 986912c9070a060e59e0f28ac7c87f89f72b9388
6060
subpackages:
6161
- credentials/oauth
62+
- package: github.com/Azure/azure-sdk-for-go
63+
version: 95361a2573b1fa92a00c5fc2707a80308483c6f9
64+
subpackages:
65+
- storage

pkg/conf/config.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import (
99

1010
const (
1111
builderKeyLocation = "/var/run/secrets/api/auth/builder-key"
12-
storageCredLocation = "/var/run/secrets/deis/builder/creds/"
12+
storageCredLocation = "/var/run/secrets/deis/objectstore/creds/"
1313
)
1414

1515
type Parameters map[string]string

pkg/storage/driver/azure/azure.go

Lines changed: 318 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,318 @@
1+
package azure
2+
3+
import (
4+
"bufio"
5+
"fmt"
6+
"io/ioutil"
7+
"net/http"
8+
"strings"
9+
"time"
10+
11+
storagedriver "github.com/deis/builder/pkg/storage/driver"
12+
"github.com/deis/builder/pkg/storage/driver/base"
13+
"github.com/deis/builder/pkg/storage/driver/factory"
14+
15+
azure "github.com/Azure/azure-sdk-for-go/storage"
16+
)
17+
18+
const driverName = "azure"
19+
20+
const (
21+
paramAccountName = "accountname"
22+
paramAccountKey = "accountkey"
23+
paramContainer = "builder-container"
24+
paramRealm = "realm"
25+
maxChunkSize = 4 * 1024 * 1024
26+
)
27+
28+
type driver struct {
29+
client azure.BlobStorageClient
30+
container string
31+
}
32+
33+
type baseEmbed struct{ base.Base }
34+
35+
// Driver is a storagedriver.StorageDriver implementation backed by
36+
// Microsoft Azure Blob Storage Service.
37+
type Driver struct{ baseEmbed }
38+
39+
func init() {
40+
factory.Register(driverName, &azureDriverFactory{})
41+
}
42+
43+
type azureDriverFactory struct{}
44+
45+
func (factory *azureDriverFactory) Create(parameters map[string]string) (storagedriver.StorageDriver, error) {
46+
return FromParameters(parameters)
47+
}
48+
49+
// FromParameters constructs a new Driver with a given parameters map.
50+
func FromParameters(parameters map[string]string) (*Driver, error) {
51+
accountName, ok := parameters[paramAccountName]
52+
if !ok || fmt.Sprint(accountName) == "" {
53+
return nil, fmt.Errorf("No %s parameter provided", paramAccountName)
54+
}
55+
56+
accountKey, ok := parameters[paramAccountKey]
57+
if !ok || fmt.Sprint(accountKey) == "" {
58+
return nil, fmt.Errorf("No %s parameter provided", paramAccountKey)
59+
}
60+
61+
container, ok := parameters[paramContainer]
62+
if !ok || fmt.Sprint(container) == "" {
63+
return nil, fmt.Errorf("No %s parameter provided", paramContainer)
64+
}
65+
66+
realm, ok := parameters[paramRealm]
67+
if !ok || fmt.Sprint(realm) == "" {
68+
realm = azure.DefaultBaseURL
69+
}
70+
71+
return New(fmt.Sprint(accountName), fmt.Sprint(accountKey), fmt.Sprint(container), fmt.Sprint(realm))
72+
}
73+
74+
// New constructs a new Driver with the given Azure Storage Account credentials
75+
func New(accountName, accountKey, container, realm string) (*Driver, error) {
76+
api, err := azure.NewClient(accountName, accountKey, realm, azure.DefaultAPIVersion, true)
77+
if err != nil {
78+
return nil, err
79+
}
80+
81+
blobClient := api.GetBlobService()
82+
83+
// Create registry container
84+
if _, err = blobClient.CreateContainerIfNotExists(container, azure.ContainerAccessTypePrivate); err != nil {
85+
return nil, err
86+
}
87+
88+
d := &driver{
89+
client: blobClient,
90+
container: container}
91+
return &Driver{baseEmbed: baseEmbed{Base: base.Base{StorageDriver: d}}}, nil
92+
}
93+
94+
// Implement the storagedriver.StorageDriver interface.
95+
func (d *driver) Name() string {
96+
return driverName
97+
}
98+
99+
func (d *driver) CheckConnectionStatus() (bool, error) {
100+
_, err := d.client.ListContainers(azure.ListContainersParameters{})
101+
if err != nil {
102+
return false, err
103+
}
104+
return true, err
105+
}
106+
107+
// GetContent retrieves the content stored at "path" as a []byte.
108+
func (d *driver) GetContent(path string) ([]byte, error) {
109+
blob, err := d.client.GetBlob(d.container, path)
110+
if err != nil {
111+
if is404(err) {
112+
return nil, storagedriver.PathNotFoundError{Path: path}
113+
}
114+
return nil, err
115+
}
116+
117+
return ioutil.ReadAll(blob)
118+
}
119+
120+
// PutContent stores the []byte content at a location designated by "path".
121+
func (d *driver) PutContent(path string, contents []byte) error {
122+
if _, err := d.client.DeleteBlobIfExists(d.container, path); err != nil {
123+
return err
124+
}
125+
writer, err := d.Writer(path, false)
126+
if err != nil {
127+
return err
128+
}
129+
defer writer.Close()
130+
_, err = writer.Write(contents)
131+
if err != nil {
132+
return err
133+
}
134+
return writer.Commit()
135+
}
136+
137+
// Writer returns a FileWriter which will store the content written to it
138+
// at the location designated by "path" after the call to Commit.
139+
func (d *driver) Writer(path string, append bool) (storagedriver.FileWriter, error) {
140+
blobExists, err := d.client.BlobExists(d.container, path)
141+
if err != nil {
142+
return nil, err
143+
}
144+
var size int64
145+
if blobExists {
146+
if append {
147+
blobProperties, err := d.client.GetBlobProperties(d.container, path)
148+
if err != nil {
149+
return nil, err
150+
}
151+
size = blobProperties.ContentLength
152+
} else {
153+
err := d.client.DeleteBlob(d.container, path)
154+
if err != nil {
155+
return nil, err
156+
}
157+
}
158+
} else {
159+
if append {
160+
return nil, storagedriver.PathNotFoundError{Path: path}
161+
}
162+
err := d.client.PutAppendBlob(d.container, path, nil)
163+
if err != nil {
164+
return nil, err
165+
}
166+
}
167+
168+
return d.newWriter(path, size), nil
169+
}
170+
171+
// Stat retrieves the FileInfo for the given path, including the current size
172+
// in bytes and the creation time.
173+
func (d *driver) Stat(path string) (storagedriver.FileInfo, error) {
174+
// Check if the path is a blob
175+
if ok, err := d.client.BlobExists(d.container, path); err != nil {
176+
return nil, err
177+
} else if ok {
178+
blob, err := d.client.GetBlobProperties(d.container, path)
179+
if err != nil {
180+
return nil, err
181+
}
182+
183+
mtim, err := time.Parse(http.TimeFormat, blob.LastModified)
184+
if err != nil {
185+
return nil, err
186+
}
187+
188+
return storagedriver.FileInfoInternal{FileInfoFields: storagedriver.FileInfoFields{
189+
Path: path,
190+
Size: int64(blob.ContentLength),
191+
ModTime: mtim,
192+
IsDir: false,
193+
}}, nil
194+
}
195+
196+
// Check if path is a virtual container
197+
virtContainerPath := path
198+
if !strings.HasSuffix(virtContainerPath, "/") {
199+
virtContainerPath += "/"
200+
}
201+
blobs, err := d.client.ListBlobs(d.container, azure.ListBlobsParameters{
202+
Prefix: virtContainerPath,
203+
MaxResults: 1,
204+
})
205+
if err != nil {
206+
return nil, err
207+
}
208+
if len(blobs.Blobs) > 0 {
209+
// path is a virtual container
210+
return storagedriver.FileInfoInternal{FileInfoFields: storagedriver.FileInfoFields{
211+
Path: path,
212+
IsDir: true,
213+
}}, nil
214+
}
215+
216+
// path is not a blob or virtual container
217+
return nil, storagedriver.PathNotFoundError{Path: path}
218+
}
219+
220+
func is404(err error) bool {
221+
statusCodeErr, ok := err.(azure.AzureStorageServiceError)
222+
return ok && statusCodeErr.StatusCode == http.StatusNotFound
223+
}
224+
225+
type writer struct {
226+
driver *driver
227+
path string
228+
size int64
229+
bw *bufio.Writer
230+
closed bool
231+
committed bool
232+
cancelled bool
233+
}
234+
235+
func (d *driver) newWriter(path string, size int64) storagedriver.FileWriter {
236+
return &writer{
237+
driver: d,
238+
path: path,
239+
size: size,
240+
bw: bufio.NewWriterSize(&blockWriter{
241+
client: d.client,
242+
container: d.container,
243+
path: path,
244+
}, maxChunkSize),
245+
}
246+
}
247+
248+
func (w *writer) Write(p []byte) (int, error) {
249+
if w.closed {
250+
return 0, fmt.Errorf("already closed")
251+
} else if w.committed {
252+
return 0, fmt.Errorf("already committed")
253+
} else if w.cancelled {
254+
return 0, fmt.Errorf("already cancelled")
255+
}
256+
257+
n, err := w.bw.Write(p)
258+
w.size += int64(n)
259+
return n, err
260+
}
261+
262+
func (w *writer) Size() int64 {
263+
return w.size
264+
}
265+
266+
func (w *writer) Close() error {
267+
if w.closed {
268+
return fmt.Errorf("already closed")
269+
}
270+
w.closed = true
271+
return w.bw.Flush()
272+
}
273+
274+
func (w *writer) Cancel() error {
275+
if w.closed {
276+
return fmt.Errorf("already closed")
277+
} else if w.committed {
278+
return fmt.Errorf("already committed")
279+
}
280+
w.cancelled = true
281+
return w.driver.client.DeleteBlob(w.driver.container, w.path)
282+
}
283+
284+
func (w *writer) Commit() error {
285+
if w.closed {
286+
return fmt.Errorf("already closed")
287+
} else if w.committed {
288+
return fmt.Errorf("already committed")
289+
} else if w.cancelled {
290+
return fmt.Errorf("already cancelled")
291+
}
292+
w.committed = true
293+
return w.bw.Flush()
294+
}
295+
296+
type blockWriter struct {
297+
client azure.BlobStorageClient
298+
container string
299+
path string
300+
}
301+
302+
func (bw *blockWriter) Write(p []byte) (int, error) {
303+
n := 0
304+
for offset := 0; offset < len(p); offset += maxChunkSize {
305+
chunkSize := maxChunkSize
306+
if offset+chunkSize > len(p) {
307+
chunkSize = len(p) - offset
308+
}
309+
err := bw.client.AppendBlock(bw.container, bw.path, p[offset:offset+chunkSize])
310+
if err != nil {
311+
return n, err
312+
}
313+
314+
n += chunkSize
315+
}
316+
317+
return n, nil
318+
}

0 commit comments

Comments
 (0)