Skip to content

Commit a425536

Browse files
authored
Merge pull request #71 from gvallee/container_manifest
Create a manifest for all containers that are created with SyMPI
2 parents b4d8e47 + 4ea489d commit a425536

File tree

7 files changed

+161
-24
lines changed

7 files changed

+161
-24
lines changed

internal/pkg/container/container.go

Lines changed: 19 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
"github.com/sylabs/singularity-mpi/internal/pkg/checker"
2222
"github.com/sylabs/singularity-mpi/internal/pkg/implem"
2323
"github.com/sylabs/singularity-mpi/internal/pkg/sy"
24+
"github.com/sylabs/singularity-mpi/internal/pkg/syexec"
2425
"github.com/sylabs/singularity-mpi/internal/pkg/sys"
2526
util "github.com/sylabs/singularity-mpi/internal/pkg/util/file"
2627
)
@@ -104,10 +105,6 @@ func Create(container *Config, sysCfg *sys.Config) error {
104105

105106
log.Printf("- Creating image %s...", container.Path)
106107

107-
// We only let the mpirun command run for 10 minutes max
108-
ctx, cancel := context.WithTimeout(context.Background(), sys.CmdTimeout*2*time.Minute)
109-
defer cancel()
110-
111108
// The definition file is ready so we simple build the container using the Singularity command
112109
if sysCfg.Debug {
113110
err = checker.CheckDefFile(container.DefFile)
@@ -117,24 +114,28 @@ func Create(container *Config, sysCfg *sys.Config) error {
117114
}
118115

119116
log.Printf("-> Using definition file %s", container.DefFile)
120-
var stdout, stderr bytes.Buffer
121-
var cmd *exec.Cmd
117+
118+
var cmd syexec.SyCmd
119+
singularityVersion := sy.GetVersion(sysCfg)
120+
cmd.ManifestData = []string{"Singularity version: " + singularityVersion}
121+
cmd.ManifestDir = container.InstallDir
122+
cmd.ExecDir = container.BuildDir
122123
if sy.IsSudoCmd("build", sysCfg) {
123-
log.Printf("-> Running %s %s %s %s %s\n", sysCfg.SudoBin, sysCfg.SingularityBin, "build", container.Path, container.DefFile)
124-
cmd = exec.CommandContext(ctx, sysCfg.SudoBin, sysCfg.SingularityBin, "build", container.Path, container.DefFile)
124+
cmd.BinPath = sysCfg.SudoBin
125+
cmd.ManifestFileHash = []string{sysCfg.SingularityBin, container.DefFile}
126+
cmd.CmdArgs = []string{sysCfg.SingularityBin, "build", container.Path, container.DefFile}
125127
} else if sysCfg.Nopriv {
126-
log.Printf("-> Running %s %s %s %s\n", sysCfg.SingularityBin, "build --fakeroot", container.Path, container.DefFile)
127-
cmd = exec.CommandContext(ctx, sysCfg.SingularityBin, "build", "--fakeroot", container.Path, container.DefFile)
128+
cmd.BinPath = sysCfg.SingularityBin
129+
cmd.ManifestFileHash = []string{container.DefFile}
130+
cmd.CmdArgs = []string{"build", "--fakeroot", container.Path, container.DefFile}
128131
} else {
129-
log.Printf("-> Running %s %s %s %s\n", sysCfg.SingularityBin, "build", container.Path, container.DefFile)
130-
cmd = exec.CommandContext(ctx, sysCfg.SingularityBin, "build", container.Path, container.DefFile)
132+
cmd.BinPath = sysCfg.SingularityBin
133+
cmd.ManifestFileHash = []string{container.DefFile}
134+
cmd.CmdArgs = []string{"build", container.Path, container.DefFile}
131135
}
132-
cmd.Dir = container.BuildDir
133-
cmd.Stdout = &stdout
134-
cmd.Stderr = &stderr
135-
err = cmd.Run()
136-
if err != nil {
137-
return fmt.Errorf("failed to execute command - stdout: %s; stderr: %s; err: %s", stdout.String(), stderr.String(), err)
136+
res := cmd.Run()
137+
if res.Err != nil {
138+
return fmt.Errorf("failed to execute command - stdout: %s; stderr: %s; err: %s", res.Stdout, res.Stderr, res.Err)
138139
}
139140

140141
// We make all SIF file executable to make it easier to integrate with other tools

internal/pkg/ldd/debian.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ func DebianGetDependencies(output string) []string {
6161

6262
lines := strings.Split(output, "\n")
6363

64-
ctx, cancel := context.WithTimeout(context.Background(), sys.CmdTimeout*time.Second)
64+
ctx, cancel := context.WithTimeout(context.Background(), sys.CmdTimeout*time.Minute)
6565
defer cancel()
6666

6767
// the package of interest is the one for the current architecture

internal/pkg/ldd/rpm.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ func RPMGetDependencies(output string) []string {
3030

3131
lines := strings.Split(output, "\n")
3232

33-
ctx, cancel := context.WithTimeout(context.Background(), sys.CmdTimeout*time.Second)
33+
ctx, cancel := context.WithTimeout(context.Background(), sys.CmdTimeout*time.Minute)
3434
defer cancel()
3535

3636
// the package of interest is the one for the current architecture

internal/pkg/manifest/manifest.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ func Create(filepath string, entries []string) error {
1818
return fmt.Errorf("failed to create %s: %s", filepath, err)
1919
}
2020

21-
_, err = f.WriteString(strings.Join(entries, " "))
21+
_, err = f.WriteString(strings.Join(entries, "\n"))
2222
if err != nil {
2323
return fmt.Errorf("failed to write to %s: %s", filepath, err)
2424
}

internal/pkg/sy/sy.go

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -244,7 +244,7 @@ func Configure(env *buildenv.Info, sysCfg *sys.Config, extraArgs []string) error
244244
}
245245
}
246246
singularityManifestPath := filepath.Join(env.InstallDir, "install.MANIFEST")
247-
err := manifest.Create(singularityManifestPath, args)
247+
err := manifest.Create(singularityManifestPath, []string{strings.Join(args, " ")})
248248
if err != nil {
249249
return fmt.Errorf("failed to create installation manifest: %s", err)
250250
}
@@ -339,3 +339,26 @@ func GetSIFArchs(imgPath string, sysCfg *sys.Config) ([]string, error) {
339339

340340
return getArchsFromSIFListOutput(stdout.String()), nil
341341
}
342+
343+
// GetVersion returned the version of Singularity that is currently used
344+
func GetVersion(sysCfg *sys.Config) string {
345+
if sysCfg.SingularityBin == "" {
346+
// Not a fatal error, we just log the error
347+
log.Printf("path to the singularity binary is undefined")
348+
return ""
349+
}
350+
351+
ctx, cancel := context.WithTimeout(context.Background(), sys.CmdTimeout*time.Minute)
352+
defer cancel()
353+
var stdout bytes.Buffer
354+
cmd := exec.CommandContext(ctx, sysCfg.SingularityBin, "version")
355+
cmd.Stdout = &stdout
356+
err := cmd.Run()
357+
if err != nil {
358+
// Not a fatal error, we just log the error
359+
log.Printf("failed to execute singularity version: %s", err)
360+
return ""
361+
}
362+
363+
return stdout.String()
364+
}

internal/pkg/syexec/syexec.go

Lines changed: 114 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,21 @@
66
package syexec
77

88
import (
9+
"bytes"
910
"context"
11+
"crypto/sha256"
12+
"encoding/hex"
13+
"io"
14+
"log"
15+
"os"
1016
"os/exec"
17+
"path/filepath"
18+
"strings"
19+
"time"
20+
21+
"github.com/sylabs/singularity-mpi/internal/pkg/manifest"
22+
"github.com/sylabs/singularity-mpi/internal/pkg/sys"
23+
util "github.com/sylabs/singularity-mpi/internal/pkg/util/file"
1124
)
1225

1326
// Result represents the result of the execution of a command
@@ -25,18 +38,118 @@ type SyCmd struct {
2538
// Cmd represents the command to execute to submit the job
2639
Cmd *exec.Cmd
2740

41+
// Timeout is the maximum time a command can run
42+
Timeout time.Duration
43+
2844
// BinPath is the path to the binary to execute
2945
BinPath string
3046

3147
// CmdArgs is a slice of string representing the command's arguments
3248
CmdArgs []string
3349

50+
// ExecDir is the directory where to execute the command
51+
ExecDir string
52+
3453
// Env is a slice of string representing the environment to be used with the command
35-
Env []string
54+
Env []string
3655

3756
// Ctx is the context of the command to execute to submit a job
3857
Ctx context.Context
3958

4059
// CancelFn is the function to cancel the command to submit a job
4160
CancelFn context.CancelFunc
61+
62+
// ManifestDir is the directory where to create the manifest related to the command execution
63+
ManifestDir string
64+
65+
// ManifestData is extra content to add to the manifest
66+
ManifestData []string
67+
68+
// ManifestFileHash is a list of absolute path to files for which we want a hash in the manifest
69+
ManifestFileHash []string
70+
}
71+
72+
func getFileHash(path string) string {
73+
f, err := os.Open(path)
74+
if err != nil {
75+
return ""
76+
}
77+
defer f.Close()
78+
79+
hasher := sha256.New()
80+
_, err = io.Copy(hasher, f)
81+
if err != nil {
82+
return ""
83+
}
84+
85+
return hex.EncodeToString(hasher.Sum(nil))
86+
}
87+
88+
func hashFiles(files []string) []string {
89+
var hashData []string
90+
91+
for _, file := range files {
92+
hash := getFileHash(file)
93+
hashData = append(hashData, file+": "+hash)
94+
}
95+
96+
return hashData
97+
}
98+
99+
// Run executes a syexec command and creates the appropriate manifest (when possible)
100+
func (c *SyCmd) Run() Result {
101+
var res Result
102+
103+
cmdTimeout := c.Timeout
104+
if cmdTimeout == 0 {
105+
cmdTimeout = sys.CmdTimeout
106+
}
107+
108+
ctx, cancel := context.WithTimeout(context.Background(), cmdTimeout*time.Minute)
109+
defer cancel()
110+
111+
var stderr, stdout bytes.Buffer
112+
if c.Cmd == nil {
113+
c.Cmd = exec.CommandContext(ctx, c.BinPath, c.CmdArgs...)
114+
c.Cmd.Dir = c.ExecDir
115+
c.Cmd.Stdout = &stdout
116+
c.Cmd.Stderr = &stderr
117+
}
118+
119+
log.Printf("-> Running %s %s\n", c.BinPath, strings.Join(c.CmdArgs, " "))
120+
err := c.Cmd.Run()
121+
res.Stderr = stderr.String()
122+
res.Stdout = stdout.String()
123+
if err != nil {
124+
res.Err = err
125+
return res
126+
}
127+
128+
if c.ManifestDir != "" {
129+
path := filepath.Join(c.ManifestDir, "exec.MANIFEST")
130+
if !util.FileExists(path) {
131+
currentTime := time.Now()
132+
data := []string{"Command: " + c.BinPath + strings.Join(c.CmdArgs, " ") + "\n"}
133+
data = append(data, "Execution path: "+c.ExecDir)
134+
data = append(data, "Execution time: "+currentTime.Format("2006-01-02 15:04:05"))
135+
data = append(data, c.ManifestData...)
136+
137+
filesToHash := []string{c.BinPath} // we always get the fingerprint of the binary we execute
138+
filesToHash = append(filesToHash, c.ManifestFileHash...)
139+
hashData := hashFiles(filesToHash)
140+
data = append(data, hashData...)
141+
142+
err := manifest.Create(path, data)
143+
if err != nil {
144+
// This is not a fatal error, we just log it
145+
log.Printf("failed to create manifest: %s", err)
146+
}
147+
log.Printf("-> Manifest successfully created (%s)", path)
148+
149+
} else {
150+
log.Printf("Manifest %s already exists, skipping...", err)
151+
}
152+
}
153+
154+
return res
42155
}

internal/pkg/sys/sys.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ const (
2222
// image containers and install MPI
2323
DefaultSympiInstallDir = ".sympi"
2424

25-
// CmdTimetout is the maximum time we allow a command to run
25+
// CmdTimeout is the maximum time we allow a command to run
2626
CmdTimeout = 20
2727

2828
// DefaultUbuntuDistro is the default Ubuntu distribution we use

0 commit comments

Comments
 (0)