1# -*- coding: utf-8 -*-
2# Copyright 2012 Google Inc. All Rights Reserved.
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8#     http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15"""Implementation of website configuration command for buckets."""
16
17from __future__ import absolute_import
18
19import sys
20
21from apitools.base.py import encoding
22
23from gslib.command import Command
24from gslib.command_argument import CommandArgument
25from gslib.cs_api_map import ApiSelector
26from gslib.exception import CommandException
27from gslib.help_provider import CreateHelpText
28from gslib.third_party.storage_apitools import storage_v1_messages as apitools_messages
29from gslib.util import NO_MAX
30
31
32_SET_SYNOPSIS = """
33  gsutil web set [-m main_page_suffix] [-e error_page] bucket_url...
34"""
35
36_GET_SYNOPSIS = """
37  gsutil web get bucket_url
38"""
39
40_SYNOPSIS = _SET_SYNOPSIS + _GET_SYNOPSIS.lstrip('\n')
41
42_SET_DESCRIPTION = """
43<B>SET</B>
44  The "gsutil web set" command will allow you to configure or disable
45  Website Configuration on your bucket(s). The "set" sub-command has the
46  following options (leave both options blank to disable):
47
48<B>SET OPTIONS</B>
49  -m <index.html>      Specifies the object name to serve when a bucket
50                       listing is requested via the CNAME alias to
51                       c.storage.googleapis.com.
52
53  -e <404.html>        Specifies the error page to serve when a request is made
54                       for a non-existent object via the CNAME alias to
55                       c.storage.googleapis.com.
56
57"""
58
59_GET_DESCRIPTION = """
60<B>GET</B>
61  The "gsutil web get" command will gets the web semantics configuration for
62  a bucket and displays a JSON representation of the configuration.
63
64  In Google Cloud Storage, this would look like:
65
66    {
67      "notFoundPage": "404.html",
68      "mainPageSuffix": "index.html"
69    }
70
71"""
72
73_DESCRIPTION = """
74  The Website Configuration feature enables you to configure a Google Cloud
75  Storage bucket to behave like a static website. This means requests made via a
76  domain-named bucket aliased using a Domain Name System "CNAME" to
77  c.storage.googleapis.com will work like any other website, i.e., a GET to the
78  bucket will serve the configured "main" page instead of the usual bucket
79  listing and a GET for a non-existent object will serve the configured error
80  page.
81
82  For example, suppose your company's Domain name is example.com. You could set
83  up a website bucket as follows:
84
85  1. Create a bucket called example.com (see the "DOMAIN NAMED BUCKETS"
86     section of "gsutil help naming" for details about creating such buckets).
87
88  2. Create index.html and 404.html files and upload them to the bucket.
89
90  3. Configure the bucket to have website behavior using the command:
91
92       gsutil web set -m index.html -e 404.html gs://www.example.com
93
94  4. Add a DNS CNAME record for example.com pointing to c.storage.googleapis.com
95     (ask your DNS administrator for help with this).
96
97  Now if you open a browser and navigate to http://www.example.com, it will
98  display the main page instead of the default bucket listing. Note: It can
99  take time for DNS updates to propagate because of caching used by the DNS,
100  so it may take up to a day for the domain-named bucket website to work after
101  you create the CNAME DNS record.
102
103  Additional notes:
104
105  1. Because the main page is only served when a bucket listing request is made
106     via the CNAME alias, you can continue to use "gsutil ls" to list the bucket
107     and get the normal bucket listing (rather than the main page).
108
109  2. The main_page_suffix applies to each subdirectory of the bucket. For
110     example, with the main_page_suffix configured to be index.html, a GET
111     request for http://www.example.com would retrieve
112     http://www.example.com/index.html, and a GET request for
113     http://www.example.com/photos would retrieve
114     http://www.example.com/photos/index.html.
115
116  3. There is just one 404.html page: For example, a GET request for
117     http://www.example.com/photos/missing would retrieve
118     http://www.example.com/404.html, not
119     http://www.example.com/photos/404.html.
120
121  4. For additional details see
122     https://developers.google.com/storage/docs/website-configuration.
123
124  The web command has two sub-commands:
125""" + _SET_DESCRIPTION + _GET_DESCRIPTION
126
127_DETAILED_HELP_TEXT = CreateHelpText(_SYNOPSIS, _DESCRIPTION)
128
129_get_help_text = CreateHelpText(_GET_SYNOPSIS, _GET_DESCRIPTION)
130_set_help_text = CreateHelpText(_SET_SYNOPSIS, _SET_DESCRIPTION)
131
132
133class WebCommand(Command):
134  """Implementation of gsutil web command."""
135
136  # Command specification. See base class for documentation.
137  command_spec = Command.CreateCommandSpec(
138      'web',
139      command_name_aliases=['setwebcfg', 'getwebcfg'],
140      usage_synopsis=_SYNOPSIS,
141      min_args=2,
142      max_args=NO_MAX,
143      supported_sub_args='m:e:',
144      file_url_ok=False,
145      provider_url_ok=False,
146      urls_start_arg=1,
147      gs_api_support=[ApiSelector.XML, ApiSelector.JSON],
148      gs_default_api=ApiSelector.JSON,
149      argparse_arguments={
150          'set': [
151              CommandArgument.MakeZeroOrMoreCloudBucketURLsArgument()
152          ],
153          'get': [
154              CommandArgument.MakeNCloudBucketURLsArgument(1)
155          ]
156      }
157  )
158  # Help specification. See help_provider.py for documentation.
159  help_spec = Command.HelpSpec(
160      help_name='web',
161      help_name_aliases=['getwebcfg', 'setwebcfg'],
162      help_type='command_help',
163      help_one_line_summary=(
164          'Set a main page and/or error page for one or more buckets'),
165      help_text=_DETAILED_HELP_TEXT,
166      subcommand_help_text={'get': _get_help_text, 'set': _set_help_text},
167  )
168
169  def _GetWeb(self):
170    """Gets website configuration for a bucket."""
171    bucket_url, bucket_metadata = self.GetSingleBucketUrlFromArg(
172        self.args[0], bucket_fields=['website'])
173
174    if bucket_url.scheme == 's3':
175      sys.stdout.write(self.gsutil_api.XmlPassThroughGetWebsite(
176          bucket_url, provider=bucket_url.scheme))
177    else:
178      if bucket_metadata.website and (bucket_metadata.website.mainPageSuffix or
179                                      bucket_metadata.website.notFoundPage):
180        sys.stdout.write(str(encoding.MessageToJson(
181            bucket_metadata.website)) + '\n')
182      else:
183        sys.stdout.write('%s has no website configuration.\n' % bucket_url)
184
185    return 0
186
187  def _SetWeb(self):
188    """Sets website configuration for a bucket."""
189    main_page_suffix = None
190    error_page = None
191    if self.sub_opts:
192      for o, a in self.sub_opts:
193        if o == '-m':
194          main_page_suffix = a
195        elif o == '-e':
196          error_page = a
197
198    url_args = self.args
199
200    website = apitools_messages.Bucket.WebsiteValue(
201        mainPageSuffix=main_page_suffix, notFoundPage=error_page)
202
203    # Iterate over URLs, expanding wildcards and setting the website
204    # configuration on each.
205    some_matched = False
206    for url_str in url_args:
207      bucket_iter = self.GetBucketUrlIterFromArg(url_str, bucket_fields=['id'])
208      for blr in bucket_iter:
209        url = blr.storage_url
210        some_matched = True
211        self.logger.info('Setting website configuration on %s...', blr)
212        bucket_metadata = apitools_messages.Bucket(website=website)
213        self.gsutil_api.PatchBucket(url.bucket_name, bucket_metadata,
214                                    provider=url.scheme, fields=['id'])
215    if not some_matched:
216      raise CommandException('No URLs matched')
217    return 0
218
219  def RunCommand(self):
220    """Command entry point for the web command."""
221    action_subcommand = self.args.pop(0)
222    self.ParseSubOpts(check_args=True)
223    if action_subcommand == 'get':
224      func = self._GetWeb
225    elif action_subcommand == 'set':
226      func = self._SetWeb
227    else:
228      raise CommandException(('Invalid subcommand "%s" for the %s command.\n'
229                              'See "gsutil help web".') %
230                             (action_subcommand, self.command_name))
231    return func()
232