Beispiel #1
0
func TestTarOutputCompat(t *testing.T) {
	Convey("Output should produce a tar recognizable to gnu tar", t,
		testutil.Requires(
			testutil.RequiresRoot,
			testutil.WithTmpdir(func() {
				for _, fixture := range filefixture.All {
					Convey(fmt.Sprintf("- Fixture %q", fixture.Name), func() {
						// create fixture
						fixture.Create("./data")

						// scan it
						transmat := New("./workdir")
						transmat.Scan(
							Kind,
							"./data",
							[]integrity.SiloURI{
								integrity.SiloURI("file://output.tar"),
							},
						)

						// sanity check that there's a file.
						So("./output.tar", testutil.ShouldBeFile, os.FileMode(0))

						// now exec tar, and check that it doesn't barf outright.
						// this is not well isolated from the host; consider improving that a todo.
						os.Mkdir("./untar", 0755)
						tarProc := gosh.Gosh(
							"tar",
							"-xf", "./output.tar",
							"-C", "./untar",
							gosh.NullIO,
						).RunAndReport()
						So(tarProc.GetExitCode(), ShouldEqual, 0)

						// should look roughly the same again even bounced through
						// some third-party tar implementation, one would hope.
						rescan := filefixture.Scan("./untar")
						comparisonLevel := filefixture.CompareDefaults &^ filefixture.CompareSubsecond
						So(rescan.Describe(comparisonLevel), ShouldEqual, fixture.Describe(comparisonLevel))
					})
				}
			}),
		),
	)
}
/*
	Arenas produced by Dir Transmats may be relocated by simple `mv`.
*/
func (t *TarExecTransmat) Materialize(
	kind integrity.TransmatKind,
	dataHash integrity.CommitID,
	siloURIs []integrity.SiloURI,
	options ...integrity.MaterializerConfigurer,
) integrity.Arena {
	var arena dirArena
	try.Do(func() {
		// Basic validation and config
		if !(kind == Kind || kind == "exec-tar") {
			panic(errors.ProgrammerError.New("This transmat supports definitions of type %q, not %q", Kind, kind))
		}

		// Ping silos
		if len(siloURIs) < 1 {
			panic(integrity.ConfigError.New("Materialization requires at least one data source!"))
			// Note that it's possible a caching layer will satisfy things even without data sources...
			//  but if that was going to happen, it already would have by now.
		}
		// Our policy is to take the first path that exists.
		//  This lets you specify a series of potential locations, and if one is unavailable we'll just take the next.
		var siloURI integrity.SiloURI
		for _, givenURI := range siloURIs {
			// TODO still assuming all local paths and not doing real uri parsing
			localPath := string(givenURI)
			_, err := os.Stat(localPath)
			if os.IsNotExist(err) {
				// TODO it'd be awfully lovely if we could log the attempt somewhere
				continue
			}
			siloURI = givenURI
			break
		}
		if siloURI == "" {
			panic(integrity.WarehouseConnectionError.New("No warehouses were available!"))
		}
		// Open the input stream; preparing decompression as necessary
		file, err := os.OpenFile(string(siloURI), os.O_RDONLY, 0755)
		if err != nil {
			panic(integrity.WarehouseConnectionError.New("Unable to read file: %s", err))
		}
		file.Close() // just checking, so we can (try to) give a more pleasant error than tar barf

		// Create staging arena to produce data into.
		arena.path, err = ioutil.TempDir(t.workPath, "")
		if err != nil {
			panic(integrity.TransmatError.New("Unable to create arena: %s", err))
		}

		// exec tar.
		// in case of a zero (a.k.a. success) exit, this returns silently.
		// in case of a non-zero exit, this panics; the panic will include the output.
		gosh.Gosh(
			"tar",
			"-xf", string(siloURI),
			"-C", arena.Path(),
			gosh.NullIO,
		).RunAndReport()

		// note: indeed, we never check the hash field.  this is *not* a compliant implementation of an input.
	}).Catch(integrity.Error, func(err *errors.Error) {
		panic(err)
	}).CatchAll(func(err error) {
		panic(integrity.UnknownError.Wrap(err))
	}).Done()
	return arena
}
func (t TarExecTransmat) Scan(
	kind integrity.TransmatKind,
	subjectPath string,
	siloURIs []integrity.SiloURI,
	options ...integrity.MaterializerConfigurer,
) integrity.CommitID {
	try.Do(func() {
		// Basic validation and config
		if kind != Kind {
			panic(errors.ProgrammerError.New("This transmat supports definitions of type %q, not %q", Kind, kind))
		}

		// If scan area doesn't exist, bail immediately.
		// No need to even start dialing warehouses if we've got nothing for em.
		_, err := os.Stat(subjectPath)
		if err != nil {
			if os.IsNotExist(err) {
				return // empty commitID
			} else {
				panic(err)
			}
		}

		// Parse save locations.
		//  (Most transmats do... significantly smarter things than this backwater.)
		var localPath string
		if len(siloURIs) == 0 {
			localPath = "/dev/null"
		} else if len(siloURIs) == 1 {
			// TODO still assuming all local paths and not doing real uri parsing
			localPath = string(siloURIs[0])
			err := os.MkdirAll(filepath.Dir(localPath), 0755)
			if err != nil {
				panic(integrity.WarehouseConnectionError.New("Unable to write file: %s", err))
			}
			file, err := os.OpenFile(localPath, os.O_CREATE|os.O_WRONLY, 0644)
			if err != nil {
				panic(integrity.WarehouseConnectionError.New("Unable to write file: %s", err))
			}
			file.Close() // just checking, so we can (try to) give a more pleasant error than tar barf
		} else {
			panic(integrity.ConfigError.New("%s transmat only supports shipping to 1 warehouse", Kind))
		}

		// exec tar.
		// in case of a zero (a.k.a. success) exit, this returns silently.
		// in case of a non-zero exit, this panics; the panic will include the output.
		gosh.Gosh(
			"tar",
			"-cf", localPath,
			"--xform", "s,"+strings.TrimLeft(subjectPath, "/")+",.,",
			subjectPath,
			gosh.NullIO,
		).RunAndReport()
	}).Catch(integrity.Error, func(err *errors.Error) {
		panic(err)
	}).CatchAll(func(err error) {
		panic(integrity.UnknownError.Wrap(err))
	}).Done()
	return ""

}
Beispiel #4
0
func New(workPath string) integrity.Transmat {
	err := os.MkdirAll(workPath, 0755)
	if err != nil {
		panic(integrity.TransmatError.New("Unable to set up workspace: %s", err))
	}
	workPath, err = filepath.Abs(workPath)
	if err != nil {
		panic(integrity.TransmatError.New("Unable to set up workspace: %s", err))
	}
	return &GitTransmat{workPath}
}

var git gosh.Command = gosh.Gosh(
	"git",
	gosh.NullIO,
	gosh.Opts{Env: map[string]string{
		"GIT_CONFIG_NOSYSTEM": "true",
		"HOME":                "/dev/null",
	}},
)

/*
	Git transmats plonk down the contents of one commit (or tree) as a filesystem.

	A fileset materialized by git does *not* include the `.git` dir by default,
	since those files are not themselves part of what's described by the hash.

	Git effectively "filters" out several attributes -- permissions are only loosely
	respected (execution only), file timestamps are undefined, uid/gid bits
	are not tracked, xattrs are not tracked, etc.  If you desired defined values,
	*you must still configure materialization to use a filter* (particularly for
	file timestamps, since they will otherwise be allowed to vary from one
Beispiel #5
0
func TestTarInputCompat(t *testing.T) {
	projPath, _ := os.Getwd()
	projPath = filepath.Dir(filepath.Dir(filepath.Dir(projPath)))

	Convey("Unpacking tars should match exec untar", t,
		testutil.Requires(testutil.RequiresRoot, testutil.WithTmpdir(func() {
			checkEquivalence := func(hash, filename string, paveBase bool) {
				transmat := New("./workdir/tar")

				// apply it; hope it doesn't blow up
				arena := transmat.Materialize(
					integrity.TransmatKind("tar"),
					integrity.CommitID(hash),
					[]integrity.SiloURI{
						integrity.SiloURI("file://" + filename),
					},
				)
				defer arena.Teardown()

				// do a native untar; since we don't have an upfront fixture
				//  for this thing, we'll compare the two as filesystems.
				// this is not well isolated from the host; consider improving that a todo.
				os.Mkdir("./untar", 0755)
				tarProc := gosh.Gosh(
					"tar",
					"-xf", filename,
					"-C", "./untar",
					gosh.NullIO,
				).RunAndReport()
				So(tarProc.GetExitCode(), ShouldEqual, 0)
				// native untar may or may not have an opinion about the base dir, depending on how it was formed.
				// but our scans do, so, if the `paveBase` flag was set to warn us that the tar was missing an "./" entry, flatten that here.
				if paveBase {
					So(fspatch.LUtimesNano("./untar", def.Epochwhen, def.Epochwhen), ShouldBeNil)
				}

				// scan and compare
				scan1 := filefixture.Scan(arena.Path())
				scan2 := filefixture.Scan("./untar")
				// boy, that's entertaining though: gnu tar does all the same stuff,
				//  except it doesn't honor our nanosecond timings.
				// also exclude bodies because they're *big*.
				comparisonLevel := filefixture.CompareDefaults &^ filefixture.CompareSubsecond &^ filefixture.CompareBody
				So(scan1.Describe(comparisonLevel), ShouldEqual, scan2.Describe(comparisonLevel))
			}

			Convey("Given a fixture tarball complete with base dir", func() {
				checkEquivalence(
					"BX0jm4jRNCg1KMbZfv4zp7ZaShx9SUXKaDrO-Xy6mWIoWOCFP5VnDHDDR3nU4PrR",
					filepath.Join(projPath, "data/fixture/tar_withBase.tgz"),
					false,
				)
			})

			Convey("Given a fixture tarball lacking base dir", func() {
				checkEquivalence(
					"ZdV3xhCGWeJmsfeHpDF4nF9stwvdskYwcepKMcOf7a2ziax1YGjQvGTJjRWFkvG1",
					filepath.Join(projPath, "data/fixture/tar_sansBase.tgz"),
					true,
				)
			})

			Convey("Given a fixture tarball containing ubuntu",
				testutil.Requires(testutil.RequiresLongRun, func() {
					checkEquivalence(
						ubuntuTarballHash,
						filepath.Join(projPath, "assets/ubuntu.tar.gz"),
						true,
					)
				}),
			)
		})),
	)

	Convey("Bouncing unusual tars should match hash", t,
		// where really all "unusual" means is "valid tar, but not from our own cannonical output".
		testutil.Requires(
			testutil.RequiresRoot,
			testutil.WithTmpdir(func() {
				checkBounce := func(hash, filename string) {
					transmat := New("./workdir/tar")

					// apply it; hope it doesn't blow up
					arena := transmat.Materialize(
						integrity.TransmatKind("tar"),
						integrity.CommitID(hash),
						[]integrity.SiloURI{
							integrity.SiloURI("file://" + filename),
						},
					)
					defer arena.Teardown()

					// scan and compare
					commitID := transmat.Scan(integrity.TransmatKind("tar"), arena.Path(), nil)
					// debug: gosh.Sh("tar", "--utc", "-xOvf", filename)
					So(commitID, ShouldEqual, integrity.CommitID(hash))

				}

				Convey("Given a fixture tarball complete with base dir", func() {
					checkBounce(
						"BX0jm4jRNCg1KMbZfv4zp7ZaShx9SUXKaDrO-Xy6mWIoWOCFP5VnDHDDR3nU4PrR",
						filepath.Join(projPath, "data/fixture/tar_withBase.tgz"),
					)
				})

				Convey("Given a fixture tarball lacking base dir", func() {
					checkBounce(
						"ZdV3xhCGWeJmsfeHpDF4nF9stwvdskYwcepKMcOf7a2ziax1YGjQvGTJjRWFkvG1",
						filepath.Join(projPath, "data/fixture/tar_sansBase.tgz"),
					)
				})

				// this won't fly until we support hardlinks; the original asset uses them.
				//	Convey("Given a fixture tarball containing ubuntu",
				//		testutil.Requires(testutil.RequiresLongRun, func() {
				//			checkBounce(
				//				ubuntuTarballHash,
				//				filepath.Join(projPath, "assets/ubuntu.tar.gz"),
				//			)
				//		}),
				//	)
			}),
		),
	)
}