Bracket is a tool for prototyping, building, staging, maintaining, and launching marketing websites at Google. It is built on Flask, uses the App Engine SDK for local development, App Engine ~in the cloud~ for staging work internally, Kintaro for editing and localizing content, and comes with build tools for generating production ready static sites on SCS soooo...
Before you do anything, make sure you have the following all installed on your machine and good to go:
gcloud components install app-engine-python app-engine-python-extras
A common list of issues can be found here.
If you're still having troubles, ask g/bracket-users-external and update the document with your findings.
To get started on a new bracket project, first you must install the Bracket CLI globally on your machine. You might encounter a couple password prompts during the install:
npm install -g git+https://webmaster.googlesource.com/bracket/
At this point you should have the bracket
command available on your machine,
test it out by printing out the help page:
bracket -h
If you get a Santa error while installing bracket, temporarily drop down to minimally-protected mode at go/upvote/hosts, then run the install command again.
Now, to start a new project, use the init command to start a new Bracket project. It will ask you a series of questions to help you get all set up:
bracket init my-project
Once you've finished the prompts, login, hop into your project, spin up a dev server and get to coding, nerd:
gcloud auth application-default login
cd my-project
npm run dev
Now that that you've got your git repository all sorted, you can spin up a local dev server.
First, login: language-bash gcloud auth application-default login
This will cause a browser window to pop up. Choose your account, then run the
npm run dev
script to spin up the local dev server along with
your front end compilers, linters, and watchers:
npm run dev
Here you should see some terminal output that ends something like this:
[19:06:08] Starting 'serve'...
[19:06:08] Finished 'serve' after 17 ms
[19:06:08] Starting 'watch'...
[BS] Proxying: http://localhost:8080
[BS] Access URLs:
---------------------------------------
Local: http://localhost:3000/
External: http://172.31.43.209:3000/
---------------------------------------
UI: http://localhost:3001
UI External: http://172.31.43.209:3001
---------------------------------------
That means we now have a dev server listening on localhost:3000 which is proxying the App Engine dev server on localhost:8080. Go ahead and make a request in your browser to localhost:3000 and you should see the bracket docs page render. Seeing a docs page is probably not what you are after, but don't worry, this is just a default top level handler for when you haven't defined your own. help
Now we need to make some routes and pages of our own. To create a new page and
route with bracket, you need to first include it in the src/meta.py
file.
This file's job is to create a variable named meta
that defines a dictionary
keyed by every page of your site (including locales). In this file you should
already see some simple logic building this dictionary from a list of locales
and routes.
To configure a new page/route add it to the routes list:
paths = [
'/' # Make sure this line is uncommented.
'/demo/'
]
The second part to adding a page is to pair that route to a Python handler and
template in the src/routes.py
.
When you open this file, you'll see a couple things going on. At the top of the
file we import flask
, import bracket
, then we import a couple things from
inside the bracket
package. Flask is the web
framework we use to register handlers, and
Bracket
is the package that initializes and
configures our Flask instance.
Underneath that, you should see a couple lines of code commented out:
help
# @root.route('/')
# @intl.route('/')
# def index():
# """Return a polite greeting to the user."""
#
# return flask.render_template('page.jinja',
# greeting='Hey there, ya big ole nerd!'
# )
Here is the same code annotated:
# The two lines starting with @ are called decorators. More specifically,
# these are Flask `blueprints` that define a method named `route` which is used
# to bind python functions to URL routes.
#
# The blueprint @root registers the handler to non-intl paths, and the @intl
# one registers, you guessed it, /intl/<locale>/ style paths.
@root.route('/')
@intl.route('/')
# This is the handler function declaration. The name does not matter since we
# are using the decorator pattern.
def index():
"""Return a polite greeting to the user.""" # Python docstring, hallelujah.
# This line returns our page content from the server. If you aren't
# familiar with flask, the `render_template` function takes a path
# relative to the `/src/` directory, looks up your template with it,
# and renders it with any subsequent arguments passed in.
#
# Some globals are automatically passed in as well but we can get to that
# later.
return flask.render_template('index.jinja',
greeting='Hey there, ya big ole nerd!'
)
Uncomment these lines and hit your browser again at localhost:3000 to see your wonderful new custom page.
Problem: You generating the list of routes in src/meta.py
based on Kintaro
data, but when you visit a route that should exist, you get a 404.
Solution: You are probably specifying the static path in meta.py. This is
deprecated. Remove the /src/static/
entry from your meta.py
meta object.
Then, set the static path in env.default.yaml
instead.
Now I'm sure you're wondering where all that weird styling came from. Not to
worry, lets just have a look at the template file at src/index.jinja
.
There isn't much in this file, but up at top you should see a line of code:
{% extends 'b/layouts/base.jinja' %}
That line makes the template extend another template hidden in the Bracket code base. Remove this line and replace it with a path to your own layout template to get a truly fresh start.
Looking further, you should notice another odd line of code at the end of the
src/index.jinja
file:
{% include 'b/utils.jinja' %}
Out of the box, Bracket declares a handful of nifty variables for you that can be used in your templates and route handlers to accomplish common tasks like generating intl paths, relative paths, referencing the current locale or printing l10n data.
These variables are available in template code automatically, under the g
namespace. In python handlers, this same variable is attached to the flask
context at flask.g
.
The b/utils.jinja
template template will print an off-canvas table containing
all the current global template variables if a ?props
url parameter is found
in the request, otherwise it's a noop
template. Test it out and see by
visiting localhost:3000/?props. Clicking the
bottom right icon should reveal the page properties rendered inside of an
iframe.
Pretty much all websites need a way to serve static files, and Bracket sites are
no different. By default, the src/static/
directory is served at the url
/static/
and needs no configuration, just drop files into this directory and
link to them from your markup with root relative urls.
Bracket also comes set up with a handy template filter for generating cache
invalidating urls for files in your static directory. Just give the |static
filter a path to your file, relative to the static directory, and it will
intelligently generate the client accessible url to the file along with an sha1
hash of the file contents as a query parameter.
For example:
<link href="{{ 'css/index.min.css'|static }}" rel="stylesheet">
<script src="{{ 'js/index.min.js'|static }}" defer></script>
Will output:
<link href="/static/css/index.min.css?cache=bbabe3f" rel="stylesheet">
<script src="/static/js/index.min.js?cache=da74462" defer></script>
This filter also performs some handy localization logic. Before generating the
cacheable url, the filter will look for localized images in the form of
my-file.<locale>.jpg
before falling back to the given file path. The fallback
functions the same as SCS fallbacks do by looking for the full locale code
first, then geo specific files, then lang files, then root.
Code Example (line #8)
Locale Demo
Geo Demo
Lang Demo
Root Demo
It's likely, also, that the /static/**/*
path won't work on your site, for
instance if your site needs to live at a subdirectory of google.com, like
www.google.com/my/cool/site/
. To change the serving url, change
env.default.yaml file to have a 'static_path' attribute:
static_path: /my/cool/site/static/directory/
Adding this new attribute will cause the static file filter to render a path to
/my/cool/site/static/directory/**/*
rather than just /static/**/*
.
By default, Bracket caches all content fetched over the network to make development cycles faster.
The exact caching policy depends on the environment:
Environment | Description |
---|---|
development |
Apps running locally in the development SDK. In this environment, data (including kcs content) is cached to memcache and the local datastore indefinitely for snappier code/refresh cycles, under the assumption that content won't be changing very often, or won't matter. Stopping the server and deleting the |
staging |
Apps running on hosted app engine instances. In this environment, data (including kcs content) is cached for 5 seconds. This is so content updates are visible quickly. |
Bracket makes flushing the cache in any environment easy as pie, To do that just
add a ?flush
parameter to a Bracket page request. This will remove the
entire cache from memory and disk before any handlers are executed.
If you are a control freak that wants to manage the cache with more granularity,
you can set a cache_ttl
key in your environment's yaml file to configure the
number of seconds to cache content (0 meaning don't cache).
If you are a control freak and don't believe in configuration files, you can also manage cache entries directly in the App Engine Admin Console at localhost:8000/datastore. From there, you can add/edit/remove things from the datastore to your hearts desire. Just make sure to flush memcache as well in order to see fresh content.
Bracket was primarily made to make building and launching public marketing projects easier, but it turns out that it is also pretty nifty for creating internal sites hosted on App Engine too.
Some of the concepts in Bracket aren't completely relevant or necessary outside
the context of static production sites though. Namely the locale and route
validation Bracket does against the configuration in the meta.py
file.
If you don't need to do a static deployment, and you'd rather not configure
locales and pages and just use Flask's dynamic router, you can set a special
cowboy_mode
key to True
in the meta.py
file to turn off the strict
page/locale validation, like so:
# Declare the meta configuration object.
meta = {
'pages': pages,
'cowboy_mode': True
}
From then on, registering routes only needs to be done via the Flask router, in
the routes.py
file, or wherever else you bind your Python handlers.
Now that you've got your new hot page up and going, you can probably get prototyping just fine without the need of a Content Management System. Eventually though, the time will come when you'll want to give someone non technical the ability to make edits to copy and image assets, and even later, you might want to translate your content into different languages.
This is where Kintaro comes in. You may already be familiar with the Kintaro Content Server, so I won't cover much here but the gist is that it is an app that stores and translates content that also has a REST API. Official documentation for KCS can be found here.
Bracket will use your individual user credentials to fetch content from KCS. To
set this up, first run gcloud auth application-default login
. Then, add an
entry for use_local_credentials: true
in your env.default.yaml
.
The Bracket service account, bracket.google.com@appspot.gserviceaccount.com
,
has been deprecated. If you are still using it, you should set up KPS
and remove it from your KCS permissions ASAP as the functionality will be
completely removed from an upcoming release for security reasons.
Included as part of Bracket, is a slim and simple Python API client for
connecting to Kintaro. Currently the client only supports 3 methods,
document()
, collection()
, and batch()
:
This method will fetch and return exactly 1 KCS document.
Args:
document_id
: Numeric id for the document to fetch.collection_id
: The collection in which the document lives.repo_id
: Repository where the document lives.project_id
(optional): Defaults to the value set in the env.*.yaml
file.
If thats not found, defaults to published
.locale
(optional): Defaults to the current request locale.depth
(optional): Determines the depth at which to resolve reference
fields. A depth = 1 would resolve only reference fields directly in the
document requested. Defaults to 0.Returns:
A single document from KCS.
Example:
from bracket import kcs
# Fetches a document.
hero = kcs.document(
document_id='4618717635805184',
collection_id='LandingPageHero',
repo_id='verily',
)
This method will fetch and return all documents for a KCS collection.
Args:
collection_id
: The collection in which the document lives.repo_id
: Repository where the document lives.project_id
(optional): Defaults to the value set in the env.*.yaml
file.
If thats not found, defaults to published
.locale
(optional): Defaults to the current request locale.query
(optional): Querystring for KCS, in the same format as Goro's kcs
filter.sorts
(optional): Sequence of fields to sort by, optionally prefixed by
'-' to sort descending.Returns:
A KCS collection
Example:
from bracket import kcs
# Fetches a KCS collection and returns it as a list.
press = kcs.collection(
collection_id='LandingPagePressArticles',
repo_id='verily',
)
# Filter for articles by a given set of authors that are live and order by
# author name (ascending) and then creation date descending.
press = kcs.collection(
collection_id='LandingPagePressArticles',
repo_id='verily',
query='(author__email = "page@google.com" or author__email = "nmarrow@google.com") and live = true',
sorts=('__meta__created_by', '-__meta__created_on'),
)
This method will fetch concurrently, both documents and/or collections.
Args:
document_id
(optional): Numeric id for the document to fetch. If this
argument is left out, the entire collection is fetched.collection_id
: The collection in which the document lives.repo_id
: Repository where the document lives.project_id
(optional): Defaults to the value set in the env.*.yaml
file. If thats not found, defaults to published
.locale
(optional): Defaults to the current request locale.Returns:
A list of results that corresponds in order to the list of objects passed in.
Example:
from bracket import kcs
# Fetches 2 documents and 3 collections concurrently using threads.
hero, articles, press, people, footer = kcs.batch([
{
'document_id': '4618717635805184',
'collection_id': 'LandingPageHero',
'repo_id': 'verily',
},
{
'collection_id': 'LandingPageArticles',
'repo_id': 'verily',
},
{
'collection_id': 'LandingPagePressArticles',
'repo_id': 'verily',
},
{
'collection_id': 'People',
'repo_id': 'verily',
},
{
'document_id': '5987066567458816',
'collection_id': 'Footer',
'repo_id': 'verily',
}
])
Scribe is a new workflow in Kintaro that provides site preview, field discovery, and lightweight content updates within a consistent user interface.
One workflow in particular revolves around enabling non-technical users (NTUs) to add new Kintaro content to a site without any Bracket template or code changes.
In order to make this possible, you need to:
When NTUs add new Kintaro content, it should automatically appear in the preview instance without any changes to Bracket template or Python code.
In order to enable this, you need to structure your Kintaro schemas and implement the templates and routes in a certain way:
{% for %}
loopFor each page / route, you need to specify the appropriate Kintaro collections and repeated fields users should be able to add to in Scribe.
Bracket provides a simple Python API function to accomplish this:
kcs.register_scribe_addable_content
.
This method will register given Collections and repeated fields as Scribe-addable, and any such content will then be listed in the Scribe sidebar
Args:
collections
: List of Kintaro collections (fetched using
kcs.collection()
).fields
: List of Kintaro repeated fields (fetched using kcs.document()
).Returns:
None
Example:
from bracket import kcs
from bracket import root
import flask
@root.route('/')
def index():
# Fetches a document.
hero = kcs.document(
document_id='4618717635805184',
collection_id='LandingPageHero',
repo_id='verily',
)
# Fetches a KCS collection.
press = kcs.collection(
collection_id='LandingPagePressArticles',
repo_id='verily',
)
kcs.register_scribe_addable_content(
collections=[press],
fields=[hero['paragraphs']])
return flask.render_template(...)
For your convenience, the collections
argument takes in an iterable of Python
dicts that you get from calling kcs.collection
and derives the appropriate
repo_id
, project_id
, and collection_id
from the dicts.
Similarly, the fields
argument takes in an iterable of
kcs_document.ContentList
entities that you get from calling kcs.document
and
indexing into the appropriate repeated field. In our particular example, the
hero
document has a repeated TextField called "paragraphs"
, and
hero['paragraphs']
is a kcs_document.ContentList
entity. Under the hood, we
use the ContentList
attributes to automatically derive the Kintaro field uris
to send over to Scribe.
For performance reasons, we do not fetch the corresponding schemas for each Kintaro field. We derive schema information based on existing content. If you are registering a repeated reference field, each possible collection that could be referenced by the reference fields must have at least 1 reference field that already points to that collection. Otherwise, that collection will not be addable.
We want to optimize the workflow for NTUs to add new content by minimizing the number of steps required to add new content. In the case of repeated reference fields, we show NTUs the corresponding Add New document page. When the new document is saved, a corresponding reference field is automatically appended to the repeated reference field pointing to the new document.
Pushing your work to App Engine is pretty simple, but you have to take care of four small steps before you can start sharing URLs.
bracket.appId
field in package.json
is set to your
App Engine Project ID.bracket push <version>
, where
version is just a string to identify a particular snapshot of your code.
Follow any prompts and select us-central
if it asks you to select a
region. It should end with a url pointing to your newly deployed App Engine
app.master
and
develop
, but you are free to use any identifiers you like.Bracket sites using KCS content must be set up for Kintaro Preview Server (KPS). KPS allows you to see a fully rendered preview of your site before pushing content, as well as unlocking editing capabilities via Kintaro Scribe, making it easy to correlate content on a rendered site page and content in the Kintaro content management system.
You can read more about the features that the Preview Server enables in this document, which gives an overview of what a complete Kintaro project should look like.
After setting up your Bracket site for KPS, you must access it through https://<App Engine Project ID>.proxy.preview.kintaro.goog instead of the appspot.com URLs.
KPS setup is now part of the bracket init
process. However, if you created
your site before this update, here's how to set it up:
Full instructions. tl;dr:
gcloud auth application-default login
.virtualenv <folder_name (should not exist yet)>
source <folder_name>/bin/activate pip install
git+https://kintaro.googlesource.com/preview_scripts deactivate
alias
kps_set_up_preview_client="<folder_name>/bin/kps_set_up_preview_client"
kps_set_up_preview_client --run_wizard
--preview_client_id=<App Engine Project ID> --app_path=<full local path to
your project folder>
KPS automatically grants viewing access to your Bracket project on App Engine to all users who can view the KCS site and KCS workspace specified in the project env.<staging/default>.yaml + URL.
If you would like to give users access to push new code to your App Engine project, go to the IAM & Admin page for your project, and give them the "Project > Editor" permission.
App Engine supports hosting different instances of a project via
versions.
Versions are deployed by using --version
flag in the gcloud CLI. Versions are
primarily useful in the staging
environment, and don't come into play in the
local development environment or when running a build.
You can access different versions of an app with https://[version]-dot-[project
id].proxy.preview.kintaro.goog
. For instance, the develop
version of the
bracket
app engine project id is
https://develop-dot-bracket.proxy.preview.kintaro.goog.
This use case is possible, but is not officially supported. You will need to control viewing permissions in the GCP Console instead.
You can ignore this section if you don't have Google machine access.
In order to push a site to SCS, you submit a Changelist (CL) with the generated
static files. The Bracket CLI can automate this process for you with bracket
deploy
. You must run this command from a machine with Google access.
Define the following variables in your project's package.json (the "."s indicate
nesting, i.e. repository.url indicates {"repository": {"url": <VALUE>}}
).
npm run build
to do this, set the
value to "build".rsync
"exclude" flag.For a full list of flags, run bracket deploy --help
.
bracket deploy
bracket deploy --build
bracket deploy --build --git=https://webmaster.googlesource.com/bs/about --branch=develop
You may wish to make certain optimizations in production code (for instance,
base64 encoding some images) that you don't want to appear in development or
staging environments. For that reason, Bracket includes an is_build_mode
function you can use to determine if the current request is part of a build
process:
from bracket import config
print config.is_build_mode()
# >> False
There are 2 environments for bracket sites, development
, and staging
. This
configuration is stored as ENV
on the config
module inside the bracket
package. You may import and read this value from anywhere in the app like so:
from bracket import config
print config.ENV
# >> development
Apps can be configured to behave differently under different environments and
versions. In the root of the project you will find 3 different env.*.yaml
files, two for the environments listed above, and an env.default.yaml
that the
each environment inherits from.
The env.development.yaml
file is git ignored so that multiple developers may
configure their dev environment without bothering anyone else. However,
env.development.sample.yaml
is included as a
convenient scaffold for this file. Copy it to env.development.yaml
and modify
it to configure your local workspace.
There are currently three configurable settings in these files:
Keys inside of the kcs
object refer to App Engine versions, the values are the
the kcs workspace id to use in that version. The special key default
is used
when running a version name that isn't defined in this file:
# Keys refer to App Engine versions, values are the kcs project ids.
#
# The `default` key is reserved for specifying the project ids for unspecified
# App Engine versions.
kcs:
master: published
develop: bracket-site
default: published
The cache settings for Bracket can be set up in these file as well via the
cache_ttl
key. Currently you can only set this differently per environment,
but per version cache settings may be enabled if there is demand for such
control.
The value of cache_ttl
is an integer representing the number of seconds to
keep the cache alive. For example:
# Will keep the cache alive for 27 years, or until flushed manually.
cache_ttl: 864000000
# Will not cache at all.
cache_ttl: 0
Kintaro treats 'root' as the root locale for all sites. If you want to use a different locale as root (strongly discouraged!) you can do so:
# This is the default value, and makes it so that Bracket pages registered
# with @root will request content from Kintaro's root locale.
root_locale: root
# This will make it such that pages registered with @root will instead fetch
# from an ALL_ALL locale in Kintaro. Users will need to make sure that an
# ALL_ALL locale exists in Kintaro.
root_locale: ALL_ALL
As of v0.3.0
, Bracket ships with a scaffolding tool, accessible via the
command line with bracket init
. Currently three scaffolds are available, named
vanilla
, Web Standard Closure Modules + SCSS
, and Web Standard CDN
. More
scaffolding options are on the roadmap, with support for things like
Typescript, so stay tuned for updates.
As of 5/30/2019, the vanilla scaffold ships with a TypeScript set up with
Webpack, Closure Compiler, and Sass. The files src/index.scss
and
src/index.ts
are the entry point files for Sass and TypeScript respectively.
The scaffold also provides a testing set up with Karma/Jasmine, and uses Browsersync in dev mode to enable live reloads on file changes.
The Web Standard ES Modules + SCSS scaffold uses comes with the latest version of Glue along with a dev set up similar to the Vanilla scaffold. An exception to this is that it uses ES Modules as opposed to Closure Modules in the Vanilla scaffold.
Scaffold maintained by the YouTube marketing eng team. Spits out a nice set up for building YT branded projects.
Scaffold for building out static "apps" with Angular and Bracket. Useful for projects that need a single page app-like experience but would like to store content in Kintaro.
Is essentially the output from Angular CLI's ng new
, modified slightly to
integrate with Bracket as an application server and content API.
For more context about this scaffold, have a read through the announcement thread.
All the commands you should need to work with Bracket are defined as npm Scripts. Although many commands are just wrappers around other CLI's like gcloud and Gulp, using npm scripts allows us to define a uniform and easy to remember convention for all of our project related tasks, regardless of the language or tools used by the task, be it Node.js, Python, or even Bash.
Each scaffold has different tasks and commands declared, but a few important ones are shared between all of them:
Alias to npm run dev
Spins up a Bracket server and asset compilers in watch mode for development.
Spins up a Bracket server and asset compilers in watch mode for development, as well as a karma test runner.
Builds the front end code for your project and pushes your project to an App Engine version for your project.
Builds all front end code and static HTML for you site, and writes it to the
dist/
directory.
For all other tasks, see the scripts
entry in the package.json
file for your
project.
Bracket assumes that you are using Git/Gerrit for Version Control and Code Review, as these are the canonical Git tools at Google. Once you've started a new Bracket project, you'll want to set up your own repository in Gerrit to collaborate and perform Code Reviews.
If you are using the webmaster Git host, there is a special All-Projects-Bracket ACL with a reasonable configuration out of the box to inherit from.
When creating your repository, in the "Rights Inherit From" field, begin typing "All-Projects-Bracket" and select it from the drop down. Then hit Create Project and you are good to go. You may override the built in ACL but most projects shouldn't need to.
The All-Projects-Bracket ACL is intended to standardize the review process across Bracket projects, define privileges for approvers and contributors, custom labels for presubmits, and potentially (but not currently), language readability enforcement.
There are many nuances to the ACL and who has access to what, but the gist is
that the master
branch is restricted and all commits to it must be reviewed
and merged through Gerrit. Contributors to each project will belong to one of
two groups:
This Group is intentionally small (for now) but the goal is to expand the group as people get experience with Bracket and learn the ins and outs of what to do and not do, and how to facilitate things like upgrading versions. You can think of it as a "Bracket Readability".
master
branch.develop
branchfeature/
, bug/
, or
hotfix/
.If you have had some experience with Bracket already and would like to be added to this group, contact nmosher@google.
Alternatively, if you would like extra groups to be approvers on your project,
just give the group Owners
access to refs/*
on your repo. That will grant
them all the same rights as
bracket-approvers.
This is literally the Google group everyone@google.com
feature/
, bug/
, or hotfix/
.master
branch.Out of the box Bracket comes with a continuous integration set up, configured to run on Google's internal Jenkins, named Kokoro. All that is needed to get CI running is to turn it on by adding your repository to the Kokoro configs in Piper.
The only integration baked into Bracket is a Presubmit that runs on all new Gerrit changes and Patchsets. It does a Sass lint/compile, and a Javascript lint/compile, but only the compile step will fail the build and block submission in Gerrit. Lint errors are logged as warnings in Sponge, and are not persisted to Gerrit.
Stay tuned for more integrations with Kokoro in the future, like automated pushes to App Engine and Karma/E2E test runners.
It takes two separate CLs in piper to get it running, the first is to add your
repository to the Git on Borg ACL file for Kokoro. Just add your repo to the
repo_url
array
in this CL:
# google3/devtools/kokoro/config/data/git_on_borg_resource_acl.gcl
...
{
entity = [
{
job_prefix = "bracket"
},
],
git_on_borg_resource = {
repo_url = [
"rpc://webmaster/bracket",
"rpc://webmaster/bracket-acl-test",
"rpc://webmaster/your-repo-here",
]
},
},
...
Then add kokoro-team
to reviewers, and submit once they LGTM.
Once that has been submitted, you will need to add a presubmit job config for
your project. This CL adds
a presubmit job for a project called bracket-acl-test
.
Copy that file to
google3/devtools/kokoro/config/prod/bracket/<project_name>/presubmit.cfg
,
replace the url
, email
, and admin
values with your own, get it LGTM'd and
submit it.
Once it is submitted, Kokoro will begin watching your repo in Gerrit for changes, and running a Javascript and Sass build for all of your Gerrit Code Reviews. How easy is that?
As of v0.3.0
, Bracket is distributed as a global and locally installed npm
package to make updating as simple as possible.
For existing bracket projects, to upgrade non breaking changes (patch and minor
updates, since we are pre 1.0.0), all users need to do is update the semver
number on the @google/bracket
dependency listed in package.json
and run npm
install
again. For instance, to update a project from v0.3.0
to v0.3.2
,
users should change:
// Inside `package.json`.
devDependencies: {
"@google/bracket": "git+https://webmaster.googlesource.com/bracket#semver:0.3.0",
}
to:
devDependencies: {
"@google/bracket": "git+https://webmaster.googlesource.com/bracket#semver:0.3.2",
...
}
then run:
npm install
For breaking change updates, after following the above steps, re-initialize their projects to assist in the upgrade process:
# Inside your project, run `bracket init` and follow the prompts.
bracket init
# See what if any changes were made to the originally scaffolded
# files, if any.
git diff
# Fix any unwanted changes and add them.
git add <file>
# Revert any unwanted files or changes.
git checkout <file>
Bracket is now Python 3-only. All your site code will need to be updated to be Python 3 compatible.
The following is a non-comprehensive list of what might need to be changed in your site code:
import meta_data
→ from src import meta_data
from google.appengine.ext import vendor
importslibraries
api_version
threadsafe
entrypoint: gunicorn -b :$PORT src.routes:app
import os
import sys
sys.path.append(os.path.join('node_modules', '@google', 'bracket', 'python'))
gunicorn==20.1.0
to your requirements.txtfrom bracket import app
is in your src/routes.pyIn its current state, Bracket v0.5.0 should be considered stable enough to build and deploy your site. One thing to keep in mind, however, is that we had to code around the fact that there isn't yet a dev_appserver for Python 3, so we aren't able to fully emulate the App Engine functionality locally such as yaml parsing, datastore caching, etc that would take place when deployed. Thus, building / running your local application in Python 3 will likely be slower than in Python 2 for now.
This release is mainly intended to bridge the potential gap so users can continue site development if the gLinux team were to remove and block the Python 2 binary on gLinux installations before the dev_appserver for Python 3 is released as part of the google-cloud-sdk.
The kcs methods (kcs.document
, kcs.collection
, kcs.search
, kcs.batch
)
now return KcsDocument objects instead of dicts. This should be
backwards-compatible for the vast majority of sites, since the new KcsDocument
objects should have the same API as the old dicts, and have all the same special
meta keys (collection_id
, document_id
, locale
, project_id
, repo_id
,
_kitana_doc
). If you are getting the meta keys the old way, though (e.g.,
some_doc['repo_id']
), you should update your code to use the new meta methods
instead (e.g., some_doc.GetRepo()
). To see the API for the new new object
check out the
KCS API DOCUMENTATION
Upgrading from 0.2.x to 0.3.x is for the most part, just transitioning your
project to using the @google/bracket
npm package instead of having all Bracket
code checked in to your repository.
To get upgraded, just install the Bracket CLI, cd into your
0.2.x project, and run bracket init
.
Follow the prompts, and let it overwrite the files in your working directory
with the 0.3.x versions. Check out the diffs it made and revert anything that
clearly breaks your project, like src/routes.py
or src/meta.py
.
The most important changes are to app.yaml
, appengine_config.py
, and
package.json
so if you've made mods to these files for your project, you'll
need to reconcile the changes manually.
Hopefully, at this point, npm run dev
will spin up a server like normal
without any errors.
The last step is to rm -rf bracket
, since the local bracket server code will
no longer be used for anything.
Then git add .
, git commit -m 'Upgrade to 0.3.x'
, and push that change
wherever it needs to go!
If you run through this process and run into hiccups or have questions, don't hesitate reaching out to g/bracket-users-external or to nmosher@google.com directly!
Under the hood Bracket is technically two projects: Bracket and Bracket Server.
Developers generally don't have to think about this as they just install Bracket
and it installs the corresponding version of Bracket Server automatically. But
in some cases you may want to use a specific version of Bracket Server, for
instance to run a version that has a bug fix or other patch. In package.json
there is a section with the key "bracket". There you can specify a version of
Bracket Server using the following syntax. The value for serverVersion is
commit-ish
meaning it can be a branch, hash, or tag.
"bracket": {
"appId": "your-gae-app-id",
"serverVersion": "git+https://webmaster.googlesource.com/bracket-server@unique-server-version"
}
File/Directory | Description |
---|---|
Your code | |
src/* |
Directory that contains all the source code for you project. Static files, Jinja templates, Python handlers, and your site meta configuration all go here. Jinja template look ups all happen from this directory |
src/static/* |
Static files to be served in prod, like minified js, css, and images. By default this directory is served at /static/ but this can be changed in the env.default.yaml |
src/routes.py |
Entry point to the Flask app. All routes/controllers should be defined in this file, but you may refactor your handlers to a directory structure that makes sense for your project as it grows. |
src/meta.py |
Must define a function meta dictionary containing a pages dict of all your pages for each locale. The value(s) assigned to each key in meta['pages'] will be set to the g.meta global template variable. |
package.json |
Contains meta data for the project, and defines npm tasks for convenience. |
app.yaml |
Contains the app engine configuration for the project. Typically you will not need to modify this file. |
.bracket_cache |
Git ignored disk location used as a cache for KCS content. Removing this file will empty cached responses from KCS so new content may be fetched. |
dist/* |
Git ignored directory where the build task writes static output. This directory is "owned" by the build script, so don't expect changes there to last. |
Bracket is supported by the Corp Eng Kintaro Team.
Issues and questions should be directed towards either the Bracket Users group for official support from Kintaro, or the Bracket Hangouts Chat Room for help from Bracket users. If you or people on your team are using Bracket, it's probably a good idea to subscribe and join these groups.
Major announcements are posted to the Google Group, and the Chat Room functions as a place for general ramblings about problems, fixes, ideas, and features.
For Bracket issues/bugs, please report them to the Kintaro Buganizer Component. For support from the Kintaro on-call engineer, post your questions to the Bracket Users Group.
Made w/ 😈 by Nathan Mosher