예제 #1
0
 def testDirWithOtherYaml(self):
     """Directory with other.yaml inside, should error."""
     self.config_yaml_mock.return_value = None
     self.Touch(self.cwd_path, name='other.yaml', contents='unused')
     with self.assertRaises(exceptions.UnknownSourceError):
         deployables.GetDeployables(['.'], self.stager,
                                    deployables.GetPathMatchers())
예제 #2
0
 def testDuplicateServices(self):
     """Duplicate services passed as deployable."""
     self.service_yaml_mock.return_value = self.appinfo1  # returned twice
     self.config_yaml_mock.return_value = None
     self.Touch(self.cwd_path, name='app.yaml', contents='unused')
     self.Touch(self.cwd_path, name='other.yaml', contents='unused')
     with self.assertRaises(exceptions.DuplicateServiceError):
         deployables.GetDeployables(['app.yaml', 'other.yaml'], self.stager,
                                    deployables.GetPathMatchers())
예제 #3
0
 def testCronYaml(self):
     """cron.yaml passed as deployable."""
     self.config_yaml_mock.return_value = self.cron
     self.Touch(self.cwd_path, name='cron.yaml', contents='unused')
     services, configs = deployables.GetDeployables(
         ['cron.yaml'], self.stager, deployables.GetPathMatchers())
     self.assertEqual(services, [])
     self.assertEqual(len(configs), 1)
     c = configs[0]
     self.assertEqual(c.name, 'cron')
예제 #4
0
 def testDuplicateConfigs(self):
     """Two cron.yaml files passed as deployables."""
     self.config_yaml_mock.return_value = self.cron
     self.Touch(self.cwd_path, name='cron.yaml', contents='unused')
     self.Touch(os.path.join(self.cwd_path, 'sub'),
                name='cron.yaml',
                contents='unused',
                makedirs=True)
     with self.assertRaises(exceptions.DuplicateConfigError):
         deployables.GetDeployables(['cron.yaml', 'sub/cron.yaml'],
                                    self.stager,
                                    deployables.GetPathMatchers())
예제 #5
0
 def testDirWithAppYamlEmptyArgs(self):
     """Directory with nothing passed as deployable."""
     self.service_yaml_mock.return_value = self.appinfo1
     self.config_yaml_mock.return_value = None
     self.Touch(self.cwd_path, name='app.yaml', contents='unused')
     services, configs = deployables.GetDeployables(
         [], self.stager, deployables.GetPathMatchers())
     self.assertEqual(len(services), 1)
     s = services[0]
     self.assertEqual(s.descriptor, os.path.join(self.cwd_path, 'app.yaml'))
     self.assertEqual(s.service_id, 'my-service')
     self.assertEqual(s.upload_dir, self.cwd_path)
     self.assertEqual(configs, [])
예제 #6
0
 def testUnidentifiedDir(self):
     """An empty directory is supplied which should trigger fingerprint logic."""
     self.config_yaml_mock.return_value = None
     service = deployables.Service('/path/to/app.yaml', '/path/to/app.yaml',
                                   self.appinfo1, '/path/to')
     m = self.StartObjectPatch(deployables,
                               'UnidentifiedDirMatcher',
                               autospec=True,
                               return_value=service)
     services, _ = deployables.GetDeployables(['.'], self.stager,
                                              deployables.GetPathMatchers())
     m.assert_called_once()
     self.assertEqual(len(services), 1)
     self.assertEqual(services[0].descriptor, '/path/to/app.yaml')
예제 #7
0
 def testOtherYaml(self):
     """Simple other.yaml passed as deployable."""
     self.service_yaml_mock.return_value = self.appinfo1
     self.config_yaml_mock.return_value = None
     self.Touch(self.cwd_path, name='other.yaml', contents='unused')
     services, configs = deployables.GetDeployables(
         ['other.yaml'], self.stager, deployables.GetPathMatchers())
     self.assertEqual(len(services), 1)
     s = services[0]
     self.assertEqual(s.descriptor, os.path.join(self.cwd_path,
                                                 'other.yaml'))
     self.assertEqual(s.service_id, 'my-service')
     self.assertEqual(s.upload_dir, self.cwd_path)
     self.assertEqual(configs, [])
예제 #8
0
 def testLinkedAppYaml(self):
     """Symlinked app.yaml."""
     self.service_yaml_mock.return_value = self.appinfo1
     self.config_yaml_mock.return_value = None
     self.Touch(self.cwd_path, name='link_target', contents='unused')
     os.symlink(os.path.join(self.cwd_path, 'link_target'),
                os.path.join(self.cwd_path, 'app.yaml'))
     services, configs = deployables.GetDeployables(
         ['app.yaml'], self.stager, deployables.GetPathMatchers())
     self.assertEqual(len(services), 1)
     s = services[0]
     self.assertEqual(s.descriptor, os.path.join(self.cwd_path, 'app.yaml'))
     self.assertEqual(s.service_id, 'my-service')
     self.assertEqual(s.upload_dir, self.cwd_path)
     self.assertEqual(configs, [])
예제 #9
0
 def testCronYamlAndAppDir(self):
     """Directory with app.yaml passed as deployable."""
     self.service_yaml_mock.return_value = self.appinfo1
     self.config_yaml_mock.side_effect = [None, self.cron]
     self.Touch(self.cwd_path, name='app.yaml', contents='unused')
     self.Touch(self.cwd_path, name='cron.yaml', contents='unused')
     services, configs = deployables.GetDeployables(
         ['.', 'cron.yaml'], self.stager, deployables.GetPathMatchers())
     self.assertEqual(len(services), 1)
     s = services[0]
     self.assertEqual(s.descriptor, os.path.join(self.cwd_path, 'app.yaml'))
     self.assertEqual(s.service_id, 'my-service')
     self.assertEqual(s.upload_dir, self.cwd_path)
     self.assertEqual(len(configs), 1)
     c = configs[0]
     self.assertEqual(c.name, 'cron')
예제 #10
0
    def testAppengineWebXmlNoStaging(self):
        """WEB_INF/appengine-web.xml passed but staging not defined.

    If staging is not defined for `java-xml`, ensure that we don't recognize
    the java descriptor at all. This is used for rolling out in beta.
    """
        self.service_yaml_mock.return_value = self.appinfo1  # synthesized app.yaml
        self.config_yaml_mock.return_value = None
        self.StartObjectPatch(staging.Stager, 'Stage', return_value=None)
        self.Touch(os.path.join(self.cwd_path, 'WEB-INF'),
                   makedirs=True,
                   name='appengine-web.xml',
                   contents='unused')
        with self.assertRaises(exceptions.UnknownSourceError):
            deployables.GetDeployables(
                [os.path.join('WEB-INF', 'appengine-web.xml')], self.stager,
                deployables.GetPathMatchers())
예제 #11
0
 def testAppengineWebXmlDir(self):
     """Directory with WEB_INF/appengine-web.xml passed as deployable."""
     self.service_yaml_mock.return_value = self.appinfo1  # synthesized app.yaml
     self.config_yaml_mock.return_value = None
     self.StartObjectPatch(staging.Stager,
                           'Stage',
                           return_value='stage-dir')
     self.Touch(os.path.join(self.cwd_path, 'WEB-INF'),
                makedirs=True,
                name='appengine-web.xml',
                contents='unused')
     services, configs = deployables.GetDeployables(
         ['.'], self.stager, deployables.GetPathMatchers())
     self.assertEqual(len(services), 1)
     s = services[0]
     self.assertEqual(
         s.descriptor,
         os.path.join(self.cwd_path, 'WEB-INF', 'appengine-web.xml'))
     self.assertEqual(s.service_id, 'my-service')
     self.assertEqual(s.upload_dir, 'stage-dir')
     self.assertEqual(configs, [])
예제 #12
0
    def testDirWithAppYamlAndAppengineWebXml(self):
        """Directory with both app.yaml and WEB-INF/appengine-web.xml.

    Here, we check that app.yaml takes precedence.
    """
        self.service_yaml_mock.return_value = self.appinfo1
        self.config_yaml_mock.return_value = None
        java_match_mock = self.StartObjectPatch(deployables,
                                                'AppengineWebMatcher')
        self.Touch(self.cwd_path, name='app.yaml', contents='unused')
        self.Touch(os.path.join(self.cwd_path, 'WEB-INF'),
                   makedirs=True,
                   name='appengine-web.xml',
                   contents='unused')
        services, configs = deployables.GetDeployables(
            ['.'], self.stager, deployables.GetPathMatchers())
        self.assertEqual(len(services), 1)
        s = services[0]
        self.assertEqual(s.descriptor, os.path.join(self.cwd_path, 'app.yaml'))
        self.assertEqual(s.service_id, 'my-service')
        self.assertEqual(s.upload_dir, self.cwd_path)
        self.assertEqual(configs, [])
        java_match_mock.assert_not_called()
예제 #13
0
def RunDeploy(
        args,
        api_client,
        use_beta_stager=False,
        runtime_builder_strategy=runtime_builders.RuntimeBuilderStrategy.NEVER,
        parallel_build=True,
        flex_image_build_option=FlexImageBuildOptions.ON_CLIENT,
        disable_build_cache=False):
    """Perform a deployment based on the given args.

  Args:
    args: argparse.Namespace, An object that contains the values for the
        arguments specified in the ArgsDeploy() function.
    api_client: api_lib.app.appengine_api_client.AppengineClient, App Engine
        Admin API client.
    use_beta_stager: Use the stager registry defined for the beta track rather
        than the default stager registry.
    runtime_builder_strategy: runtime_builders.RuntimeBuilderStrategy, when to
      use the new CloudBuild-based runtime builders (alternative is old
      externalized runtimes).
    parallel_build: bool, whether to use parallel build and deployment path.
      Only supported in v1beta and v1alpha App Engine Admin API.
    flex_image_build_option: FlexImageBuildOptions, whether a flex deployment
      should upload files so that the server can build the image or build the
      image on client.
    disable_build_cache: bool, disable the build cache.

  Returns:
    A dict on the form `{'versions': new_versions, 'configs': updated_configs}`
    where new_versions is a list of version_util.Version, and updated_configs
    is a list of config file identifiers, see yaml_parsing.ConfigYamlInfo.
  """
    project = properties.VALUES.core.project.Get(required=True)
    deploy_options = DeployOptions.FromProperties(
        runtime_builder_strategy=runtime_builder_strategy,
        parallel_build=parallel_build,
        flex_image_build_option=flex_image_build_option)

    with files.TemporaryDirectory() as staging_area:
        stager = _MakeStager(args.skip_staging, use_beta_stager,
                             args.staging_command, staging_area)
        services, configs = deployables.GetDeployables(
            args.deployables, stager, deployables.GetPathMatchers())
        service_infos = [d.service_info for d in services]

        flags.ValidateImageUrl(args.image_url, service_infos)

        # pylint: disable=protected-access
        log.debug(
            'API endpoint: [{endpoint}], API version: [{version}]'.format(
                endpoint=api_client.client.url,
                version=api_client.client._VERSION))
        # The legacy admin console API client.
        # The Admin Console API existed long before the App Engine Admin API, and
        # isn't being improved. We're in the process of migrating all of the calls
        # over to the Admin API, but a few things (notably config deployments)
        # haven't been ported over yet.
        ac_client = appengine_client.AppengineClient(args.server,
                                                     args.ignore_bad_certs)

        app = _PossiblyCreateApp(api_client, project)
        _RaiseIfStopped(api_client, app)
        app = _PossiblyRepairApp(api_client, app)

        # Tell the user what is going to happen, and ask them to confirm.
        version_id = args.version or util.GenerateVersionId()
        deployed_urls = output_helpers.DisplayProposedDeployment(
            app, project, services, configs, version_id,
            deploy_options.promote)
        console_io.PromptContinue(cancel_on_no=True)
        if service_infos:
            # Do generic app setup if deploying any services.
            # All deployment paths for a service involve uploading source to GCS.
            metrics.CustomTimedEvent(metric_names.GET_CODE_BUCKET_START)
            code_bucket_ref = args.bucket or flags.GetCodeBucket(app, project)
            metrics.CustomTimedEvent(metric_names.GET_CODE_BUCKET)
            log.debug(
                'Using bucket [{b}].'.format(b=code_bucket_ref.ToBucketUrl()))

            # Prepare Flex if any service is going to deploy an image.
            if any([s.RequiresImage() for s in service_infos]):
                if deploy_options.use_service_management:
                    deploy_command_util.PossiblyEnableFlex(project)
                else:
                    deploy_command_util.DoPrepareManagedVms(ac_client)

            all_services = dict([(s.id, s) for s in api_client.ListServices()])
        else:
            code_bucket_ref = None
            all_services = {}
        new_versions = []
        deployer = ServiceDeployer(api_client, deploy_options)

        # Track whether a service has been deployed yet, for metrics.
        service_deployed = False
        for service in services:
            if not service_deployed:
                metrics.CustomTimedEvent(
                    metric_names.FIRST_SERVICE_DEPLOY_START)
            new_version = version_util.Version(project, service.service_id,
                                               version_id)
            deployer.Deploy(service,
                            new_version,
                            code_bucket_ref,
                            args.image_url,
                            all_services,
                            app.gcrDomain,
                            disable_build_cache=disable_build_cache,
                            flex_image_build_option=flex_image_build_option)
            new_versions.append(new_version)
            log.status.Print('Deployed service [{0}] to [{1}]'.format(
                service.service_id, deployed_urls[service.service_id]))
            if not service_deployed:
                metrics.CustomTimedEvent(metric_names.FIRST_SERVICE_DEPLOY)
            service_deployed = True

    # Deploy config files.
    if configs:
        metrics.CustomTimedEvent(metric_names.UPDATE_CONFIG_START)
        for config in configs:
            message = 'Updating config [{config}]'.format(config=config.name)
            with progress_tracker.ProgressTracker(message):
                ac_client.UpdateConfig(config.name, config.parsed)
        metrics.CustomTimedEvent(metric_names.UPDATE_CONFIG)

    updated_configs = [c.name for c in configs]

    PrintPostDeployHints(new_versions, updated_configs)

    # Return all the things that were deployed.
    return {'versions': new_versions, 'configs': updated_configs}