{"action":"created","comment":{"url":"https://api.github.com/repos/letsencrypt/boulder/pulls/comments/79058746","id":79058746,"diff_hunk":"@@ -53,6 +53,97 @@ In general, we would like our deploy process to be: deploy new code + old config\n \n When you add a new RPC to a Boulder service (e.g. `SA.GetFoo()`), all components that call that RPC should wrap those calls in some flag. Generally this will be a boolean config field `UseFoo`. Since `UseFoo`'s zero value is false, a deploy with the existing config will not call `SA.GetFoo()`. Then, once the deploy is complete and we know that all SA instances support the `GetFoo()` RPC, we do a followup config deploy that sets `UseFoo()` to true.\n \n+## Flag-gated migrations\n+\n+We use [database migrations](https://en.wikipedia.org/wiki/Schema_migration)\n+to modify the existing schema. These migrations will be run on live\n+data while Boulder is still running, so we need Boulder code at any given commit to\n+be capable of running without depending on any changes in schemas that have not\n+yet been applied.\n+\n+For instance, if we're adding a new column to an existing table, Boulder should\n+run correctly in three states:\n+ 1. Migration not yet applied.\n+ 2. Migration applied, flag not yet flipped.\n+ 3. Migration applied, flag flipped.\n+\n+Specifically, that means that all of our `SELECT` statements should enumerate\n+columns to select, and not use `*`. Also, generally speaking, we will need a\n+separate model `struct` for serializing and deserializing data before and after the\n+migration. This is because the ORM package we use,\n+[`gorp`](https://github.com/go-gorp/gorp), expects every field in a struct to\n+map to a column in the table. If we add a new field to a model struct and\n+Boulder attempts to write that struct to a table that doesn't yet have the\n+corresponding column (case 1), gorp wil fail with\n+`Insert failed table posts has no column named Foo`.\n+There are examples of such models in sa/model.go, along with code to\n+turn a model into a `struct` used internally. An example of a flag-gated\n+migration, adding a new `IsWizard` field to Person:\n+\n+```\n+struct Person {\n+  HatSize  int\n+  IsWizard bool // Added!\n+}\n+\n+struct personModel {\n+  HatSize int\n+}\n+\n+// Added!\n+struct personModel2 {\n+  HatSize  int\n+  IsWizard bool\n+}\n+\n+func (ssa *SQLStorageAuthority) GetPerson() (Person, error) {\n+  if ssa.allowWizards { // Added!\n+    var model personModel2\n+    ssa.dbMap.SelectOne(&mode, \"SELECT hatSize, isWizard FROM people\")\n+    return Person{\n+      HatSize:  model.HatSize,\n+      IsWizard: model.IsWizard,\n+    }\n+  } else {\n+    var model personModel\n+    ssa.dbMap.SelectOne(&mode, \"SELECT hatSize FROM people\")\n+    return Person{\n+      HatSize:  model.HatSize,\n+      IsWizard: false,\n+    }\n+  }\n+}\n+\n+func (ssa *SQLStorageAuthority) AddPerson(p Person) (error) {\n+  if ssa.allowWizards { // Added!\n+    return ssa.dbMap.Insert(personModel2{","path":"CONTRIBUTING.md","position":67,"original_position":67,"commit_id":"85544925a27849d8b420cd25d2319ed08da9a8a2","original_commit_id":"85544925a27849d8b420cd25d2319ed08da9a8a2","user":{"login":"rolandshoemaker","id":5080674,"avatar_url":"https://avatars.githubusercontent.com/u/5080674?v=3","gravatar_id":"","url":"https://api.github.com/users/rolandshoemaker","html_url":"https://github.com/rolandshoemaker","followers_url":"https://api.github.com/users/rolandshoemaker/followers","following_url":"https://api.github.com/users/rolandshoemaker/following{/other_user}","gists_url":"https://api.github.com/users/rolandshoemaker/gists{/gist_id}","starred_url":"https://api.github.com/users/rolandshoemaker/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/rolandshoemaker/subscriptions","organizations_url":"https://api.github.com/users/rolandshoemaker/orgs","repos_url":"https://api.github.com/users/rolandshoemaker/repos","events_url":"https://api.github.com/users/rolandshoemaker/events{/privacy}","received_events_url":"https://api.github.com/users/rolandshoemaker/received_events","type":"User","site_admin":false},"body":"The associations between types and tables are defined in `sa/database.go:initTables()`.","created_at":"2016-09-15T20:50:38Z","updated_at":"2016-09-15T20:50:38Z","html_url":"https://github.com/letsencrypt/boulder/pull/2179#discussion_r79058746","pull_request_url":"https://api.github.com/repos/letsencrypt/boulder/pulls/2179","_links":{"self":{"href":"https://api.github.com/repos/letsencrypt/boulder/pulls/comments/79058746"},"html":{"href":"https://github.com/letsencrypt/boulder/pull/2179#discussion_r79058746"},"pull_request":{"href":"https://api.github.com/repos/letsencrypt/boulder/pulls/2179"}}},"pull_request":{"url":"https://api.github.com/repos/letsencrypt/boulder/pulls/2179","id":85308961,"html_url":"https://github.com/letsencrypt/boulder/pull/2179","diff_url":"https://github.com/letsencrypt/boulder/pull/2179.diff","patch_url":"https://github.com/letsencrypt/boulder/pull/2179.patch","issue_url":"https://api.github.com/repos/letsencrypt/boulder/issues/2179","number":2179,"state":"open","locked":false,"title":"Add details about flag-gating DB migrations.","user":{"login":"jsha","id":220205,"avatar_url":"https://avatars.githubusercontent.com/u/220205?v=3","gravatar_id":"","url":"https://api.github.com/users/jsha","html_url":"https://github.com/jsha","followers_url":"https://api.github.com/users/jsha/followers","following_url":"https://api.github.com/users/jsha/following{/other_user}","gists_url":"https://api.github.com/users/jsha/gists{/gist_id}","starred_url":"https://api.github.com/users/jsha/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jsha/subscriptions","organizations_url":"https://api.github.com/users/jsha/orgs","repos_url":"https://api.github.com/users/jsha/repos","events_url":"https://api.github.com/users/jsha/events{/privacy}","received_events_url":"https://api.github.com/users/jsha/received_events","type":"User","site_admin":false},"body":"","created_at":"2016-09-14T21:30:29Z","updated_at":"2016-09-15T20:50:38Z","closed_at":null,"merged_at":null,"merge_commit_sha":"c3e4235dad63c79ac48a8a5466c89f0739df8e49","assignee":null,"assignees":[],"milestone":null,"commits_url":"https://api.github.com/repos/letsencrypt/boulder/pulls/2179/commits","review_comments_url":"https://api.github.com/repos/letsencrypt/boulder/pulls/2179/comments","review_comment_url":"https://api.github.com/repos/letsencrypt/boulder/pulls/comments{/number}","comments_url":"https://api.github.com/repos/letsencrypt/boulder/issues/2179/comments","statuses_url":"https://api.github.com/repos/letsencrypt/boulder/statuses/85544925a27849d8b420cd25d2319ed08da9a8a2","head":{"label":"letsencrypt:iswizard","ref":"iswizard","sha":"85544925a27849d8b420cd25d2319ed08da9a8a2","user":{"login":"letsencrypt","id":9289019,"avatar_url":"https://avatars.githubusercontent.com/u/9289019?v=3","gravatar_id":"","url":"https://api.github.com/users/letsencrypt","html_url":"https://github.com/letsencrypt","followers_url":"https://api.github.com/users/letsencrypt/followers","following_url":"https://api.github.com/users/letsencrypt/following{/other_user}","gists_url":"https://api.github.com/users/letsencrypt/gists{/gist_id}","starred_url":"https://api.github.com/users/letsencrypt/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/letsencrypt/subscriptions","organizations_url":"https://api.github.com/users/letsencrypt/orgs","repos_url":"https://api.github.com/users/letsencrypt/repos","events_url":"https://api.github.com/users/letsencrypt/events{/privacy}","received_events_url":"https://api.github.com/users/letsencrypt/received_events","type":"Organization","site_admin":false},"repo":{"id":28283593,"name":"boulder","full_name":"letsencrypt/boulder","owner":{"login":"letsencrypt","id":9289019,"avatar_url":"https://avatars.githubusercontent.com/u/9289019?v=3","gravatar_id":"","url":"https://api.github.com/users/letsencrypt","html_url":"https://github.com/letsencrypt","followers_url":"https://api.github.com/users/letsencrypt/followers","following_url":"https://api.github.com/users/letsencrypt/following{/other_user}","gists_url":"https://api.github.com/users/letsencrypt/gists{/gist_id}","starred_url":"https://api.github.com/users/letsencrypt/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/letsencrypt/subscriptions","organizations_url":"https://api.github.com/users/letsencrypt/orgs","repos_url":"https://api.github.com/users/letsencrypt/repos","events_url":"https://api.github.com/users/letsencrypt/events{/privacy}","received_events_url":"https://api.github.com/users/letsencrypt/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/letsencrypt/boulder","description":"An ACME-based CA, written in Go.","fork":false,"url":"https://api.github.com/repos/letsencrypt/boulder","forks_url":"https://api.github.com/repos/letsencrypt/boulder/forks","keys_url":"https://api.github.com/repos/letsencrypt/boulder/keys{/key_id}","collaborators_url":"https://api.github.com/repos/letsencrypt/boulder/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/letsencrypt/boulder/teams","hooks_url":"https://api.github.com/repos/letsencrypt/boulder/hooks","issue_events_url":"https://api.github.com/repos/letsencrypt/boulder/issues/events{/number}","events_url":"https://api.github.com/repos/letsencrypt/boulder/events","assignees_url":"https://api.github.com/repos/letsencrypt/boulder/assignees{/user}","branches_url":"https://api.github.com/repos/letsencrypt/boulder/branches{/branch}","tags_url":"https://api.github.com/repos/letsencrypt/boulder/tags","blobs_url":"https://api.github.com/repos/letsencrypt/boulder/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/letsencrypt/boulder/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/letsencrypt/boulder/git/refs{/sha}","trees_url":"https://api.github.com/repos/letsencrypt/boulder/git/trees{/sha}","statuses_url":"https://api.github.com/repos/letsencrypt/boulder/statuses/{sha}","languages_url":"https://api.github.com/repos/letsencrypt/boulder/languages","stargazers_url":"https://api.github.com/repos/letsencrypt/boulder/stargazers","contributors_url":"https://api.github.com/repos/letsencrypt/boulder/contributors","subscribers_url":"https://api.github.com/repos/letsencrypt/boulder/subscribers","subscription_url":"https://api.github.com/repos/letsencrypt/boulder/subscription","commits_url":"https://api.github.com/repos/letsencrypt/boulder/commits{/sha}","git_commits_url":"https://api.github.com/repos/letsencrypt/boulder/git/commits{/sha}","comments_url":"https://api.github.com/repos/letsencrypt/boulder/comments{/number}","issue_comment_url":"https://api.github.com/repos/letsencrypt/boulder/issues/comments{/number}","contents_url":"https://api.github.com/repos/letsencrypt/boulder/contents/{+path}","compare_url":"https://api.github.com/repos/letsencrypt/boulder/compare/{base}...{head}","merges_url":"https://api.github.com/repos/letsencrypt/boulder/merges","archive_url":"https://api.github.com/repos/letsencrypt/boulder/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/letsencrypt/boulder/downloads","issues_url":"https://api.github.com/repos/letsencrypt/boulder/issues{/number}","pulls_url":"https://api.github.com/repos/letsencrypt/boulder/pulls{/number}","milestones_url":"https://api.github.com/repos/letsencrypt/boulder/milestones{/number}","notifications_url":"https://api.github.com/repos/letsencrypt/boulder/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/letsencrypt/boulder/labels{/name}","releases_url":"https://api.github.com/repos/letsencrypt/boulder/releases{/id}","deployments_url":"https://api.github.com/repos/letsencrypt/boulder/deployments","created_at":"2014-12-21T00:29:54Z","updated_at":"2016-09-15T16:28:49Z","pushed_at":"2016-09-15T20:40:42Z","git_url":"git://github.com/letsencrypt/boulder.git","ssh_url":"git@github.com:letsencrypt/boulder.git","clone_url":"https://github.com/letsencrypt/boulder.git","svn_url":"https://github.com/letsencrypt/boulder","homepage":null,"size":23746,"stargazers_count":1358,"watchers_count":1358,"language":"Go","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":173,"mirror_url":null,"open_issues_count":171,"forks":173,"open_issues":171,"watchers":1358,"default_branch":"master"}},"base":{"label":"letsencrypt:master","ref":"master","sha":"104f409a5e601b15d29049ed2f168486c4d1d536","user":{"login":"letsencrypt","id":9289019,"avatar_url":"https://avatars.githubusercontent.com/u/9289019?v=3","gravatar_id":"","url":"https://api.github.com/users/letsencrypt","html_url":"https://github.com/letsencrypt","followers_url":"https://api.github.com/users/letsencrypt/followers","following_url":"https://api.github.com/users/letsencrypt/following{/other_user}","gists_url":"https://api.github.com/users/letsencrypt/gists{/gist_id}","starred_url":"https://api.github.com/users/letsencrypt/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/letsencrypt/subscriptions","organizations_url":"https://api.github.com/users/letsencrypt/orgs","repos_url":"https://api.github.com/users/letsencrypt/repos","events_url":"https://api.github.com/users/letsencrypt/events{/privacy}","received_events_url":"https://api.github.com/users/letsencrypt/received_events","type":"Organization","site_admin":false},"repo":{"id":28283593,"name":"boulder","full_name":"letsencrypt/boulder","owner":{"login":"letsencrypt","id":9289019,"avatar_url":"https://avatars.githubusercontent.com/u/9289019?v=3","gravatar_id":"","url":"https://api.github.com/users/letsencrypt","html_url":"https://github.com/letsencrypt","followers_url":"https://api.github.com/users/letsencrypt/followers","following_url":"https://api.github.com/users/letsencrypt/following{/other_user}","gists_url":"https://api.github.com/users/letsencrypt/gists{/gist_id}","starred_url":"https://api.github.com/users/letsencrypt/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/letsencrypt/subscriptions","organizations_url":"https://api.github.com/users/letsencrypt/orgs","repos_url":"https://api.github.com/users/letsencrypt/repos","events_url":"https://api.github.com/users/letsencrypt/events{/privacy}","received_events_url":"https://api.github.com/users/letsencrypt/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/letsencrypt/boulder","description":"An ACME-based CA, written in Go.","fork":false,"url":"https://api.github.com/repos/letsencrypt/boulder","forks_url":"https://api.github.com/repos/letsencrypt/boulder/forks","keys_url":"https://api.github.com/repos/letsencrypt/boulder/keys{/key_id}","collaborators_url":"https://api.github.com/repos/letsencrypt/boulder/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/letsencrypt/boulder/teams","hooks_url":"https://api.github.com/repos/letsencrypt/boulder/hooks","issue_events_url":"https://api.github.com/repos/letsencrypt/boulder/issues/events{/number}","events_url":"https://api.github.com/repos/letsencrypt/boulder/events","assignees_url":"https://api.github.com/repos/letsencrypt/boulder/assignees{/user}","branches_url":"https://api.github.com/repos/letsencrypt/boulder/branches{/branch}","tags_url":"https://api.github.com/repos/letsencrypt/boulder/tags","blobs_url":"https://api.github.com/repos/letsencrypt/boulder/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/letsencrypt/boulder/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/letsencrypt/boulder/git/refs{/sha}","trees_url":"https://api.github.com/repos/letsencrypt/boulder/git/trees{/sha}","statuses_url":"https://api.github.com/repos/letsencrypt/boulder/statuses/{sha}","languages_url":"https://api.github.com/repos/letsencrypt/boulder/languages","stargazers_url":"https://api.github.com/repos/letsencrypt/boulder/stargazers","contributors_url":"https://api.github.com/repos/letsencrypt/boulder/contributors","subscribers_url":"https://api.github.com/repos/letsencrypt/boulder/subscribers","subscription_url":"https://api.github.com/repos/letsencrypt/boulder/subscription","commits_url":"https://api.github.com/repos/letsencrypt/boulder/commits{/sha}","git_commits_url":"https://api.github.com/repos/letsencrypt/boulder/git/commits{/sha}","comments_url":"https://api.github.com/repos/letsencrypt/boulder/comments{/number}","issue_comment_url":"https://api.github.com/repos/letsencrypt/boulder/issues/comments{/number}","contents_url":"https://api.github.com/repos/letsencrypt/boulder/contents/{+path}","compare_url":"https://api.github.com/repos/letsencrypt/boulder/compare/{base}...{head}","merges_url":"https://api.github.com/repos/letsencrypt/boulder/merges","archive_url":"https://api.github.com/repos/letsencrypt/boulder/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/letsencrypt/boulder/downloads","issues_url":"https://api.github.com/repos/letsencrypt/boulder/issues{/number}","pulls_url":"https://api.github.com/repos/letsencrypt/boulder/pulls{/number}","milestones_url":"https://api.github.com/repos/letsencrypt/boulder/milestones{/number}","notifications_url":"https://api.github.com/repos/letsencrypt/boulder/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/letsencrypt/boulder/labels{/name}","releases_url":"https://api.github.com/repos/letsencrypt/boulder/releases{/id}","deployments_url":"https://api.github.com/repos/letsencrypt/boulder/deployments","created_at":"2014-12-21T00:29:54Z","updated_at":"2016-09-15T16:28:49Z","pushed_at":"2016-09-15T20:40:42Z","git_url":"git://github.com/letsencrypt/boulder.git","ssh_url":"git@github.com:letsencrypt/boulder.git","clone_url":"https://github.com/letsencrypt/boulder.git","svn_url":"https://github.com/letsencrypt/boulder","homepage":null,"size":23746,"stargazers_count":1358,"watchers_count":1358,"language":"Go","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":173,"mirror_url":null,"open_issues_count":171,"forks":173,"open_issues":171,"watchers":1358,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/letsencrypt/boulder/pulls/2179"},"html":{"href":"https://github.com/letsencrypt/boulder/pull/2179"},"issue":{"href":"https://api.github.com/repos/letsencrypt/boulder/issues/2179"},"comments":{"href":"https://api.github.com/repos/letsencrypt/boulder/issues/2179/comments"},"review_comments":{"href":"https://api.github.com/repos/letsencrypt/boulder/pulls/2179/comments"},"review_comment":{"href":"https://api.github.com/repos/letsencrypt/boulder/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/letsencrypt/boulder/pulls/2179/commits"},"statuses":{"href":"https://api.github.com/repos/letsencrypt/boulder/statuses/85544925a27849d8b420cd25d2319ed08da9a8a2"}}}}
{"actor":{"display_login":"rolandshoemaker"}}