-
Notifications
You must be signed in to change notification settings - Fork 21
support for reproducible builds #225
base: master
Are you sure you want to change the base?
Changes from all commits
7a70482
0985dd9
73c22dd
0b99037
f8924d1
1eb4548
c6f20d9
1eb67f7
8f6bc07
ba7b411
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -29,6 +29,7 @@ type Builder struct { | |
| aciTargetPath string | ||
| upperId string | ||
| pod *stage1commontypes.Pod | ||
| fullManifest *common.AciManifest // includes non-standard sections such as 'Builder' | ||
| } | ||
|
|
||
| func NewBuilder(podRoot string, podUUID *types.UUID) (*Builder, error) { | ||
|
|
@@ -123,6 +124,8 @@ func (b *Builder) writeManifest() error { | |
| aciManifest.NameAndVersion = *common.NewACFullName(aciManifest.NameAndVersion.Name() + ":" + common.GenerateVersion(b.aciTargetPath)) | ||
| } | ||
|
|
||
| b.fullManifest = aciManifest | ||
|
|
||
| if err := common.WriteAciManifest(aciManifest, target, aciManifest.NameAndVersion.Name(), dgrVersion); err != nil { | ||
| return errs.WithEF(err, b.fields.WithField("file", target), "Failed to write manifest") | ||
| } | ||
|
|
@@ -136,39 +139,52 @@ func (b *Builder) tarAci() error { | |
| } | ||
|
|
||
| upperPath := b.pod.Root + PATH_OVERLAY + "/" + upperId + PATH_UPPER | ||
| upperNamedRootfs := upperPath + "/" + manifestApp(b.pod).Name.String() | ||
| upperRootfs := upperPath + common.PathRootfs | ||
|
|
||
| if err := os.RemoveAll(upperNamedRootfs + PATH_TMP); err != nil { | ||
| logs.WithEF(err, b.fields.WithField("path", upperNamedRootfs+PATH_TMP)).Warn("Failed to clean tmp directory") | ||
| } | ||
|
|
||
| if err := os.Rename(upperNamedRootfs, upperRootfs); err != nil { // TODO this is dirty and can probably be renamed during tar | ||
| return errs.WithEF(err, b.fields.WithField("path", upperNamedRootfs), "Failed to rename rootfs") | ||
| } | ||
| defer os.Rename(upperRootfs, upperNamedRootfs) | ||
|
|
||
| dir, err := os.Getwd() | ||
| if err != nil { | ||
| return errs.WithEF(err, b.fields, "Failed to get current working directory") | ||
| rootfsAlias := manifestApp(b.pod).Name.String() | ||
| destination := b.aciTargetPath + common.PathImageAci // absolute dir, outside upperPath (think: /tmp/…) | ||
|
|
||
| params := []string{"--sort=name", "--numeric-owner", "--exclude", rootfsAlias + PATH_TMP + "/*"} | ||
| params = append(params, "-C", upperPath, "--transform", "s@^"+rootfsAlias+"@rootfs@") | ||
| for _, expression := range b.fullManifest.Build.Transform { | ||
| params = append(params, "--transform", expression) | ||
| } | ||
| for _, path := range b.fullManifest.Build.Exclude { | ||
| if strings.HasPrefix(path, "/") { | ||
| params = append(params, "--exclude", rootfsAlias+path) | ||
| } else { | ||
| params = append(params, "--exclude", path) | ||
| } | ||
| } | ||
| defer func() { | ||
| if err := os.Chdir(dir); err != nil { | ||
| logs.WithEF(err, b.fields.WithField("path", dir)).Warn("Failed to chdir back") | ||
| if v, ok := b.fullManifest.Aci.Annotations.Get("build-date"); ok { | ||
| logs.WithFields(b.fields).WithField("build-date", v).Info("Using the given fixed build-date") | ||
| params = append(params, "--mtime", v, "--clamp-mtime") | ||
| } else { | ||
| logs.WithFields(b.fields).Info("This is no reproducible build: no build-date has been set") | ||
| } | ||
| params = append(params, "-cf", destination, common.PathManifest[1:], rootfsAlias) | ||
|
|
||
| logs.WithF(b.fields).Debug("Calling tar to collect all files") | ||
| if err := common.ExecCmd("tar", params...); err != nil { | ||
| // In case the host's tar is too old, try the builder's if it exists. | ||
| stage1Tar := rktcommon.Stage1RootfsPath(b.pod.Root) | ||
| var buildersTar string | ||
| for _, t := range []string{"/dgr/usr/bin/tar", "/bin/tar", "/usr/bin/tar"} { | ||
| buildersTar = filepath.Join(stage1Tar, t) | ||
| if _, err := os.Stat(buildersTar); err == nil { | ||
| break | ||
| } | ||
| } | ||
| }() | ||
|
|
||
| if err := os.Chdir(upperPath); err != nil { | ||
| return errs.WithEF(err, b.fields.WithField("path", upperPath), "Failed to chdir to upper base path") | ||
| } | ||
| if err := common.Tar(b.aciTargetPath+common.PathImageAci, common.PathManifest[1:], common.PathRootfs[1:]+"/"); err != nil { | ||
| return errs.WithEF(err, b.fields, "Failed to tar aci") | ||
| if err2 := common.ExecCmd(buildersTar, params...); err2 != nil { | ||
| // If that failed, output the original error nevertheless. | ||
| logs.WithFields(b.fields).WithField("params", params).Error("Parameters to 'tar' within the builder") | ||
| return errs.WithEF(err, b.fields, "Failed to tar aci") | ||
| } | ||
| logs.WithF(b.fields).Debug("Had to resort to 'tar' from the builder to create the aci file") | ||
| } | ||
| // common.ExecCmd sometimes silently fails, hence the redundant check. | ||
| if _, err := os.Stat(b.aciTargetPath + common.PathImageAci); os.IsNotExist(err) { | ||
| if _, err := os.Stat(destination); os.IsNotExist(err) { | ||
| return errs.WithEF(err, b.fields, "Expected aci has not been created") | ||
| } | ||
| logs.WithField("path", dir).Debug("chdir") | ||
| return nil | ||
| } | ||
|
|
||
|
|
@@ -272,6 +288,7 @@ func (b *Builder) prepareNspawnArgsAndEnv(commandPath string) ([]string, []strin | |
| if err != nil { | ||
| return args, env, errs.WithEF(err, b.fields.WithField("content", string(content)), "Failed to process manifest template") | ||
| } | ||
| b.fullManifest = aciManifest | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think is is useless, in the flow it rewritten by |
||
| for _, mount := range aciManifest.Builder.MountPoints { | ||
| if strings.HasPrefix(mount.From, "~/") { | ||
| user, err := user.Current() | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,6 +6,7 @@ import ( | |
| "io/ioutil" | ||
| "os" | ||
| "path/filepath" | ||
| "strings" | ||
| "time" | ||
|
|
||
| "github.com/appc/spec/aci" | ||
|
|
@@ -96,20 +97,38 @@ func WriteAciManifest(m *AciManifest, targetFile string, projectName string, dgr | |
|
|
||
| //dgrBuilderIdentifier, _ := types.NewACIdentifier(ManifestDrgBuilder) | ||
| dgrVersionIdentifier, _ := types.NewACIdentifier(ManifestDrgVersion) | ||
| buildDateIdentifier, _ := types.NewACIdentifier("build-date") | ||
| im.Annotations.Set(*dgrVersionIdentifier, dgrVersion) | ||
| //im.Annotations.Set(*dgrBuilderIdentifier, m.Builder.Image.String()) | ||
| im.Annotations.Set(*buildDateIdentifier, time.Now().Format(time.RFC3339)) | ||
|
|
||
| if _, ok := im.Annotations.Get("build-date"); !ok { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm wondering if setting the
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ok after full read, you are using it to fixe file dates as long as manifest build date. It can stay like that |
||
| buildDateIdentifier, _ := types.NewACIdentifier("build-date") | ||
| im.Annotations.Set(*buildDateIdentifier, time.Now().Format(time.RFC3339)) | ||
| } | ||
|
|
||
| im.Dependencies, err = ToAppcDependencies(m.Aci.Dependencies) | ||
| if err != nil { | ||
| return errs.WithEF(err, fields, "Failed to prepare dependencies for manifest") | ||
| } | ||
| im.Name = *name | ||
| im.Labels = labels | ||
|
|
||
| for _, exclusion := range m.Build.Exclude { | ||
| if strings.HasPrefix("/dgr/bin", exclusion) { | ||
| goto collectionIsComplete | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please do not use
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. After reading 10 times, I start to see you want to exclude completly dgr prestart process if /dgr/bin is excluded. Is it needed for reproductible build or can you add it in another PR ?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nested or backwards gotos are frowned upon. Forward-gotos (in this case to skip steps not needed) are no bad style. If you still wish I will substitute it, though.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| } | ||
| } | ||
| if len(m.Aci.App.Exec) == 0 { | ||
| m.Aci.App.Exec = []string{"/dgr/bin/busybox", "sh"} | ||
| } | ||
| // Set a pre-start handler, to run dgr's scripts. | ||
| for i := range m.Aci.App.EventHandlers { | ||
| if m.Aci.App.EventHandlers[i].Name == "pre-start" { | ||
| goto collectionIsComplete | ||
| } | ||
| } | ||
| m.Aci.App.EventHandlers = append(m.Aci.App.EventHandlers, | ||
| types.EventHandler{Name: "pre-start", Exec: []string{"/dgr/bin/prestart"}}) | ||
|
|
||
| collectionIsComplete: | ||
|
|
||
| isolators, err := ToAppcIsolators(m.Aci.App.Isolators) | ||
| if err != nil { | ||
|
|
@@ -118,7 +137,7 @@ func WriteAciManifest(m *AciManifest, targetFile string, projectName string, dgr | |
|
|
||
| im.App = &types.App{ | ||
| Exec: m.Aci.App.Exec, | ||
| EventHandlers: []types.EventHandler{{Name: "pre-start", Exec: []string{"/dgr/bin/prestart"}}}, | ||
| EventHandlers: m.Aci.App.EventHandlers, | ||
| User: m.Aci.App.User, | ||
| Group: m.Aci.App.Group, | ||
| WorkingDirectory: m.Aci.App.WorkingDirectory, | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -74,6 +74,8 @@ type BuilderDefinition struct { | |
|
|
||
| type BuildDefinition struct { | ||
| MountPoints []MountInfo `json:"mountPoints,omitempty" yaml:"mountPoints,omitempty"` | ||
| Exclude []string `json:"exclude,omitempty" yaml:"exclude,omitempty"` | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. exclude & transform is not linked to reproductible build, can you put it in a separate PR please ?
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also it's not meaningful, we don't know what will be excluded or transformed
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't get why we need them, the builder scripts goal is to prepare the filesystem to be ready for tar. it should be done there
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is the build, and we need this option to exclude dgr from the resulting tarball as well as intermediate items. For example, you can include a file but exclude its parent directory (tar allows for that!) to relinquish control over it to a previous layer. |
||
| Transform []string `json:"transform,omitempty" yaml:"transform,omitempty"` | ||
| } | ||
|
|
||
| type AciManifest struct { | ||
|
|
@@ -97,15 +99,16 @@ type AciDefinition struct { | |
| } | ||
|
|
||
| type DgrApp struct { | ||
| Exec types.Exec `json:"exec,omitempty" yaml:"exec,omitempty"` | ||
| User string `json:"user,omitempty" yaml:"user,omitempty"` | ||
| Group string `json:"group,omitempty" yaml:"group,omitempty"` | ||
| SupplementaryGIDs []int `json:"supplementaryGIDs,omitempty" yaml:"supplementaryGIDs,omitempty"` | ||
| WorkingDirectory string `json:"workingDirectory,omitempty" yaml:"workingDirectory,omitempty"` | ||
| Environment types.Environment `json:"environment,omitempty" yaml:"environment,omitempty"` | ||
| MountPoints []types.MountPoint `json:"mountPoints,omitempty" yaml:"mountPoints,omitempty"` | ||
| Ports []types.Port `json:"ports,omitempty" yaml:"ports,omitempty"` | ||
| Isolators []Isolator `json:"isolators,omitempty" yaml:"isolators,omitempty"` | ||
| Exec types.Exec `json:"exec,omitempty" yaml:"exec,omitempty"` | ||
| User string `json:"user,omitempty" yaml:"user,omitempty"` | ||
| Group string `json:"group,omitempty" yaml:"group,omitempty"` | ||
| SupplementaryGIDs []int `json:"supplementaryGIDs,omitempty" yaml:"supplementaryGIDs,omitempty"` | ||
| WorkingDirectory string `json:"workingDirectory,omitempty" yaml:"workingDirectory,omitempty"` | ||
| Environment types.Environment `json:"environment,omitempty" yaml:"environment,omitempty"` | ||
| EventHandlers []types.EventHandler `json:"eventHandlers,omitempty" yaml:"eventHandlers,omitempty"` | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. event handler is not linked to reproductible build, can you put it in another PR?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The option to remove anything dgr from the result entails the |
||
| MountPoints []types.MountPoint `json:"mountPoints,omitempty" yaml:"mountPoints,omitempty"` | ||
| Ports []types.Port `json:"ports,omitempty" yaml:"ports,omitempty"` | ||
| Isolators []Isolator `json:"isolators,omitempty" yaml:"isolators,omitempty"` | ||
| } | ||
|
|
||
| type LinuxCapabilitiesSetValue struct { | ||
|
|
||
This file was deleted.

There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of storing fullManifest in
Builder, it should be return byWriteManifest()and passed toTar().Here you are storing the result of the manifest processing globally in the middle of the build process. This leads to think it's always available which is not true.