@@ -48,8 +48,15 @@ type Builder struct {
4848const (
4949 staleBuildDirAge = 1 * time .Hour
5050 staleTempArtifactAge = 24 * time .Hour
51+ dockerfilePathKey = "TECHULUS_DOCKERFILE_PATH"
5152)
5253
54+ type dockerfileConfig struct {
55+ directory string
56+ filename string
57+ found bool
58+ }
59+
5360var managedTempArtifactPattern = regexp .MustCompile (`^(backup|restore)-[0-9a-fA-F-]{36}\.tar\.gz$|^restore-extract-[0-9a-fA-F-]{36}$` )
5461
5562func NewBuilder (dataDir string , logSender LogSender ) * Builder {
@@ -226,23 +233,9 @@ func (b *Builder) buildAndPush(ctx context.Context, config *Config, buildDir str
226233 b .sendLog (config , fmt .Sprintf ("Using root directory: %s" , config .RootDir ))
227234 }
228235
229- dockerfileRelPath := "Dockerfile"
230- hasDockerfile := false
231- forcedDockerfile := false
232- if customPath := config .Secrets ["TECHULUS_DOCKERFILE_PATH" ]; customPath != "" {
233- if ! filepath .IsLocal (customPath ) {
234- return fmt .Errorf ("TECHULUS_DOCKERFILE_PATH must be a relative path within the build context, got %q" , customPath )
235- }
236- resolved := filepath .Join (contextDir , customPath )
237- if info , err := os .Stat (resolved ); err != nil || info .IsDir () {
238- b .sendLog (config , fmt .Sprintf ("TECHULUS_DOCKERFILE_PATH is set but %s was not found in the repository" , customPath ))
239- return fmt .Errorf ("TECHULUS_DOCKERFILE_PATH %q not found in build context" , customPath )
240- }
241- dockerfileRelPath = filepath .Clean (customPath )
242- hasDockerfile = true
243- forcedDockerfile = true
244- } else if _ , err := os .Stat (filepath .Join (contextDir , "Dockerfile" )); err == nil {
245- hasDockerfile = true
236+ dockerfile , err := resolveDockerfile (contextDir , config .Secrets )
237+ if err != nil {
238+ return err
246239 }
247240
248241 buildkitAddr := os .Getenv ("BUILDKIT_HOST" )
@@ -253,6 +246,9 @@ func (b *Builder) buildAndPush(ctx context.Context, config *Config, buildDir str
253246 var secretArgs []string
254247 var secretEnv []string
255248 for key , value := range config .Secrets {
249+ if key == dockerfilePathKey {
250+ continue
251+ }
256252 secretArgs = append (secretArgs , "--secret" , fmt .Sprintf ("id=%s,env=%s" , key , key ))
257253 secretEnv = append (secretEnv , fmt .Sprintf ("%s=%s" , key , value ))
258254 }
@@ -266,10 +262,10 @@ func (b *Builder) buildAndPush(ctx context.Context, config *Config, buildDir str
266262 archImageUri := config .ImageURI + "-" + arch
267263 archOutputFlag := fmt .Sprintf ("type=image,name=%s,push=true,registry.insecure=true" , archImageUri )
268264
269- if hasDockerfile {
270- log .Printf ("[build:%s] building with Dockerfile %s via buildctl for %s" , truncateStr (config .BuildID , 8 ), dockerfileRelPath , platform )
271- if forcedDockerfile {
272- b .sendLog (config , fmt .Sprintf ("Using Dockerfile %s (from TECHULUS_DOCKERFILE_PATH) " , dockerfileRelPath ))
265+ if dockerfile . found {
266+ log .Printf ("[build:%s] building with Dockerfile via buildctl for %s" , truncateStr (config .BuildID , 8 ), platform )
267+ if configuredPath := strings . TrimSpace ( config . Secrets [ dockerfilePathKey ]); configuredPath != "" {
268+ b .sendLog (config , fmt .Sprintf ("Using Dockerfile: %s" , configuredPath ))
273269 } else {
274270 b .sendLog (config , "Using existing Dockerfile" )
275271 }
@@ -280,8 +276,8 @@ func (b *Builder) buildAndPush(ctx context.Context, config *Config, buildDir str
280276 "build" ,
281277 "--frontend" , "dockerfile.v0" ,
282278 "--local" , "context=." ,
283- "--local" , fmt .Sprintf ("dockerfile=%s" , filepath . Dir ( dockerfileRelPath ) ),
284- "--opt" , fmt .Sprintf ("filename=%s" , filepath . Base ( dockerfileRelPath ) ),
279+ "--local" , fmt .Sprintf ("dockerfile=%s" , dockerfile . directory ),
280+ "--opt" , fmt .Sprintf ("filename=%s" , dockerfile . filename ),
285281 "--opt" , fmt .Sprintf ("platform=%s" , platform ),
286282 "--output" , archOutputFlag ,
287283 }
@@ -350,6 +346,54 @@ func (b *Builder) buildAndPush(ctx context.Context, config *Config, buildDir str
350346 return nil
351347}
352348
349+ func resolveDockerfile (contextDir string , secrets map [string ]string ) (dockerfileConfig , error ) {
350+ configuredPath , configured := secrets [dockerfilePathKey ]
351+ configuredPath = strings .TrimSpace (configuredPath )
352+ if configured && configuredPath == "" {
353+ return dockerfileConfig {}, fmt .Errorf ("%s cannot be empty" , dockerfilePathKey )
354+ }
355+
356+ if ! configured {
357+ if _ , err := os .Stat (filepath .Join (contextDir , "Dockerfile" )); err == nil {
358+ return dockerfileConfig {directory : "." , filename : "Dockerfile" , found : true }, nil
359+ } else if ! os .IsNotExist (err ) {
360+ return dockerfileConfig {}, fmt .Errorf ("failed to inspect Dockerfile: %w" , err )
361+ }
362+ return dockerfileConfig {}, nil
363+ }
364+
365+ cleanedPath := filepath .Clean (configuredPath )
366+ if filepath .IsAbs (cleanedPath ) || cleanedPath == ".." || strings .HasPrefix (cleanedPath , ".." + string (filepath .Separator )) {
367+ return dockerfileConfig {}, fmt .Errorf ("%s must be relative to the service root directory" , dockerfilePathKey )
368+ }
369+
370+ info , err := os .Stat (filepath .Join (contextDir , cleanedPath ))
371+ if err != nil {
372+ return dockerfileConfig {}, fmt .Errorf ("dockerfile %s does not exist: %w" , configuredPath , err )
373+ }
374+ if info .IsDir () {
375+ return dockerfileConfig {}, fmt .Errorf ("dockerfile path %s is a directory" , configuredPath )
376+ }
377+ resolvedContextDir , err := filepath .EvalSymlinks (contextDir )
378+ if err != nil {
379+ return dockerfileConfig {}, fmt .Errorf ("failed to resolve service root directory: %w" , err )
380+ }
381+ resolvedPath , err := filepath .EvalSymlinks (filepath .Join (contextDir , cleanedPath ))
382+ if err != nil {
383+ return dockerfileConfig {}, fmt .Errorf ("failed to resolve Dockerfile %s: %w" , configuredPath , err )
384+ }
385+ relativePath , err := filepath .Rel (resolvedContextDir , resolvedPath )
386+ if err != nil || relativePath == ".." || strings .HasPrefix (relativePath , ".." + string (filepath .Separator )) {
387+ return dockerfileConfig {}, fmt .Errorf ("%s must resolve inside the service root directory" , dockerfilePathKey )
388+ }
389+
390+ return dockerfileConfig {
391+ directory : filepath .Dir (cleanedPath ),
392+ filename : filepath .Base (cleanedPath ),
393+ found : true ,
394+ }, nil
395+ }
396+
353397func (b * Builder ) runCommand (cmd * exec.Cmd , config * Config ) (string , error ) {
354398 output , err := cmd .CombinedOutput ()
355399 outputStr := string (output )
0 commit comments