Skip to content

Commit 9715cc1

Browse files
authored
Merge pull request #107 from EmmEff/impl-build-context
Build context files support
2 parents 8d59d12 + bd1d0b7 commit 9715cc1

File tree

9 files changed

+462
-132
lines changed

9 files changed

+462
-132
lines changed

client/build_context.go

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,14 +34,18 @@ func writeArchive(w io.Writer, fsys fs.FS, paths []string) error {
3434

3535
for _, path := range paths {
3636
if err := ar.WriteFiles(path); err != nil {
37-
return fmt.Errorf("failed to write to archive: %w", err)
37+
return err
3838
}
3939
}
4040

4141
return nil
4242
}
4343

44+
var errContextAlreadyPresent = errors.New("build context already present")
45+
4446
// getBuildContextUploadLocation obtains an upload location for a build context.
47+
//
48+
// If errContextAlreadyPresent is returned, (re)upload of build context is not required.
4549
func (c *Client) getBuildContextUploadLocation(ctx context.Context, size int64, digest string) (*url.URL, error) {
4650
ref := &url.URL{
4751
Path: "v1/build-context",
@@ -76,6 +80,11 @@ func (c *Client) getBuildContextUploadLocation(ctx context.Context, size int64,
7680
return nil, fmt.Errorf("%w", errorFromResponse(res))
7781
}
7882

83+
if res.Header.Get("Location") == "" {
84+
// "Location" header is not present; build context does not need to be uploaded
85+
return nil, errContextAlreadyPresent
86+
}
87+
7988
return url.Parse(res.Header.Get("Location"))
8089
}
8190

@@ -127,6 +136,9 @@ func (c *Client) uploadBuildContext(ctx context.Context, rw io.ReadWriteSeeker,
127136
// Get the build context upload location.
128137
loc, err := c.getBuildContextUploadLocation(ctx, size, digest)
129138
if err != nil {
139+
if errors.Is(err, errContextAlreadyPresent) {
140+
return digest, nil
141+
}
130142
return "", fmt.Errorf("failed to get build context upload location: %w", err)
131143
}
132144

cmd/scs-build/build.go

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -113,19 +113,5 @@ func executeBuildCmd(cmd *cobra.Command, args []string) error {
113113
cancel()
114114
}()
115115

116-
archsToBuild := v.GetStringSlice(keyArch)
117-
118-
if len(archsToBuild) > 1 {
119-
fmt.Printf("Performing build for following architectures: %v\n", strings.Join(archsToBuild, " "))
120-
}
121-
122-
for _, arch := range v.GetStringSlice(keyArch) {
123-
fmt.Printf("Building for %v...\n", arch)
124-
125-
if err := app.Run(ctx, arch); err != nil {
126-
return fmt.Errorf("failed to build %v: %w", arch, err)
127-
}
128-
}
129-
130-
return nil
116+
return app.Run(ctx, v.GetStringSlice(keyArch))
131117
}

internal/app/buildclient/build.go

Lines changed: 44 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,19 @@ import (
1111
"errors"
1212
"fmt"
1313
"os"
14+
"strings"
1415

1516
build "github.com/sylabs/scs-build-client/client"
1617
)
1718

18-
// buildArtifact sends a build request for the specified def and arch, optionally publishing it to
19+
// buildArtifact sends a build request for the specified arch, optionally publishing it to
1920
// libraryRef. Output is streamed to standard output. If the build cannot be submitted, or does not
2021
// succeed, an error is returned.
21-
func (app *App) buildArtifact(ctx context.Context, def []byte, arch string, libraryRef string) (*build.BuildInfo, error) {
22-
bi, err := app.buildClient.Submit(ctx, bytes.NewReader(def),
22+
func (app *App) buildArtifact(ctx context.Context, arch, libraryRef, digest string, rawDef []byte) (*build.BuildInfo, error) {
23+
bi, err := app.buildClient.Submit(ctx, bytes.NewReader(rawDef),
2324
build.OptBuildLibraryRef(libraryRef),
2425
build.OptBuildArchitecture(arch),
26+
build.OptBuildContext(digest),
2527
)
2628
if err != nil {
2729
return nil, fmt.Errorf("error submitting remote build: %w", err)
@@ -36,8 +38,46 @@ func (app *App) buildArtifact(ctx context.Context, def []byte, arch string, libr
3638
// The returned info doesn't indicate an exit code, but a zero-sized image tells us something
3739
// went wrong.
3840
if bi.ImageSize() <= 0 {
39-
return bi, errors.New("failed to build image")
41+
return nil, errors.New("failed to build image")
42+
}
43+
44+
if digest != "" {
45+
_ = app.buildClient.DeleteBuildContext(ctx, digest)
4046
}
4147

4248
return bi, nil
4349
}
50+
51+
// definitionFromURI attempts to parse a URI from raw. If raw contains a URI, a definition file
52+
// representing it is returned, and ok is set to true. Otherwise, ok is set to false.
53+
func definitionFromURI(raw string) (def []byte, ok bool) {
54+
var u []string
55+
if strings.Contains(raw, "://") {
56+
u = strings.SplitN(raw, "://", 2)
57+
} else if strings.Contains(raw, ":") {
58+
u = strings.SplitN(raw, ":", 2)
59+
} else {
60+
return nil, false
61+
}
62+
63+
var b bytes.Buffer
64+
65+
fmt.Fprintln(&b, "bootstrap:", u[0])
66+
fmt.Fprintln(&b, "from:", u[1])
67+
68+
return b.Bytes(), true
69+
}
70+
71+
func (app *App) getBuildDef() ([]byte, error) {
72+
// Build spec could be a URI, or the path to a definition file.
73+
if b, ok := definitionFromURI(app.buildSpec); ok {
74+
return b, nil
75+
}
76+
77+
// Attempt to read app.buildSpec as a file
78+
b, err := os.ReadFile(app.buildSpec)
79+
if err != nil {
80+
return nil, fmt.Errorf("error reading def file %v: %w", app.buildSpec, err)
81+
}
82+
return b, nil
83+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
// Copyright (c) 2022, Sylabs Inc. All rights reserved.
2+
// This software is licensed under a 3-clause BSD license. Please consult the
3+
// LICENSE.md file distributed with the sources of this project regarding your
4+
// rights to use or distribute this software.
5+
6+
package buildclient
7+
8+
import "testing"
9+
10+
func Test_definitionFromURI(t *testing.T) {
11+
tests := []struct {
12+
name string
13+
raw string
14+
wantDef string
15+
wantOK bool
16+
}{
17+
{
18+
name: "NonURI",
19+
raw: "file.txt",
20+
wantOK: false,
21+
},
22+
{
23+
name: "Docker",
24+
raw: "docker://alpine",
25+
wantDef: "bootstrap: docker\nfrom: alpine\n",
26+
wantOK: true,
27+
},
28+
{
29+
name: "Library",
30+
raw: "library://alpine",
31+
wantDef: "bootstrap: library\nfrom: alpine\n",
32+
wantOK: true,
33+
},
34+
}
35+
for _, tt := range tests {
36+
tt := tt
37+
38+
t.Run(tt.name, func(t *testing.T) {
39+
def, ok := definitionFromURI(tt.raw)
40+
41+
if got, want := string(def), tt.wantDef; got != want {
42+
t.Errorf("got def %#v, want %#v", got, want)
43+
}
44+
45+
if got, want := ok, tt.wantOK; got != want {
46+
t.Errorf("got OK %v, want %v", got, want)
47+
}
48+
})
49+
}
50+
}

0 commit comments

Comments
 (0)