def _ServiceFlagCompletionCallback(list_type, project):
    """Callback function for service tab-completion.

  Args:
    list_type: str, should be one of 'produced', 'enabled', or 'available'
    project: str, the name of the project for which to retrieve candidates

  Returns:
    The list of arguments that the gcloud infrastructure will use to retrieve
    candidate services.
  """
    # Sanity check the type of list_type argument
    if not isinstance(list_type, basestring):
        raise exceptions.InternalError(
            'Could not read list type in service flag completion callback.')

    # Sanity check the list_type contents
    if list_type not in ['produced', 'enabled']:
        raise exceptions.InternalError(
            'Invalid list type in service flag completion callback.')

    result = [
        'service-management', 'list',
        '--%s' % list_type, '--format=value(serviceConfig.name)'
    ]
    if project:
        result.extend(['--project', project])

    return result
def _SetDefaultVersion(new_version, api_client):
    """Sets the given version as the default.

  Args:
    new_version: Version, The version to promote.
    api_client: appengine_api_client.AppengineApiClient to use to make requests.
  """
    metrics.CustomTimedEvent(metric_names.SET_DEFAULT_VERSION_API_START)

    # TODO(b/31824825): It sometimes takes a while for a new service to show up.
    # Retry it if we get a service not found error.
    def ShouldRetry(exc_type, unused_exc_value, unused_traceback,
                    unused_state):
        return issubclass(exc_type, core_api_exceptions.HttpException)

    try:
        retryer = retry.Retryer(max_retrials=3, exponential_sleep_multiplier=2)
        retryer.RetryOnException(api_client.SetDefaultVersion,
                                 [new_version.service, new_version.id],
                                 should_retry_if=ShouldRetry,
                                 sleep_ms=1000)
    except retry.MaxRetrialsException as e:
        (unused_result, exc_info) = e.last_result
        if exc_info:
            # This is the 3 tuple of the last exception the function threw.
            raise exc_info[0], exc_info[1], exc_info[2]
        else:
            # This shouldn't happen, but if we don't have the exception info for some
            # reason, just convert the MaxRetrialsException.
            raise exceptions.InternalError()
    metrics.CustomTimedEvent(metric_names.SET_DEFAULT_VERSION_API)
Beispiel #3
0
    def Run(self, args):
        if not grpc_available:
            raise core_exceptions.InternalError('gRPC is not available')

        channel = grpc_util.MakeSecureChannel(
            'bigtableadmin.googleapis.com:443')
        instances = args.instances
        results = []
        for instance in instances:
            instance_ref = resources.REGISTRY.Parse(
                instance,
                params={
                    'projectsId': properties.VALUES.core.project.GetOrFail
                },
                collection='bigtableadmin.projects.instances')

            stub = bigtable_table_admin_pb2_grpc.BigtableTableAdminStub(
                channel)

            request = bigtable_table_admin_pb2.ListTablesRequest(
                parent=instance_ref.RelativeName(), )
            for table in grpc_util.YieldFromList(stub.ListTables,
                                                 request,
                                                 items_field='tables'):
                results.append(table)
        return results
Beispiel #4
0
    def Run(self, args):
        """Returns a list of backendServiceGroupHealth objects."""
        self.backend_service_ref = self.CreateGlobalReference(
            args.name, resource_type='backendServices')
        backend_service = self.GetBackendService(args)
        if not backend_service.backends:
            return

        # Call GetHealth for each group in the backend service
        requests = []
        for backend in backend_service.backends:
            request_message = self.messages.ComputeBackendServicesGetHealthRequest(
                resourceGroupReference=self.messages.ResourceGroupReference(
                    group=backend.group),
                project=self.project,
                backendService=self.backend_service_ref.Name())
            requests.append((self.service, 'GetHealth', request_message))

        # Instead of batching-up all requests and making a single
        # request_helper.MakeRequests call, go one backend at a time.
        # We do this because getHealth responses don't say what resource
        # they correspond to.  It's not obvious how to reliably match up
        # responses and backends when there are errors.  Addtionally the contract
        # for MakeRequests doesn't guarantee response order will match
        # request order.
        #
        # TODO(b/25015230) Simply make a batch request once the response
        # gives more information.
        errors = []
        for request in requests:
            # The list() call below is itended to force the generator returned by
            # MakeRequests.  If there are exceptions the command will abort, which is
            # expected.  Having a list simplifies some of the checks that follow.
            resources = list(
                request_helper.MakeRequests(requests=[request],
                                            http=self.http,
                                            batch_url=self.batch_url,
                                            errors=errors,
                                            custom_get_requests=None))

            if len(resources) is 0:
                #  Request failed, error information will accumulate in errors
                continue

            try:
                [resource] = resources
            except ValueError:
                # Intended to throw iff resources contains more than one element.  Just
                # want to avoid a user potentially seeing an index out of bounds
                # exception.
                raise exceptions.InternalError('Invariant failure')

            yield {
                'backend': request[2].resourceGroupReference.group,
                'status': resource
            }

        if errors:
            utils.RaiseToolException(
                errors, error_message='Could not get health for some groups:')
Beispiel #5
0
def ActivateNamedConfig(name):
    """Activates an existing named configuration."""

    _ValidateConfigNameOrRaise(name)

    configs = tuple(c.name for c in ListNamedConfigs())
    if name not in configs + _RESERVED_NAMED_CONFIG_NAMES:
        raise NamedConfigWriteError(
            'Activating named configuration failed because configuration '
            '[{0}] cannot be found.'.format(name))

    config_path = GetPathForConfigName(name)
    if not IsPathReadable(config_path):
        raise NamedConfigWriteError(
            'Activating named configuration failed because configuration '
            'file [{0}] is missing or cannot be read.'.format(config_path))

    _EnsureDir(config.Paths().global_config_dir)
    activator_path = config.Paths().named_config_activator_path

    try:
        with open(activator_path, 'w') as f:
            f.write(name)
    except IOError as exp:
        raise NamedConfigWriteError(
            'Activating named configuration failed when writing '
            'file [{0}] because [{1}]'.format(activator_path,
                                              _OSErrorReason(exp)))

    if ReadActivatorFile(silent=True) != name:
        # Fail rather than erronously report success.  Should be dead code.
        raise exceptions.InternalError('Configuration creation or activation '
                                       'failed for an unknown reason.')
Beispiel #6
0
def _GetEventTriggerEventParams(trigger_event, trigger_resource):
    """Get the args for creating an event trigger.

  Args:
    trigger_event: The trigger event
    trigger_resource: The trigger resource
  Returns:
    A dictionary containing trigger_provider, trigger_event, and
    trigger_resource.
  """
    trigger_provider = triggers.INPUT_TRIGGER_PROVIDER_REGISTRY.ProviderForEvent(
        trigger_event).label
    resource_type = triggers.INPUT_TRIGGER_PROVIDER_REGISTRY.Event(
        trigger_provider, trigger_event).resource_type
    if resource_type == triggers.Resources.TOPIC:
        trigger_resource = api_util.ValidatePubsubTopicNameOrRaise(
            trigger_resource)
    elif resource_type == triggers.Resources.BUCKET:
        trigger_resource = storage_util.BucketReference.FromBucketUrl(
            trigger_resource).bucket
    elif resource_type == triggers.Resources.PROJECT:
        if trigger_resource:
            properties.VALUES.core.project.Validate(trigger_resource)
    else:
        # Check if programmer allowed other methods in
        # api_util.PROVIDER_EVENT_RESOURCE but forgot to update code here
        raise core_exceptions.InternalError()
    # checked if provided resource and path have correct format
    return {
        'trigger_provider': trigger_provider,
        'trigger_event': trigger_event,
        'trigger_resource': trigger_resource,
    }
def _CheckTriggerProviderArgs(args):
    """Check --trigger-provider dependent arguments and deduce if possible.

  0. Check if --trigger-provider is correct.
  1. Check if --trigger-event is present, assign default if not.
  2. Check if --trigger-event is correct WRT to --trigger-provider.
  3. Check if --trigger-resource is present if necessary.
  4. Check if --trigger-resource is correct WRT to *-provider and *-event.
  5. Check if --trigger-path is present if necessary.
  6. Check if --trigger-path is not present if forbidden.
  7. Check if --trigger-path is correct if present.

  Args:
    args: The argument namespace.

  Returns:
    None, when using HTTPS trigger. Otherwise a dictionary containing
    trigger_provider, trigger_event, and trigger_resource.
  """
    if args.trigger_http:
        return None
    if args.trigger_bucket:
        return _BucketTrigger(args.trigger_bucket)
    if args.trigger_topic:
        return _TopicTrigger(args.trigger_topic)
    if args.trigger_provider is None:
        return None

    trigger_provider = args.trigger_provider
    trigger_event = args.trigger_event
    trigger_resource = args.trigger_resource
    # check and infer correct usage of flags accompanying --trigger-provider
    if trigger_event is None:
        trigger_event = util.input_trigger_provider_registry.Provider(
            trigger_provider).default_event.label

    resource_type = util.input_trigger_provider_registry.Event(
        trigger_provider, trigger_event).resource_type
    if resource_type == util.Resources.TOPIC:
        trigger_resource = util.ValidatePubsubTopicNameOrRaise(
            trigger_resource)
    elif resource_type == util.Resources.BUCKET:
        trigger_resource = storage_util.BucketReference.FromBucketUrl(
            trigger_resource).bucket
    elif resource_type == util.Resources.PROJECT:
        if trigger_resource:
            properties.VALUES.core.project.Validate(trigger_resource)
    else:
        # Check if programmer allowed other methods in
        # util.PROVIDER_EVENT_RESOURCE but forgot to update code here
        raise core_exceptions.InternalError()
    # checked if provided resource and path have correct format
    return {
        'trigger_provider': trigger_provider,
        'trigger_event': trigger_event,
        'trigger_resource': trigger_resource,
    }
 def _RaiseIfFailed(self):
     if self._pull_future.done():
         e = self._pull_future.exception()
         if e:
             raise SubscribeOperationException(
                 'Subscribe operation failed with error: {error}'.format(
                     error=e))
         log.debug(
             'The streaming pull future completed unexpectedly without '
             'raising an exception.')
         raise exceptions.InternalError(
             'The subscribe stream terminated unexpectedly.')
Beispiel #9
0
def _CheckTriggerEventArgs(args):
    """Check --trigger-*  arguments and deduce if possible.

  0. if --trigger-http is return None.
  1. if --trigger-bucket return bucket trigger args (_BucketTrigger)
  2. if --trigger-topic return pub-sub trigger args (_TopicTrigger)
  3. if --trigger-event, deduce provider and resource from registry and return

  Args:
    args: The argument namespace.

  Returns:
    None, when using HTTPS trigger. Otherwise a dictionary containing
    trigger_provider, trigger_event, and trigger_resource.
  """
    if args.trigger_http:
        return None
    if args.trigger_bucket:
        return _BucketTrigger(args.trigger_bucket)
    if args.trigger_topic:
        return _TopicTrigger(args.trigger_topic)
    if not args.trigger_event:
        return None

    trigger_event = args.trigger_event
    trigger_provider = util.input_trigger_provider_registry.ProviderForEvent(
        trigger_event).label
    trigger_resource = args.trigger_resource
    resource_type = util.input_trigger_provider_registry.Event(
        trigger_provider, trigger_event).resource_type
    if resource_type == util.Resources.TOPIC:
        trigger_resource = util.ValidatePubsubTopicNameOrRaise(
            trigger_resource)
    elif resource_type == util.Resources.BUCKET:
        trigger_resource = storage_util.BucketReference.FromBucketUrl(
            trigger_resource).bucket
    elif resource_type == util.Resources.PROJECT:
        if trigger_resource:
            properties.VALUES.core.project.Validate(trigger_resource)
    else:
        # Check if programmer allowed other methods in
        # util.PROVIDER_EVENT_RESOURCE but forgot to update code here
        raise core_exceptions.InternalError()
    # checked if provided resource and path have correct format
    return {
        'trigger_provider': trigger_provider,
        'trigger_event': trigger_event,
        'trigger_resource': trigger_resource,
    }
Beispiel #10
0
    def UpdateCache(self, operation, uris):
        """Updates the cache using operation on uris.

    Args:
      operation: AddToCacheOp, DeleteFromCacheOp, or ReplaceCacheOp.
      uris: The list of uris for the operation.

    Raises:
      InternalError: if operation is invalid.
    """
        if operation not in (AddToCacheOp, DeleteFromCacheOp, ReplaceCacheOp):
            raise exceptions.InternalError(
                'RemoteCompletion.UpdateCache operation [{0}] must be an '
                '_UpdateCacheOp.'.format(operation))
        operation().UpdateCache(self, uris)
Beispiel #11
0
def _CheckTriggerProviderArgs(args):
    """Check --trigger-provider dependent arguments and deduce if possible.

  0. Check if --trigger-provider is correct.
  1. Check if --trigger-event is present, assign default if not.
  2. Check if --trigger-event is correct WRT to --trigger-provider.
  3. Check if --trigger-resource is present if necessary.
  4. Check if --trigger-resource is correct WRT to *-provider and *-event.
  5. Check if --trigger-path is present if necessary.
  6. Check if --trigger-path is not present if forbidden.
  7. Check if --trigger-path is correct if present.

  Args:
    args: The argument namespace.

  Returns:
    args with all implicit information turned into explicit form.
  """

    # Create a copy of namespace (copy.copy doesn't work here)
    result = argparse.Namespace(**vars(args))
    # check and infer correct usage of flags accompanying --trigger-provider
    if result.trigger_event is None:
        result.trigger_event = util.trigger_provider_registry.Provider(
            result.trigger_provider).default_event.label
    elif result.trigger_event not in util.trigger_provider_registry.EventsLabels(
            result.trigger_provider):
        raise exceptions.FunctionsError('You can use only one of [' + ','.join(
            util.trigger_provider_registry.EventsLabels(
                result.trigger_provider)) + '] with --trigger-provider=' +
                                        result.trigger_provider)
    # checked if Event Type is correct

    if result.trigger_resource is None and util.trigger_provider_registry.Event(
            result.trigger_provider,
            result.trigger_event).resource_type != util.Resources.PROJECT:
        raise exceptions.FunctionsError(
            'You must provide --trigger-resource when using '
            '--trigger-provider={0} and --trigger-event={1}'.format(
                result.trigger_provider, result.trigger_event))
    path_allowance = util.trigger_provider_registry.Event(
        result.trigger_provider, result.trigger_event).path_obligatoriness
    if result.trigger_path is None and (path_allowance
                                        == util.Obligatoriness.REQUIRED):
        raise exceptions.FunctionsError(
            'You must provide --trigger-path when using '
            '--trigger-provider={0} and --trigger-event={1}'.format(
                result.trigger_provider, result.trigger_event))
    if result.trigger_path is not None and (path_allowance
                                            == util.Obligatoriness.FORBIDDEN):
        raise exceptions.FunctionsError(
            'You must not provide --trigger-path when using '
            '--trigger-provider={0} and --trigger-event={1}'.format(
                result.trigger_provider, result.trigger_event))
    # checked if Resource Type and Path were provided or not as required

    resource_type = util.trigger_provider_registry.Event(
        result.trigger_provider, result.trigger_event).resource_type
    if resource_type == util.Resources.TOPIC:
        result.trigger_resource = util.ValidatePubsubTopicNameOrRaise(
            result.trigger_resource)
    elif resource_type == util.Resources.BUCKET:
        result.trigger_resource = storage_util.BucketReference.FromBucketUrl(
            result.trigger_resource).bucket
    elif resource_type == util.Resources.PROJECT:
        if result.trigger_resource:
            properties.VALUES.core.project.Validate(result.trigger_resource)
    else:
        # Check if programmer allowed other methods in
        # util.PROVIDER_EVENT_RESOURCE but forgot to update code here
        raise core_exceptions.InternalError()
    if result.trigger_path is not None:
        util.ValidatePathOrRaise(result.trigger_path)
    # checked if provided resource and path have correct format
    return result
Beispiel #12
0
def _CheckTriggerProviderArgs(args):
    """Check --trigger-provider dependent arguments and deduce if possible.

  0. Check if --trigger-provider is correct.
  1. Check if --trigger-event is present, assign default if not.
  2. Check if --trigger-event is correct WRT to --trigger-provider.
  3. Check if --trigger-resource is present if necessary.
  4. Check if --trigger-resource is correct WRT to *-provider and *-event.
  5. Check if --trigger-path is present if necessary.
  6. Check if --trigger-path is not present if forbidden.
  7. Check if --trigger-path is correct if present.

  Args:
    args: The argument namespace.

  Returns:
    None, when using HTTPS trigger. Otherwise a dictionary containing
    trigger_provider, trigger_event, and trigger_resource.
  """
    if args.trigger_http:
        return None
    if args.trigger_bucket:
        return _BucketTrigger(args.trigger_bucket)
    if args.trigger_topic:
        return _TopicTrigger(args.trigger_topic)

    # TODO(b/36020181): move validation to a separate function.
    trigger_provider = args.trigger_provider
    trigger_event = args.trigger_event
    trigger_resource = args.trigger_resource
    # check and infer correct usage of flags accompanying --trigger-provider
    if trigger_event is None:
        trigger_event = util.input_trigger_provider_registry.Provider(
            trigger_provider).default_event.label
    elif trigger_event not in util.input_trigger_provider_registry.EventsLabels(
            trigger_provider):
        raise exceptions.FunctionsError('You can use only one of [' + ','.join(
            util.input_trigger_provider_registry.EventsLabels(
                trigger_provider)) + '] with --trigger-provider=' +
                                        trigger_provider)
    # checked if Event Type is correct

    if trigger_resource is None and util.input_trigger_provider_registry.Event(
            trigger_provider,
            trigger_event).resource_type != util.Resources.PROJECT:
        raise exceptions.FunctionsError(
            'You must provide --trigger-resource when using '
            '--trigger-provider={0} and --trigger-event={1}'.format(
                trigger_provider, trigger_event))
    # checked if Resource Type and Path were provided or not as required

    resource_type = util.input_trigger_provider_registry.Event(
        trigger_provider, trigger_event).resource_type
    if resource_type == util.Resources.TOPIC:
        trigger_resource = util.ValidatePubsubTopicNameOrRaise(
            trigger_resource)
    elif resource_type == util.Resources.BUCKET:
        trigger_resource = storage_util.BucketReference.FromBucketUrl(
            trigger_resource).bucket
    elif resource_type == util.Resources.PROJECT:
        if trigger_resource:
            properties.VALUES.core.project.Validate(trigger_resource)
    else:
        # Check if programmer allowed other methods in
        # util.PROVIDER_EVENT_RESOURCE but forgot to update code here
        raise core_exceptions.InternalError()
    # checked if provided resource and path have correct format
    return {
        'trigger_provider': trigger_provider,
        'trigger_event': trigger_event,
        'trigger_resource': trigger_resource,
    }
Beispiel #13
0
    def Run(self, args):
        """This is what gets called when the user runs this command.

    Args:
      args: an argparse namespace, All the arguments that were provided to this
        command invocation.
    Returns:
      None
    Raises:
      bigqueryError.BigqueryError: If the source and destination files are not
        both specified.
      calliope_exceptions.ToolException: If user cancels this operation.
      Exception: If an unexpected value for the --if-exists flag passed gcloud
        validation (which should never happen)
    """
        apitools_client = self.context[commands.APITOOLS_CLIENT_KEY]
        bigquery_messages = self.context[commands.BIGQUERY_MESSAGES_MODULE_KEY]
        resource_parser = self.context[commands.BIGQUERY_REGISTRY_KEY]
        project_id = properties.VALUES.core.project.Get(required=True)

        source_reference = resource_parser.Parse(args.source,
                                                 collection='bigquery.tables')
        source_reference_message = message_conversions.TableResourceToReference(
            bigquery_messages, source_reference)

        destination_resource = resource_parser.Parse(
            args.destination, collection='bigquery.tables')
        destination_reference = message_conversions.TableResourceToReference(
            bigquery_messages, destination_resource)

        if args.if_exists == 'append':
            write_disposition = 'WRITE_APPEND'
            ignore_already_exists = True
        elif args.if_exists == 'fail':
            write_disposition = 'WRITE_EMPTY'
            ignore_already_exists = False
        elif args.if_exists == 'prompt':
            write_disposition = 'WRITE_TRUNCATE'
            ignore_already_exists = False
            if bigquery_client_helper.TableExists(apitools_client,
                                                  bigquery_messages,
                                                  destination_reference):
                if not console_io.PromptContinue(prompt_string='Replace {0}'.
                                                 format(destination_resource)):
                    raise calliope_exceptions.ToolException('canceled by user')
        elif args.if_exists == 'replace':
            write_disposition = 'WRITE_TRUNCATE'
            ignore_already_exists = False
        elif args.if_exists == 'skip':
            if bigquery_client_helper.TableExists(apitools_client,
                                                  bigquery_messages,
                                                  destination_reference):
                return
        else:
            # This should be unreachable.
            raise core_exceptions.InternalError(
                'Unexpected value "{0}" for --if-exists flag.'.format(
                    args.if_exists))

        copy_config = bigquery_messages.JobConfigurationTableCopy(
            sourceTable=source_reference_message,
            destinationTable=destination_reference,
            writeDisposition=write_disposition)

        job_id = job_ids.JobIdProvider().GetJobId(args.job_id,
                                                  args.fingerprint_job_id)

        try:
            job = job_control.ExecuteJob(
                apitools_client,
                bigquery_messages,
                args,
                configuration=bigquery_messages.JobConfiguration(
                    copy=copy_config),
                project_id=project_id,
                job_id=job_id)
        except bigquery.DuplicateError as e:
            if ignore_already_exists:
                job = None
            else:
                raise e

        if job is None:
            log.status.Print('Table "{0}" already exists, skipping'.format(
                destination_resource))
        elif args. async:
            registry = self.context[commands.BIGQUERY_REGISTRY_KEY]
            job_resource = registry.Create(
                'bigquery.jobs',
                projectId=job.jobReference.projectId,
                jobId=job.jobReference.jobId)
            log.CreatedResource(job_resource)
        else:
            log.status.Print('Table [{0}] successfully copied to [{1}]'.format(
                source_reference, destination_resource))
Beispiel #14
0
def PersistProperty(prop, value, scope=None, properties_file=None):
    """Sets the given property in the properties file.

  This function should not generally be used as part of normal program
  execution.  The property files are user editable config files that they should
  control.  This is mostly for initial setup of properties that get set during
  SDK installation.

  Args:
    prop: properties.Property, The property to set.
    value: str, The value to set for the property. If None, the property is
      removed.
    scope: Scope, The config location to set the property in.  If given, only
      this location will be udpated and it is an error if that location does
      not exist.  If not given, it will attempt to update the property in the
      first of the following places that exists:
        - the workspace config
        - the active named config
        - user level config
      It will never fall back to installation properties; you must
      use that scope explicitly to set that value.
    properties_file: str, Path to an explicit properties file to use (instead of
      one of the known locations).  It is an error to specify a scope and an
      explicit file.

  Raises:
    ValueError: If you give both a scope and a properties file.
    MissingConfigLocationError: If there is not file for the given scope.
    ReadOnlyNamedConfigNotSettableError: If the user is attempting to set
      a property in a read-only configuration.
    InternalError: If there's a programming error.
  """
    prop.Validate(value)
    if scope and properties_file:
        raise ValueError(
            'You cannot provide both a scope and a specific properties'
            ' file.')
    if not properties_file:
        if scope:
            if scope == Scope.INSTALLATION:
                config.EnsureSDKWriteAccess()
            properties_file = scope.get_file()
            if not properties_file:
                raise MissingConfigLocationError(scope)
        else:
            properties_file = Scope.WORKSPACE.get_file()
            if not properties_file:
                properties_file = named_configs.GetEffectiveNamedConfigFile()
                if properties_file is None:
                    # Should be dead code.
                    raise exceptions.InternalError(
                        'Unexpected None properties file.')
                if properties_file == os.path.devnull:
                    # Refuse to write and fail with an informative error
                    # TODO(b/22817095) Simplify control flow and update
                    # messaging when moving to automatic upgrade scenario
                    # on all release tracks.
                    if (named_configs.GetNameOfActiveNamedConfig() ==
                            named_configs.RESERVED_NAMED_CONFIG_NAME_NONE):
                        raise ReadOnlyNamedConfigNotSettableError(
                            named_configs.RESERVED_NAMED_CONFIG_NAME_NONE)
                    if not Scope.USER.get_file():
                        raise MissingConfigLocationError(Scope.USER)

    parsed_config = ConfigParser.ConfigParser()
    parsed_config.read(properties_file)

    if not parsed_config.has_section(prop.section):
        if value is None:
            return
        parsed_config.add_section(prop.section)

    if value is None:
        parsed_config.remove_option(prop.section, prop.name)
    else:
        parsed_config.set(prop.section, prop.name, str(value))

    properties_dir, unused_name = os.path.split(properties_file)
    files.MakeDir(properties_dir)
    with open(properties_file, 'w') as fp:
        parsed_config.write(fp)

    PropertiesFile.Invalidate()