github.com/inspektor-gadget/inspektor-gadget@v0.28.1/pkg/container-utils/testutils/docker.go (about) 1 // Copyright 2022 The Inspektor Gadget authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package testutils 16 17 import ( 18 "bytes" 19 "context" 20 "fmt" 21 "io" 22 "testing" 23 24 "github.com/docker/docker/api/types/container" 25 "github.com/docker/docker/api/types/image" 26 "github.com/docker/docker/client" 27 ) 28 29 func NewDockerContainer(name, cmd string, options ...Option) Container { 30 c := &DockerContainer{ 31 containerSpec: containerSpec{ 32 name: name, 33 cmd: cmd, 34 options: defaultContainerOptions(), 35 }, 36 } 37 for _, o := range options { 38 o(c.options) 39 } 40 return c 41 } 42 43 type DockerContainer struct { 44 containerSpec 45 46 client *client.Client 47 } 48 49 func (d *DockerContainer) initClient() error { 50 var err error 51 d.client, err = client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation()) 52 if err != nil { 53 return fmt.Errorf("creating a client: %w", err) 54 } 55 return nil 56 } 57 58 func (d *DockerContainer) Run(t *testing.T) { 59 if err := d.initClient(); err != nil { 60 t.Fatalf("Failed to initialize client: %s", err) 61 } 62 63 _ = d.client.ContainerRemove(d.options.ctx, d.name, container.RemoveOptions{}) 64 65 reader, err := d.client.ImagePull(d.options.ctx, d.options.image, image.PullOptions{}) 66 if err != nil { 67 t.Fatalf("Failed to pull image container: %s", err) 68 } 69 io.Copy(io.Discard, reader) 70 71 hostConfig := &container.HostConfig{} 72 if d.options.seccompProfile != "" { 73 hostConfig.SecurityOpt = []string{fmt.Sprintf("seccomp=%s", d.options.seccompProfile)} 74 } 75 76 if d.options.portBindings != nil { 77 hostConfig.PortBindings = d.options.portBindings 78 } 79 80 resp, err := d.client.ContainerCreate(d.options.ctx, &container.Config{ 81 Image: d.options.image, 82 Cmd: []string{"/bin/sh", "-c", d.cmd}, 83 Tty: false, 84 }, hostConfig, nil, nil, d.name) 85 if err != nil { 86 t.Fatalf("Failed to create container: %s", err) 87 } 88 if err := d.client.ContainerStart(d.options.ctx, resp.ID, container.StartOptions{}); err != nil { 89 t.Fatalf("Failed to start container: %s", err) 90 } 91 d.id = resp.ID 92 93 if d.options.wait { 94 statusCh, errCh := d.client.ContainerWait(d.options.ctx, resp.ID, container.WaitConditionNotRunning) 95 select { 96 case err := <-errCh: 97 if err != nil { 98 t.Fatalf("Failed to wait for container: %s", err) 99 } 100 case <-statusCh: 101 } 102 } 103 containerJSON, err := d.client.ContainerInspect(d.options.ctx, d.id) 104 if err != nil { 105 t.Fatalf("Failed to inspect container: %s", err) 106 } 107 d.pid = containerJSON.State.Pid 108 d.portBindings = containerJSON.NetworkSettings.Ports 109 110 if d.options.logs { 111 out, err := d.client.ContainerLogs(d.options.ctx, resp.ID, container.LogsOptions{ShowStdout: true, ShowStderr: true}) 112 if err != nil { 113 t.Fatalf("Failed to get container logs: %s", err) 114 } 115 buf := new(bytes.Buffer) 116 buf.ReadFrom(out) 117 t.Logf("Container %q output:\n%s", d.name, string(buf.Bytes())) 118 } 119 120 if d.options.removal { 121 err := d.removeAndClose() 122 if err != nil { 123 t.Fatalf("Failed to remove container: %s", err) 124 } 125 } 126 } 127 128 func (d *DockerContainer) Start(t *testing.T) { 129 if d.started { 130 t.Logf("Warn(%s): trying to start already running container\n", d.name) 131 return 132 } 133 d.start(t) 134 d.started = true 135 } 136 137 func (d *DockerContainer) start(t *testing.T) { 138 for _, o := range []Option{WithoutWait(), withoutRemoval()} { 139 o(d.options) 140 } 141 d.Run(t) 142 } 143 144 func (d *DockerContainer) Stop(t *testing.T) { 145 if !d.started && !d.options.forceDelete { 146 t.Logf("Warn(%s): trying to stop already stopped container\n", d.name) 147 return 148 } 149 if d.client == nil { 150 if d.options.forceDelete { 151 t.Logf("Warn(%s): trying to stop container with nil client. Forcing deletion\n", d.name) 152 if err := d.initClient(); err != nil { 153 t.Fatalf("Failed to initialize client: %s", err) 154 } 155 } else { 156 t.Fatalf("Client is not initialized") 157 } 158 } 159 160 if err := d.removeAndClose(); err != nil { 161 t.Fatalf("Failed to stop container: %s", err) 162 } 163 d.started = false 164 } 165 166 func (d *DockerContainer) removeAndClose() error { 167 err := d.client.ContainerRemove(d.options.ctx, d.name, container.RemoveOptions{Force: true}) 168 if err != nil { 169 return fmt.Errorf("removing container: %w", err) 170 } 171 172 err = d.client.Close() 173 if err != nil { 174 return fmt.Errorf("closing client: %w", err) 175 } 176 177 return nil 178 } 179 180 func RunDockerFailedContainer(ctx context.Context, t *testing.T) { 181 NewDockerContainer("test-ig-failed-container", "/none", WithoutLogs(), WithoutWait(), WithContext(ctx)).Run(t) 182 }