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())
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())
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')
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())
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, [])
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')
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, [])
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, [])
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')
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())
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, [])
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()
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}