Skip to content

Commit eac1ec7

Browse files
author
Keerthan Mala
committed
fix(procfile): get the procfile from the slug if not present
1 parent 358933a commit eac1ec7

7 files changed

Lines changed: 236 additions & 12 deletions

File tree

Makefile

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,6 @@ test:
4949

5050
docker-build:
5151
docker build --rm -t ${IMAGE} rootfs
52-
perl -pi -e "s|image: [a-z0-9.:]+\/deis\/bp${SHORT_NAME}:[0-9a-z-.]+|image: ${IMAGE}|g" ${RC}
5352

5453
# Push to a registry that Kubernetes can access.
5554
docker-push:

pkg/gitreceive/build.go

Lines changed: 32 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -99,17 +99,6 @@ func build(conf *Config, s3Client *storage.Client, kubeClient *client.Client, fs
9999
bType := getBuildTypeForDir(tmpDir)
100100
usingDockerfile := bType == buildTypeDockerfile
101101

102-
procType := pkg.ProcessType{}
103-
if bType == buildTypeProcfile {
104-
rawProcFile, err := ioutil.ReadFile(fmt.Sprintf("%s/Procfile", tmpDir))
105-
if err != nil {
106-
return fmt.Errorf("reading %s/Procfile", tmpDir)
107-
}
108-
if err := yaml.Unmarshal(rawProcFile, &procType); err != nil {
109-
return fmt.Errorf("procfile %s/ProcFile is malformed (%s)", tmpDir, err)
110-
}
111-
}
112-
113102
if err := storage.CreateBucket(s3Client, conf.Bucket); err != nil {
114103
log.Warn("create bucket error: %+v", err)
115104
}
@@ -237,6 +226,15 @@ func build(conf *Config, s3Client *storage.Client, kubeClient *client.Client, fs
237226
return fmt.Errorf("Timed out waiting for object in storage, aborting build (%s)", err)
238227
}
239228
}
229+
230+
procType := pkg.ProcessType{}
231+
if bType == buildTypeProcfile {
232+
getter := &storage.RealObjectGetter{Client: s3Client.Client}
233+
if procType, err = getProcFile(getter, tmpDir, conf.Bucket, slugBuilderInfo.AbsoluteProcfileKey()); err != nil {
234+
return err
235+
}
236+
}
237+
240238
log.Info("Build complete.")
241239
log.Info("Launching app.")
242240
log.Info("Launching...")
@@ -274,3 +272,26 @@ func prettyPrintJSON(data interface{}) (string, error) {
274272
}
275273
return string(formatted.Bytes()), nil
276274
}
275+
276+
func getProcFile(getter storage.ObjectGetter, dirName, bucketName, procfileKey string) (pkg.ProcessType, error) {
277+
procType := pkg.ProcessType{}
278+
if _, err := os.Stat(fmt.Sprintf("%s/Procfile", dirName)); err == nil {
279+
rawProcFile, err := ioutil.ReadFile(fmt.Sprintf("%s/Procfile", dirName))
280+
if err != nil {
281+
return nil, fmt.Errorf("error in reading %s/Procfile (%s)", dirName, err)
282+
}
283+
if err := yaml.Unmarshal(rawProcFile, &procType); err != nil {
284+
return nil, fmt.Errorf("procfile %s/ProcFile is malformed (%s)", dirName, err)
285+
}
286+
return procType, nil
287+
}
288+
log.Debug("Procfile not present. Getting it from the buildpack")
289+
rawProcFile, err := storage.DownloadObject(getter, bucketName, procfileKey)
290+
if err != nil {
291+
return nil, fmt.Errorf("error in reading %s/%s (%s)", bucketName, procfileKey, err)
292+
}
293+
if err := yaml.Unmarshal(rawProcFile, &procType); err != nil {
294+
return nil, fmt.Errorf("procfile %s/%s is malformed (%s)", bucketName, procfileKey, err)
295+
}
296+
return procType, nil
297+
}

pkg/gitreceive/build_test.go

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
package gitreceive
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
"io/ioutil"
7+
"os"
8+
"testing"
9+
10+
"gopkg.in/yaml.v2"
11+
12+
"github.com/arschles/assert"
13+
"github.com/deis/builder/pkg"
14+
"github.com/deis/builder/pkg/gitreceive/storage"
15+
)
16+
17+
const (
18+
bucketName = "mybucket"
19+
objKey = "myobj"
20+
)
21+
22+
func TestGetProcFileFromRepoSuccess(t *testing.T) {
23+
tmpDir, err := ioutil.TempDir("", "tmpdir")
24+
if err != nil {
25+
t.Fatalf("error creating temp directory (%s)", err)
26+
}
27+
data := []byte("web: example-go")
28+
if err := ioutil.WriteFile(tmpDir+"/Procfile", data, 0644); err != nil {
29+
t.Fatalf("error creating %s/Procfile (%s)", tmpDir, err)
30+
}
31+
defer func() {
32+
if err := os.RemoveAll(tmpDir); err != nil {
33+
t.Fatalf("failed to remove Procfile from %s (%s)", tmpDir, err)
34+
}
35+
}()
36+
getter := &storage.RealObjectGetter{}
37+
procType, err := getProcFile(getter, tmpDir, bucketName, objKey)
38+
actualData := pkg.ProcessType{}
39+
yaml.Unmarshal(data, &actualData)
40+
assert.NoErr(t, err)
41+
assert.Equal(t, procType, actualData, "data")
42+
}
43+
44+
func TestGetProcFileFromRepoFailure(t *testing.T) {
45+
tmpDir, err := ioutil.TempDir("", "tmpdir")
46+
if err != nil {
47+
t.Fatalf("error creating temp directory (%s)", err)
48+
}
49+
data := []byte("web= example-go")
50+
if err := ioutil.WriteFile(tmpDir+"/Procfile", data, 0644); err != nil {
51+
t.Fatalf("error creating %s/Procfile (%s)", tmpDir, err)
52+
}
53+
defer func() {
54+
if err := os.RemoveAll(tmpDir); err != nil {
55+
t.Fatalf("failed to remove Procfile from %s (%s)", tmpDir, err)
56+
}
57+
}()
58+
getter := &storage.RealObjectGetter{}
59+
_, err = getProcFile(getter, tmpDir, bucketName, objKey)
60+
61+
assert.True(t, err != nil, "no error received when there should have been")
62+
}
63+
64+
func TestGetProcFileFromServerSuccess(t *testing.T) {
65+
data := []byte("web: example-go")
66+
obj := &storage.FakeObject{Data: string(data)}
67+
getter := &storage.FakeObjectGetter{
68+
Fn: func(string, string) (storage.Object, error) {
69+
return obj, nil
70+
},
71+
}
72+
73+
procType, err := getProcFile(getter, "", bucketName, objKey)
74+
actualData := pkg.ProcessType{}
75+
yaml.Unmarshal(data, &actualData)
76+
assert.NoErr(t, err)
77+
assert.Equal(t, procType, actualData, "data")
78+
}
79+
80+
func TestGetProcFileFromServerFailure(t *testing.T) {
81+
data := []byte("web: example-go")
82+
obj := &storage.FakeObject{Data: string(data)}
83+
expectedErr := errors.New("test error")
84+
getter := &storage.FakeObjectGetter{
85+
Fn: func(string, string) (storage.Object, error) {
86+
return obj, expectedErr
87+
},
88+
}
89+
90+
_, err := getProcFile(getter, "", bucketName, objKey)
91+
assert.Err(t, err, fmt.Errorf("error in reading %s/%s (%s)", bucketName, objKey, expectedErr))
92+
assert.True(t, err != nil, "no error received when there should have been")
93+
}

pkg/gitreceive/storage/interfaces.go

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,3 +79,84 @@ func (f *FakeObjectPutter) PutObject(bucketName, objectKey string, reader io.Rea
7979
})
8080
return f.Fn(bucketName, objectKey, reader, contentType)
8181
}
82+
83+
// The s3.Object already satisfies this interface. As long as it has some or all of the functions as s3.Object, it satisfies it. We're making it have all of the functions in this case. You can create a fake object that also has all of these functions.
84+
type Object interface {
85+
// This is called an interface composition - it automatically gives your interface the function in io.Reader (https://godoc.org/io#Reader) and the function in io.Closer (https://godoc.org/io#Closer)
86+
io.ReadCloser
87+
// This is also an interface composition. It gives your interface the function in io.Seeker (https://godoc.org/io#Seeker)
88+
io.Seeker
89+
// This is also interface composition. It gives your interface the function in io.ReaderAt (https://godoc.org/io#ReaderAt)
90+
io.ReaderAt
91+
// This function is the last one we have to add to make this interface have all the same functions as s3.Object
92+
Stat() (s3.ObjectInfo, error)
93+
}
94+
95+
// The minio client doesn't already satisfy this interface, because the GetObject func (https://godoc.org/github.com/minio/minio-go#Client.GetObject) doesn't return an Object. Instead, it returns a *s3.Object. We'll create an adapter below
96+
type ObjectGetter interface {
97+
// GetObject is *almost* the same function as the GetObject func in the minio client, but it returns Object instead of *s3.Object
98+
GetObject(string, string) (Object, error)
99+
}
100+
101+
// RealObjectGetter is an adapter to make the *s3.Client GetObject function compatible with the ObjectGetter interface
102+
type RealObjectGetter struct {
103+
Client *s3.Client
104+
}
105+
106+
func (r *RealObjectGetter) GetObject(bucket, objKey string) (Object, error) {
107+
obj, err := r.Client.GetObject(bucket, objKey)
108+
if err != nil {
109+
// you can return nil for an interface
110+
return nil, err
111+
}
112+
// obj is a *s3.Object, but as we said above, it automatically implements our Object interface without any other work
113+
return obj, nil
114+
}
115+
116+
type FakeGetObjectCall struct {
117+
BucketName string
118+
ObjectKey string
119+
}
120+
121+
// FakeObjectGetter is a mock function that can be swapped in for an ObjectGetter, so you can unit test your code
122+
type FakeObjectGetter struct {
123+
Fn func(string, string) (Object, error)
124+
Calls []FakeGetObjectCall
125+
}
126+
127+
// GetObject is the interface definition
128+
func (f *FakeObjectGetter) GetObject(bucketName, objectKey string) (Object, error) {
129+
f.Calls = append(f.Calls, FakeGetObjectCall{BucketName: bucketName, ObjectKey: objectKey})
130+
return f.Fn(bucketName, objectKey)
131+
}
132+
133+
// FakeObject is a mock function that can be swapped in for an *s3.Object, so you can unit test your code
134+
type FakeObject struct {
135+
Data string
136+
}
137+
138+
// Read is the interface definition
139+
func (f *FakeObject) Read(b []byte) (n int, err error) {
140+
copy(b, f.Data[:])
141+
return len(f.Data), io.EOF
142+
}
143+
144+
// Close is the interface definition
145+
func (f *FakeObject) Close() (err error) {
146+
return nil
147+
}
148+
149+
// ReadAt is the interface definition
150+
func (f *FakeObject) ReadAt(b []byte, offset int64) (n int, err error) {
151+
return 0, nil
152+
}
153+
154+
// Seek is the interface definition
155+
func (f *FakeObject) Seek(offset int64, whence int) (n int64, err error) {
156+
return 0, nil
157+
}
158+
159+
// Stat is the interface definition
160+
func (f *FakeObject) Stat() (s3.ObjectInfo, error) {
161+
return s3.ObjectInfo{}, nil
162+
}

pkg/gitreceive/storage/object.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"errors"
55
"fmt"
66
"io"
7+
"io/ioutil"
78
"time"
89

910
s3 "github.com/minio/minio-go"
@@ -72,3 +73,16 @@ func WaitForObject(statter ObjectStatter, bucketName, objKey string, tick, timeo
7273
}
7374
}
7475
}
76+
77+
func DownloadObject(getter ObjectGetter, bucketName, objKey string) ([]byte, error) {
78+
reader, err := getter.GetObject(bucketName, objKey)
79+
if err != nil {
80+
return nil, err
81+
}
82+
defer reader.Close()
83+
data, err := ioutil.ReadAll(reader)
84+
if err != nil {
85+
return nil, err
86+
}
87+
return data, err
88+
}

pkg/gitreceive/storage/object_test.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,3 +104,18 @@ func TestWaitForObjectExists(t *testing.T) {
104104
// it should make 1 call immediately, then immediateley succeed
105105
assert.Equal(t, len(statter.Calls), 1, "number of calls to the statter")
106106
}
107+
108+
func TestDownloadObjectSuccess(t *testing.T) {
109+
obj := &FakeObject{Data: "web: example-go"}
110+
getter := &FakeObjectGetter{
111+
Fn: func(string, string) (Object, error) {
112+
return obj, nil
113+
},
114+
}
115+
data, err := DownloadObject(getter, bucketName, objKey)
116+
assert.NoErr(t, err)
117+
assert.Equal(t, string(data), "web: example-go", "data")
118+
assert.Equal(t, len(getter.Calls), 1, "number of calls to GetObject")
119+
assert.Equal(t, getter.Calls[0].BucketName, bucketName, "the bucket name")
120+
assert.Equal(t, getter.Calls[0].ObjectKey, objKey, "the object key")
121+
}

pkg/gitreceive/storage/slug_builder_info.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ func (s SlugBuilderInfo) PushURL() string { return s.pushURL }
3737
func (s SlugBuilderInfo) TarKey() string { return s.tarKey }
3838
func (s SlugBuilderInfo) TarURL() string { return s.tarURL }
3939
func (s SlugBuilderInfo) AbsoluteSlugObjectKey() string { return s.PushKey() + "/" + slugTGZName }
40+
func (s SlugBuilderInfo) AbsoluteProcfileKey() string { return s.PushKey() + "/Procfile" }
4041
func (s SlugBuilderInfo) AbsoluteSlugURL() string {
4142
return s.PushURL() + "/" + slugTGZName
4243
}

0 commit comments

Comments
 (0)