Phil Pennock, Apcera Inc.
Viewable online at http://gosftalk.grumpy-troll.org/
Source repo available on GitHub as https://github.com/syscomet/talk-gosf-2013feb/
Video of talk (second speaker) on Youtube
Nomenclature:
(This presentation made with Landslide.)
Twenty minutes, so will go quite quickly, but given how established the GoSF group is, I think folks can handle it readily enough.
Cover git, GitHub, Travis for Continuous Integration, web hooks, git hooks, and some bits about the build and test frameworks.
If I start reading out each slide in a monotone then stop me.
The slides will be available as reference material.
Most Go presentations seem to reference various features provided by the Go project itself. We'll keep that to just this slide.
You should definitely know about the online documentation, a production running instance of godoc.
godoc -http=:6060
and point a web-browser at http://localhost:6060/The main other tool you should get in the habit of using is the Go Playground
Please:
Getting the core bits tied to the language itself out of the way; if you don't know these, start here.
Treat is as your REPL. Aaand on we go.
Much like $PATH
or $CLASSPATH
, building go code uses $GOPATH
. On
Unix-like systems, that's a colon-separated list of dirs. On Windows,
semi-colons are used. Unlike $PATH, Go uses a fixed layout within each entry.
Run ls $(go env GOROOT)
to see the system code. Pick a place to anchor your
main work. I use ~/src/go
. So code I write is in ~/src/go/src/...
.
Libraries installed with go install
go into ~/src/go/pkg/$GOOS_$GOARCH/
,
commands into ~/src/go/bin/
.
If you're writing code that deals with checking stuff out automatically, please
do allow for multiple entries and take just the first element in the list. In
shell, that's ${GOPATH%%:*}
.
$GOPATH
affects where code lives. You need to take the time to understand this.
Much more interesting, go get
, go install
, etc can understand if an import
path has a hostname embedded in it and grok how to auto-fetch code, including
dependencies, recursively. So if you can build entirely using Go project build
rules, everything needed to build can be fetched, if not already present, just
with go install site.tld/top/level/cmd
.
The details are documented at: http://golang.org/doc/code.html#remote
This affects how you write the code:
1 2 3 4 5 | package main
import "github.com/syscomet/namespace_test"
func main() {
namespace_test.Demonstration("Go Steel Programmers")
}
|
Downside: hosting site is in the source code. But the prefix parts of the path
are not part of the code namespace, even without using a renaming import. Thus
namespace_test.
and the only parts to change are the import lines.
go get
/ go build
?go build
Simplicity is a virtue, especially when it's time to move away
For this talk, yes.
An opinion which might inflame.
There is always a trade-off in build-systems between simplicity and power. Often, eg in Java, folks start with something simple because they don't need the power. Then they make a few tweaks, just to add the one little bit needed. By the time they switch to something more powerful, they have a huge tangled mess to deal with.
Go gets around this: it supplies a simple system with no configuration files,
which handles many basic cases but doesn't let you invest effort into creating
a tangled mess. By the time you're ready to move past go build
, you know what
you need, you know what to look for, and can choose a build framework to suit.
Porting is simple, because there's just the standard rules.
Telling that Google talks have mentioned google/
as their namespace, which does
not use automatic fetching.
That was a code fetch using a version control system. Go supports several. It doesn't matter much which you use, but Git is currently the In Thing with wide support and some nice features.
It doesn't matter too much which version control system you use, as long as Go supports it.
Beware: Git also has horribly bad default command-line commands (the "porcelain"), with an attitude of "just write porcelain which does what you want", combining with traditional geek macho, to keep the default commands complicated and confusing until you get used to them.
It's powerful, it's good, but don't try learning Git all at once and accept that you'll learn it in bits and pieces.
Git further reading:
git help tutorial
git help tutorial-2
git help gitcore-tutorial
git instaweb
— runs a web-server wrapper around gitweb.cgi
and
points a browser at it, for browsing the repo.
1 2 3 4 5 | [browser "firefox"]
path = /Applications/Firefox.app/Contents/MacOS/firefox
[instaweb]
local = true
browser = firefox
|
Storing password credentials in OSX keychain, or other native password management, is worthwhile to set up.
1 2 | [credential]
helper = osxkeychain
|
Passphrase-based SSH with ssh-agent is your friend; set the public keys for the services you want to access.
You should do these things to Git if you haven't already done so; git instaweb
fires up a web-browser talking to an ad-hoc web-server, showing the gitweb.cgi
view of the current repository.
Credential helpers rock.
Basic revision control lets you check in files, check them out, check in changes, and look back to get the content at any previous point in time, generating diffs as wanted. Where you check them into might be a sub-directory (SCCS, RCS), a central server, or "anywhere".
In place of a mandatory central server, DVCS have a model where folks don't normally retrieve just the most recent version. Instead, they keep a local copy of the entire history so that most operations are local.
The content can then be reconciled between instances, on the same host or remotely, over HTTP, SSH or other stuff, depending upon the DVCS.
Once you have this, you can choose to make some location canonical, or preferred, particularly if it provides useful additional tools.
Distributed version control: no imposed authority, only the authority you choose to submit to.
If you stick with the default build system, then yes, you've chosen to make some location canonical.
Exim uses Git, the canonical store is on git.exim.org. But we also use GitHub, https://github.com/Exim/exim. Pushes to the canonical store are automatically pushed to GitHub.
As an example of this distributed nature, Exim has its own canonical location, and uses GitHub for some hooks (covered later). The image in the top-right is gitweb on the canonical server, the main image is the GitHub mirror.
I'm using GitHub for this talk. I don't work for them. But they're decent and there's a reason I'm using them as the example, and that my employer uses them and an Open Source project I'm involved with increasingly uses them.
There are a few sites providing Git hosting with augmented tools. Currently, probably the most popular is GitHub. Free unlimited hosting for public repositories, my employer is a customer of the private repository support.
At a low level, the most important thing provided is a production-quality staffed Git hosting facility, available over a couple of protocols, with decent authentication infrastructure.
On top of that, are some decent sharing tools for choosing who gets commit access, some adequate light code review and bug-tracking systems (optional), wiki hosting, site hosting for simple static sites which are git checkouts.
And then the magic: hooks
Pretty darned solid git hosting, for a price that's right for open source, professionally backed, with nice custom tools that can be addictive. If you haven't used their lightweight code review and issue tracking systems, you really should take a look. The checkbox feature on the issues system is one of their great recent additions.
Biggest downside: issues aren't themselves available as a git repo, AFAICT.
Post commit details to IRC, trigger regression tests, update bug-tracking systems …
A webhook, fundamentally, is “take some data representing an event, POST it to a URL, and perhaps take action based on the response code”.
Add authorisation tokens that can be embedded in a URL, and a standard encoding format (typically JSON) and you're getting somewhere.
Add in a protocol translator site, taking site A's event and changing it to something understood by site B and you have something powerful. Provide a library of those translations as part of the generating site and you can:
https://github.com/$account/$project/admin/hooks Eg: https://github.com/syscomet/sks_spider/admin/hooks
Webhooks are powerful and your friend, as is having a common language so that at most you need to do some minor data model munging to glue two systems together.
Going into a little more detail: something has happened, you POST about it, don't care about what happens on the remote side. IRC notifications, Android/iOS, continuous integration and more.
Don't worry about it. Code was a weekend migration hack, is pretty ugly in places.
This is just some code I wrote, rewriting a Python WSGI app to be a Golang HTTP server; in the process, it became faster and more responsive, using less CPU and less RAM. I'm using it as an example because it's on GitHub, has tests, and is using Travis CI for Continuous Integration testing.
Not relevant, but context: PGP keyservers hold public keys, this maps the peering meshes for statistics and building DNS pools. See http://sks.spodhuis.org/sks-peers if you really care.
Seriously, don't worry about what the code does, it's just a suitable demo.
Waffle re PGP keyservers if folks are interested; public keys & signatures in WoT; SKS, written oCaml, reconciliation; peering, peering mesh. sks_spider walks the mesh, checking status, counting keys, lets public pools of servers be published in DNS. This is a research/exploration project, not the code used for the main public pool, but has influenced that code in friendly competition.
sks_spider
I used a btree project which was written using the gotgo preprocessor to support templates. It's powerful and pleasant, but not at all supported by the normal build system.
Downside: normal build process doesn't work
Upside: I get to show a bit more about Travis configuration
Code generation is an example of the limited power of the default build system.
That it happens to be gotgo in this case is irrelevant, it could as well be
go tool yacc
.
go test
The Go build system will skip files with names matching *_test.go
when
creating a library or binary; instead, the go test
command takes those files
and combines them into a test binary, supplying func main()
itself.
Functions with signatures matching func TestFoo(t *testing.T)
for some Foo
will be candidates for running in a test.
The sks_spider
program currently has too few tests, but it has some: Go makes
it easy and I'm gradually building them up.
% go test -v github.com/syscomet/sks_spider
=== RUN TestCountrySpodhuis
--- PASS: TestCountrySpodhuis (0.01 seconds)
=== RUN TestCountrySets
--- PASS: TestCountrySets (0.00 seconds)
countries_test.go:80: Countryset OK: NL,UK,US
=== RUN TestDepthSnapshot
--- PASS: TestDepthSnapshot (0.02 seconds)
depth_test.go:55: Depth OK; 84 entries, max distance 3
PASS
ok github.com/syscomet/sks_spider 0.048s
A build system which provides for documentation and tests as part of it, is a nice
low bar to set for the competition. Use it, take advantage of it, don't give this
up if you move away. You don't get to control main()
for tests, you just get to
create stand-alone units that plug in.
gocheck
is, I'm told, a better testing framework for apps:The Go language provides an internal testing library, named testing, which is relatively slim due to the fact that the standard library correctness by itself is verified using it. The gocheck package, on the other hand, expects the standard library from Go to be working correctly, and builds on it to offer a richer testing framework for libraries and applications to use.
Using go test
as a base, go check
is worth looking at.
The same person who recommended it to me has gone back to mostly using go
test
, because it's "just there".
If you keep your tests in the same directory:
If you put tests in a sub-directory:
Example*()
tests look like real client codeBoth are useful.
There are two approaches to where to place tests. They both have use and it can make sense to use both. If in the same directory, you're inside the package. If in a sub-directory, you import as per normal, so your API-documenting Example* tests use the code just as the client would; in these tests, you're checking the API, the what. In-package, you're testing the how and that the how works.
Loosely: every time you push a change to the repository, it triggers a CI system to fetch the code, build it, run the tests and report the results. When it generates a status badge, you can include that badge into even README.md files to provide a convenient status report. APIs will let you build more comprehensive dashboards.
https://travis-ci.org/syscomet/sks_spider
My employer pays for Travis, they've been very responsive with Golang issues, we're happy. Free public service covers Go too (and is what sks_spider uses).
Continuous Integration is worth doing, always. Travis is free for open source projects and has a commercial offering, they support Go. The biggest competition is probably self-hosting Jenkins.
Continuous Integration: every time (by default) that you push a change, the code gets rebuilt and the tests run.
Two ways to configure the pairing GitHub ⇆ Travis link:
It basically boils down to generating a token on the Travis site, and configuring a commit hook on GitHub. Since it's a public repository, there's no auth required to fetch the content. Things are more involved for private repositories. Then, by default, pushes will trigger a test. That simple?
If you're working with a public repo, just tell Travis to make the changes; it's git, you have copies, you can always revert. The private repo method is evolving and becoming simpler, and I won't go into it here.
If you do need to set things up manually, the link in this slide will take you to the correct part of the docs.
Okay, you need to tell Travis how to test your project. Most simply:
1 | language: go
|
That's it. Because Go has a built-in test framework, that's normally all that's needed.
The configuration is in YAML and the most relevant additional top-level keys
are script
and install
, each of which can be a list of multiple steps.
In Travis, the steps are combined into shell input, so you can set variables in earlier steps and use them in later steps.
Used to be correct for Automatic Fetching, they'll fix it; for Go, Travis will
create ~/gopath/src
and export GOPATH=~/gopath
and go get
paths work, but
by default layout for current repo is not presently inside $GOPATH
. ☹
The content is pulled as a git checkout of the version that triggered a commit,
linked into place, and your current working directory is inside your project
dir, so that you can access data/
files with relative paths. Next slide
shows fixups.
The configuration file is simple YAML; this talk is badly timed for Travis, they recently pushed something unfortunate, they're fixing it.
1 2 3 4 5 | language: go
script:
- go vet
- go test -v
|
The go vet
command will catch things like .Printf() calls with mismatches in
%-expandos against the supplied arguments. I've not seen a false positive yet,
thus so far I consider it a test failure if go vet
complains.
Use travis-lint
Use travis-lint
. go vet
is nice.
Hopefully not needed by the time you try this.
1 2 3 4 5 6 7 8 9 10 11 12 13 | language: go
install:
- CANONICAL="github.com/username/frobozz"
- here=$(/bin/pwd -P)
- TOP="${GOPATH%%:*}/src"
- "if ! [ -d \"$TOP/$CANONICAL\" ]; then ⏎
mkdir -pv \"$TOP/$(dirname $CANONICAL)\" ⏎
&& ln -s \"$here\" \"$TOP/$CANONICAL\"; fi"
- cd \"$TOP/$CANONICAL\"
- go get -d -v
- go build -v frobozz.go
- go test -i
|
[The ⏎ is purely to mark slide-fitting linebreaks you should skip]
This is the complexity you create for yourself by breaking out of pure Go build
compatibility. You probably want to move most of the work into an install.sh
script, or something using your build framework.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | language: go
install:
- repo_dir=$(/bin/pwd -P)
- spider_parent="github.com/syscomet"
- SPIDER_DIR="github.com/syscomet/sks_spider"
- BTREE_DIR="github.com/runningwild/go-btree"
- GOTGO_DIR="github.com/droundy/gotgo/gotgo"
- CHECKOUT_TOP="${GOPATH%%:*}/src"
- "if ! [ -d \"$CHECKOUT_TOP/$spider_parent\" ]; then mkdir -pv \"$CHECKOUT_TOP/$spider_parent\" && ln -s \"$repo_dir\" \"$CHECKOUT_TOP/$SPIDER_DIR\"; fi"
- go get -fix -d -v "$BTREE_DIR"
- "( cd \"$CHECKOUT_TOP/$BTREE_DIR\" && rm bench.go test.go )"
- go get -d "$GOTGO_DIR"
- go build -v "$GOTGO_DIR"
- ./gotgo -o "$CHECKOUT_TOP/$BTREE_DIR/btree.go" "$CHECKOUT_TOP/$BTREE_DIR/btree.got" string
- go get -d -v
- go build -v sks_stats_daemon.go
- go test -i
|
By default, Travis runs on every commit. This is good when your first commits
to get Travis working are on a travis_setup
branch.
Quickly, you get to where you want commits, say, only on the main branch and on
pull requests. Configure it, and it shall be so.
This too goes in .travis.yml
.
Magic phrase in commit message: [ci skip]
Recent addition: easy UI to force a retry when something failed because of a transient glitch (eg, fetch from GitHub failed).
commit 796ceba300fd8d88064808220e070a243d15036f
Author: Phil Pennock <pdp@spodhuis.org>
Date: Sun Jan 20 01:11:30 2013 -0500
Copyright year updates.
vi $(git whatchanged --since=2013-01-01 | grep '^:100' | ⏎
sed 's/^[^M]*M//' | sort -u )
[ci skip]
You can't run your own code on GitHub to verify a push. It's not your service and the security implications would be awkward. If you run your own central repository, you can do this.
You can run pre-commit scripts to check before you even commit into your local repository. But beware that the pre-commit scripts are not natively part of the repository, because you shouldn't be running arbitrary unseen code when you run a local git command whenever you pull content.
So $repo/.git/hooks/pre-commit
will need to be manually created when you set
up your checkout.
A good approach is to keep a collection of trusted pre-commit scripts in a repo.
A trusting approach is to create git-hooks/
within your repo and create
pre-commit
as a symlink pointing to ../../git-hooks/pre-commit
— instant
security hole, if anyone else can commit.
https://github.com/syscomet/sks_spider/blob/master/git-hooks/pre-commit
Done this with sks_spider for demonstration purposes and to provide a reference; hopefully will remember to remove should I ever grant someone else push access.
foo=$(git config hooks.foo)
to grab any config items from
$repo/.git/config
Further, additional build targets can be added, to exercise the package main
wrappers.
BSD license on sks_spider pre-commit and is a good place to start.
Will be linked to from the meetup page.
© 2012, 2013 Apcera, Inc.
This work is licensed under a
Creative Commons Attribution 3.0 United States License.
Table of Contents | t |
---|---|
Exposé | ESC |
Full screen slides | e |
Presenter View | p |
Source Files | s |
Slide Numbers | n |
Toggle screen blanking | b |
Show/hide slide context | c |
Notes | 2 |
Help | h |