diff options
| author | Mark Otto <[email protected]> | 2014-01-20 22:12:02 -0600 |
|---|---|---|
| committer | Mark Otto <[email protected]> | 2014-01-20 22:12:02 -0600 |
| commit | e4f33a91d9969edbddc369230965b3a5628f24ac (patch) | |
| tree | ff3724fa51b93296c8b3b69e126fcded36159d16 | |
| parent | 317aa8092dcbf182ef3bdcc8d0222096fc6a51ea (diff) | |
| parent | ebc4f998dccf0d709a143348c02777d9c4695abb (diff) | |
| download | bootstrap-e4f33a91d9969edbddc369230965b3a5628f24ac.tar.xz bootstrap-e4f33a91d9969edbddc369230965b3a5628f24ac.zip | |
Merge branch 'master' of github.com:twbs/bootstrap
| -rw-r--r-- | Gruntfile.js | 15 | ||||
| -rw-r--r-- | docs/_includes/customizer-variables.html | 4 | ||||
| -rw-r--r-- | docs/_includes/footer.html | 12 | ||||
| -rw-r--r-- | docs/_includes/header.html | 10 | ||||
| -rw-r--r-- | docs/_includes/nav-customize.html | 80 | ||||
| -rw-r--r-- | docs/_includes/nav-main.html | 12 | ||||
| -rw-r--r-- | docs/components.html | 11 | ||||
| -rw-r--r-- | docs/customizer-nav.jade | 15 | ||||
| -rw-r--r-- | docs/customizer-variables.jade | 4 | ||||
| -rw-r--r-- | docs/getting-started.html | 2 | ||||
| -rw-r--r-- | docs/grunt/bs-glyphicons-data-generator.js | 6 | ||||
| -rw-r--r-- | docs/grunt/bs-lessdoc-parser.js | 6 | ||||
| -rw-r--r-- | docs/grunt/bs-raw-files-generator.js | 6 | ||||
| -rw-r--r-- | docs/index.html | 2 | ||||
| -rw-r--r-- | docs/javascript.html | 6 | ||||
| -rw-r--r-- | package.json | 2 | ||||
| -rw-r--r-- | test-infra/README.md | 100 |
17 files changed, 224 insertions, 69 deletions
diff --git a/Gruntfile.js b/Gruntfile.js index 8170a69a4..eaa985809 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -1,4 +1,10 @@ /* jshint node: true */ +/*! + * Bootstrap's Gruntfile + * http://getbootstrap.com + * Copyright 2013-2014 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + */ module.exports = function (grunt) { 'use strict'; @@ -286,7 +292,8 @@ module.exports = function (grunt) { } }, files: { - 'docs/_includes/customizer-variables.html': 'docs/customizer-variables.jade' + 'docs/_includes/customizer-variables.html': 'docs/customizer-variables.jade', + 'docs/_includes/nav-customize.html': 'docs/customizer-nav.jade' } } }, @@ -357,7 +364,7 @@ module.exports = function (grunt) { var testSubtasks = []; // Skip core tests if running a different subset of the test suite if (!process.env.TWBS_TEST || process.env.TWBS_TEST === 'core') { - testSubtasks = testSubtasks.concat(['dist-css', 'csslint', 'jshint', 'jscs', 'qunit', 'build-customizer-vars-form']); + testSubtasks = testSubtasks.concat(['dist-css', 'csslint', 'jshint', 'jscs', 'qunit', 'build-customizer-html']); } // Skip HTML validation if running a different subset of the test suite if (!process.env.TWBS_TEST || process.env.TWBS_TEST === 'validate-html') { @@ -401,8 +408,8 @@ module.exports = function (grunt) { grunt.registerTask('build-glyphicons-data', generateGlyphiconsData); // task for building customizer - grunt.registerTask('build-customizer', ['build-customizer-vars-form', 'build-raw-files']); - grunt.registerTask('build-customizer-vars-form', 'jade'); + grunt.registerTask('build-customizer', ['build-customizer-html', 'build-raw-files']); + grunt.registerTask('build-customizer-html', 'jade'); grunt.registerTask('build-raw-files', 'Add scripts/less files to customizer.', function () { var banner = grunt.template.process('<%= banner %>'); generateRawFilesJs(banner); diff --git a/docs/_includes/customizer-variables.html b/docs/_includes/customizer-variables.html index d03c3e6e8..3de586c7e 100644 --- a/docs/_includes/customizer-variables.html +++ b/docs/_includes/customizer-variables.html @@ -1,5 +1,5 @@ -<!-- NOTE: DO NOT EDIT THE FOLLOWING SECTION DIRECTLY! It is autogenerated via the `build-customizer-vars-form` Grunt task using the customizer-variables.jade template.--> +<!-- NOTE: DO NOT EDIT THE FOLLOWING SECTION DIRECTLY! It is autogenerated via the `build-customizer-html` Grunt task using the customizer-variables.jade template.--> <h2 id="colors">Colors</h2> <p>Gray and brand colors for use across Bootstrap.</p> <div class="row"> @@ -1683,4 +1683,4 @@ <p class="help-block">For <code>@screen-lg-min</code> and up.</p> </div> </div> -<!-- NOTE: DO NOT EDIT THE PRECEDING SECTION DIRECTLY! It is autogenerated via the `build-customizer-vars-form` Grunt task using the customizer-variables.jade template.-->
\ No newline at end of file +<!-- NOTE: DO NOT EDIT THE PRECEDING SECTION DIRECTLY! It is autogenerated via the `build-customizer-html` Grunt task using the customizer-variables.jade template.-->
\ No newline at end of file diff --git a/docs/_includes/footer.html b/docs/_includes/footer.html index 341dc3410..78e3b9f1d 100644 --- a/docs/_includes/footer.html +++ b/docs/_includes/footer.html @@ -12,11 +12,11 @@ <li>·</li> <li><a href="{{ site.repo }}">GitHub</a></li> <li>·</li> - <li><a href="{{ site.baseurl }}getting-started/#examples">Examples</a></li> + <li><a href="../getting-started/#examples">Examples</a></li> <li>·</li> - <li><a href="{{ site.baseurl }}2.3.2/">v2.3.2 docs</a></li> + <li><a href="../2.3.2/">v2.3.2 docs</a></li> <li>·</li> - <li><a href="{{ site.baseurl }}about/">About</a></li> + <li><a href="../about/">About</a></li> <li>·</li> <li><a href="{{ site.expo }}">Expo</a></li> <li>·</li> @@ -33,10 +33,10 @@ ================================================== --> <!-- Placed at the end of the document so the pages load faster --> <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script> -<script src="{{ site.baseurl }}dist/js/bootstrap.min.js"></script> -<script src="{{ site.baseurl }}assets/js/docs.min.js"></script> +<script src="../dist/js/bootstrap.min.js"></script> +<script src="../assets/js/docs.min.js"></script> {% if page.slug == "customize" %} -<script src="{{ site.baseurl }}assets/js/customize.min.js"></script> +<script src="../assets/js/customize.min.js"></script> {% endif %} {% comment %} diff --git a/docs/_includes/header.html b/docs/_includes/header.html index 8bb49f980..bc262e208 100644 --- a/docs/_includes/header.html +++ b/docs/_includes/header.html @@ -14,11 +14,11 @@ </title> <!-- Bootstrap core CSS --> -<link href="{{ site.baseurl }}dist/css/bootstrap.min.css" rel="stylesheet"> +<link href="../dist/css/bootstrap.min.css" rel="stylesheet"> <!-- Documentation extras --> -<link href="{{ site.baseurl }}assets/css/pack.min.css" rel="stylesheet"> -<!--[if lt IE 9]><script src="{{ site.baseurl }}assets/js/ie8-responsive-file-warning.js"></script><![endif]--> +<link href="../assets/css/pack.min.css" rel="stylesheet"> +<!--[if lt IE 9]><script src="../assets/js/ie8-responsive-file-warning.js"></script><![endif]--> <!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries --> <!--[if lt IE 9]> @@ -27,8 +27,8 @@ <![endif]--> <!-- Favicons --> -<link rel="apple-touch-icon-precomposed" sizes="144x144" href="{{ site.baseurl }}assets/ico/apple-touch-icon-144-precomposed.png"> - <link rel="shortcut icon" href="{{ site.baseurl }}assets/ico/favicon.ico"> +<link rel="apple-touch-icon-precomposed" sizes="144x144" href="../assets/ico/apple-touch-icon-144-precomposed.png"> + <link rel="shortcut icon" href="../assets/ico/favicon.ico"> <script> var _gaq = _gaq || []; diff --git a/docs/_includes/nav-customize.html b/docs/_includes/nav-customize.html index 363a5db8b..af8558fa1 100644 --- a/docs/_includes/nav-customize.html +++ b/docs/_includes/nav-customize.html @@ -1,40 +1,46 @@ -<li> - <a href="#less">Less components</a> -</li> -<li> - <a href="#plugins">jQuery plugins</a> -</li> -<li> - <a href="#less-variables">Less variables</a> + +<!-- NOTE: DO NOT EDIT THE FOLLOWING SECTION DIRECTLY! It is autogenerated via the `build-customizer-html` Grunt task using the customizer-nav.jade template.--> +<li><a href="#less">Less components</a></li> +<li><a href="#plugins">jQuery plugins</a></li> +<li><a href="#less-variables">Less variables</a> <ul class="nav"> - <li><a href="#variables-basics">Basics</a></li> - <li><a href="#variables-buttons">Buttons</a></li> - <li><a href="#variables-form-states">Form states</a></li> - <li><a href="#variables-alerts">Alerts</a></li> - <li><a href="#variables-navbar">Navbar</a></li> - <li><a href="#variables-nav">Nav</a></li> - <li><a href="#variables-tables">Tables</a></li> - <li><a href="#variables-forms">Forms</a></li> - <li><a href="#variables-dropdowns">Dropdowns</a></li> - <li><a href="#variables-panels-wells">Panels and wells</a></li> - <li><a href="#variables-accordion">Accordion</a></li> - <li><a href="#variables-badges">Badges</a></li> - <li><a href="#variables-breadcrumbs">Breadcrumbs</a></li> - <li><a href="#variables-jumbotron">Jumbotron</a></li> - <li><a href="#variables-modals">Modals</a></li> - <li><a href="#variables-carousel">Carousel</a></li> - <li><a href="#variables-list-group">List group</a></li> - <li><a href="#variables-thumbnails">Thumbnails</a></li> - <li><a href="#variables-progress">Progress bars</a></li> - <li><a href="#variables-pagination">Pagination</a></li> - <li><a href="#variables-pager">Pager</a></li> - <li><a href="#variables-labels">Labels</a></li> - <li><a href="#variables-tooltips-popovers">Tooltips and popovers</a></li> - <li><a href="#variables-close">Close button</a></li> - <li><a href="#variables-type">Type</a></li> - <li><a href="#variables-other">Other</a></li> + <li><a href="#colors">Colors</a></li> + <li><a href="#scaffolding">Scaffolding</a></li> + <li><a href="#typography">Typography</a></li> + <li><a href="#components">Components</a></li> + <li><a href="#tables">Tables</a></li> + <li><a href="#buttons">Buttons</a></li> + <li><a href="#forms">Forms</a></li> + <li><a href="#dropdowns">Dropdowns</a></li> + <li><a href="#media-queries-breakpoints">Media queries breakpoints</a></li> + <li><a href="#grid-system">Grid system</a></li> + <li><a href="#navbar">Navbar</a></li> + <li><a href="#navs">Navs</a></li> + <li><a href="#tabs">Tabs</a></li> + <li><a href="#pills">Pills</a></li> + <li><a href="#pagination">Pagination</a></li> + <li><a href="#pager">Pager</a></li> + <li><a href="#jumbotron">Jumbotron</a></li> + <li><a href="#form-states-and-alerts">Form states and alerts</a></li> + <li><a href="#tooltips">Tooltips</a></li> + <li><a href="#popovers">Popovers</a></li> + <li><a href="#labels">Labels</a></li> + <li><a href="#modals">Modals</a></li> + <li><a href="#alerts">Alerts</a></li> + <li><a href="#progress-bars">Progress bars</a></li> + <li><a href="#list-group">List group</a></li> + <li><a href="#panels">Panels</a></li> + <li><a href="#thumbnails">Thumbnails</a></li> + <li><a href="#wells">Wells</a></li> + <li><a href="#badges">Badges</a></li> + <li><a href="#breadcrumbs">Breadcrumbs</a></li> + <li><a href="#carousel">Carousel</a></li> + <li><a href="#close">Close</a></li> + <li><a href="#code">Code</a></li> + <li><a href="#type">Type</a></li> + <li><a href="#miscellaneous">Miscellaneous</a></li> + <li><a href="#container-sizes">Container sizes</a></li> </ul> </li> -<li> - <a href="#download">Download</a> -</li> +<li><a href="#download">Download</a></li> +<!-- NOTE: DO NOT EDIT THE PRECEDING SECTION DIRECTLY! It is autogenerated via the `build-customizer-html` Grunt task using the customizer-nav.jade template.-->
\ No newline at end of file diff --git a/docs/_includes/nav-main.html b/docs/_includes/nav-main.html index 5b1b89744..6c5567e91 100644 --- a/docs/_includes/nav-main.html +++ b/docs/_includes/nav-main.html @@ -7,24 +7,24 @@ <span class="icon-bar"></span> <span class="icon-bar"></span> </button> - <a href="{{ site.baseurl }}" class="navbar-brand">Bootstrap</a> + <a href="../" class="navbar-brand">Bootstrap</a> </div> <nav class="collapse navbar-collapse bs-navbar-collapse" role="navigation"> <ul class="nav navbar-nav"> <li{% if page.slug == "getting-started" %} class="active"{% endif %}> - <a href="{{ site.baseurl }}getting-started">Getting started</a> + <a href="../getting-started">Getting started</a> </li> <li{% if page.slug == "css" %} class="active"{% endif %}> - <a href="{{ site.baseurl }}css">CSS</a> + <a href="../css">CSS</a> </li> <li{% if page.slug == "components" %} class="active"{% endif %}> - <a href="{{ site.baseurl }}components">Components</a> + <a href="../components">Components</a> </li> <li{% if page.slug == "js" %} class="active"{% endif %}> - <a href="{{ site.baseurl }}javascript">JavaScript</a> + <a href="../javascript">JavaScript</a> </li> <li{% if page.slug == "customize" %} class="active"{% endif %}> - <a href="{{ site.baseurl }}customize">Customize</a> + <a href="../customize">Customize</a> </li> </ul> <ul class="nav navbar-nav navbar-right"> diff --git a/docs/components.html b/docs/components.html index 259354e7f..2f562908c 100644 --- a/docs/components.html +++ b/docs/components.html @@ -1254,9 +1254,14 @@ lead: "Over a dozen reusable components built to provide iconography, dropdowns, <h2 id="navbar-default">Default navbar</h2> <p>Navbars are responsive meta components that serve as navigation headers for your application or site. They begin collapsed (and are toggleable) in mobile views and become horizontal as the available viewport width increases.</p> - <div class="bs-callout bs-callout-info"> - <h4>Customize the collapsing point</h4> - <p>Depending on the content in your navbar, you might need to change the point at which your navbar switches between collapsed and horizontal mode. Customize the <code>@grid-float-breakpoint</code> variable or add your own media query.</p> + <div class="bs-callout bs-callout-warning" id="callout-navbar-overflow"> + <h4>Overflowing content</h4> + <p>Since Bootstrap doesn't know how much space the content in your navbar needs, you might run into issues with content wrapping into a second row. To resolve this, you can:</p> + <ol type="a"> + <li>Reduce the amount or width of navbar items.</li> + <li>Hide certain navbar items at certain screen sizes using <a href="../css/#responsive-utilities">responsive utility classes</a>.</li> + <li>Change the point at which your navbar switches between collapsed and horizontal mode. Customize the <code>@grid-float-breakpoint</code> variable or add your own media query.</li> + </ol> </div> <div class="bs-callout bs-callout-danger"> <h4>Requires JavaScript</h4> diff --git a/docs/customizer-nav.jade b/docs/customizer-nav.jade new file mode 100644 index 000000000..c4f6ddf10 --- /dev/null +++ b/docs/customizer-nav.jade @@ -0,0 +1,15 @@ +// NOTE: DO NOT EDIT THE FOLLOWING SECTION DIRECTLY! It is autogenerated via the `build-customizer-html` Grunt task using the customizer-nav.jade template. +li + a(href='#less') Less components +li + a(href='#plugins') jQuery plugins +li + a(href='#less-variables') Less variables + ul.nav + each section in sections + if section.customizable + li + a(href='#'+section.id)= section.heading +li + a(href='#download') Download +// NOTE: DO NOT EDIT THE PRECEDING SECTION DIRECTLY! It is autogenerated via the `build-customizer-html` Grunt task using the customizer-nav.jade template. diff --git a/docs/customizer-variables.jade b/docs/customizer-variables.jade index 19f3672b0..3c74573be 100644 --- a/docs/customizer-variables.jade +++ b/docs/customizer-variables.jade @@ -1,4 +1,4 @@ -// NOTE: DO NOT EDIT THE FOLLOWING SECTION DIRECTLY! It is autogenerated via the `build-customizer-vars-form` Grunt task using the customizer-variables.jade template. +// NOTE: DO NOT EDIT THE FOLLOWING SECTION DIRECTLY! It is autogenerated via the `build-customizer-html` Grunt task using the customizer-variables.jade template. each section in sections if section.customizable h2(id=section.id)= section.heading @@ -18,4 +18,4 @@ each section in sections data-var=variable.name) if variable.docstring p.help-block!= variable.docstring.html -// NOTE: DO NOT EDIT THE PRECEDING SECTION DIRECTLY! It is autogenerated via the `build-customizer-vars-form` Grunt task using the customizer-variables.jade template. +// NOTE: DO NOT EDIT THE PRECEDING SECTION DIRECTLY! It is autogenerated via the `build-customizer-html` Grunt task using the customizer-variables.jade template. diff --git a/docs/getting-started.html b/docs/getting-started.html index f8eb3db3c..a055857ae 100644 --- a/docs/getting-started.html +++ b/docs/getting-started.html @@ -115,7 +115,7 @@ bootstrap/ <p>Copy the HTML below to begin working with a minimal Bootstrap document.</p> {% highlight html %} <!DOCTYPE html> -<html> +<html lang="en"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> diff --git a/docs/grunt/bs-glyphicons-data-generator.js b/docs/grunt/bs-glyphicons-data-generator.js index 2ecd54393..3e32698a1 100644 --- a/docs/grunt/bs-glyphicons-data-generator.js +++ b/docs/grunt/bs-glyphicons-data-generator.js @@ -1,4 +1,10 @@ /* jshint node: true */ +/*! + * Bootstrap Grunt task for Glyphicons data generation + * http://getbootstrap.com + * Copyright 2014 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + */ var fs = require('fs') diff --git a/docs/grunt/bs-lessdoc-parser.js b/docs/grunt/bs-lessdoc-parser.js index 50c6ebbd3..9d0b2ffaf 100644 --- a/docs/grunt/bs-lessdoc-parser.js +++ b/docs/grunt/bs-lessdoc-parser.js @@ -1,4 +1,10 @@ /* jshint node: true */ +/*! + * Bootstrap Grunt task for parsing Less docstrings + * http://getbootstrap.com + * Copyright 2014 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + */ var markdown = require('markdown').markdown; diff --git a/docs/grunt/bs-raw-files-generator.js b/docs/grunt/bs-raw-files-generator.js index 722c42d7d..255508b7f 100644 --- a/docs/grunt/bs-raw-files-generator.js +++ b/docs/grunt/bs-raw-files-generator.js @@ -1,4 +1,10 @@ /* jshint node: true */ +/*! + * Bootstrap Grunt task for generating raw-files.min.js for the Customizer + * http://getbootstrap.com + * Copyright 2014 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + */ var btoa = require('btoa') // jshint ignore:line var fs = require('fs') diff --git a/docs/index.html b/docs/index.html index 8f36b3417..ac790176f 100644 --- a/docs/index.html +++ b/docs/index.html @@ -50,7 +50,7 @@ title: Bootstrap <div class="bs-featurette"> <div class="container"> <h2 class="bs-featurette-title">Built with Bootstrap.</h2> - <p class="lead">Millions of amazing sites across the web are being built with Bootstrap. Get started on your own with our growing <a href="{{ site.url }}/getting-started/#examples">collection of examples</a> or by exploring some of our favorites.</p> + <p class="lead">Millions of amazing sites across the web are being built with Bootstrap. Get started on your own with our growing <a href="../getting-started/#examples">collection of examples</a> or by exploring some of our favorites.</p> <hr class="half-rule"> diff --git a/docs/javascript.html b/docs/javascript.html index 323f1f684..7d41a0378 100644 --- a/docs/javascript.html +++ b/docs/javascript.html @@ -102,6 +102,10 @@ $('#myModal').on('show.bs.modal', function (e) { <h4>Overlapping modals not supported</h4> <p>Be sure not to open a modal while another is still visible. Showing more than one modal at a time requires custom code.</p> </div> + <div class="bs-callout bs-callout-warning" id="callout-modal-markup-placement"> + <h4>Modal markup placement</h4> + <p>Always try to place a modal's HTML code in a top-level position in your document to avoid other components affecting the modal's appearance and/or functionality.</p> + </div> <div class="bs-callout bs-callout-warning"> <h4>Mobile device caveats</h4> <p>There are some caveats regarding using modals on mobile devices. <a href="../getting-started/#support-fixed-position-keyboards">See our browser support docs</a> for details.</p> @@ -1366,7 +1370,7 @@ $('#myPopover').on('hidden.bs.popover', function () { <h3>Markup</h3> <p>Just add <code>data-dismiss="alert"</code> to your close button to automatically give an alert close functionality.</p> - {% highlight html %}<a class="close" data-dismiss="alert" href="#" aria-hidden="true">×</a>{% endhighlight %} + {% highlight html %}<button type="button" class="close" data-dismiss="alert" aria-hidden="true">×</button>{% endhighlight %} <h3>Methods</h3> diff --git a/package.json b/package.json index 4d73b69d1..14e0400aa 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,7 @@ "grunt-contrib-jshint": "~0.8.0", "grunt-contrib-less": "~0.9.0", "grunt-contrib-qunit": "~0.4.0", - "grunt-contrib-uglify": "~0.2.7", + "grunt-contrib-uglify": "~0.3.0", "grunt-contrib-watch": "~0.5.3", "grunt-csscomb": "~2.0.1", "grunt-html-validation": "~0.1.13", diff --git a/test-infra/README.md b/test-infra/README.md new file mode 100644 index 000000000..75b2fbcd9 --- /dev/null +++ b/test-infra/README.md @@ -0,0 +1,100 @@ +## What does `s3_cache.py` do? + +### In general +`s3_cache.py` maintains a cache, stored in an Amazon S3 (Simple Storage Service) bucket, of a given directory whose contents are considered non-critical and are completely & solely determined by (and should be able to be regenerated from) a single given file. + +The SHA-256 hash of the single file is used as the key for the cache. The directory is stored as a gzipped tarball. + +All the tarballs are stored in S3's Reduced Redundancy Storage (RRS) storage class, since this is cheaper and the data is non-critical. + +`s3_cache.py` itself never deletes cache entries; deletion should either be done manually or using automatic S3 lifecycle rules on the bucket. + +Similar to git, `s3_cache.py` makes the assumption that [SHA-256 will effectively never have a collision](http://stackoverflow.com/questions/4014090/is-it-safe-to-ignore-the-possibility-of-sha-collisions-in-practice). + + +### For Bootstrap specifically +`s3_cache.py` is used to cache the npm packages that our Grunt tasks depend on and the RubyGems that Jekyll depends on. (Jekyll is needed to compile our docs to HTML so that we can run them thru an HTML5 validator.) + +For npm, the `node_modules` directory is cached based on our `package.json` file. + +For RubyGems, the `gemdir` of the current RVM-selected Ruby is cached based on the `pseudo_Gemfile.lock` file generated by our Travis build script. +`pseudo_Gemfile.lock` contains the versions of Ruby and Jekyll that we're using (read our `.travis.yml` for details). + + +## Why is `s3_cache.py` necessary? +`s3_cache.py` is used to speed up Bootstrap's Travis builds. Installing npm packages and RubyGems used to take up a significant fraction of our total build times. Also, at the time that `s3_cache.py` was written, npm was occasionally unreliable. + +Travis does offer built-in caching on their paid plans, but this do-it-ourselves S3 solution is significantly cheaper since we only need caching and not Travis' other paid features. + + +## Setup + +### Overview +1. Create an Amazon Web Services (AWS) account. +2. Create an Identity & Access Management (IAM) user, and note their credentials. +3. Create an S3 bucket. +4. Set permissions on the bucket to grant the user read+write access. +5. Set the user credentials as secure Travis environment variables. + +### In detail +1. Create an AWS account. +2. Login to the [AWS Management Console](https://console.aws.amazon.com). +3. Go to the IAM Management Console. +4. Create a new user (named e.g. `travis-ci`) and generate an access key for them. Note both the Access Key ID and the Secret Access Key. +5. Note the user's ARN (Amazon Resource Name), which can be found in the "Summary" tab of the user browser. This will be of the form: `arn:aws:iam::XXXXXXXXXXXXXX:user/the-username-goes-here` +6. Note the user's access key, which can be found in the "Security Credentials" tab of the user browser. +7. Go to the S3 Management Console. +8. Create a new bucket. For a non-publicly-accessible bucket (like Bootstrap uses), it's recommended that the bucket name be random to increase security. On most *nix machines, you can easily generate a random UUID to use as the bucket name using Python: + + ```bash + python -c "import uuid; print(uuid.uuid4())" + ``` + +9. Determine and note what your bucket's ARN is. The ARN for an S3 bucket is of the form: `arn:aws:s3:::the-bucket-name-goes-here` +10. In the bucket's Properties pane, in the "Permissions" section, click the "Edit bucket policy" button. +11. Input and submit an IAM Policy that grants the user at least read+write rights to the bucket. AWS has a policy generator and some examples to help with crafting the policy. Here's the policy that Bootstrap uses, with the sensitive bits censored: + + ```json + { + "Version": "2012-10-17", + "Id": "PolicyTravisReadWriteNoAdmin", + "Statement": [ + { + "Sid": "StmtXXXXXXXXXXXXXX", + "Effect": "Allow", + "Principal": { + "AWS": "arn:aws:iam::XXXXXXXXXXXXXX:user/travis-ci" + }, + "Action": [ + "s3:AbortMultipartUpload", + "s3:GetObjectVersion", + "s3:ListBucket", + "s3:DeleteObject", + "s3:DeleteObjectVersion", + "s3:GetObject", + "s3:PutObject" + ], + "Resource": [ + "arn:aws:s3:::XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX", + "arn:aws:s3:::XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX/*" + ] + } + ] + } + ``` + +12. If you want deletion from the cache to be done automatically based on age (like Bootstrap does): In the bucket's Properties pane, in the "Lifecycle" section, add a rule to expire/delete files based on creation date. +13. Install the [`travis` RubyGem](https://github.com/travis-ci/travis): `gem install travis` +14. Encrypt the environment variables: + + ```bash + travis encrypt --repo twbs/bootstrap "AWS_ACCESS_KEY_ID=XXX" + travis encrypt --repo twbs/bootstrap "AWS_SECRET_ACCESS_KEY=XXX" + travis encrypt --repo twbs/bootstrap "TWBS_S3_BUCKET=XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX" + ``` + +14. Add the resulting secure environment variables to `.travis.yml`. + + +## Usage +Read `s3_cache.py`'s source code and Bootstrap's `.travis.yml` for how to invoke and make use of `s3_cache.py`. |
