Exemple #1
0
func TestModifyResources(t *testing.T) {
	assert := assert.New(t)
	version := "1.2.3-test"

	pair := baseDeployablePair()

	pair.Prior.Deployment.SourceID.Version = semv.MustParse(version)
	pair.Prior.Deployment.Resources["memory"] = "100"

	pair.Post.Deployment.SourceID.Version = semv.MustParse(version)
	pair.Post.Deployment.Resources["memory"] = "500"
	pair.Post.BuildArtifact.Name = "1.2.3"

	mods := make(chan *sous.DeployablePair, 1)
	errs := make(chan error, 10)

	client := sous.NewDummyRectificationClient()
	deployer := NewDeployer(client)

	mods <- pair
	close(mods)
	deployer.RectifyModifies(mods, errs)
	close(errs)

	for e := range errs {
		t.Error(e)
	}

	assert.Len(client.Created, 0)

	if assert.Len(client.Deployed, 1) {
		assert.Regexp("1.2.3", client.Deployed[0].ImageName)
		assert.Regexp("500", client.Deployed[0].Res["memory"])
	}
}
Exemple #2
0
func TestModifyImage(t *testing.T) {
	assert := assert.New(t)
	sous.Log.Warn.SetOutput(os.Stderr)
	Log.Debug.SetOutput(os.Stderr)

	before := "1.2.3-test"
	after := "2.3.4-new"
	pair := baseDeployablePair()
	pair.Prior.Deployment.SourceID.Version = semv.MustParse(before)
	pair.Post.Deployment.SourceID.Version = semv.MustParse(after)
	pair.Post.BuildArtifact.Name = "2.3.4"

	mods := make(chan *sous.DeployablePair, 1)
	errs := make(chan error, 10)

	client := sous.NewDummyRectificationClient()
	deployer := NewDeployer(client)

	mods <- pair
	close(mods)
	deployer.RectifyModifies(mods, errs)
	close(errs)

	for e := range errs {
		t.Error(e)
	}

	assert.Len(client.Created, 0)

	if assert.Len(client.Deployed, 1) {
		assert.Regexp("2.3.4", client.Deployed[0].ImageName)
	}
}
func makeTestState() *sous.State {
	cluster1 := &sous.Cluster{
		Name:    "cluster-1",
		Kind:    "singularity",
		BaseURL: "http://nothing.here.one",
		Env: sous.EnvDefaults{
			"CLUSTER_LONG_NAME": sous.Var("Cluster One"),
		},
	}
	cluster2 := &sous.Cluster{
		Name:    "cluster-2",
		Kind:    "singularity",
		BaseURL: "http://nothing.here.two",
		Env: sous.EnvDefaults{
			"CLUSTER_LONG_NAME": sous.Var("Cluster Two"),
		},
	}
	return &sous.State{
		Defs: sous.Defs{
			DockerRepo: "some.docker.repo",
			Clusters: sous.Clusters{
				"cluster-1": cluster1,
				"cluster-2": cluster2,
			},
		},
		Manifests: sous.NewManifests(
			&sous.Manifest{
				Source: project1,
				Owners: []string{"owner1"},
				Kind:   sous.ManifestKindService,
				Deployments: sous.DeploySpecs{
					"cluster-1": {
						Version: semv.MustParse("1.0.0"),
						DeployConfig: sous.DeployConfig{
							Metadata: sous.Metadata{
								"BuildBranch": "master",
								"DeployOn":    "build success",
							},
							NumInstances: 2,
						},
					},
					"cluster-2": {
						Version: semv.MustParse("2.0.0"),
						DeployConfig: sous.DeployConfig{
							Metadata: sous.Metadata{
								"BuildBranch": "master",
								"DeployOn":    "version advance",
							},
							NumInstances: 3,
						},
					},
				},
			},
		),
	}
}
Exemple #4
0
func manifest(nc sous.Registry, drepo, containerDir, sourceURL, version string) *sous.Manifest {
	in := BuildImageName(drepo, version)
	BuildAndPushContainer(containerDir, in)

	nc.GetSourceID(docker.NewBuildArtifact(in, nil))

	return &sous.Manifest{
		Source: sous.SourceLocation{
			Repo: sourceURL,
		},
		Owners: []string{`xyz`},
		Kind:   sous.ManifestKindService,
		Deployments: sous.DeploySpecs{
			"test-cluster": sous.DeploySpec{
				DeployConfig: sous.DeployConfig{
					Resources:    sous.Resources{"cpus": "0.1", "memory": "100", "ports": "1"},
					Args:         []string{},
					Env:          sous.Env{"repo": drepo}, //map[s]s
					NumInstances: 1,
					Volumes:      sous.Volumes{{"/tmp", "/tmp", sous.VolumeMode("RO")}},
				},
				Version: semv.MustParse(version),
			},
		},
	}
}
Exemple #5
0
func TestBuildDeployment(t *testing.T) {
	assert := assert.New(t)
	m := &Manifest{
		Source: SourceLocation{},
		Owners: []string{"*****@*****.**"},
		Kind:   ManifestKindService,
	}
	sp := DeploySpec{
		DeployConfig: DeployConfig{
			Resources:    Resources{},
			Args:         []string{},
			Env:          Env{},
			NumInstances: 3,
			Volumes: Volumes{
				&Volume{"h", "c", "RO"},
			},
		},
		Version:     semv.MustParse("1.2.3"),
		clusterName: "cluster.name",
	}
	var ih []DeploySpec
	nick := "cn"

	state := &State{Defs: Defs{Clusters: Clusters{nick: &Cluster{BaseURL: "http://not"}}}}

	d, err := BuildDeployment(state, m, nick, sp, ih)

	if assert.NoError(err) {
		if assert.Len(d.DeployConfig.Volumes, 1) {
			assert.Equal("c", d.DeployConfig.Volumes[0].Container)
		}
		assert.Equal(nick, d.ClusterName)
	}
}
Exemple #6
0
func nearestVersion(tags []Tag) semv.Version {
	for _, t := range tags {
		v, err := semv.Parse(t.Name)
		if err == nil {
			return v
		}
	}
	return semv.MustParse("0.0.0-unversioned")
}
Exemple #7
0
func TestModify(t *testing.T) {
	Log.Debug.SetOutput(os.Stderr)
	defer Log.Debug.SetOutput(ioutil.Discard)
	assert := assert.New(t)
	before := "1.2.3-test"
	after := "2.3.4-new"

	pair := baseDeployablePair()

	pair.Prior.Deployment.SourceID.Version = semv.MustParse(before)
	pair.Prior.Deployment.DeployConfig.NumInstances = 1
	pair.Prior.Deployment.DeployConfig.Volumes = sous.Volumes{{"host", "container", "RO"}}

	pair.Post.Deployment.SourceID.Version = semv.MustParse(after)
	pair.Post.Deployment.DeployConfig.NumInstances = 24
	pair.Post.Deployment.DeployConfig.Volumes = sous.Volumes{{"host", "container", "RW"}}
	pair.Post.BuildArtifact.Name = "2.3.4"

	mods := make(chan *sous.DeployablePair, 1)
	errs := make(chan error, 10)

	client := sous.NewDummyRectificationClient()
	deployer := NewDeployer(client)

	mods <- pair
	close(mods)
	deployer.RectifyModifies(mods, errs)
	close(errs)

	for e := range errs {
		t.Error(e)
	}

	if assert.Len(client.Created, 1) {
		assert.Equal(24, client.Created[0].Count)
	}

	if assert.Len(client.Deployed, 1) {
		assert.Regexp("2.3.4", client.Deployed[0].ImageName)
		log.Print(client.Deployed[0].Vols)
		assert.Equal("RW", string(client.Deployed[0].Vols[0].Mode))
	}

}
Exemple #8
0
func prepareCommand(t *testing.T, cl []string) (*CLI, *cmdr.PreparedExecution, fmt.Stringer, fmt.Stringer) {
	require := require.New(t)

	stdin := &bytes.Buffer{}
	stdout := &bytes.Buffer{}
	stderr := &bytes.Buffer{}

	s := &Sous{Version: semv.MustParse(`1.2.3`)}
	c, err := NewSousCLI(s, stdin, stdout, stderr)
	require.NoError(err)

	exe, err := c.Prepare(cl)
	require.NoError(err)

	return c, exe, stdout, stderr
}
Exemple #9
0
func TestInvokeWithUnknownFlags(t *testing.T) {
	log.SetFlags(log.Flags() | log.Lshortfile)
	assert := assert.New(t)
	require := require.New(t)

	stdin := &bytes.Buffer{}
	stdout := &bytes.Buffer{}
	stderr := &bytes.Buffer{}

	s := &Sous{Version: semv.MustParse(`1.2.3`)}
	c, err := NewSousCLI(s, stdin, stdout, stderr)
	require.NoError(err)

	c.Invoke([]string{`sous`, `-cobblers`})
	assert.Regexp(`flag provided but not defined`, stderr.String())
}
Exemple #10
0
// Version returns the SourceID.
func (sc *SourceContext) Version() SourceID {
	v, err := semv.Parse(sc.NearestTagName)
	if err != nil {
		v = nearestVersion(sc.Tags)
	}
	// Append revision ID.
	v = semv.MustParse(v.Format("M.m.p-?") + "+" + sc.Revision)
	sv := SourceID{
		Location: SourceLocation{
			Repo: sc.RemoteURL,
			Dir:  sc.OffsetDir,
		},
		Version: v,
	}
	Log.Debug.Printf("Version: % #v", sv)
	return sv
}
Exemple #11
0
// NewTerminal creates a new test terminal.
func NewTerminal(t *testing.T, vstr string) *Terminal {
	v := semv.MustParse(vstr)
	baseout := TestOutput{"stdout", &bytes.Buffer{}, t}
	baseerr := TestOutput{"stderr", &bytes.Buffer{}, t}
	combined := TestOutput{"combined output", &bytes.Buffer{}, t}

	in := &bytes.Buffer{}
	out := io.MultiWriter(baseout.Buffer, combined.Buffer)
	err := io.MultiWriter(baseerr.Buffer, combined.Buffer)

	s := &cli.Sous{Version: v}
	c, er := cli.NewSousCLI(s, in, out, err)
	if er != nil {
		panic(er)
	}

	return &Terminal{c, baseout, baseerr, combined, []string{}, t}
}
func buildManifest(cluster, repo, version string) *sous.Manifest {
	return &sous.Manifest{
		Owners: []string{"tom", "dick", "harry"},
		Source: sous.SourceLocation{Repo: repo},
		Kind:   sous.ManifestKindService,
		Deployments: sous.DeploySpecs{
			cluster: sous.DeploySpec{
				Version: semv.MustParse(version),
				DeployConfig: sous.DeployConfig{
					Resources: sous.Resources{
						"cpus":   "1",
						"memory": "256",
						"ports":  "1",
					},
				},
			},
		},
	}
}
func TestHTTPNameInserter(t *testing.T) {

	reqd := false
	h := func(rw http.ResponseWriter, r *http.Request) {
		if meth := r.Method; strings.ToUpper(meth) != "PUT" {
			t.Errorf("Method should be PUT was: %s", meth)
		}
		if path := r.URL.Path; path != "/artifact" {
			t.Errorf("Path should be '/artifact' but was: %s", path)
		}
		body, err := ioutil.ReadAll(r.Body)
		if err != nil {
			t.Error(err)
		}
		if len(body) <= 0 {
			t.Errorf("Empty body")
		}
		rw.WriteHeader(200)
		reqd = true
	}

	srv := httptest.NewServer(http.HandlerFunc(h))

	hni, err := NewHTTPNameInserter(srv.URL)
	if err != nil {
		t.Error(err)
	}
	err = hni.Insert(
		SourceID{Location: SourceLocation{Repo: "a-repo", Dir: "offset"}, Version: semv.MustParse("5.5.5")},
		"dockerthin.com/repo/latest",
		"",
		[]Quality{},
	)
	if err != nil {
		t.Error(err)
	}
	if !reqd {
		t.Errorf("No request issued")
	}
}
Exemple #14
0
func TestState_Validate(t *testing.T) {

	mid := MustParseManifestID("github.com/user/repo")

	// TODO: Expand the definition of "valid". At the time of initial writing,
	// only the individual manifests are validated without reference to
	// definitions in State.Defs; they should additionally be validated against
	// these definitions.

	validState := &State{
		Manifests: NewManifestsFromMap(map[ManifestID]*Manifest{
			mid: &Manifest{
				Source: mid.Source,
				Kind:   ManifestKindService,
				Deployments: DeploySpecs{
					"some-cluster": DeploySpec{
						DeployConfig: DeployConfig{
							Resources: Resources{
								"cpus":   "1",
								"memory": "256",
								"ports":  "1",
							},
							NumInstances: 3,
						},
						Version: semv.MustParse("1"),
					},
				},
			},
		}),
	}

	flaws := validState.Validate()
	if len(flaws) != 0 {
		for _, f := range flaws {
			t.Error(f)
		}
		t.Fatalf("got %d flaws; want 0", len(flaws))
	}

}
Exemple #15
0
func (nc *NameCache) dbQueryAllSourceIds() (ids []sous.SourceID, err error) {
	rows, err := nc.DB.Query("select docker_search_location.repo, " +
		"docker_search_location.offset, " +
		"docker_search_metadata.version " +
		"from " +
		"docker_search_location natural join docker_search_metadata")
	if err != nil {
		return
	}
	for rows.Next() {
		var r, o, v string
		rows.Scan(&r, &o, &v)
		ids = append(ids, sous.SourceID{
			Location: sous.SourceLocation{
				Repo: r, Dir: o,
			},
			Version: semv.MustParse(v),
		})
	}
	err = rows.Err()
	return
}
Exemple #16
0
		RepairError:      "unable to repair invalid ManifestKind",
	},
	{
		OriginalManifest: &Manifest{
			Kind: ManifestKindService,
			Deployments: DeploySpecs{
				"some-cluster": DeploySpec{
					DeployConfig: DeployConfig{
						Resources: Resources{
							"cpus": "1",
							// NOTE: Missing memory.
							"ports": "1",
						},
						NumInstances: 3,
					},
					Version: semv.MustParse("1"),
				},
			},
		},
		FixedManifest: &Manifest{
			Kind: ManifestKindService,
			Deployments: DeploySpecs{
				"some-cluster": DeploySpec{
					DeployConfig: DeployConfig{
						Resources: Resources{
							"cpus": "1",
							// NOTE: Memory repaired by setting to default.
							"memory": "100",
							"ports":  "1",
						},
						NumInstances: 3,
Exemple #17
0
package sous

import (
	"testing"

	"github.com/samsalisbury/semv"
)

var parseSourceIDTests = map[string]SourceID{
	"github.com/opentable/sous,1,": {
		Location: SourceLocation{
			Repo: "github.com/opentable/sous",
		},
		Version: semv.MustParse("1"),
	},
	":github.com/opentable/sous:1:": {
		Location: SourceLocation{
			Repo: "github.com/opentable/sous",
		},
		Version: semv.MustParse("1"),
	},
	"github.com/opentable/sous,1": {
		Location: SourceLocation{
			Repo: "github.com/opentable/sous",
		},
		Version: semv.MustParse("1"),
	},
	",github.com/opentable/sous,1,util": {
		Location: SourceLocation{
			Repo: "github.com/opentable/sous",
			Dir:  "util",
Exemple #18
0
package main

import (
	"runtime"

	"github.com/samsalisbury/semv"
)

// VersionString is the version of Sous.
const VersionString = "0.1.1-beta.1"

var (
	// Version is the version of Sous.
	Version = semv.MustParse(VersionString + "+" + Revision)
	// OS is the OS this Sous is running on.
	OS = runtime.GOOS
	// Arch is the architecture this Sous is running on.
	Arch = runtime.GOARCH
	// GoVersion is the version of Go this sous was built with.
	GoVersion = runtime.Version()
	// Revision may be set by the build process using build flags.
	Revision string
)
							},
							Env: Env{
								"ENV_2": "ENV TWO FLAVORED",
							},
							NumInstances: 5,
						},
					},
				},
			},
		),
	}
}

var expectedDeployments = NewDeployments(
	&Deployment{
		SourceID:    project1.SourceID(semv.MustParse("1.0.0")),
		ClusterName: "cluster-1",
		Cluster:     cluster1,
		Kind:        ManifestKindService,
		Owners:      NewOwnerSet("owner1"),
		DeployConfig: DeployConfig{
			Resources: Resources{
				"cpus": "1",
				"mem":  "1024",
			},
			Env: Env{
				"ALL":               "IS ONE",
				"ENV_1":             "ENV ONE",
				"CLUSTER_LONG_NAME": "Cluster One",
			},
			Metadata: Metadata{
func makeTestState() *State {
	return &State{
		Defs: Defs{
			DockerRepo: "some.docker.repo",
			Clusters: Clusters{
				"cluster-1": cluster1,
				"cluster-2": cluster2,
			},
			EnvVars: EnvDefs{
				{
					Name:  "CLUSTER_LONG_NAME",
					Desc:  "The human-friendly name of this cluster.",
					Scope: "cluster",
					Type:  VarType("string"),
				},
			},
			Resources: FieldDefinitions{
				{Name: "cpus", Type: "float"},
				{Name: "mem", Type: "memory_size"},
			},
		},
		Manifests: NewManifests(
			&Manifest{
				Source: project1,
				Owners: []string{"owner1"},
				Kind:   ManifestKindService,
				Deployments: DeploySpecs{
					"cluster-1": {
						Version: semv.MustParse("1.0.0"),
						DeployConfig: DeployConfig{
							Resources: Resources{
								"cpus": "1",
								"mem":  "1024",
							},
							Env: Env{
								"ALL":   "IS ONE",
								"ENV_1": "ENV ONE",
							},
							Metadata: Metadata{
								"everybody": "wants to be a cat",
								"name":      "O'Malley",
							},
							NumInstances: 2,
						},
					},
					"cluster-2": {
						Version: semv.MustParse("2.0.0"),
						DeployConfig: DeployConfig{
							Resources: Resources{
								"cpus": "2",
								"mem":  "2048",
							},
							Env: Env{
								"ALL":   "IS ONE",
								"ENV_2": "ENV TWO",
							},
							Metadata: Metadata{
								"everybody": "wants to be a cat",
								"name":      "Duchess",
							},
							NumInstances: 3,
						},
					},
				},
			},
			&Manifest{
				Source: project1,
				Flavor: "some-flavor",
				Owners: []string{"owner1flav"},
				Kind:   ManifestKindService,
				Deployments: DeploySpecs{
					"cluster-1": {
						Version: semv.MustParse("1.0.1"),
						DeployConfig: DeployConfig{
							Resources: Resources{
								"cpus": "1.5",
								"mem":  "1024",
							},
							Env: Env{
								"ENV_1": "ENV ONE FLAVORED",
							},
							NumInstances: 4,
						},
					},
					"cluster-2": {
						Version: semv.MustParse("2.0.1"),
						DeployConfig: DeployConfig{
							Resources: Resources{
								"cpus": "2.5",
								"mem":  "2048",
							},
							Env: Env{
								"ENV_2": "ENV TWO FLAVORED",
							},
							NumInstances: 5,
						},
					},
				},
			},
		),
	}
}
func TestWriteState(t *testing.T) {
	steadyManifest := buildManifest("test-cluster", "github.com/opentable/steady", "1.2.3")
	diesManifest := buildManifest("test-cluster", "github.com/opentable/dies", "133.56.987431")
	changesManifest := buildManifest("test-cluster", "github.com/opentable/changes", "0.17.19")
	newManifest := buildManifest("test-cluster", "github.com/opentable/new", "0.0.1")

	state := &sous.State{}
	state.Defs.Clusters = make(sous.Clusters)
	state.Defs.Clusters["test-cluster"] = &sous.Cluster{Name: "test-cluster"}

	// Current issue: "incomplete" manifests never complete to get updates
	// There aren't any deploy specs for extra, which mimics this bug
	state.Defs.Clusters["extra-cluster"] = &sous.Cluster{Name: "cluster-cluster"}

	state.Manifests = sous.NewManifests()
	state.Manifests.Add(steadyManifest)
	state.Manifests.Add(diesManifest)
	state.Manifests.Add(changesManifest)

	sm := sous.DummyStateManager{State: state}
	smm, err := sm.ReadState()
	if err != nil {
		t.Fatal("State manager double is broken", err)
	}
	if smm.Manifests.Len() <= 0 {
		t.Fatal("State manager double is empty")
	}

	gf := func() server.Injector {
		di := psyringe.New()
		//di.Add(sous.NewLogSet(os.Stderr, os.Stderr, ioutil.Discard))
		di.Add(sous.NewLogSet(os.Stderr, ioutil.Discard, ioutil.Discard))
		graph.AddInternals(di)
		di.Add(
			func() graph.StateReader { return graph.StateReader{StateReader: &sm} },
			func() graph.StateWriter { return graph.StateWriter{StateWriter: &sm} },
		)
		di.Add(&config.Verbosity{})
		return di
	}

	testServer := httptest.NewServer(server.SousRouteMap.BuildRouter(gf))
	defer testServer.Close()

	hsm, err := sous.NewHTTPStateManager(testServer.URL)
	if err != nil {
		t.Fatal(err)
	}

	originalState, err := hsm.ReadState()
	if err != nil {
		t.Fatal(err)
	}

	for id, m := range originalState.Manifests.Snapshot() {
		t.Logf("hsm INITIAL state: Manifest %q; Kind = %q\n  %#v\n", id, m.Kind, m)
	}

	log.Printf("original state: %#v", originalState)
	if originalState.Manifests.Len() != state.Manifests.Len() {
		t.Errorf("Local state has %d manifests to remote's %d", originalState.Manifests.Len(), state.Manifests.Len())
	}

	originalState.Manifests.Remove(diesManifest.ID())
	originalState.Manifests.Add(newManifest)
	ch, there := originalState.Manifests.Get(changesManifest.ID())
	if !there {
		t.Fatalf("Changed manifest %q not in local manifests!", changesManifest.ID())
	}
	changedDeployment := ch.Deployments["test-cluster"]
	changedDeployment.Version = semv.MustParse("0.18.0")
	ch.Deployments["test-cluster"] = changedDeployment
	originalState.Manifests.Set(ch.ID(), ch)

	log.Printf("state after update: %#v", originalState)

	if err := hsm.WriteState(originalState); err != nil {
		t.Fatalf("Failed to write state: %+v", err)
	}

	state, err = hsm.ReadState()
	if err != nil {
		t.Fatal(err)
	}

	for id, m := range state.Manifests.Snapshot() {
		t.Logf("hsm UPDATED state: Manifest %q; Kind = %q\n  %#v\n", id, m.Kind, m)
	}

	if originalState.Manifests.Len() != state.Manifests.Len() {
		t.Errorf("After write, local state has %d manifests to remote's %d", originalState.Manifests.Len(), state.Manifests.Len())
	}

	d, there := state.Manifests.Get(diesManifest.ID())
	if there {
		t.Errorf("Removed manifest still in server's state: %#v", d)
	}
	_, there = state.Manifests.Get(steadyManifest.ID())
	if !there {
		t.Errorf("Untouched manifest not in server's state")
	}
	_, there = state.Manifests.Get(newManifest.ID())
	if !there {
		t.Errorf("Added manifest not in server's state")
	}
	c, there := state.Manifests.Get(changesManifest.ID())
	if !there {
		t.Errorf("Changed manifest missing from server's state")
	}
	expectedVersion := "0.18.0"
	actualVersion := c.Deployments["test-cluster"].Version.String()
	if actualVersion != expectedVersion {
		t.Errorf("Server's version of changed state was %q; want %q", actualVersion, expectedVersion)
	}
}
func TestRealDiffConcentration(t *testing.T) {
	log.SetFlags(log.Flags() | log.Lshortfile)
	assert := assert.New(t)
	require := require.New(t)

	intended := NewDeployments()
	existing := NewDeployments()
	defs := Defs{Clusters: map[string]*Cluster{"test": &Cluster{}}}

	makeDepl := func(repo, verstr string, num int) *Deployment {
		version := semv.MustParse(verstr)
		cl := defs.Clusters["test"]
		owners := OwnerSet{}
		owners.Add("judson")
		return &Deployment{
			SourceID: SourceID{
				Location: SourceLocation{
					Repo: repo,
				},
				Version: version,
			},
			Cluster:     cl,
			ClusterName: "test",
			DeployConfig: DeployConfig{
				NumInstances: num,
				Env:          map[string]string{},
				Resources: map[string]string{
					"cpu":    ".1",
					"memory": "100",
					"ports":  "1",
				},
			},
			Owners: owners,
		}
	}

	repoOne := "github.com/opentable/one"
	repoTwo := "github.com/opentable/two"
	repoThree := "github.com/opentable/three"
	repoFour := "github.com/opentable/four"
	repoFive := "github.com/opentable/five"

	existing.MustAdd(makeDepl(repoOne, "111.1.1", 1)) //remove
	//intended: gone

	existing.MustAdd(makeDepl(repoTwo, "1.0.0", 1)) //same
	intended.MustAdd(makeDepl(repoTwo, "1.0.0", 1)) //same

	// existing: doesn't yet
	intended.MustAdd(makeDepl(repoFour, "1.0.0", 1)) //create

	existing.MustAdd(makeDepl(repoThree, "1.0.0", 1)) //changed
	intended.MustAdd(makeDepl(repoThree, "1.0.0", 2)) //changed

	existing.MustAdd(makeDepl(repoFive, "1.0.0", 1)) //changed
	intended.MustAdd(makeDepl(repoFive, "2.0.0", 1)) //changed

	dc := existing.Diff(intended).Concentrate(defs)
	ds, err := dc.collect()
	require.NoError(err)

	if assert.Len(ds.Gone.Snapshot(), 1, "Should have one deleted item.") {
		it, _ := ds.Gone.Any(func(*Manifest) bool { return true })
		assert.Equal(string(it.Source.Repo), repoOne)
	}

	if assert.Len(ds.Same.Snapshot(), 1, "Should have one unchanged item.") {
		it, _ := ds.Same.Any(func(*Manifest) bool { return true })
		assert.Equal(string(it.Source.Repo), repoTwo)
	}

	if assert.Len(ds.Changed, 2, "Should have two modified items.") {
		chNum, chVer := ds.Changed[0], ds.Changed[1]
		if repoThree == chVer.name.Source.Repo {
			chNum, chVer = chVer, chNum
		}
		assert.Equal(repoThree, string(chNum.name.Source.Repo))
		assert.Equal(repoThree, string(chNum.Prior.Source.Repo))
		assert.Equal(repoThree, string(chNum.Post.Source.Repo))
		log.Printf("%+v", chNum)
		log.Printf("%+v", chNum.Prior)
		log.Printf("%+v", chNum.Post)
		assert.Equal(chNum.Prior.Deployments["test"].NumInstances, 1)
		assert.Equal(chNum.Post.Deployments["test"].NumInstances, 2)

		assert.Equal(repoFive, string(chVer.name.Source.Repo))
		assert.Equal(repoFive, string(chVer.Prior.Source.Repo))
		assert.Equal(repoFive, string(chVer.Post.Source.Repo))
		ver1 := semv.MustParse("1.0.0")
		ver2 := semv.MustParse("2.0.0")
		assert.Equal(ver1, chVer.Prior.Deployments["test"].Version)
		assert.Equal(ver2, chVer.Post.Deployments["test"].Version)
	}

	if assert.Equal(ds.New.Len(), 1, "Should have one added item.") {
		it, _ := ds.New.Any(func(*Manifest) bool { return true })
		assert.Equal(string(it.Source.Repo), repoFour)
	}

}
func exampleState() *sous.State {
	sl := sous.SourceLocation{
		Repo: "github.com/opentable/sous",
	}
	sl2 := sous.SourceLocation{
		Repo: "github.com/user/project",
	}
	return &sous.State{
		Manifests: sous.NewManifests(
			&sous.Manifest{
				Source: sl,
				Owners: []string{"Judson", "Sam"},
				Kind:   "http-service",
				Deployments: map[string]sous.DeploySpec{
					"cluster-1": sous.DeploySpec{
						DeployConfig: sous.DeployConfig{
							Env: sous.Env{
								"SOME_DB_URL": "https://some.database",
							},
							Resources: sous.Resources{
								"cpus":   "0.1",
								"memory": "2GB",
								"ports":  "1",
							},
							NumInstances: 6,
							Volumes:      sous.Volumes{},
						},
						Version: semv.MustParse("1.0.0-rc.1+deadbeef"),
					},
				},
			},
			&sous.Manifest{
				Source: sl2,
				Owners: []string{"Sous Team"},
				Kind:   "http-service",
				Deployments: map[string]sous.DeploySpec{
					"other-cluster": {
						DeployConfig: sous.DeployConfig{
							Env: sous.Env{
								"DEBUG": "YES",
							},
							Resources: sous.Resources{
								"cpus":   "1",
								"memory": "256MB",
								"ports":  "1",
							},
							Volumes: sous.Volumes{},
						},
						Version: semv.MustParse("0.3.1-beta+b4d455ee"),
					},
				},
			},
		),
		Defs: sous.Defs{
			DockerRepo: "docker.somewhere.horse",
			Clusters: sous.Clusters{
				"cluster-1": &sous.Cluster{
					Kind:    "singularity",
					BaseURL: "http://singularity.example.com",
				},
				"other-cluster": &sous.Cluster{
					Kind:    "singularity",
					BaseURL: "http://some.singularity.cluster",
				},
			},
			EnvVars:   sous.EnvDefs{},
			Resources: sous.FieldDefinitions{},
			Metadata:  sous.FieldDefinitions{},
		},
	}
}