Ejemplo n.º 1
0
// GenerateRoleImages generates all role images using dev releases
func (f *Fissile) GenerateRoleImages(targetPath, repository, metricsPath string, noBuild, force bool, workerCount int, rolesManifestPath, compiledPackagesPath, lightManifestPath, darkManifestPath string) error {
	if len(f.releases) == 0 {
		return fmt.Errorf("Releases not loaded")
	}

	if metricsPath != "" {
		stampy.Stamp(metricsPath, "fissile", "create-role-images", "start")
		defer stampy.Stamp(metricsPath, "fissile", "create-role-images", "done")
	}

	roleManifest, err := model.LoadRoleManifest(rolesManifestPath, f.releases)
	if err != nil {
		return fmt.Errorf("Error loading roles manifest: %s", err.Error())
	}

	packagesImageBuilder, err := builder.NewPackagesImageBuilder(
		repository,
		compiledPackagesPath,
		targetPath,
		f.Version,
		f.UI,
	)
	if err != nil {
		return err
	}

	err = f.GeneratePackagesRoleImage(repository, roleManifest, noBuild, force, packagesImageBuilder)
	if err != nil {
		return err
	}

	packagesLayerImageName := packagesImageBuilder.GetRolePackageImageName(roleManifest)

	roleBuilder, err := builder.NewRoleImageBuilder(
		repository,
		compiledPackagesPath,
		targetPath,
		lightManifestPath,
		darkManifestPath,
		metricsPath,
		"",
		f.Version,
		f.UI,
	)
	if err != nil {
		return err
	}

	if err := roleBuilder.BuildRoleImages(roleManifest.Roles, repository, packagesLayerImageName, force, noBuild, workerCount); err != nil {
		return err
	}

	return nil
}
Ejemplo n.º 2
0
func TestGenerateRoleImageDockerfile(t *testing.T) {
	assert := assert.New(t)

	ui := termui.New(
		&bytes.Buffer{},
		ioutil.Discard,
		nil,
	)

	releaseVersion := "3.14.15"

	workDir, err := os.Getwd()
	assert.NoError(err)

	releasePath := filepath.Join(workDir, "../test-assets/tor-boshrelease")
	releasePathCache := filepath.Join(releasePath, "bosh-cache")
	compiledPackagesDir := filepath.Join(workDir, "../test-assets/tor-boshrelease-fake-compiled")
	targetPath, err := ioutil.TempDir("", "fissile-test")
	assert.NoError(err)
	defer os.RemoveAll(targetPath)

	release, err := model.NewDevRelease(releasePath, "", "", releasePathCache)
	assert.NoError(err)

	roleManifestPath := filepath.Join(workDir, "../test-assets/role-manifests/tor-good.yml")
	rolesManifest, err := model.LoadRoleManifest(roleManifestPath, []*model.Release{release})
	assert.NoError(err)

	torOpinionsDir := filepath.Join(workDir, "../test-assets/tor-opinions")
	lightOpinionsPath := filepath.Join(torOpinionsDir, "opinions.yml")
	darkOpinionsPath := filepath.Join(torOpinionsDir, "dark-opinions.yml")
	roleImageBuilder, err := NewRoleImageBuilder("foo", compiledPackagesDir, targetPath, lightOpinionsPath, darkOpinionsPath, "", releaseVersion, "6.28.30", ui)
	assert.NoError(err)

	var dockerfileContents bytes.Buffer
	baseImage := GetBaseImageName(roleImageBuilder.repository, roleImageBuilder.fissileVersion)
	err = roleImageBuilder.generateDockerfile(rolesManifest.Roles[0], baseImage, &dockerfileContents)
	assert.NoError(err)

	dockerfileString := dockerfileContents.String()
	assert.Contains(dockerfileString, "foo-role-base:6.28.30")
	assert.Contains(dockerfileString, "MAINTAINER", "release images should contain maintainer information")
	assert.Contains(
		dockerfileString,
		fmt.Sprintf(`LABEL "role"="%s" "version"="%s"`, rolesManifest.Roles[0].Name, releaseVersion),
		"Expected role label",
	)

	dockerfileContents.Reset()
	err = roleImageBuilder.generateDockerfile(rolesManifest.Roles[0], baseImage, &dockerfileContents)
	assert.NoError(err)
	dockerfileString = dockerfileContents.String()
	assert.Contains(dockerfileString, "MAINTAINER", "dev mode should generate a maintainer layer")
}
Ejemplo n.º 3
0
func TestGenerateRoleImageRunScript(t *testing.T) {
	assert := assert.New(t)

	ui := termui.New(
		&bytes.Buffer{},
		ioutil.Discard,
		nil,
	)

	workDir, err := os.Getwd()
	assert.NoError(err)

	releasePath := filepath.Join(workDir, "../test-assets/tor-boshrelease")
	releasePathCache := filepath.Join(releasePath, "bosh-cache")
	compiledPackagesDir := filepath.Join(workDir, "../test-assets/tor-boshrelease-fake-compiled")
	targetPath, err := ioutil.TempDir("", "fissile-test")
	assert.NoError(err)
	defer os.RemoveAll(targetPath)

	release, err := model.NewDevRelease(releasePath, "", "", releasePathCache)
	assert.NoError(err)

	roleManifestPath := filepath.Join(workDir, "../test-assets/role-manifests/tor-good.yml")
	rolesManifest, err := model.LoadRoleManifest(roleManifestPath, []*model.Release{release})
	assert.NoError(err)
	torOpinionsDir := filepath.Join(workDir, "../test-assets/tor-opinions")
	lightOpinionsPath := filepath.Join(torOpinionsDir, "opinions.yml")
	darkOpinionsPath := filepath.Join(torOpinionsDir, "dark-opinions.yml")

	roleImageBuilder, err := NewRoleImageBuilder("foo", compiledPackagesDir, targetPath, lightOpinionsPath, darkOpinionsPath, "", "3.14.15", "6.28.30", ui)
	assert.NoError(err)

	runScriptContents, err := roleImageBuilder.generateRunScript(rolesManifest.Roles[0])
	assert.NoError(err)
	assert.Contains(string(runScriptContents), "source /opt/hcf/startup/environ.sh")
	assert.Contains(string(runScriptContents), "source /environ/script/with/absolute/path.sh")
	assert.NotContains(string(runScriptContents), "/opt/hcf/startup/environ/script/with/absolute/path.sh")
	assert.NotContains(string(runScriptContents), "/opt/hcf/startup//environ/script/with/absolute/path.sh")
	assert.Contains(string(runScriptContents), "bash /opt/hcf/startup/myrole.sh")
	assert.Contains(string(runScriptContents), "bash /script/with/absolute/path.sh")
	assert.NotContains(string(runScriptContents), "/opt/hcf/startup/script/with/absolute/path.sh")
	assert.NotContains(string(runScriptContents), "/opt/hcf/startup//script/with/absolute/path.sh")
	assert.Contains(string(runScriptContents), "bash /opt/hcf/startup/post_config_script.sh")
	assert.Contains(string(runScriptContents), "bash /var/vcap/jobs/myrole/pre-start")
	assert.NotContains(string(runScriptContents), "/opt/hcf/startup/var/vcap/jobs/myrole/pre-start")
	assert.NotContains(string(runScriptContents), "/opt/hcf//startup/var/vcap/jobs/myrole/pre-start")
	assert.Contains(string(runScriptContents), "exec dumb-init -- monit -vI")

	runScriptContents, err = roleImageBuilder.generateRunScript(rolesManifest.Roles[1])
	assert.NoError(err)
	assert.NotContains(string(runScriptContents), "monit -vI")
	assert.Contains(string(runScriptContents), "/var/vcap/jobs/tor/bin/run")
}
Ejemplo n.º 4
0
// ListRoleImages lists all dev role images
func (f *Fissile) ListRoleImages(repository string, rolesManifestPath string, existingOnDocker, withVirtualSize bool) error {
	if withVirtualSize && !existingOnDocker {
		return fmt.Errorf("Cannot list image virtual sizes if not matching image names with docker")
	}

	if len(f.releases) == 0 {
		return fmt.Errorf("Releases not loaded")
	}

	var dockerManager *docker.ImageManager
	var err error

	if existingOnDocker {
		dockerManager, err = docker.NewImageManager()
		if err != nil {
			return fmt.Errorf("Error connecting to docker: %s", err.Error())
		}
	}

	rolesManifest, err := model.LoadRoleManifest(rolesManifestPath, f.releases)
	if err != nil {
		return fmt.Errorf("Error loading roles manifest: %s", err.Error())
	}

	for _, role := range rolesManifest.Roles {
		imageName := builder.GetRoleDevImageName(repository, role, role.GetRoleDevVersion())

		if !existingOnDocker {
			f.UI.Println(imageName)
			continue
		}

		image, err := dockerManager.FindImage(imageName)

		if err == docker.ErrImageNotFound {
			continue
		} else if err != nil {
			return fmt.Errorf("Error looking up image: %s", err.Error())
		}

		if withVirtualSize {
			f.UI.Printf(
				"%s (%sMB)\n",
				color.GreenString(imageName),
				color.YellowString("%.2f", float64(image.VirtualSize)/(1024*1024)),
			)
		} else {
			f.UI.Println(imageName)
		}
	}

	return nil
}
Ejemplo n.º 5
0
func TestGenerateRoleImageJobsConfig(t *testing.T) {
	assert := assert.New(t)

	ui := termui.New(
		&bytes.Buffer{},
		ioutil.Discard,
		nil,
	)

	workDir, err := os.Getwd()
	assert.NoError(err)

	releasePath := filepath.Join(workDir, "../test-assets/tor-boshrelease")
	releasePathCache := filepath.Join(releasePath, "bosh-cache")
	compiledPackagesDir := filepath.Join(workDir, "../test-assets/tor-boshrelease-fake-compiled")
	targetPath, err := ioutil.TempDir("", "fissile-test")
	assert.NoError(err)
	defer os.RemoveAll(targetPath)

	release, err := model.NewDevRelease(releasePath, "", "", releasePathCache)
	assert.NoError(err)

	roleManifestPath := filepath.Join(workDir, "../test-assets/role-manifests/tor-good.yml")
	rolesManifest, err := model.LoadRoleManifest(roleManifestPath, []*model.Release{release})
	assert.NoError(err)

	torOpinionsDir := filepath.Join(workDir, "../test-assets/tor-opinions")
	lightOpinionsPath := filepath.Join(torOpinionsDir, "opinions.yml")
	darkOpinionsPath := filepath.Join(torOpinionsDir, "dark-opinions.yml")
	roleImageBuilder, err := NewRoleImageBuilder("foo", compiledPackagesDir, targetPath, lightOpinionsPath, darkOpinionsPath, "", "3.14.15", "6.28.30", ui)
	assert.NoError(err)

	jobsConfigContents, err := roleImageBuilder.generateJobsConfig(rolesManifest.Roles[0])
	assert.NoError(err)
	assert.Contains(string(jobsConfigContents), "/var/vcap/jobs/tor/bin/tor_ctl")
	assert.Contains(string(jobsConfigContents), "/var/vcap/jobs-src/tor/templates/data/properties.sh.erb")
	assert.Contains(string(jobsConfigContents), "/etc/monitrc")
	assert.Contains(string(jobsConfigContents), "/var/vcap/jobs/new_hostname/bin/run")

	jobsConfigContents, err = roleImageBuilder.generateJobsConfig(rolesManifest.Roles[1])
	assert.NoError(err)
	assert.Contains(string(jobsConfigContents), "/var/vcap/jobs/tor/bin/tor_ctl")
	assert.Contains(string(jobsConfigContents), "/var/vcap/jobs-src/tor/templates/data/properties.sh.erb")
	assert.NotContains(string(jobsConfigContents), "/etc/monitrc")
	assert.NotContains(string(jobsConfigContents), "/var/vcap/jobs/new_hostname/bin/run")
}
Ejemplo n.º 6
0
// Compile will compile a list of dev BOSH releases
func (f *Fissile) Compile(repository, targetPath, roleManifestPath, metricsPath string, workerCount int) error {
	if len(f.releases) == 0 {
		return fmt.Errorf("Releases not loaded")
	}

	if metricsPath != "" {
		stampy.Stamp(metricsPath, "fissile", "compile-packages", "start")
		defer stampy.Stamp(metricsPath, "fissile", "compile-packages", "done")
	}

	dockerManager, err := docker.NewImageManager()
	if err != nil {
		return fmt.Errorf("Error connecting to docker: %s", err.Error())
	}

	roleManifest, err := model.LoadRoleManifest(roleManifestPath, f.releases)
	if err != nil {
		return fmt.Errorf("Error loading roles manifest: %s", err.Error())
	}

	f.UI.Println(color.GreenString("Compiling packages for dev releases:"))
	for _, release := range f.releases {
		f.UI.Printf("         %s (%s)\n", color.YellowString(release.Name), color.MagentaString(release.Version))
	}

	comp, err := compilator.NewCompilator(dockerManager, targetPath, metricsPath, repository, compilation.UbuntuBase, f.Version, false, f.UI)
	if err != nil {
		return fmt.Errorf("Error creating a new compilator: %s", err.Error())
	}

	if err := comp.Compile(workerCount, f.releases, roleManifest); err != nil {
		return fmt.Errorf("Error compiling packages: %s", err.Error())
	}

	return nil
}
Ejemplo n.º 7
0
func TestBuildRoleImages(t *testing.T) {

	origNewDockerImageBuilder := newDockerImageBuilder
	defer func() {
		newDockerImageBuilder = origNewDockerImageBuilder
	}()

	type dockerBuilderMock struct {
	}

	mockBuilder := mockDockerImageBuilder{}
	newDockerImageBuilder = func() (dockerImageBuilder, error) {
		return &mockBuilder, nil
	}

	assert := assert.New(t)

	ui := termui.New(
		&bytes.Buffer{},
		ioutil.Discard,
		nil,
	)

	workDir, err := os.Getwd()
	assert.NoError(err)

	releasePath := filepath.Join(workDir, "../test-assets/tor-boshrelease")
	releasePathCache := filepath.Join(releasePath, "bosh-cache")

	compiledPackagesDir := filepath.Join(workDir, "../test-assets/tor-boshrelease-fake-compiled")
	targetPath, err := ioutil.TempDir("", "fissile-test")
	assert.NoError(err)
	defer os.RemoveAll(targetPath)

	release, err := model.NewDevRelease(releasePath, "", "", releasePathCache)
	assert.NoError(err)

	roleManifestPath := filepath.Join(workDir, "../test-assets/role-manifests/tor-good.yml")
	rolesManifest, err := model.LoadRoleManifest(roleManifestPath, []*model.Release{release})
	assert.NoError(err)
	torOpinionsDir := filepath.Join(workDir, "../test-assets/tor-opinions")
	lightOpinionsPath := filepath.Join(torOpinionsDir, "opinions.yml")
	darkOpinionsPath := filepath.Join(torOpinionsDir, "dark-opinions.yml")

	roleImageBuilder, err := NewRoleImageBuilder(
		"test-repository",
		compiledPackagesDir,
		targetPath,
		lightOpinionsPath,
		darkOpinionsPath,
		"",
		"3.14.15",
		"6.28.30",
		ui,
	)
	assert.NoError(err)

	// Check that making the first wait for the second job works
	secondJobReady := make(chan struct{})
	mockBuilder.callback = func(name string) error {
		if strings.Contains(name, "-myrole:") {
			<-secondJobReady
			return nil
		}
		if strings.Contains(name, "-foorole:") {
			close(secondJobReady)
			return nil
		}
		t.Errorf("Got unexpected job %s", name)
		return fmt.Errorf("Unknown docker image name %s", name)
	}

	err = roleImageBuilder.BuildRoleImages(
		rolesManifest.Roles,
		"test-repository",
		"",
		false,
		false,
		2,
	)
	assert.NoError(err)

	err = os.RemoveAll(targetPath)
	assert.NoError(err, "Failed to remove target")

	targetPath, err = ioutil.TempDir("", "fissile-test")
	assert.NoError(err)
	defer os.RemoveAll(targetPath)
	roleImageBuilder.targetPath = targetPath

	// Should not allow invalid worker counts
	err = roleImageBuilder.BuildRoleImages(
		rolesManifest.Roles,
		"test-repository",
		"",
		false,
		false,
		0,
	)
	assert.Error(err, "Invalid worker count should result in an error")
	assert.Contains(err.Error(), "count", "Building the image should have failed due to invalid worker count")

	// Check that failing the first job will not run the second job
	hasRunSecondJob := false
	mockBuilder.callback = func(name string) error {
		if strings.Contains(name, "-myrole:") {
			return fmt.Errorf("Deliberate failure")
		}
		if strings.Contains(name, "-foorole:") {
			assert.False(hasRunSecondJob, "Second job should not run if first job failed")
			hasRunSecondJob = true
		}
		t.Errorf("Got unexpected job %s", name)
		return fmt.Errorf("Unknown docker image name %s", name)
	}

	err = roleImageBuilder.BuildRoleImages(
		rolesManifest.Roles,
		"test-repository",
		"",
		false,
		false,
		1,
	)
	assert.Contains(err.Error(), "Deliberate failure", "Returned error should be from first job failing")
	assert.False(hasRunSecondJob, "Second job should not have run")

	// Check that we do not attempt to rebuild images
	mockBuilder.hasImage = true
	var buildersRan []string
	mockBuilder.callback = func(name string) error {
		buildersRan = append(buildersRan, name)
		return nil
	}
	err = roleImageBuilder.BuildRoleImages(
		rolesManifest.Roles,
		"test-repository",
		"",
		false,
		false,
		len(rolesManifest.Roles),
	)
	assert.NoError(err)
	assert.Empty(buildersRan, "should not have ran any builders")

	// Check that we write timestamps to the metrics file
	file, err := ioutil.TempFile("", "metrics")
	assert.NoError(err)

	metrics := file.Name()
	defer os.Remove(metrics)
	roleImageBuilder.metricsPath = metrics

	err = os.RemoveAll(targetPath)
	assert.NoError(err, "Failed to remove target")

	targetPath, err = ioutil.TempDir("", "fissile-test")
	assert.NoError(err)
	defer os.RemoveAll(targetPath)
	roleImageBuilder.targetPath = targetPath

	mockBuilder.hasImage = false
	mockBuilder.callback = func(name string) error {
		return nil
	}
	err = roleImageBuilder.BuildRoleImages(
		rolesManifest.Roles,
		"test-repository",
		"",
		false,
		false,
		1,
	)
	assert.NoError(err)

	expected := `.*,fissile,create-role-images::test-repository-myrole:[a-z0-9]{40},start
.*,fissile,create-role-images::test-repository-myrole:[a-z0-9]{40},done
.*,fissile,create-role-images::test-repository-foorole:[a-z0-9]{40},start
.*,fissile,create-role-images::test-repository-foorole:[a-z0-9]{40},done`

	contents, err := ioutil.ReadFile(metrics)
	assert.NoError(err)
	assert.Regexp(regexp.MustCompile(expected), string(contents))
}
Ejemplo n.º 8
0
func TestGenerateRoleImageDockerfileDir(t *testing.T) {
	assert := assert.New(t)

	ui := termui.New(
		&bytes.Buffer{},
		ioutil.Discard,
		nil,
	)

	workDir, err := os.Getwd()
	assert.NoError(err)

	releasePath := filepath.Join(workDir, "../test-assets/tor-boshrelease")
	releasePathCache := filepath.Join(releasePath, "bosh-cache")
	releasePathConfigSpec := filepath.Join(releasePath, "config_spec")

	compiledPackagesDir := filepath.Join(workDir, "../test-assets/tor-boshrelease-fake-compiled")
	targetPath, err := ioutil.TempDir("", "fissile-test")
	assert.NoError(err)
	defer os.RemoveAll(targetPath)

	release, err := model.NewDevRelease(releasePath, "", "", releasePathCache)
	assert.NoError(err)

	roleManifestPath := filepath.Join(workDir, "../test-assets/role-manifests/tor-good.yml")
	rolesManifest, err := model.LoadRoleManifest(roleManifestPath, []*model.Release{release})
	assert.NoError(err)

	torOpinionsDir := filepath.Join(workDir, "../test-assets/tor-opinions")
	lightOpinionsPath := filepath.Join(torOpinionsDir, "opinions.yml")
	darkOpinionsPath := filepath.Join(torOpinionsDir, "dark-opinions.yml")

	roleImageBuilder, err := NewRoleImageBuilder("foo", compiledPackagesDir, targetPath, lightOpinionsPath, darkOpinionsPath, "", "3.14.15", "6.28.30", ui)
	assert.NoError(err)

	dockerfileDir, err := roleImageBuilder.CreateDockerfileDir(
		rolesManifest.Roles[0],
		releasePathConfigSpec,
	)
	assert.NoError(err)
	defer os.RemoveAll(dockerfileDir)

	assert.Equal(targetPath, filepath.Dir(dockerfileDir), "Docker file %s not created in directory %s", dockerfileDir, targetPath)

	for _, info := range []struct {
		path  string
		isDir bool
		desc  string
	}{
		{path: ".", isDir: true, desc: "role dir"},
		{path: "Dockerfile", isDir: false, desc: "Dockerfile"},
		{path: "root", isDir: true, desc: "image root"},
		{path: "root/opt/hcf/share/doc/tor/LICENSE", isDir: false, desc: "release license file"},
		{path: "root/opt/hcf/run.sh", isDir: false, desc: "run script"},
		{path: "root/opt/hcf/startup/", isDir: true, desc: "role startup scripts dir"},
		{path: "root/opt/hcf/startup/myrole.sh", isDir: false, desc: "role specific startup script"},
		{path: "root/var/vcap/jobs-src/tor/monit", isDir: false, desc: "job monit file"},
		{path: "root/var/vcap/jobs-src/tor/templates/bin/monit_debugger", isDir: false, desc: "job template file"},
		{path: "root/var/vcap/jobs-src/tor/config_spec.json", isDir: false, desc: "tor config spec"},
		{path: "root/var/vcap/jobs-src/new_hostname", isDir: true, desc: "new_hostname spec dir"},
		{path: "root/var/vcap/jobs-src/new_hostname/config_spec.json", isDir: false, desc: "new_hostname config spec"},
		{path: "root/var/vcap/packages/tor", isDir: false, desc: "package symlink"},
	} {
		path := filepath.ToSlash(filepath.Join(dockerfileDir, info.path))
		assert.NoError(util.ValidatePath(path, info.isDir, info.desc))
	}

	symlinkPath := filepath.Join(dockerfileDir, "root/var/vcap/packages/tor")
	if pathInfo, err := os.Lstat(symlinkPath); assert.NoError(err) {
		assert.Equal(os.ModeSymlink, pathInfo.Mode()&os.ModeSymlink)
		if target, err := os.Readlink(symlinkPath); assert.NoError(err) {
			pkg := getPackage(rolesManifest.Roles, "myrole", "tor", "tor")
			if assert.NotNil(pkg, "Failed to find package") {
				expectedTarget := filepath.Join("..", "packages-src", pkg.Fingerprint)
				assert.Equal(expectedTarget, target)
			}
		}
	}

	// job.MF should not be there
	assert.Error(util.ValidatePath(filepath.ToSlash(filepath.Join(dockerfileDir, "root/var/vcap/jobs-src/tor/job.MF")), false, "job manifest file"))

	// And verify the config specs are as expected
	jsonPath := filepath.Join(dockerfileDir, "root/var/vcap/jobs-src/new_hostname/config_spec.json")
	buf, err := ioutil.ReadFile(jsonPath)
	if !assert.NoError(err, "Failed to read new_hostname/config_spec.json %s\n", jsonPath) {
		return
	}
	var result map[string]interface{}
	err = json.Unmarshal(buf, &result)
	if !assert.NoError(err, "Error unmarshalling output") {
		return
	}
	assert.Empty(result["properties"].(map[string]interface{}))

	jsonPath = filepath.Join(dockerfileDir, "root/var/vcap/jobs-src/tor/config_spec.json")
	buf, err = ioutil.ReadFile(jsonPath)
	if !assert.NoError(err, "Failed to read tor/config_spec.json %s\n", jsonPath) {
		return
	}

	expectedString := `{
		"job": {
			"name": "myrole",
			"templates": [
				{"name":"new_hostname"},
				{"name":"tor"}
			]
		},
		"networks":{
			"default":{}
		},
		"parameters":{},
		"properties": {
			"tor": {
				"hashed_control_password":null,
				"hostname":"localhost",
				"private_key": null,
				"client_keys":null
			}
		}
	}`
	assert.JSONEq(expectedString, string(buf))
}
Ejemplo n.º 9
0
func TestNewDockerPopulator(t *testing.T) {
	assert := assert.New(t)

	ui := termui.New(
		&bytes.Buffer{},
		ioutil.Discard,
		nil,
	)

	workDir, err := os.Getwd()
	assert.NoError(err)

	baseImageOverride = defaultDockerTestImage
	defer func() { baseImageOverride = "" }()

	releasePath := filepath.Join(workDir, "../test-assets/tor-boshrelease")
	releasePathCache := filepath.Join(releasePath, "bosh-cache")

	compiledPackagesDir := filepath.Join(workDir, "../test-assets/tor-boshrelease-fake-compiled")
	targetPath, err := ioutil.TempDir("", "fissile-test")
	assert.NoError(err)
	defer os.RemoveAll(targetPath)

	release, err := model.NewDevRelease(releasePath, "", "", releasePathCache)
	assert.NoError(err)

	roleManifestPath := filepath.Join(workDir, "../test-assets/role-manifests/tor-good.yml")
	rolesManifest, err := model.LoadRoleManifest(roleManifestPath, []*model.Release{release})
	assert.NoError(err)

	packagesImageBuilder, err := NewPackagesImageBuilder("foo", compiledPackagesDir, targetPath, "3.14.15", ui)
	assert.NoError(err)

	tarFile := &bytes.Buffer{}

	tarPopulator := packagesImageBuilder.NewDockerPopulator(rolesManifest, false)
	tarWriter := tar.NewWriter(tarFile)
	assert.NoError(tarPopulator(tarWriter))
	assert.NoError(tarWriter.Close())

	pkg := getPackage(rolesManifest.Roles, "myrole", "tor", "tor")
	if !assert.NotNil(pkg) {
		return
	}

	// Get the docker id for the image we'll be building from...
	dockerManager, err := docker.NewImageManager()
	assert.NoError(err)
	baseImage, err := dockerManager.FindImage(baseImageOverride)
	assert.NoError(err)

	// From test-assets/tor-boshrelease/dev_releases/tor/tor-0.3.5+dev.3.yml
	const torFingerprint = "59523b1cc4042dff1217ab5b79ff885cdd2de032"

	testFunctions := map[string]func(string){
		"Dockerfile": func(contents string) {
			var i int
			var line string
			testers := []func(){
				func() { assert.Equal(fmt.Sprintf("FROM %s", baseImage.ID), line, "line 1 should start with FROM") },
				func() { assert.Equal("ADD packages-src /var/vcap/packages-src/", line, "line 3 mismatch") },
				func() {
					expected := []string{
						"LABEL",
						fmt.Sprintf(`"fingerprint.%s"="libevent"`, getPackage(rolesManifest.Roles, "myrole", "tor", "libevent").Fingerprint),
						fmt.Sprintf(`"fingerprint.%s"="tor"`, getPackage(rolesManifest.Roles, "myrole", "tor", "tor").Fingerprint),
					}
					actual := strings.Fields(line)
					sort.Strings(expected[1:])
					sort.Strings(actual[1:])
					assert.Equal(expected, actual, "line 4 has unexpected fields")
				},
			}
			for i, line = range getDockerfileLines(contents) {
				if assert.True(i < len(testers), "Extra line #%d: %s", i+1, line) {
					testers[i]()
				}
			}
			assert.Equal(len(testers), len(getDockerfileLines(contents)), "Not enough lines")
		},
		"packages-src/" + torFingerprint + "/bar": func(contents string) {
			assert.Empty(contents)
		},
	}

	tarReader := tar.NewReader(tarFile)
	for {
		header, err := tarReader.Next()
		if err == io.EOF {
			break
		}
		if !assert.NoError(err) {
			break
		}
		if tester, ok := testFunctions[header.Name]; ok {
			actual, err := ioutil.ReadAll(tarReader)
			assert.NoError(err)
			tester(string(actual))
			delete(testFunctions, header.Name)
		}
	}
	assert.Empty(testFunctions, "Missing files in tar stream")
}