4
4
"bufio"
5
5
"bytes"
6
6
"context"
7
- "crypto/x509"
8
7
"encoding/base64"
9
8
"encoding/json"
10
9
"errors"
@@ -41,7 +40,6 @@ import (
41
40
_ "github.com/distribution/distribution/v3/registry/storage/driver/filesystem"
42
41
"github.com/docker/cli/cli/config/configfile"
43
42
"github.com/fatih/color"
44
- "github.com/go-git/go-git/v5/plumbing/transport"
45
43
v1 "github.com/google/go-containerregistry/pkg/v1"
46
44
"github.com/google/go-containerregistry/pkg/v1/remote"
47
45
"github.com/kballard/go-shellquote"
@@ -88,23 +86,6 @@ func Run(ctx context.Context, opts options.Options) error {
88
86
89
87
opts .Logger (log .LevelInfo , "%s - Build development environments from repositories in a container" , newColor (color .Bold ).Sprintf ("envbuilder" ))
90
88
91
- var caBundle []byte
92
- if opts .SSLCertBase64 != "" {
93
- certPool , err := x509 .SystemCertPool ()
94
- if err != nil {
95
- return xerrors .Errorf ("get global system cert pool: %w" , err )
96
- }
97
- data , err := base64 .StdEncoding .DecodeString (opts .SSLCertBase64 )
98
- if err != nil {
99
- return xerrors .Errorf ("base64 decode ssl cert: %w" , err )
100
- }
101
- ok := certPool .AppendCertsFromPEM (data )
102
- if ! ok {
103
- return xerrors .Errorf ("failed to append the ssl cert to the global pool: %s" , data )
104
- }
105
- caBundle = data
106
- }
107
-
108
89
if opts .DockerConfigBase64 != "" {
109
90
decoded , err := base64 .StdEncoding .DecodeString (opts .DockerConfigBase64 )
110
91
if err != nil {
@@ -125,51 +106,23 @@ func Run(ctx context.Context, opts options.Options) error {
125
106
}
126
107
}
127
108
109
+ buildTimeWorkspaceFolder := opts .WorkspaceFolder
128
110
var fallbackErr error
129
111
var cloned bool
130
112
if opts .GitURL != "" {
113
+ cloneOpts , err := git .CloneOptionsFromOptions (opts )
114
+ if err != nil {
115
+ return fmt .Errorf ("git clone options: %w" , err )
116
+ }
117
+
131
118
endStage := startStage ("📦 Cloning %s to %s..." ,
132
119
newColor (color .FgCyan ).Sprintf (opts .GitURL ),
133
- newColor (color .FgCyan ).Sprintf (opts . WorkspaceFolder ),
120
+ newColor (color .FgCyan ).Sprintf (cloneOpts . Path ),
134
121
)
135
122
136
- reader , writer := io .Pipe ()
137
- defer reader .Close ()
138
- defer writer .Close ()
139
- go func () {
140
- data := make ([]byte , 4096 )
141
- for {
142
- read , err := reader .Read (data )
143
- if err != nil {
144
- return
145
- }
146
- content := data [:read ]
147
- for _ , line := range strings .Split (string (content ), "\r " ) {
148
- if line == "" {
149
- continue
150
- }
151
- opts .Logger (log .LevelInfo , "#1: %s" , strings .TrimSpace (line ))
152
- }
153
- }
154
- }()
155
-
156
- cloneOpts := git.CloneRepoOptions {
157
- Path : opts .WorkspaceFolder ,
158
- Storage : opts .Filesystem ,
159
- Insecure : opts .Insecure ,
160
- Progress : writer ,
161
- SingleBranch : opts .GitCloneSingleBranch ,
162
- Depth : int (opts .GitCloneDepth ),
163
- CABundle : caBundle ,
164
- }
165
-
166
- cloneOpts .RepoAuth = git .SetupRepoAuth (& opts )
167
- if opts .GitHTTPProxyURL != "" {
168
- cloneOpts .ProxyOptions = transport.ProxyOptions {
169
- URL : opts .GitHTTPProxyURL ,
170
- }
171
- }
172
- cloneOpts .RepoURL = opts .GitURL
123
+ w := git .ProgressWriter (func (line string ) { opts .Logger (log .LevelInfo , "#%d: %s" , stageNumber , line ) })
124
+ defer w .Close ()
125
+ cloneOpts .Progress = w
173
126
174
127
cloned , fallbackErr = git .CloneRepo (ctx , cloneOpts )
175
128
if fallbackErr == nil {
@@ -182,6 +135,40 @@ func Run(ctx context.Context, opts options.Options) error {
182
135
opts .Logger (log .LevelError , "Failed to clone repository: %s" , fallbackErr .Error ())
183
136
opts .Logger (log .LevelError , "Falling back to the default image..." )
184
137
}
138
+
139
+ // The repo wasn't cloned (i.e. may not be up-to-date with remote), so
140
+ // we need to clone it to get the right build context. This is only
141
+ // necessary when the repo is in remote build mode. If the repo is in
142
+ // local build mode, the build context is already available on the host
143
+ // filesystem.
144
+ //
145
+ // Skipping clone here if we believe workspace repo matches remote repo
146
+ // is a performance optimization.
147
+ if fallbackErr == nil && ! cloned && opts .RepoBuildMode == options .RepoBuildModeRemote {
148
+ cloneOpts , err := git .CloneOptionsFromOptions (opts )
149
+ if err != nil {
150
+ return fmt .Errorf ("git clone options: %w" , err )
151
+ }
152
+ cloneOpts .Path = constants .MagicRemoteRepoDir
153
+
154
+ endStage := startStage ("📦 Remote build mode enabled, cloning %s to %s for build context..." ,
155
+ newColor (color .FgCyan ).Sprintf (opts .GitURL ),
156
+ newColor (color .FgCyan ).Sprintf (cloneOpts .Path ),
157
+ )
158
+
159
+ w := git .ProgressWriter (func (line string ) { opts .Logger (log .LevelInfo , "#%d: %s" , stageNumber , line ) })
160
+ defer w .Close ()
161
+ cloneOpts .Progress = w
162
+
163
+ fallbackErr = git .ShallowCloneRepo (ctx , cloneOpts )
164
+ if fallbackErr == nil {
165
+ endStage ("📦 Cloned repository!" )
166
+ buildTimeWorkspaceFolder = cloneOpts .Path
167
+ } else {
168
+ opts .Logger (log .LevelError , "Failed to clone repository for remote repo mode: %s" , err .Error ())
169
+ opts .Logger (log .LevelError , "Falling back to the default image..." )
170
+ }
171
+ }
185
172
}
186
173
187
174
defaultBuildParams := func () (* devcontainer.Compiled , error ) {
@@ -222,7 +209,7 @@ func Run(ctx context.Context, opts options.Options) error {
222
209
// devcontainer is a standard, so it's reasonable to be the default.
223
210
var devcontainerDir string
224
211
var err error
225
- devcontainerPath , devcontainerDir , err = findDevcontainerJSON (opts )
212
+ devcontainerPath , devcontainerDir , err = findDevcontainerJSON (buildTimeWorkspaceFolder , opts )
226
213
if err != nil {
227
214
opts .Logger (log .LevelError , "Failed to locate devcontainer.json: %s" , err .Error ())
228
215
opts .Logger (log .LevelError , "Falling back to the default image..." )
@@ -614,10 +601,10 @@ ENTRYPOINT [%q]`, exePath, exePath, exePath)
614
601
if err != nil {
615
602
return fmt .Errorf ("unmarshal metadata: %w" , err )
616
603
}
617
- opts .Logger (log .LevelInfo , "#3 : 👀 Found devcontainer.json label metadata in image..." )
604
+ opts .Logger (log .LevelInfo , "#%d : 👀 Found devcontainer.json label metadata in image..." , stageNumber )
618
605
for _ , container := range devContainer {
619
606
if container .RemoteUser != "" {
620
- opts .Logger (log .LevelInfo , "#3 : 🧑 Updating the user to %q!" , container .RemoteUser )
607
+ opts .Logger (log .LevelInfo , "#%d : 🧑 Updating the user to %q!" , stageNumber , container .RemoteUser )
621
608
622
609
configFile .Config .User = container .RemoteUser
623
610
}
@@ -724,7 +711,7 @@ ENTRYPOINT [%q]`, exePath, exePath, exePath)
724
711
username = buildParams .User
725
712
}
726
713
if username == "" {
727
- opts .Logger (log .LevelWarn , "#3 : no user specified, using root" )
714
+ opts .Logger (log .LevelWarn , "#%d : no user specified, using root" , stageNumber )
728
715
}
729
716
730
717
userInfo , err := getUser (username )
@@ -1030,7 +1017,11 @@ func newColor(value ...color.Attribute) *color.Color {
1030
1017
return c
1031
1018
}
1032
1019
1033
- func findDevcontainerJSON (options options.Options ) (string , string , error ) {
1020
+ func findDevcontainerJSON (workspaceFolder string , options options.Options ) (string , string , error ) {
1021
+ if workspaceFolder == "" {
1022
+ workspaceFolder = options .WorkspaceFolder
1023
+ }
1024
+
1034
1025
// 0. Check if custom devcontainer directory or path is provided.
1035
1026
if options .DevcontainerDir != "" || options .DevcontainerJSONPath != "" {
1036
1027
devcontainerDir := options .DevcontainerDir
@@ -1040,7 +1031,7 @@ func findDevcontainerJSON(options options.Options) (string, string, error) {
1040
1031
1041
1032
// If `devcontainerDir` is not an absolute path, assume it is relative to the workspace folder.
1042
1033
if ! filepath .IsAbs (devcontainerDir ) {
1043
- devcontainerDir = filepath .Join (options . WorkspaceFolder , devcontainerDir )
1034
+ devcontainerDir = filepath .Join (workspaceFolder , devcontainerDir )
1044
1035
}
1045
1036
1046
1037
// An absolute location always takes a precedence.
@@ -1059,20 +1050,20 @@ func findDevcontainerJSON(options options.Options) (string, string, error) {
1059
1050
return devcontainerPath , devcontainerDir , nil
1060
1051
}
1061
1052
1062
- // 1. Check `options.WorkspaceFolder `/.devcontainer/devcontainer.json.
1063
- location := filepath .Join (options . WorkspaceFolder , ".devcontainer" , "devcontainer.json" )
1053
+ // 1. Check `workspaceFolder `/.devcontainer/devcontainer.json.
1054
+ location := filepath .Join (workspaceFolder , ".devcontainer" , "devcontainer.json" )
1064
1055
if _ , err := options .Filesystem .Stat (location ); err == nil {
1065
1056
return location , filepath .Dir (location ), nil
1066
1057
}
1067
1058
1068
- // 2. Check `options.WorkspaceFolder `/devcontainer.json.
1069
- location = filepath .Join (options . WorkspaceFolder , "devcontainer.json" )
1059
+ // 2. Check `workspaceFolder `/devcontainer.json.
1060
+ location = filepath .Join (workspaceFolder , "devcontainer.json" )
1070
1061
if _ , err := options .Filesystem .Stat (location ); err == nil {
1071
1062
return location , filepath .Dir (location ), nil
1072
1063
}
1073
1064
1074
- // 3. Check every folder: `options.WorkspaceFolder `/.devcontainer/<folder>/devcontainer.json.
1075
- devcontainerDir := filepath .Join (options . WorkspaceFolder , ".devcontainer" )
1065
+ // 3. Check every folder: `workspaceFolder `/.devcontainer/<folder>/devcontainer.json.
1066
+ devcontainerDir := filepath .Join (workspaceFolder , ".devcontainer" )
1076
1067
1077
1068
fileInfos , err := options .Filesystem .ReadDir (devcontainerDir )
1078
1069
if err != nil {
0 commit comments