{"action":"created","comment":{"url":"https://api.github.com/repos/mlab-lattice/lattice/pulls/comments/212447930","pull_request_review_id":149086145,"id":212447930,"node_id":"MDI0OlB1bGxSZXF1ZXN0UmV2aWV3Q29tbWVudDIxMjQ0NzkzMA==","diff_hunk":"@@ -0,0 +1,733 @@\n+package mock\n+\n+import (\n+\t\"fmt\"\n+\t\"io\"\n+\t\"io/ioutil\"\n+\t\"strings\"\n+\t\"sync\"\n+\t\"time\"\n+\n+\t\"github.com/mlab-lattice/lattice/pkg/api/v1\"\n+\t\"github.com/mlab-lattice/lattice/pkg/definition/resolver\"\n+\t\"github.com/mlab-lattice/lattice/pkg/definition/tree\"\n+\tdefinitionv1 \"github.com/mlab-lattice/lattice/pkg/definition/v1\"\n+\t\"github.com/mlab-lattice/lattice/pkg/util/git\"\n+\t\"github.com/satori/go.uuid\"\n+)\n+\n+type Backend struct {\n+\tsystemRegistry map[v1.SystemID]*systemRecord\n+\tregistryLock   sync.RWMutex\n+\tgitResolver    *git.Resolver\n+}\n+\n+func newMockBackend() (*Backend, error) {\n+\tgitResolver, err := git.NewResolver(\"/tmp/lattice-api-mock\", true)\n+\n+\tif err != nil {\n+\t\treturn nil, err\n+\t}\n+\treturn &Backend{\n+\t\tsystemRegistry: make(map[v1.SystemID]*systemRecord),\n+\t\tgitResolver:    gitResolver,\n+\t}, nil\n+}\n+\n+type systemRecord struct {\n+\tsystem     *v1.System\n+\tbuilds     map[v1.BuildID]*v1.Build\n+\tdeploys    map[v1.DeployID]*v1.Deploy\n+\tteardowns  map[v1.TeardownID]*v1.Teardown\n+\tsecrets    []v1.Secret\n+\tnodePools  []v1.NodePool\n+\tjobs       map[v1.JobID]*v1.Job\n+\trecordLock sync.RWMutex\n+}\n+\n+// newSystemRecord\n+func newSystemRecord(system *v1.System) *systemRecord {\n+\treturn &systemRecord{\n+\t\tsystem:    system,\n+\t\tbuilds:    make(map[v1.BuildID]*v1.Build),\n+\t\tdeploys:   make(map[v1.DeployID]*v1.Deploy),\n+\t\tteardowns: make(map[v1.TeardownID]*v1.Teardown),\n+\t\tjobs:      make(map[v1.JobID]*v1.Job),\n+\t\tsecrets:   []v1.Secret{},\n+\t\tnodePools: []v1.NodePool{},\n+\t}\n+}\n+\n+// Systems\n+func (backend *Backend) CreateSystem(systemID v1.SystemID, definitionURL string) (*v1.System, error) {\n+\t// lock for writing\n+\tbackend.registryLock.Lock()\n+\tdefer backend.registryLock.Unlock()\n+\n+\tif _, exists := backend.systemRegistry[systemID]; exists {\n+\t\treturn nil, v1.NewSystemAlreadyExistsError(systemID)\n+\t}\n+\n+\t// create system\n+\tsystem := &v1.System{\n+\t\tID:            systemID,\n+\t\tState:         v1.SystemStateStable,\n+\t\tDefinitionURL: definitionURL,\n+\t}\n+\t// register it with in memory map\n+\tbackend.systemRegistry[systemID] = newSystemRecord(system)\n+\treturn system, nil\n+}\n+\n+func (backend *Backend) ListSystems() ([]v1.System, error) {\n+\n+\tsystems := []v1.System{}\n+\tfor _, v := range backend.systemRegistry {\n+\t\tsystems = append(systems, *v.system)\n+\t}\n+\treturn systems, nil\n+}\n+\n+func (backend *Backend) GetSystem(systemID v1.SystemID) (*v1.System, error) {\n+\tsystemRecord, err := backend.getSystemRecord(systemID)\n+\tif err != nil {\n+\t\treturn nil, err\n+\t}\n+\n+\treturn systemRecord.system, nil\n+}\n+\n+func (backend *Backend) DeleteSystem(systemID v1.SystemID) error {\n+\n+\t_, err := backend.getSystemRecord(systemID)\n+\tif err != nil {\n+\t\treturn err\n+\t}\n+\n+\t// lock for writing\n+\tbackend.registryLock.Lock()\n+\tdefer backend.registryLock.Unlock()\n+\n+\tdelete(backend.systemRegistry, systemID)\n+\treturn nil\n+}\n+\n+// Builds\n+\n+func (backend *Backend) Build(\n+\tsystemID v1.SystemID,\n+\tdef *definitionv1.SystemNode,\n+\tri resolver.ResolutionInfo,\n+\tv v1.SystemVersion) (*v1.Build, error) {\n+\n+\trecord, err := backend.getSystemRecord(systemID)\n+\tif err != nil {\n+\t\treturn nil, err\n+\t}\n+\n+\t// validate definition URL\n+\tif !backend.gitResolver.IsValidRepositoryURI(record.system.DefinitionURL) {\n+\t\treturn nil, fmt.Errorf(\"bad url: %v\", record.system.DefinitionURL)\n+\t}\n+\t// validate version\n+\tif v != \"1.0.0\" {\n+\t\treturn nil, &v1.InvalidSystemVersionError{Version: string(v)}\n+\t}\n+\n+\tbuild := backend.newMockBuild(systemID, v)\n+\n+\trecord.builds[build.ID] = build\n+\n+\t// run the build\n+\tgo backend.runBuild(build)\n+\n+\treturn build, nil\n+}\n+\n+func (backend *Backend) ListBuilds(systemID v1.SystemID) ([]v1.Build, error) {\n+\trecord, err := backend.getSystemRecord(systemID)\n+\tif err != nil {\n+\t\treturn nil, err\n+\t}\n+\n+\tbuilds := []v1.Build{}\n+\tfor _, build := range record.builds {\n+\t\tbuilds = append(builds, *build)\n+\t}\n+\treturn builds, nil\n+\n+}\n+\n+func (backend *Backend) GetBuild(systemID v1.SystemID, buildID v1.BuildID) (*v1.Build, error) {\n+\trecord, err := backend.getSystemRecord(systemID)\n+\tif err != nil {\n+\t\treturn nil, err\n+\t}\n+\n+\trecord.recordLock.RLock()\n+\tdefer record.recordLock.RUnlock()\n+\tbuild, exists := record.builds[buildID]\n+\n+\tif !exists {\n+\t\treturn nil, v1.NewInvalidBuildIDError(buildID)\n+\t}\n+\n+\treturn build, nil\n+}\n+\n+func (backend *Backend) BuildLogs(\n+\tsystemID v1.SystemID,\n+\tbuildID v1.BuildID,\n+\tpath tree.Path,\n+\tsidecar *string,\n+\tlogOptions *v1.ContainerLogOptions,\n+) (io.ReadCloser, error) {\n+\treturn ioutil.NopCloser(strings.NewReader(\"this is a long line\")), nil\n+}\n+\n+// Deploys\n+func (backend *Backend) DeployBuild(systemID v1.SystemID, buildID v1.BuildID) (*v1.Deploy, error) {\n+\trecord, err := backend.getSystemRecord(systemID)\n+\tif err != nil {\n+\t\treturn nil, err\n+\t}\n+\t// ensure that build exists\n+\t_, err = backend.GetBuild(systemID, buildID)\n+\tif err != nil {\n+\t\treturn nil, err\n+\t}\n+\n+\trecord.recordLock.Lock()\n+\tdefer record.recordLock.Unlock()\n+\n+\tdeploy := &v1.Deploy{\n+\t\tID:      v1.DeployID(uuid.NewV4().String()),\n+\t\tBuildID: buildID,\n+\t\tState:   v1.DeployStatePending,\n+\t}\n+\n+\trecord.deploys[deploy.ID] = deploy\n+\n+\t// run the deploy\n+\tgo backend.monitorDeployBuild(deploy, buildID, systemID)\n+\n+\treturn deploy, nil\n+}\n+\n+func (backend *Backend) DeployVersion(\n+\tsystemID v1.SystemID,\n+\tdef *definitionv1.SystemNode,\n+\tri resolver.ResolutionInfo,\n+\tversion v1.SystemVersion) (*v1.Deploy, error) {\n+\t// this ensures the system is created as well\n+\tbuild, err := backend.Build(systemID, def, ri, version)\n+\tif err != nil {\n+\t\treturn nil, err\n+\t}\n+\n+\treturn backend.DeployBuild(systemID, build.ID)\n+}\n+\n+func (backend *Backend) ListDeploys(systemID v1.SystemID) ([]v1.Deploy, error) {\n+\trecord, err := backend.getSystemRecord(systemID)\n+\tif err != nil {\n+\t\treturn nil, err\n+\t}\n+\n+\trecord.recordLock.RLock()\n+\tdefer record.recordLock.RUnlock()\n+\n+\tdeploys := []v1.Deploy{}\n+\tfor _, deploy := range record.deploys {\n+\t\tdeploys = append(deploys, *deploy)\n+\t}\n+\treturn deploys, nil\n+}\n+\n+func (backend *Backend) GetDeploy(systemID v1.SystemID, deployID v1.DeployID) (*v1.Deploy, error) {\n+\trecord, err := backend.getSystemRecord(systemID)\n+\tif err != nil {\n+\t\treturn nil, err\n+\t}\n+\n+\trecord.recordLock.RLock()\n+\tdefer record.recordLock.RUnlock()\n+\n+\tdeploy, exists := record.deploys[deployID]\n+\n+\tif !exists {\n+\t\treturn nil, v1.NewInvalidDeployIDError(deployID)\n+\t}\n+\n+\treturn deploy, nil\n+}\n+\n+// Teardown\n+func (backend *Backend) TearDown(systemID v1.SystemID) (*v1.Teardown, error) {\n+\trecord, err := backend.getSystemRecord(systemID)\n+\tif err != nil {\n+\t\treturn nil, err\n+\t}\n+\n+\trecord.recordLock.Lock()\n+\tdefer record.recordLock.Unlock()\n+\n+\tteardown := &v1.Teardown{\n+\t\tID:    v1.TeardownID(uuid.NewV4().String()),\n+\t\tState: v1.TeardownStatePending,\n+\t}\n+\n+\trecord.teardowns[teardown.ID] = teardown\n+\t// run the teardown\n+\tgo backend.runTeardown(teardown)\n+\treturn teardown, nil\n+}\n+\n+func (backend *Backend) ListTeardowns(systemID v1.SystemID) ([]v1.Teardown, error) {\n+\trecord, err := backend.getSystemRecord(systemID)\n+\tif err != nil {\n+\t\treturn nil, err\n+\t}\n+\n+\trecord.recordLock.RLock()\n+\tdefer record.recordLock.RUnlock()\n+\n+\tteardowns := []v1.Teardown{}\n+\tfor _, teardown := range record.teardowns {\n+\t\tteardowns = append(teardowns, *teardown)\n+\t}\n+\treturn teardowns, nil\n+}\n+\n+func (backend *Backend) GetTeardown(systemID v1.SystemID, teardownID v1.TeardownID) (*v1.Teardown, error) {\n+\trecord, err := backend.getSystemRecord(systemID)\n+\tif err != nil {\n+\t\treturn nil, err\n+\t}\n+\n+\trecord.recordLock.RLock()\n+\tdefer record.recordLock.RUnlock()\n+\n+\tteardown, exists := record.teardowns[teardownID]\n+\n+\tif !exists {\n+\t\treturn nil, v1.NewInvalidTeardownIDError(teardownID)\n+\t}\n+\n+\treturn teardown, nil\n+}\n+\n+// Services\n+func (backend *Backend) ListServices(systemID v1.SystemID) ([]v1.Service, error) {\n+\trecord, err := backend.getSystemRecord(systemID)\n+\tif err != nil {\n+\t\treturn nil, err\n+\t}\n+\n+\tservices := []v1.Service{}\n+\tfor _, service := range record.system.Services {\n+\t\tservices = append(services, service)\n+\t}\n+\treturn services, nil\n+}\n+\n+func (backend *Backend) GetService(systemID v1.SystemID, serviceID v1.ServiceID) (*v1.Service, error) {\n+\trecord, err := backend.getSystemRecord(systemID)\n+\tif err != nil {\n+\t\treturn nil, err\n+\t}\n+\n+\tfor _, service := range record.system.Services {\n+\t\tif service.ID == serviceID {\n+\t\t\treturn &service, nil\n+\t\t}\n+\t}\n+\n+\treturn nil, v1.NewInvalidServiceIDError(serviceID)\n+}\n+\n+func (backend *Backend) GetServiceByPath(systemID v1.SystemID, path tree.Path) (*v1.Service, error) {\n+\trecord, err := backend.getSystemRecord(systemID)\n+\tif err != nil {\n+\t\treturn nil, err\n+\t}\n+\n+\tservice, exists := record.system.Services[path]\n+\n+\tif !exists {\n+\t\treturn nil, v1.NewInvalidServicePathError(path)\n+\t}\n+\n+\treturn &service, nil\n+}\n+\n+func (backend *Backend) ServiceLogs(\n+\tsystemID v1.SystemID,\n+\tserviceID v1.ServiceID,\n+\tsidecar *string,\n+\tinstance string,\n+\tlogOptions *v1.ContainerLogOptions,\n+) (io.ReadCloser, error) {\n+\n+\treturn ioutil.NopCloser(strings.NewReader(\"this is a long line\")), nil\n+}\n+\n+// Secrets\n+func (backend *Backend) ListSystemSecrets(systemID v1.SystemID) ([]v1.Secret, error) {\n+\trecord, err := backend.getSystemRecord(systemID)\n+\tif err != nil {\n+\t\treturn nil, err\n+\t}\n+\n+\trecord.recordLock.RLock()\n+\tdefer record.recordLock.RUnlock()\n+\n+\treturn record.secrets, nil\n+}\n+\n+func (backend *Backend) GetSystemSecret(systemID v1.SystemID, path tree.Path, name string) (*v1.Secret, error) {\n+\trecord, err := backend.getSystemRecord(systemID)\n+\tif err != nil {\n+\t\treturn nil, err\n+\t}\n+\n+\trecord.recordLock.RLock()\n+\tdefer record.recordLock.RUnlock()\n+\n+\tfor _, secret := range record.secrets {\n+\t\tif secret.Path == path && secret.Name == name {\n+\t\t\tscrt := &secret\n+\t\t\treturn scrt, nil\n+\t\t}\n+\t}\n+\n+\treturn nil, v1.NewInvalidSystemSecretError(path, name)\n+}\n+\n+func (backend *Backend) SetSystemSecret(systemID v1.SystemID, path tree.Path, name, value string) error {\n+\trecord, err := backend.getSystemRecord(systemID)\n+\tif err != nil {\n+\t\treturn err\n+\t}\n+\n+\trecord.recordLock.Lock()\n+\tdefer record.recordLock.Unlock()\n+\n+\tsecret := v1.Secret{\n+\t\tPath:  path,\n+\t\tName:  name,\n+\t\tValue: value,\n+\t}\n+\n+\trecord.secrets = append(record.secrets, secret)\n+\n+\treturn nil\n+}\n+\n+func (backend *Backend) UnsetSystemSecret(systemID v1.SystemID, path tree.Path, name string) error {\n+\trecord, err := backend.getSystemRecord(systemID)\n+\tif err != nil {\n+\t\treturn err\n+\t}\n+\n+\trecord.recordLock.Lock()\n+\tdefer record.recordLock.Unlock()\n+\n+\tfor i, secret := range record.secrets {\n+\t\tif secret.Path == path && secret.Name == name {\n+\t\t\t// delete secret\n+\t\t\trecord.secrets = append(record.secrets[:i], record.secrets[i+1:]...)\n+\n+\t\t\treturn nil\n+\t\t}\n+\t}\n+\n+\treturn v1.NewInvalidSystemSecretError(path, name)\n+}\n+\n+func (backend *Backend) ListNodePools(systemID v1.SystemID) ([]v1.NodePool, error) {\n+\trecord, err := backend.getSystemRecord(systemID)\n+\tif err != nil {\n+\t\treturn nil, err\n+\t}\n+\n+\trecord.recordLock.RLock()\n+\tdefer record.recordLock.RUnlock()\n+\n+\treturn record.nodePools, nil\n+}\n+\n+func (backend *Backend) GetNodePool(systemID v1.SystemID, path v1.NodePoolPath) (*v1.NodePool, error) {\n+\trecord, err := backend.getSystemRecord(systemID)\n+\tif err != nil {\n+\t\treturn nil, err\n+\t}\n+\trecord.recordLock.RLock()\n+\tdefer record.recordLock.RUnlock()\n+\n+\tfor _, nodePool := range record.nodePools {\n+\t\tif nodePool.Path == path.String() {\n+\t\t\tnp := &nodePool\n+\t\t\treturn np, nil\n+\t\t}\n+\t}\n+\treturn nil, nil\n+}\n+\n+// Jobs\n+func (backend *Backend) RunJob(systemID v1.SystemID, path tree.Path, command []string,\n+\tenvironment definitionv1.ContainerEnvironment,\n+) (*v1.Job, error) {\n+\n+\trecord, err := backend.getSystemRecord(systemID)\n+\tif err != nil {\n+\t\treturn nil, err\n+\t}\n+\n+\tjob := &v1.Job{\n+\t\tID:    v1.JobID(uuid.NewV4().String()),\n+\t\tState: v1.JobStatePending,\n+\t\tPath:  path,\n+\t}\n+\n+\trecord.jobs[job.ID] = job\n+\n+\t// run the job\n+\tgo backend.runJob(job)\n+\n+\treturn job, nil\n+}\n+\n+func (backend *Backend) ListJobs(systemID v1.SystemID) ([]v1.Job, error) {\n+\trecord, err := backend.getSystemRecord(systemID)\n+\tif err != nil {\n+\t\treturn nil, err\n+\t}\n+\n+\tjobs := []v1.Job{}\n+\tfor _, job := range record.jobs {\n+\t\tjobs = append(jobs, *job)\n+\t}\n+\treturn jobs, nil\n+}\n+func (backend *Backend) GetJob(systemID v1.SystemID, jobID v1.JobID) (*v1.Job, error) {\n+\trecord, err := backend.getSystemRecord(systemID)\n+\tif err != nil {\n+\t\treturn nil, err\n+\t}\n+\n+\trecord.recordLock.RLock()\n+\tdefer record.recordLock.RUnlock()\n+\tjob, exists := record.jobs[jobID]\n+\n+\tif !exists {\n+\t\treturn nil, v1.NewInvalidJobIDError(jobID)\n+\t}\n+\n+\treturn job, nil\n+}\n+func (backend *Backend) JobLogs(systemID v1.SystemID, jobID v1.JobID, sidecar *string, logOptions *v1.ContainerLogOptions,\n+) (io.ReadCloser, error) {\n+\treturn ioutil.NopCloser(strings.NewReader(\"this is a long line\")), nil\n+}\n+\n+// helpers\n+\n+func (backend *Backend) getSystemRecord(systemID v1.SystemID) (*systemRecord, error) {\n+\tbackend.registryLock.RLock()\n+\tdefer backend.registryLock.RUnlock()\n+\tsystemRecord, exists := backend.systemRegistry[systemID]\n+\n+\tif !exists {\n+\t\treturn nil, v1.NewInvalidSystemIDError(systemID)\n+\t}\n+\n+\treturn systemRecord, nil\n+}\n+\n+func (backend *Backend) getSystemRecordForBuild(buildID v1.BuildID) *systemRecord {\n+\tfor _, systemRecord := range backend.systemRegistry {\n+\t\tif _, exists := systemRecord.builds[buildID]; exists {\n+\t\t\treturn systemRecord\n+\t\t}\n+\t}\n+\n+\treturn nil\n+}\n+\n+func (backend *Backend) newMockBuild(systemID v1.SystemID, v v1.SystemVersion) *v1.Build {\n+\tservice1Path := tree.Path(fmt.Sprintf(\"/%s/api\", systemID))\n+\tbuild := &v1.Build{\n+\t\tID:      v1.BuildID(uuid.NewV4().String()),\n+\t\tState:   v1.BuildStatePending,\n+\t\tVersion: v,\n+\t\tServices: map[tree.Path]v1.ServiceBuild{\n+\t\t\tservice1Path: {\n+\t\t\t\tContainerBuild: v1.ContainerBuild{\n+\t\t\t\t\tState: v1.ContainerBuildStatePending,\n+\t\t\t\t},\n+\t\t\t},\n+\t\t},\n+\t}\n+\n+\treturn build\n+}\n+\n+func (backend *Backend) runBuild(build *v1.Build) {\n+\t// try to simulate reality by making things take a little longer. Sleep for a bit...\n+\ttime.Sleep(2 * time.Second)\n+\n+\t// change state to running\n+\tbuild.State = v1.BuildStateRunning\n+\tnow := time.Now()\n+\tbuild.StartTimestamp = &now\n+\n+\t// run service builds\n+\tfor sp, s := range build.Services {\n+\t\ts.State = v1.ContainerBuildStateRunning\n+\t\ts.StartTimestamp = &now\n+\t\ts.ContainerBuild.State = v1.ContainerBuildStateRunning\n+\t\ts.ContainerBuild.StartTimestamp = &now\n+\n+\t\tbuild.Services[sp] = s\n+\t}\n+\n+\t// sleep\n+\tfmt.Printf(\"Mock: Running build %s. Sleeping for 7 seconds\\n\", build.ID)\n+\ttime.Sleep(7 * time.Second)\n+\tbackend.finishBuild(build)\n+\n+}\n+\n+func (backend *Backend) finishBuild(build *v1.Build) {\n+\t// change state to succeeded\n+\tnow := time.Now()\n+\n+\t// finish service builds\n+\tfor sp, s := range build.Services {\n+\t\ts.State = v1.ContainerBuildStateSucceeded\n+\t\ts.CompletionTimestamp = &now\n+\t\ts.ContainerBuild.CompletionTimestamp = &now\n+\t\ts.ContainerBuild.State = v1.ContainerBuildStateSucceeded\n+\t\ts.CompletionTimestamp = &now\n+\t\tbuild.Services[sp] = s\n+\t}\n+\n+\tbuild.CompletionTimestamp = &now\n+\tbuild.State = v1.BuildStateSucceeded\n+\n+\tfmt.Printf(\"Build %s finished\\n\", build.ID)\n+\n+\t// update system services...\n+\tsystemRecord := backend.getSystemRecordForBuild(build.ID)\n+\tservices := make(map[tree.Path]v1.Service)\n+\tfor _, build := range systemRecord.builds {\n+\t\tfor path := range build.Services {\n+\t\t\tservices[path] = v1.Service{\n+\t\t\t\tID:                 v1.ServiceID(uuid.NewV4().String()),\n+\t\t\t\tState:              v1.ServiceStateStable,\n+\t\t\t\tInstances:          []string{uuid.NewV4().String()},\n+\t\t\t\tPath:               path,\n+\t\t\t\tAvailableInstances: 1,\n+\t\t\t}\n+\t\t}\n+\t\tbreak\n+\t}\n+\n+\tsystemRecord.system.Services = services\n+\n+}\n+\n+func (backend *Backend) monitorDeployBuild(deploy *v1.Deploy, buildID v1.BuildID, systemID v1.SystemID) {\n+\n+\tfor i := 0; i <= 200; i++ {\n+\t\tbuild, _ := backend.GetBuild(systemID, buildID)\n+\t\tnewState := getDeployStateByBuild(build.State)\n+\t\tif deploy.State != newState {\n+\t\t\tdeploy.State = newState\n+\t\t}\n+\n+\t\tif deploy.State == v1.DeployStateSucceeded || deploy.State == v1.DeployStateFailed {\n+\t\t\treturn\n+\t\t}\n+\n+\t\ttime.Sleep(200 * time.Millisecond)\n+\t}\n+\n+\t// timed out... fail\n+\tdeploy.State = v1.DeployStateFailed\n+\n+}\n+\n+func getDeployStateByBuild(buildState v1.BuildState) v1.DeployState {\n+\tswitch buildState {","path":"pkg/api/server/mock/backend.go","position":663,"original_position":663,"commit_id":"eff9708796070ceb9c0e8a85c978647d0ead2155","original_commit_id":"eff9708796070ceb9c0e8a85c978647d0ead2155","user":{"login":"kevindrosendahl","id":3181256,"node_id":"MDQ6VXNlcjMxODEyNTY=","avatar_url":"https://avatars0.githubusercontent.com/u/3181256?v=4","gravatar_id":"","url":"https://api.github.com/users/kevindrosendahl","html_url":"https://github.com/kevindrosendahl","followers_url":"https://api.github.com/users/kevindrosendahl/followers","following_url":"https://api.github.com/users/kevindrosendahl/following{/other_user}","gists_url":"https://api.github.com/users/kevindrosendahl/gists{/gist_id}","starred_url":"https://api.github.com/users/kevindrosendahl/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/kevindrosendahl/subscriptions","organizations_url":"https://api.github.com/users/kevindrosendahl/orgs","repos_url":"https://api.github.com/users/kevindrosendahl/repos","events_url":"https://api.github.com/users/kevindrosendahl/events{/privacy}","received_events_url":"https://api.github.com/users/kevindrosendahl/received_events","type":"User","site_admin":false},"body":"still a bit of a mismatch here:\r\n\r\ncurrently the semantics are:\r\n- there can only ever be one deploy per system at a time that is in accepted or in-progress\r\n- a deploy is first set into pending\r\n- a lock is grabbed, and if there aren't any other deploys in accepted or in-progress, the deploy can move to accepted. if there are, the deploy moves to failed\r\n- the deploy then stays in accepted until its build has succeeded or failed. if the build fails, the deploy moves to failed. if the build succeeds, the deploy moves from accepted to in progress\r\n- the deploy can then succeed or fail\r\n\r\nsorry my prior explanations were much more confusing. does this make sense?\r\n\r\nthis is also subject to change in the future, but i'll be happy to update this to match those semantics as they are introduced","created_at":"2018-08-23T20:34:04Z","updated_at":"2018-08-23T20:34:46Z","html_url":"https://github.com/mlab-lattice/lattice/pull/185#discussion_r212447930","pull_request_url":"https://api.github.com/repos/mlab-lattice/lattice/pulls/185","author_association":"COLLABORATOR","_links":{"self":{"href":"https://api.github.com/repos/mlab-lattice/lattice/pulls/comments/212447930"},"html":{"href":"https://github.com/mlab-lattice/lattice/pull/185#discussion_r212447930"},"pull_request":{"href":"https://api.github.com/repos/mlab-lattice/lattice/pulls/185"}}},"pull_request":{"url":"https://api.github.com/repos/mlab-lattice/lattice/pulls/185","id":204908795,"node_id":"MDExOlB1bGxSZXF1ZXN0MjA0OTA4Nzk1","html_url":"https://github.com/mlab-lattice/lattice/pull/185","diff_url":"https://github.com/mlab-lattice/lattice/pull/185.diff","patch_url":"https://github.com/mlab-lattice/lattice/pull/185.patch","issue_url":"https://api.github.com/repos/mlab-lattice/lattice/issues/185","number":185,"state":"open","locked":false,"title":"Mock api","user":{"login":"abdulito","id":1302521,"node_id":"MDQ6VXNlcjEzMDI1MjE=","avatar_url":"https://avatars1.githubusercontent.com/u/1302521?v=4","gravatar_id":"","url":"https://api.github.com/users/abdulito","html_url":"https://github.com/abdulito","followers_url":"https://api.github.com/users/abdulito/followers","following_url":"https://api.github.com/users/abdulito/following{/other_user}","gists_url":"https://api.github.com/users/abdulito/gists{/gist_id}","starred_url":"https://api.github.com/users/abdulito/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/abdulito/subscriptions","organizations_url":"https://api.github.com/users/abdulito/orgs","repos_url":"https://api.github.com/users/abdulito/repos","events_url":"https://api.github.com/users/abdulito/events{/privacy}","received_events_url":"https://api.github.com/users/abdulito/received_events","type":"User","site_admin":false},"body":"https://github.com/mlab-lattice/lattice/issues/157\r\n\r\n","created_at":"2018-07-30T19:40:56Z","updated_at":"2018-08-23T20:34:46Z","closed_at":null,"merged_at":null,"merge_commit_sha":"c8c66aa2d94f02c35d398d82fc199269d1d273dd","assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[],"milestone":null,"commits_url":"https://api.github.com/repos/mlab-lattice/lattice/pulls/185/commits","review_comments_url":"https://api.github.com/repos/mlab-lattice/lattice/pulls/185/comments","review_comment_url":"https://api.github.com/repos/mlab-lattice/lattice/pulls/comments{/number}","comments_url":"https://api.github.com/repos/mlab-lattice/lattice/issues/185/comments","statuses_url":"https://api.github.com/repos/mlab-lattice/lattice/statuses/eff9708796070ceb9c0e8a85c978647d0ead2155","head":{"label":"mlab-lattice:mock-api","ref":"mock-api","sha":"eff9708796070ceb9c0e8a85c978647d0ead2155","user":{"login":"mlab-lattice","id":31323173,"node_id":"MDEyOk9yZ2FuaXphdGlvbjMxMzIzMTcz","avatar_url":"https://avatars2.githubusercontent.com/u/31323173?v=4","gravatar_id":"","url":"https://api.github.com/users/mlab-lattice","html_url":"https://github.com/mlab-lattice","followers_url":"https://api.github.com/users/mlab-lattice/followers","following_url":"https://api.github.com/users/mlab-lattice/following{/other_user}","gists_url":"https://api.github.com/users/mlab-lattice/gists{/gist_id}","starred_url":"https://api.github.com/users/mlab-lattice/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/mlab-lattice/subscriptions","organizations_url":"https://api.github.com/users/mlab-lattice/orgs","repos_url":"https://api.github.com/users/mlab-lattice/repos","events_url":"https://api.github.com/users/mlab-lattice/events{/privacy}","received_events_url":"https://api.github.com/users/mlab-lattice/received_events","type":"Organization","site_admin":false},"repo":{"id":101960189,"node_id":"MDEwOlJlcG9zaXRvcnkxMDE5NjAxODk=","name":"lattice","full_name":"mlab-lattice/lattice","owner":{"login":"mlab-lattice","id":31323173,"node_id":"MDEyOk9yZ2FuaXphdGlvbjMxMzIzMTcz","avatar_url":"https://avatars2.githubusercontent.com/u/31323173?v=4","gravatar_id":"","url":"https://api.github.com/users/mlab-lattice","html_url":"https://github.com/mlab-lattice","followers_url":"https://api.github.com/users/mlab-lattice/followers","following_url":"https://api.github.com/users/mlab-lattice/following{/other_user}","gists_url":"https://api.github.com/users/mlab-lattice/gists{/gist_id}","starred_url":"https://api.github.com/users/mlab-lattice/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/mlab-lattice/subscriptions","organizations_url":"https://api.github.com/users/mlab-lattice/orgs","repos_url":"https://api.github.com/users/mlab-lattice/repos","events_url":"https://api.github.com/users/mlab-lattice/events{/privacy}","received_events_url":"https://api.github.com/users/mlab-lattice/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/mlab-lattice/lattice","description":null,"fork":false,"url":"https://api.github.com/repos/mlab-lattice/lattice","forks_url":"https://api.github.com/repos/mlab-lattice/lattice/forks","keys_url":"https://api.github.com/repos/mlab-lattice/lattice/keys{/key_id}","collaborators_url":"https://api.github.com/repos/mlab-lattice/lattice/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/mlab-lattice/lattice/teams","hooks_url":"https://api.github.com/repos/mlab-lattice/lattice/hooks","issue_events_url":"https://api.github.com/repos/mlab-lattice/lattice/issues/events{/number}","events_url":"https://api.github.com/repos/mlab-lattice/lattice/events","assignees_url":"https://api.github.com/repos/mlab-lattice/lattice/assignees{/user}","branches_url":"https://api.github.com/repos/mlab-lattice/lattice/branches{/branch}","tags_url":"https://api.github.com/repos/mlab-lattice/lattice/tags","blobs_url":"https://api.github.com/repos/mlab-lattice/lattice/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/mlab-lattice/lattice/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/mlab-lattice/lattice/git/refs{/sha}","trees_url":"https://api.github.com/repos/mlab-lattice/lattice/git/trees{/sha}","statuses_url":"https://api.github.com/repos/mlab-lattice/lattice/statuses/{sha}","languages_url":"https://api.github.com/repos/mlab-lattice/lattice/languages","stargazers_url":"https://api.github.com/repos/mlab-lattice/lattice/stargazers","contributors_url":"https://api.github.com/repos/mlab-lattice/lattice/contributors","subscribers_url":"https://api.github.com/repos/mlab-lattice/lattice/subscribers","subscription_url":"https://api.github.com/repos/mlab-lattice/lattice/subscription","commits_url":"https://api.github.com/repos/mlab-lattice/lattice/commits{/sha}","git_commits_url":"https://api.github.com/repos/mlab-lattice/lattice/git/commits{/sha}","comments_url":"https://api.github.com/repos/mlab-lattice/lattice/comments{/number}","issue_comment_url":"https://api.github.com/repos/mlab-lattice/lattice/issues/comments{/number}","contents_url":"https://api.github.com/repos/mlab-lattice/lattice/contents/{+path}","compare_url":"https://api.github.com/repos/mlab-lattice/lattice/compare/{base}...{head}","merges_url":"https://api.github.com/repos/mlab-lattice/lattice/merges","archive_url":"https://api.github.com/repos/mlab-lattice/lattice/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/mlab-lattice/lattice/downloads","issues_url":"https://api.github.com/repos/mlab-lattice/lattice/issues{/number}","pulls_url":"https://api.github.com/repos/mlab-lattice/lattice/pulls{/number}","milestones_url":"https://api.github.com/repos/mlab-lattice/lattice/milestones{/number}","notifications_url":"https://api.github.com/repos/mlab-lattice/lattice/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/mlab-lattice/lattice/labels{/name}","releases_url":"https://api.github.com/repos/mlab-lattice/lattice/releases{/id}","deployments_url":"https://api.github.com/repos/mlab-lattice/lattice/deployments","created_at":"2017-08-31T04:45:48Z","updated_at":"2018-08-23T18:33:56Z","pushed_at":"2018-08-23T18:37:12Z","git_url":"git://github.com/mlab-lattice/lattice.git","ssh_url":"git@github.com:mlab-lattice/lattice.git","clone_url":"https://github.com/mlab-lattice/lattice.git","svn_url":"https://github.com/mlab-lattice/lattice","homepage":null,"size":177542,"stargazers_count":0,"watchers_count":0,"language":"Go","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"open_issues_count":74,"license":{"key":"apache-2.0","name":"Apache License 2.0","spdx_id":"Apache-2.0","url":"https://api.github.com/licenses/apache-2.0","node_id":"MDc6TGljZW5zZTI="},"forks":0,"open_issues":74,"watchers":0,"default_branch":"master"}},"base":{"label":"mlab-lattice:master","ref":"master","sha":"6d04d2952e0665c294c1e51b0509679d9342b6b4","user":{"login":"mlab-lattice","id":31323173,"node_id":"MDEyOk9yZ2FuaXphdGlvbjMxMzIzMTcz","avatar_url":"https://avatars2.githubusercontent.com/u/31323173?v=4","gravatar_id":"","url":"https://api.github.com/users/mlab-lattice","html_url":"https://github.com/mlab-lattice","followers_url":"https://api.github.com/users/mlab-lattice/followers","following_url":"https://api.github.com/users/mlab-lattice/following{/other_user}","gists_url":"https://api.github.com/users/mlab-lattice/gists{/gist_id}","starred_url":"https://api.github.com/users/mlab-lattice/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/mlab-lattice/subscriptions","organizations_url":"https://api.github.com/users/mlab-lattice/orgs","repos_url":"https://api.github.com/users/mlab-lattice/repos","events_url":"https://api.github.com/users/mlab-lattice/events{/privacy}","received_events_url":"https://api.github.com/users/mlab-lattice/received_events","type":"Organization","site_admin":false},"repo":{"id":101960189,"node_id":"MDEwOlJlcG9zaXRvcnkxMDE5NjAxODk=","name":"lattice","full_name":"mlab-lattice/lattice","owner":{"login":"mlab-lattice","id":31323173,"node_id":"MDEyOk9yZ2FuaXphdGlvbjMxMzIzMTcz","avatar_url":"https://avatars2.githubusercontent.com/u/31323173?v=4","gravatar_id":"","url":"https://api.github.com/users/mlab-lattice","html_url":"https://github.com/mlab-lattice","followers_url":"https://api.github.com/users/mlab-lattice/followers","following_url":"https://api.github.com/users/mlab-lattice/following{/other_user}","gists_url":"https://api.github.com/users/mlab-lattice/gists{/gist_id}","starred_url":"https://api.github.com/users/mlab-lattice/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/mlab-lattice/subscriptions","organizations_url":"https://api.github.com/users/mlab-lattice/orgs","repos_url":"https://api.github.com/users/mlab-lattice/repos","events_url":"https://api.github.com/users/mlab-lattice/events{/privacy}","received_events_url":"https://api.github.com/users/mlab-lattice/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/mlab-lattice/lattice","description":null,"fork":false,"url":"https://api.github.com/repos/mlab-lattice/lattice","forks_url":"https://api.github.com/repos/mlab-lattice/lattice/forks","keys_url":"https://api.github.com/repos/mlab-lattice/lattice/keys{/key_id}","collaborators_url":"https://api.github.com/repos/mlab-lattice/lattice/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/mlab-lattice/lattice/teams","hooks_url":"https://api.github.com/repos/mlab-lattice/lattice/hooks","issue_events_url":"https://api.github.com/repos/mlab-lattice/lattice/issues/events{/number}","events_url":"https://api.github.com/repos/mlab-lattice/lattice/events","assignees_url":"https://api.github.com/repos/mlab-lattice/lattice/assignees{/user}","branches_url":"https://api.github.com/repos/mlab-lattice/lattice/branches{/branch}","tags_url":"https://api.github.com/repos/mlab-lattice/lattice/tags","blobs_url":"https://api.github.com/repos/mlab-lattice/lattice/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/mlab-lattice/lattice/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/mlab-lattice/lattice/git/refs{/sha}","trees_url":"https://api.github.com/repos/mlab-lattice/lattice/git/trees{/sha}","statuses_url":"https://api.github.com/repos/mlab-lattice/lattice/statuses/{sha}","languages_url":"https://api.github.com/repos/mlab-lattice/lattice/languages","stargazers_url":"https://api.github.com/repos/mlab-lattice/lattice/stargazers","contributors_url":"https://api.github.com/repos/mlab-lattice/lattice/contributors","subscribers_url":"https://api.github.com/repos/mlab-lattice/lattice/subscribers","subscription_url":"https://api.github.com/repos/mlab-lattice/lattice/subscription","commits_url":"https://api.github.com/repos/mlab-lattice/lattice/commits{/sha}","git_commits_url":"https://api.github.com/repos/mlab-lattice/lattice/git/commits{/sha}","comments_url":"https://api.github.com/repos/mlab-lattice/lattice/comments{/number}","issue_comment_url":"https://api.github.com/repos/mlab-lattice/lattice/issues/comments{/number}","contents_url":"https://api.github.com/repos/mlab-lattice/lattice/contents/{+path}","compare_url":"https://api.github.com/repos/mlab-lattice/lattice/compare/{base}...{head}","merges_url":"https://api.github.com/repos/mlab-lattice/lattice/merges","archive_url":"https://api.github.com/repos/mlab-lattice/lattice/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/mlab-lattice/lattice/downloads","issues_url":"https://api.github.com/repos/mlab-lattice/lattice/issues{/number}","pulls_url":"https://api.github.com/repos/mlab-lattice/lattice/pulls{/number}","milestones_url":"https://api.github.com/repos/mlab-lattice/lattice/milestones{/number}","notifications_url":"https://api.github.com/repos/mlab-lattice/lattice/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/mlab-lattice/lattice/labels{/name}","releases_url":"https://api.github.com/repos/mlab-lattice/lattice/releases{/id}","deployments_url":"https://api.github.com/repos/mlab-lattice/lattice/deployments","created_at":"2017-08-31T04:45:48Z","updated_at":"2018-08-23T18:33:56Z","pushed_at":"2018-08-23T18:37:12Z","git_url":"git://github.com/mlab-lattice/lattice.git","ssh_url":"git@github.com:mlab-lattice/lattice.git","clone_url":"https://github.com/mlab-lattice/lattice.git","svn_url":"https://github.com/mlab-lattice/lattice","homepage":null,"size":177542,"stargazers_count":0,"watchers_count":0,"language":"Go","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"open_issues_count":74,"license":{"key":"apache-2.0","name":"Apache License 2.0","spdx_id":"Apache-2.0","url":"https://api.github.com/licenses/apache-2.0","node_id":"MDc6TGljZW5zZTI="},"forks":0,"open_issues":74,"watchers":0,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/mlab-lattice/lattice/pulls/185"},"html":{"href":"https://github.com/mlab-lattice/lattice/pull/185"},"issue":{"href":"https://api.github.com/repos/mlab-lattice/lattice/issues/185"},"comments":{"href":"https://api.github.com/repos/mlab-lattice/lattice/issues/185/comments"},"review_comments":{"href":"https://api.github.com/repos/mlab-lattice/lattice/pulls/185/comments"},"review_comment":{"href":"https://api.github.com/repos/mlab-lattice/lattice/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/mlab-lattice/lattice/pulls/185/commits"},"statuses":{"href":"https://api.github.com/repos/mlab-lattice/lattice/statuses/eff9708796070ceb9c0e8a85c978647d0ead2155"}},"author_association":"CONTRIBUTOR"}}
{"actor":{"display_login":"kevindrosendahl"}}