1313import logging
1414import os
1515import pwd
16- import subprocess
1716import shutil
1817import tempfile
1918import time
3130from . import __version__
3231from .buildpacks import (
3332 PythonBuildPack , DockerBuildPack , LegacyBinderDockerBuildPack ,
34- CondaBuildPack , JuliaBuildPack , BaseImage ,
35- RBuildPack , NixBuildPack
33+ CondaBuildPack , JuliaBuildPack , RBuildPack , NixBuildPack
3634)
3735from . import contentproviders
3836from .utils import (
@@ -364,7 +362,21 @@ def fetch(self, url, ref, checkout_path):
364362 spec , checkout_path , yield_output = self .json_logs ):
365363 self .log .info (log_line , extra = dict (phase = 'fetching' ))
366364
367-
365+ if not self .output_image_spec :
366+ self .output_image_spec = (
367+ 'r2d' + escapism .escape (self .repo , escape_char = '-' ).lower ()
368+ )
369+ # if we are building from a subdirectory include that in the
370+ # image name so we can tell builds from different sub-directories
371+ # apart.
372+ if self .subdir :
373+ self .output_image_spec += (
374+ escapism .escape (self .subdir , escape_char = '-' ).lower ()
375+ )
376+ if picked_content_provider .content_id is not None :
377+ self .output_image_spec += picked_content_provider .content_id
378+ else :
379+ self .output_image_spec += str (int (time .time ()))
368380
369381 def json_excepthook (self , etype , evalue , traceback ):
370382 """Called on an uncaught exception when using json logging
@@ -399,15 +411,6 @@ def initialize(self):
399411 fmt = '%(message)s'
400412 )
401413
402- if self .output_image_spec == "" :
403- # Attempt to set a sane default!
404- # HACK: Provide something more descriptive?
405- self .output_image_spec = (
406- 'r2d' +
407- escapism .escape (self .repo , escape_char = '-' ).lower () +
408- str (int (time .time ()))
409- )
410-
411414 if self .dry_run and (self .run or self .push ):
412415 raise ValueError ("Cannot push or run image if we are not building it" )
413416
@@ -546,15 +549,29 @@ def _get_free_port(self):
546549 s .close ()
547550 return port
548551
552+ def find_image (self ):
553+ # if this is a dry run it is Ok for dockerd to be unreachable so we
554+ # always return False for dry runs.
555+ if self .dry_run :
556+ return False
557+ # check if we already have an image for this content
558+ client = docker .APIClient (version = 'auto' , ** kwargs_from_env ())
559+ for image in client .images ():
560+ if image ['RepoTags' ] is not None :
561+ for tag in image ['RepoTags' ]:
562+ if tag == self .output_image_spec + ":latest" :
563+ return True
564+ return False
565+
549566 def build (self ):
550567 """
551568 Build docker image
552569 """
553570 # Check if r2d can connect to docker daemon
554571 if not self .dry_run :
555572 try :
556- api_client = docker .APIClient (version = 'auto' ,
557- ** kwargs_from_env ())
573+ docker_client = docker .APIClient (version = 'auto' ,
574+ ** kwargs_from_env ())
558575 except DockerException as e :
559576 self .log .exception (e )
560577 raise
@@ -574,6 +591,14 @@ def build(self):
574591 try :
575592 self .fetch (self .repo , self .ref , checkout_path )
576593
594+ if self .find_image ():
595+ self .log .info ("Reusing existing image ({}), not "
596+ "building." .format (self .output_image_spec ))
597+ # no need to build, so skip to the end by `return`ing here
598+ # this will still execute the finally clause and let's us
599+ # avoid having to indent the build code by an extra level
600+ return
601+
577602 if self .subdir :
578603 checkout_path = os .path .join (checkout_path , self .subdir )
579604 if not os .path .isdir (checkout_path ):
@@ -610,8 +635,11 @@ def build(self):
610635 self .log .info ('Using %s builder\n ' , bp .__class__ .__name__ ,
611636 extra = dict (phase = 'building' ))
612637
613- for l in picked_buildpack .build (api_client , self .output_image_spec ,
614- self .build_memory_limit , build_args , self .cache_from ):
638+ for l in picked_buildpack .build (docker_client ,
639+ self .output_image_spec ,
640+ self .build_memory_limit ,
641+ build_args ,
642+ self .cache_from ):
615643 if 'stream' in l :
616644 self .log .info (l ['stream' ],
617645 extra = dict (phase = 'building' ))
@@ -624,8 +652,9 @@ def build(self):
624652 else :
625653 self .log .info (json .dumps (l ),
626654 extra = dict (phase = 'building' ))
655+
627656 finally :
628- # Cheanup checkout if necessary
657+ # Cleanup checkout if necessary
629658 if self .cleanup_checkout :
630659 shutil .rmtree (checkout_path , ignore_errors = True )
631660
0 commit comments