9 "github.com/edwarnicke/exechelper"
18 type Container struct {
23 extraRunningArgs string
24 volumes map[string]Volume
25 envVars map[string]string
26 vppInstance *VppInstance
29 func NewContainer(yamlInput ContainerConfig) (*Container, error) {
30 containerName := yamlInput["name"].(string)
31 if len(containerName) == 0 {
32 err := fmt.Errorf("container name must not be blank")
36 var container = new(Container)
37 container.volumes = make(map[string]Volume)
38 container.envVars = make(map[string]string)
39 container.name = containerName
41 if image, ok := yamlInput["image"]; ok {
42 container.image = image.(string)
44 container.image = "hs-test/vpp"
47 if args, ok := yamlInput["extra-args"]; ok {
48 container.extraRunningArgs = args.(string)
50 container.extraRunningArgs = ""
53 if isOptional, ok := yamlInput["is-optional"]; ok {
54 container.isOptional = isOptional.(bool)
56 container.isOptional = false
59 if _, ok := yamlInput["volumes"]; ok {
60 r := strings.NewReplacer("$HST_DIR", workDir)
61 for _, volu := range yamlInput["volumes"].([]interface{}) {
62 volumeMap := volu.(ContainerConfig)
63 hostDir := r.Replace(volumeMap["host-dir"].(string))
64 containerDir := volumeMap["container-dir"].(string)
65 isDefaultWorkDir := false
67 if isDefault, ok := volumeMap["is-default-work-dir"]; ok {
68 isDefaultWorkDir = isDefault.(bool)
71 container.addVolume(hostDir, containerDir, isDefaultWorkDir)
76 if _, ok := yamlInput["vars"]; ok {
77 for _, envVar := range yamlInput["vars"].([]interface{}) {
78 envVarMap := envVar.(ContainerConfig)
79 name := envVarMap["name"].(string)
80 value := envVarMap["value"].(string)
81 container.addEnvVar(name, value)
87 func (c *Container) getWorkDirVolume() (res Volume, exists bool) {
88 for _, v := range c.volumes {
89 if v.isDefaultWorkDir {
98 func (c *Container) GetHostWorkDir() (res string) {
99 if v, ok := c.getWorkDirVolume(); ok {
105 func (c *Container) GetContainerWorkDir() (res string) {
106 if v, ok := c.getWorkDirVolume(); ok {
112 func (c *Container) getRunCommand() string {
113 syncPath := fmt.Sprintf(" -v %s:/tmp/sync", c.getSyncPath())
114 cmd := "docker run --cap-add=all -d --privileged --network host --rm"
116 cmd += c.getVolumesAsCliOption()
117 cmd += c.getEnvVarsAsCliOption()
118 cmd += " --name " + c.name + " " + c.image + " " + c.extraRunningArgs
122 func (c *Container) run() error {
124 return fmt.Errorf("run container failed: name is blank")
127 exechelper.Run(fmt.Sprintf("mkdir -p /tmp/%s/sync", c.name))
128 cmd := c.getRunCommand()
129 err := exechelper.Run(cmd)
131 return fmt.Errorf("container run failed: %s", err)
137 func (c *Container) addVolume(hostDir string, containerDir string, isDefaultWorkDir bool) {
139 volume.hostDir = hostDir
140 volume.containerDir = containerDir
141 volume.isDefaultWorkDir = isDefaultWorkDir
142 c.volumes[hostDir] = volume
145 func (c *Container) getVolumeByHostDir(hostDir string) Volume {
146 return c.volumes[hostDir]
149 func (c *Container) getVolumesAsCliOption() string {
152 if len(c.volumes) > 0 {
153 for _, volume := range c.volumes {
154 cliOption += fmt.Sprintf(" -v %s:%s", volume.hostDir, volume.containerDir)
161 func (c *Container) getWorkDirAsCliOption() string {
162 if _, ok := c.getWorkDirVolume(); ok {
163 return fmt.Sprintf(" --workdir=\"%s\"", c.GetContainerWorkDir())
168 func (c *Container) addEnvVar(name string, value string) {
169 c.envVars[name] = value
172 func (c *Container) getEnvVarsAsCliOption() string {
174 if len(c.envVars) == 0 {
178 for name, value := range c.envVars {
179 cliOption += fmt.Sprintf(" -e %s=%s", name, value)
184 func (c *Container) getSyncPath() string {
185 return fmt.Sprintf("/tmp/%s/sync", c.name)
188 func (c *Container) newVppInstance(additionalConfig ...Stanza) (*VppInstance, error) {
189 vppConfig := new(VppConfig)
190 vppConfig.CliSocketFilePath = defaultCliSocketFilePath
191 if len(additionalConfig) > 0 {
192 vppConfig.additionalConfig = additionalConfig[0]
195 vpp := new(VppInstance)
197 vpp.config = vppConfig
204 func (c *Container) copy(sourceFileName string, targetFileName string) error {
205 cmd := exec.Command("docker", "cp", sourceFileName, c.name+":"+targetFileName)
209 func (c *Container) createFile(destFileName string, content string) error {
210 f, err := os.CreateTemp("/tmp", "hst-config")
214 defer os.Remove(f.Name())
216 if _, err := f.Write([]byte(content)); err != nil {
219 if err := f.Close(); err != nil {
222 c.copy(f.Name(), destFileName)
227 * Executes in detached mode so that the started application can continue to run
228 * without blocking execution of test
230 func (c *Container) execServer(command string) error {
231 return exechelper.Run("docker exec -d" + c.getEnvVarsAsCliOption() + " " + c.name + " " + command)
234 func (c *Container) exec(command string) (string, error) {
235 cliCommand := "docker exec" + c.getEnvVarsAsCliOption() + " " + c.name + " " + command
236 byteOutput, err := exechelper.CombinedOutput(cliCommand)
237 return string(byteOutput), err
240 func (c *Container) execAction(args string) (string, error) {
241 syncFile := c.getSyncPath() + "/rc"
244 workDir := c.getWorkDirAsCliOption()
245 cmd := fmt.Sprintf("docker exec -d %s %s hs-test %s",
249 err := exechelper.Run(cmd)
253 res, err := waitForSyncFile(syncFile)
255 return "", fmt.Errorf("failed to read sync file while executing 'hs-test %s': %v", args, err)
257 o := res.StdOutput + res.ErrOutput
259 return o, fmt.Errorf("cmd resulted in non-zero value %d: %s", res.Code, res.Desc)
264 func (c *Container) stop() error {
265 if c.vppInstance != nil && c.vppInstance.apiChannel != nil {
266 c.vppInstance.disconnect()
269 return exechelper.Run("docker stop " + c.name + " -t 0")