| OLD | NEW |
| 1 # -*- coding: utf-8 -*- |
| 1 # Copyright 2013 Google Inc. All Rights Reserved. | 2 # Copyright 2013 Google Inc. All Rights Reserved. |
| 2 # | 3 # |
| 3 # Licensed under the Apache License, Version 2.0 (the "License"); | 4 # Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 # you may not use this file except in compliance with the License. | 5 # you may not use this file except in compliance with the License. |
| 5 # You may obtain a copy of the License at | 6 # You may obtain a copy of the License at |
| 6 # | 7 # |
| 7 # http://www.apache.org/licenses/LICENSE-2.0 | 8 # http://www.apache.org/licenses/LICENSE-2.0 |
| 8 # | 9 # |
| 9 # Unless required by applicable law or agreed to in writing, software | 10 # Unless required by applicable law or agreed to in writing, software |
| 10 # distributed under the License is distributed on an "AS IS" BASIS, | 11 # distributed under the License is distributed on an "AS IS" BASIS, |
| 11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 # See the License for the specific language governing permissions and | 13 # See the License for the specific language governing permissions and |
| 13 # limitations under the License. | 14 # limitations under the License. |
| 15 """Implementation of lifecycle configuration command for GCS buckets.""" |
| 16 |
| 17 from __future__ import absolute_import |
| 14 | 18 |
| 15 import sys | 19 import sys |
| 16 import xml | |
| 17 | 20 |
| 18 from boto import handler | |
| 19 from boto.gs.lifecycle import LifecycleConfig | |
| 20 from gslib.command import Command | 21 from gslib.command import Command |
| 21 from gslib.command import COMMAND_NAME | 22 from gslib.cs_api_map import ApiSelector |
| 22 from gslib.command import COMMAND_NAME_ALIASES | |
| 23 from gslib.command import FILE_URIS_OK | |
| 24 from gslib.command import MAX_ARGS | |
| 25 from gslib.command import MIN_ARGS | |
| 26 from gslib.command import NO_MAX | |
| 27 from gslib.command import PROVIDER_URIS_OK | |
| 28 from gslib.command import SUPPORTED_SUB_ARGS | |
| 29 from gslib.command import URIS_START_ARG | |
| 30 from gslib.exception import CommandException | 23 from gslib.exception import CommandException |
| 31 from gslib.help_provider import CreateHelpText | 24 from gslib.help_provider import CreateHelpText |
| 32 from gslib.help_provider import HELP_NAME | 25 from gslib.third_party.storage_apitools import storage_v1_messages as apitools_m
essages |
| 33 from gslib.help_provider import HELP_NAME_ALIASES | 26 from gslib.translation_helper import LifecycleTranslation |
| 34 from gslib.help_provider import HELP_ONE_LINE_SUMMARY | 27 from gslib.util import NO_MAX |
| 35 from gslib.help_provider import HELP_TEXT | 28 from gslib.util import UrlsAreForSingleProvider |
| 36 from gslib.help_provider import HelpType | |
| 37 from gslib.help_provider import HELP_TYPE | |
| 38 from gslib.help_provider import SUBCOMMAND_HELP_TEXT | |
| 39 | 29 |
| 40 | 30 |
| 41 _GET_SYNOPSIS = """ | 31 _GET_SYNOPSIS = """ |
| 42 gsutil lifecycle get uri | 32 gsutil lifecycle get url |
| 43 """ | 33 """ |
| 44 | 34 |
| 45 _SET_SYNOPSIS = """ | 35 _SET_SYNOPSIS = """ |
| 46 gsutil lifecycle set config-xml-file uri... | 36 gsutil lifecycle set config-json-file url... |
| 47 """ | 37 """ |
| 48 | 38 |
| 49 _SYNOPSIS = _GET_SYNOPSIS + _SET_SYNOPSIS.lstrip('\n') + '\n' | 39 _SYNOPSIS = _GET_SYNOPSIS + _SET_SYNOPSIS.lstrip('\n') + '\n' |
| 50 | 40 |
| 51 _GET_DESCRIPTION = """ | 41 _GET_DESCRIPTION = """ |
| 52 <B>GET</B> | 42 <B>GET</B> |
| 53 Gets the lifecycle configuration for a given bucket. You can get the | 43 Gets the lifecycle configuration for a given bucket. You can get the |
| 54 lifecycle configuration for only one bucket at a time. The output can be | 44 lifecycle configuration for only one bucket at a time. The output can be |
| 55 redirected into a file, edited and then updated via the set sub-command. | 45 redirected into a file, edited and then updated via the set sub-command. |
| 56 | 46 |
| 57 """ | 47 """ |
| 58 | 48 |
| 59 _SET_DESCRIPTION = """ | 49 _SET_DESCRIPTION = """ |
| 60 <B>SET</B> | 50 <B>SET</B> |
| 61 Sets the lifecycle configuration on one or more buckets. The config-xml-file | 51 Sets the lifecycle configuration on one or more buckets. The config-json-file |
| 62 specified on the command line should be a path to a local file containing | 52 specified on the command line should be a path to a local file containing |
| 63 the lifecycle congfiguration XML document. | 53 the lifecycle congfiguration JSON document. |
| 64 | 54 |
| 65 """ | 55 """ |
| 66 | 56 |
| 67 _DESCRIPTION = """ | 57 _DESCRIPTION = """ |
| 68 The lifecycle command can be used to get or set lifecycle management policies | 58 The lifecycle command can be used to get or set lifecycle management policies |
| 69 for the given bucket(s). This command is supported for buckets only, not | 59 for the given bucket(s). This command is supported for buckets only, not |
| 70 objects. For more information on object lifecycle management, please see the | 60 objects. For more information on object lifecycle management, please see the |
| 71 `developer guide <https://developers.google.com/storage/docs/lifecycle>`_. | 61 `developer guide <https://developers.google.com/storage/docs/lifecycle>`_. |
| 72 | 62 |
| 73 The lifecycle command has two sub-commands: | 63 The lifecycle command has two sub-commands: |
| 74 """ + _GET_DESCRIPTION + _SET_DESCRIPTION + """ | 64 """ + _GET_DESCRIPTION + _SET_DESCRIPTION + """ |
| 75 <B>EXAMPLES</B> | 65 <B>EXAMPLES</B> |
| 76 The following lifecycle configuration XML document specifies that all objects | 66 The following lifecycle configuration JSON document specifies that all objects |
| 77 that are more than 365 days old will be deleted automatically: | 67 in this bucket that are more than 365 days old will be deleted automatically: |
| 78 | 68 |
| 79 <?xml version="1.0" ?> | 69 { |
| 80 <LifecycleConfiguration> | 70 "rule": |
| 81 <Rule> | 71 [ |
| 82 <Action> | 72 { |
| 83 <Delete/> | 73 "action": {"type": "Delete"}, |
| 84 </Action> | 74 "condition": {"age": 365} |
| 85 <Condition> | 75 } |
| 86 <Age>365</Age> | 76 ] |
| 87 </Condition> | 77 } |
| 88 </Rule> | 78 |
| 89 </LifecycleConfiguration> | 79 The following (empty) lifecycle configuration JSON document removes all |
| 80 lifecycle configuration for a bucket: |
| 81 |
| 82 {} |
| 83 |
| 90 """ | 84 """ |
| 91 | 85 |
| 92 _detailed_help_text = CreateHelpText(_SYNOPSIS, _DESCRIPTION) | 86 _DETAILED_HELP_TEXT = CreateHelpText(_SYNOPSIS, _DESCRIPTION) |
| 93 | 87 |
| 94 _get_help_text = CreateHelpText(_GET_SYNOPSIS, _GET_DESCRIPTION) | 88 _get_help_text = CreateHelpText(_GET_SYNOPSIS, _GET_DESCRIPTION) |
| 95 _set_help_text = CreateHelpText(_SET_SYNOPSIS, _SET_DESCRIPTION) | 89 _set_help_text = CreateHelpText(_SET_SYNOPSIS, _SET_DESCRIPTION) |
| 96 | 90 |
| 97 | 91 |
| 98 class LifecycleCommand(Command): | 92 class LifecycleCommand(Command): |
| 99 """Implementation of gsutil lifecycle command.""" | 93 """Implementation of gsutil lifecycle command.""" |
| 100 | 94 |
| 101 # Command specification (processed by parent class). | 95 # Command specification. See base class for documentation. |
| 102 command_spec = { | 96 command_spec = Command.CreateCommandSpec( |
| 103 # Name of command. | 97 'lifecycle', |
| 104 COMMAND_NAME : 'lifecycle', | 98 command_name_aliases=['lifecycleconfig'], |
| 105 # List of command name aliases. | 99 min_args=2, |
| 106 COMMAND_NAME_ALIASES : ['lifecycleconfig'], | 100 max_args=NO_MAX, |
| 107 # Min number of args required by this command. | 101 supported_sub_args='', |
| 108 MIN_ARGS : 2, | 102 file_url_ok=True, |
| 109 # Max number of args required by this command, or NO_MAX. | 103 provider_url_ok=False, |
| 110 MAX_ARGS : NO_MAX, | 104 urls_start_arg=1, |
| 111 # Getopt-style string specifying acceptable sub args. | 105 gs_api_support=[ApiSelector.XML, ApiSelector.JSON], |
| 112 SUPPORTED_SUB_ARGS : '', | 106 gs_default_api=ApiSelector.JSON, |
| 113 # True if file URIs acceptable for this command. | 107 ) |
| 114 FILE_URIS_OK : True, | 108 # Help specification. See help_provider.py for documentation. |
| 115 # True if provider-only URIs acceptable for this command. | 109 help_spec = Command.HelpSpec( |
| 116 PROVIDER_URIS_OK : False, | 110 help_name='lifecycle', |
| 117 # Index in args of first URI arg. | 111 help_name_aliases=['getlifecycle', 'setlifecycle'], |
| 118 URIS_START_ARG : 1, | 112 help_type='command_help', |
| 119 } | 113 help_one_line_summary=( |
| 120 help_spec = { | 114 'Get or set lifecycle configuration for a bucket'), |
| 121 # Name of command or auxiliary help info for which this help applies. | 115 help_text=_DETAILED_HELP_TEXT, |
| 122 HELP_NAME : 'lifecycle', | 116 subcommand_help_text={'get': _get_help_text, 'set': _set_help_text}, |
| 123 # List of help name aliases. | 117 ) |
| 124 HELP_NAME_ALIASES : ['getlifecycle', 'setlifecycle'], | |
| 125 # Type of help) | |
| 126 HELP_TYPE : HelpType.COMMAND_HELP, | |
| 127 # One line summary of this help. | |
| 128 HELP_ONE_LINE_SUMMARY : 'Get or set lifecycle configuration for a bucket', | |
| 129 # The full help text. | |
| 130 HELP_TEXT : _detailed_help_text, | |
| 131 # Help text for sub-commands. | |
| 132 SUBCOMMAND_HELP_TEXT : {'get' : _get_help_text, | |
| 133 'set' : _set_help_text}, | |
| 134 } | |
| 135 | 118 |
| 136 # Get lifecycle configuration | |
| 137 def _GetLifecycleConfig(self): | |
| 138 # Wildcarding is allowed but must resolve to just one bucket. | |
| 139 uris = list(self.WildcardIterator(self.args[0]).IterUris()) | |
| 140 if len(uris) == 0: | |
| 141 raise CommandException('No URIs matched') | |
| 142 if len(uris) != 1: | |
| 143 raise CommandException('%s matched more than one URI, which is not\n' | |
| 144 'allowed by the %s command' % (self.args[0], self.command_name)) | |
| 145 uri = uris[0] | |
| 146 if not uri.names_bucket(): | |
| 147 raise CommandException('"%s" command must specify a bucket' % | |
| 148 self.command_name) | |
| 149 lifecycle_config = uri.get_lifecycle_config(False, self.headers) | |
| 150 # Pretty-print the XML to make it more easily human editable. | |
| 151 parsed_xml = xml.dom.minidom.parseString( | |
| 152 lifecycle_config.to_xml().encode('utf-8')) | |
| 153 sys.stdout.write(parsed_xml.toprettyxml(indent=' ')) | |
| 154 return 0 | |
| 155 | |
| 156 # Set lifecycle configuration | |
| 157 def _SetLifecycleConfig(self): | 119 def _SetLifecycleConfig(self): |
| 120 """Sets lifecycle configuration for a Google Cloud Storage bucket.""" |
| 158 lifecycle_arg = self.args[0] | 121 lifecycle_arg = self.args[0] |
| 159 uri_args = self.args[1:] | 122 url_args = self.args[1:] |
| 160 # Disallow multi-provider setlifecycle requests. | 123 # Disallow multi-provider 'lifecycle set' requests. |
| 161 storage_uri = self.UrisAreForSingleProvider(uri_args) | 124 if not UrlsAreForSingleProvider(url_args): |
| 162 if not storage_uri: | |
| 163 raise CommandException('"%s" command spanning providers not allowed.' % | 125 raise CommandException('"%s" command spanning providers not allowed.' % |
| 164 self.command_name) | 126 self.command_name) |
| 165 | 127 |
| 166 # Open, read and parse file containing XML document. | 128 # Open, read and parse file containing JSON document. |
| 167 lifecycle_file = open(lifecycle_arg, 'r') | 129 lifecycle_file = open(lifecycle_arg, 'r') |
| 168 lifecycle_txt = lifecycle_file.read() | 130 lifecycle_txt = lifecycle_file.read() |
| 169 lifecycle_file.close() | 131 lifecycle_file.close() |
| 170 lifecycle_config = LifecycleConfig() | 132 lifecycle = LifecycleTranslation.JsonLifecycleToMessage(lifecycle_txt) |
| 171 | 133 |
| 172 # Parse XML document and convert into LifecycleConfig object. | 134 # Iterate over URLs, expanding wildcards and setting the lifecycle on each. |
| 173 h = handler.XmlHandler(lifecycle_config, None) | 135 some_matched = False |
| 174 try: | 136 for url_str in url_args: |
| 175 xml.sax.parseString(lifecycle_txt, h) | 137 bucket_iter = self.GetBucketUrlIterFromArg(url_str, |
| 176 except xml.sax._exceptions.SAXParseException, e: | 138 bucket_fields=['lifecycle']) |
| 177 raise CommandException( | 139 for blr in bucket_iter: |
| 178 'Requested lifecycle config is invalid: %s at line %s, column %s' % | 140 url = blr.storage_url |
| 179 (e.getMessage(), e.getLineNumber(), e.getColumnNumber())) | 141 some_matched = True |
| 142 self.logger.info('Setting lifecycle configuration on %s...', blr) |
| 143 if url.scheme == 's3': |
| 144 self.gsutil_api.XmlPassThroughSetLifecycle( |
| 145 lifecycle_txt, url, provider=url.scheme) |
| 146 else: |
| 147 bucket_metadata = apitools_messages.Bucket(lifecycle=lifecycle) |
| 148 self.gsutil_api.PatchBucket(url.bucket_name, bucket_metadata, |
| 149 provider=url.scheme, fields=['id']) |
| 150 if not some_matched: |
| 151 raise CommandException('No URLs matched') |
| 152 return 0 |
| 180 | 153 |
| 181 # Iterate over URIs, expanding wildcards, and setting the lifecycle config | 154 def _GetLifecycleConfig(self): |
| 182 # on each. | 155 """Gets lifecycle configuration for a Google Cloud Storage bucket.""" |
| 183 some_matched = False | 156 bucket_url, bucket_metadata = self.GetSingleBucketUrlFromArg( |
| 184 for uri_str in uri_args: | 157 self.args[0], bucket_fields=['lifecycle']) |
| 185 for blr in self.WildcardIterator(uri_str): | 158 |
| 186 uri = blr.GetUri() | 159 if bucket_url.scheme == 's3': |
| 187 if not uri.names_bucket(): | 160 sys.stdout.write(self.gsutil_api.XmlPassThroughGetLifecycle( |
| 188 raise CommandException('URI %s must name a bucket for the %s command' | 161 bucket_url, provider=bucket_url.scheme)) |
| 189 % (str(uri), self.command_name)) | 162 else: |
| 190 some_matched = True | 163 if bucket_metadata.lifecycle and bucket_metadata.lifecycle.rule: |
| 191 self.logger.info('Setting lifecycle configuration on %s...', uri) | 164 sys.stdout.write(LifecycleTranslation.JsonLifecycleFromMessage( |
| 192 uri.configure_lifecycle(lifecycle_config, False, self.headers) | 165 bucket_metadata.lifecycle)) |
| 193 if not some_matched: | 166 else: |
| 194 raise CommandException('No URIs matched') | 167 sys.stdout.write('%s has no lifecycle configuration.\n' % bucket_url) |
| 195 | 168 |
| 196 return 0 | 169 return 0 |
| 197 | 170 |
| 198 # Command entry point. | |
| 199 def RunCommand(self): | 171 def RunCommand(self): |
| 172 """Command entry point for the lifecycle command.""" |
| 200 subcommand = self.args.pop(0) | 173 subcommand = self.args.pop(0) |
| 201 if subcommand == 'get': | 174 if subcommand == 'get': |
| 202 return self._GetLifecycleConfig() | 175 return self._GetLifecycleConfig() |
| 203 elif subcommand == 'set': | 176 elif subcommand == 'set': |
| 204 return self._SetLifecycleConfig() | 177 return self._SetLifecycleConfig() |
| 205 else: | 178 else: |
| 206 raise CommandException('Invalid subcommand "%s" for the %s command.' % | 179 raise CommandException('Invalid subcommand "%s" for the %s command.' % |
| 207 (subcommand, self.command_name)) | 180 (subcommand, self.command_name)) |
| OLD | NEW |