class TestDoubleSourceChecksumStorage(_TestChecksumStorage):
    """
    Tests for `DoubleSourceChecksumStorage`.
    """
    def setUp(self):
        self.local_storage = MemoryChecksumStorage()
        self.remote_storage = MemoryChecksumStorage()
        super().setUp()

    def create_storage(self) -> ChecksumStorage:
        return DoubleSourceChecksumStorage(self.local_storage, self.remote_storage)

    def test_get_when_only_in_local(self):
        self.local_storage.set_checksum(EXAMPLE_1_CONFIGURATION_ID, EXAMPLE_1_CHECKSUM)
        self.assertEqual({EXAMPLE_1_CONFIGURATION_ID: EXAMPLE_1_CHECKSUM}, self.storage.get_all_checksums())

    def test_get_when_only_in_external(self):
        self.remote_storage.set_checksum(EXAMPLE_1_CONFIGURATION_ID, EXAMPLE_1_CHECKSUM)
        self.assertEqual({EXAMPLE_1_CONFIGURATION_ID: EXAMPLE_1_CHECKSUM}, self.storage.get_all_checksums())

    def test_get_when_in_local_and_external(self):
        self.local_storage.set_checksum(EXAMPLE_1_CONFIGURATION_ID, EXAMPLE_1_CHECKSUM)
        self.remote_storage.set_checksum(EXAMPLE_1_CONFIGURATION_ID, EXAMPLE_2_CHECKSUM)
        self.assertEqual(EXAMPLE_1_CHECKSUM, self.storage.get_checksum(EXAMPLE_1_CONFIGURATION_ID))
        self.assertEqual({EXAMPLE_1_CONFIGURATION_ID: EXAMPLE_1_CHECKSUM}, self.storage.get_all_checksums())

    def test_set_only_affects_local(self):
        self.storage.set_checksum(EXAMPLE_1_CONFIGURATION_ID, EXAMPLE_1_CHECKSUM)
        self.assertEqual({EXAMPLE_1_CONFIGURATION_ID: EXAMPLE_1_CHECKSUM}, self.local_storage.get_all_checksums())
        self.assertEqual({}, self.remote_storage.get_all_checksums())
Exemple #2
0
    def build_all(self) -> Dict[BuildConfigurationType, BuildResultType]:
        """
        Builds all managed images and their managed dependencies.
        :return: mapping between built configurations and their associated build result
        """
        logger.info("Building all...")

        checksum_storage = DoubleSourceChecksumStorage(MemoryChecksumStorage(),
                                                       self.checksum_retriever)

        all_build_results: Dict[BuildConfigurationType:BuildResultType] = {}
        left_to_build: Set[BuildConfigurationType] = set(
            self.managed_build_configurations)

        while len(left_to_build) != 0:
            build_configuration = left_to_build.pop()
            assert build_configuration not in all_build_results.keys()

            # Build configuration
            build_results = self.build(build_configuration,
                                       left_to_build,
                                       _checksum_storage=checksum_storage)
            all_build_results.update(build_results)

            # Update known configuration checksums
            checksums = {
                x.identifier: self.checksum_calculator.calculate_checksum(x)
                for x in build_results.keys()
            }
            checksum_storage.set_all_checksums(checksums)

            left_to_build = left_to_build - set(build_results.keys())

        logger.info(f"Built: {all_build_results}")
        return all_build_results
Exemple #3
0
 def decode(self, obj_as_json, **kwargs):
     parsed_json = super().decode(obj_as_json)
     if parsed_json[CHECKSUM_STORAGE_TYPE_PROPERTY] == CHECKSUM_STORAGE_TYPE_VALUE_MAP[MemoryChecksumStorage]:
         return MemoryChecksumStorage()
     return {
         CHECKSUM_STORAGE_TYPE_VALUE_MAP[DiskChecksumStorage]: DiskChecksumStorageJSONDecoder(),
         CHECKSUM_STORAGE_TYPE_VALUE_MAP[ConsulChecksumStorage]: ConsulChecksumStorageJSONDecoder()
     }[parsed_json[CHECKSUM_STORAGE_TYPE_PROPERTY]].decode(obj_as_json)
Exemple #4
0
    def setUp(self):
        super().setUp()
        self._captured_main = CaptureWrapBuilder(
            capture_stdout=True,
            capture_exceptions=lambda e: isinstance(
                e, SystemExit) and e.code == 0).build(main)

        self.build_configurations = BuildConfigurationContainer[
            DockerBuildConfiguration](self.create_docker_setup()[1]
                                      for _ in range(3))
        self.run_configuration = Configuration(self.build_configurations)

        self.pre_built_configuration = list(self.build_configurations)[0]
        builder = DockerBuilder(self.build_configurations)
        build_result = builder.build(self.pre_built_configuration)
        assert len(build_result) == 1
        self.run_configuration.checksum_storage = MemoryChecksumStorage({
            self.pre_built_configuration.identifier:
            builder.checksum_calculator.calculate_checksum(
                self.pre_built_configuration)
        })
Exemple #5
0
 def __init__(self,
              docker_build_configurations: BuildConfigurationContainer[
                  DockerBuildConfiguration] = None,
              docker_registries: Iterable[DockerRegistry] = (),
              checksum_storage: ChecksumStorage = None):
     self.docker_build_configurations = docker_build_configurations if docker_build_configurations is not None \
         else BuildConfigurationContainer[DockerBuildConfiguration]()
     self.docker_registries = list(docker_registries)
     self.checksum_storage = checksum_storage if checksum_storage is not None else MemoryChecksumStorage(
     )
Exemple #6
0
 def setUp(self):
     super().setUp()
     self.checksum_storage = MemoryChecksumStorage()
     self.configuration = self.create_built_configuration()
     self.uploader = self.create_uploader()
 def setUp(self):
     self.local_storage = MemoryChecksumStorage()
     self.remote_storage = MemoryChecksumStorage()
     super().setUp()
 def create_storage(self) -> ChecksumStorage:
     return MemoryChecksumStorage()
 def setUp(self):
     super().setUp()
     self.checksum_storage = MemoryChecksumStorage()
     self.docker_builder = DockerBuilder(checksum_retriever=self.checksum_storage)
class TestDockerBuilder(TestWithDockerBuildConfiguration):
    """
    Tests for `DockerBuilder`.
    """
    def setUp(self):
        super().setUp()
        self.checksum_storage = MemoryChecksumStorage()
        self.docker_builder = DockerBuilder(checksum_retriever=self.checksum_storage)

    def test_build_when_dockerfile_is_invalid(self):
        _, configuration = self.create_docker_setup(commands=["invalid"])
        self.docker_builder.managed_build_configurations.add(configuration)
        self.assertRaises(InvalidDockerfileBuildError, self.docker_builder.build, configuration)

    def test_build_when_build_fails(self):
        _, configuration = self.create_docker_setup(commands=[f"{RUN_DOCKER_COMMAND} exit 1"])
        self.docker_builder.managed_build_configurations.add(configuration)
        self.assertRaises(BuildStepError, self.docker_builder.build, configuration)

    def test_build_when_from_image_is_not_managed(self):
        _, configuration = self.create_docker_setup()
        assert configuration.identifier not in \
               itertools.chain(*(image.tags for image in self.docker_client.images.list()))
        self.assertRaises(UnmanagedBuildError, self.docker_builder.build, configuration)

    def test_build_when_from_image_is_managed(self):
        configurations = self.create_dependent_docker_build_configurations(4)
        self.docker_builder.managed_build_configurations.add_all(configurations)
        self.docker_builder.managed_build_configurations.add(self.create_docker_setup()[1])

        build_results = self.docker_builder.build(configurations[-1])
        self.assertCountEqual(
            {configuration: configuration.identifier for configuration in configurations}, build_results)

    def test_build_when_circular_dependency(self):
        configurations = [
            self.create_docker_setup(image_name=EXAMPLE_IMAGE_NAME_1, from_image_name=EXAMPLE_IMAGE_NAME_2)[1],
            self.create_docker_setup(image_name=EXAMPLE_IMAGE_NAME_2, from_image_name=EXAMPLE_IMAGE_NAME_1)[1]]
        self.docker_builder.managed_build_configurations.add_all(configurations)
        self.assertRaises(CircularDependencyBuildError, self.docker_builder.build_all)

    def test_build_when_up_to_date(self):
        _, configuration = self.create_docker_setup()
        self.docker_builder.managed_build_configurations.add(configuration)
        self.checksum_storage.set_checksum(
            configuration.identifier, self.docker_builder.checksum_calculator.calculate_checksum(configuration))

        build_results = self.docker_builder.build(configuration)
        self.assertEqual(0, len(build_results))

    def test_build_all_when_none_managed(self):
        built = self.docker_builder.build_all()
        self.assertEqual(0, len(built))

    def test_build_all_when_one_fails(self):
        configurations = self.create_dependent_docker_build_configurations(4)
        configurations += [self.create_docker_setup(commands=[f"{RUN_DOCKER_COMMAND} exit 1"])[1]]
        self.docker_builder.managed_build_configurations.add_all(configurations)
        self.assertRaises(BuildStepError, self.docker_builder.build_all)

    def test_build_all_when_managed(self):
        configurations = self.create_dependent_docker_build_configurations(4)
        self.docker_builder.managed_build_configurations.add_all(configurations)

        build_results = self.docker_builder.build_all()
        self.assertCountEqual(
            {configuration: configuration.identifier for configuration in configurations}, build_results)

    def test_build_all_when_some_up_to_date(self):
        import logging
        logging.getLogger().setLevel(logging.DEBUG)
        configurations = self.create_dependent_docker_build_configurations(4)
        self.docker_builder.managed_build_configurations.add_all(configurations)

        build_results = self.docker_builder.build(configurations[1])
        assert len(build_results) == 2

        for configuration in build_results.keys():
            checksum = self.docker_builder.checksum_calculator.calculate_checksum(configuration)
            self.checksum_storage.set_checksum(configuration.identifier, checksum)

        build_results = self.docker_builder.build_all()
        self.assertCountEqual(configurations[2:], build_results)

    def test_build_when_from_image_updated(self):
        configurations = self.create_dependent_docker_build_configurations(2)
        self.docker_builder.managed_build_configurations.add_all(configurations)

        build_results = self.docker_builder.build_all()
        assert len(build_results) == 2

        for configuration in build_results.keys():
            checksum = self.docker_builder.checksum_calculator.calculate_checksum(configuration)
            self.checksum_storage.set_checksum(configuration.identifier, checksum)

        parent_image = configurations[0]

        with open(parent_image.dockerfile_location, "a") as file:
            file.write(f"{RUN_DOCKER_COMMAND} echo 1")
        parent_image.reload()

        build_results = self.docker_builder.build_all()
        self.assertEqual(2, len(build_results))
Exemple #11
0
 def __init__(
     self,
     managed_build_configurations: Iterable[BuildConfigurationType] = None,
     checksum_retriever: ChecksumRetriever = None,
     checksum_calculator_factory: Callable[[],
                                           ChecksumCalculatorType] = None):
     """
     Constructor.
     :param managed_build_configurations: build configurations that are managed by this builder
     :param checksum_retriever: checksum retriever
     :param checksum_calculator_factory: callable that returns a checksum calculator
     """
     super().__init__(managed_build_configurations)
     self.checksum_retriever = checksum_retriever if checksum_retriever is not None else MemoryChecksumStorage(
     )
     self.checksum_calculator = checksum_calculator_factory()
Exemple #12
0
    def build(self, build_configuration: BuildConfigurationType,
              allowed_builds: Iterable[BuildConfigurationType]=None, *, _building: Set[BuildConfigurationType]=None,
              _checksum_storage: ChecksumStorage=None) \
            -> Dict[BuildConfigurationType, BuildResultType]:
        """
        Builds the given build configuration, including any (allowed and managed) dependencies.
        :param build_configuration: the configuration to build
        :param allowed_builds: dependencies that can get built in order to build the configuration. If set
        to `None`, all dependencies will be built (default)
        :param _building: internal use only (tracks build stack to detect circular dependencies)
        :param _checksum_storage: internal use only (has checksums of newly built configurations)
        :return: mapping between built configurations and their associated build result
        :raises UnmanagedBuildError: when requested to potentially build an unmanaged build
        :raises CircularDependencyBuildError: when circular dependency in FROM image
        :raises BuildFailedError: raised if the build fails
        """
        if build_configuration not in self.managed_build_configurations:
            raise UnmanagedBuildError(
                f"Build configuration {build_configuration} cannot be built as it is not in the "
                f"set of managed build configurations")

        building = _building if _building is not None else set()
        checksum_storage = _checksum_storage if _checksum_storage is not None else self.checksum_retriever
        allowed_builds = set(allowed_builds if allowed_builds is not None else
                             self.managed_build_configurations)

        # Storing checksums of updated dependency builds
        checksum_storage = DoubleSourceChecksumStorage(MemoryChecksumStorage(),
                                                       checksum_storage)

        # Manage collection of what configurations can be built
        allowed_builds.add(build_configuration)
        if not allowed_builds.issubset(self.managed_build_configurations):
            raise UnmanagedBuildError(
                f"Allowed builds is not a subset of managed build configurations. Unmanaged builds in `allowed_build`: "
                f"{allowed_builds.difference(self.managed_build_configurations)}"
            )

        if self._already_up_to_date(build_configuration):
            return {}

        # TODO: Break dependency build into separate function
        # Build dependent configurations
        build_results: OrderedDict[
            BuildConfigurationType:BuildResultType] = OrderedDict()
        for required_build_configuration_identifier in build_configuration.requires:
            required_build_configuration = self.managed_build_configurations.get(
                required_build_configuration_identifier, default=None)

            if required_build_configuration in allowed_builds \
                    and not self._already_up_to_date(required_build_configuration):
                left_allowed_builds = allowed_builds - set(
                    build_results.keys())

                if required_build_configuration in building:
                    raise CircularDependencyBuildError(
                        f"Circular dependency detected on {required_build_configuration.identifier}"
                    )

                # Build dependency ("parent")
                building.add(required_build_configuration)
                parent_build_results = self.build(
                    required_build_configuration,
                    left_allowed_builds,
                    _building=building,
                    _checksum_storage=checksum_storage)
                building.remove(required_build_configuration)

                # Store dependency build results
                assert set(
                    build_results.keys()).isdisjoint(parent_build_results)
                build_results.update(parent_build_results)

                # Update known configuration checksums
                checksums = {
                    x.identifier:
                    self.checksum_calculator.calculate_checksum(x)
                    for x in build_results.keys()
                }
                checksum_storage.set_all_checksums(checksums)

        # Build main configuration
        build_result = self._build(build_configuration)
        assert build_configuration not in build_results
        build_results[build_configuration] = build_result
        assert set(build_results.keys()).issubset(allowed_builds)

        return build_results