File: unpack.go

package info (click to toggle)
golang-github-mudler-docker-companion 0.4.5%2Bgit20250528.7d707b9%2Bds-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 184 kB
  • sloc: makefile: 31
file content (157 lines) | stat: -rw-r--r-- 3,818 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
package api

import (
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"os/signal"
	"path/filepath"
	"syscall"

	"github.com/docker/docker/pkg/archive"
	docker "github.com/fsouza/go-dockerclient"
	jww "github.com/spf13/jwalterweatherman"
)

// SEPARATOR contains system-specific separator
const SEPARATOR = string(filepath.Separator)

// ROOTFS is our temporary rootfs path
const ROOTFS = "." + SEPARATOR + "rootfs_overlay"

// Unpack unpacks a docker image into a path
func Unpack(client *docker.Client, image string, dirname string, fatal bool) error {
	var err error
	r, w := io.Pipe()

	if dirname == "" {
		dirname = ROOTFS
	}

	os.MkdirAll(dirname, 0777)

	filename, err := ioutil.TempFile(os.TempDir(), "artemide")
	if err != nil {
		return fmt.Errorf("Couldn't create the temporary file")
	}
	os.Remove(filename.Name())

	jww.INFO.Println("Creating container")

	container, err := client.CreateContainer(docker.CreateContainerOptions{
		Config: &docker.Config{
			Image: image,
			Cmd:   []string{"true"},
		},
	})
	if err != nil {
		jww.FATAL.Fatalln("Couldn't export container, sorry", err)
	}
	defer func(*docker.Container) {
		client.RemoveContainer(docker.RemoveContainerOptions{
			ID:    container.ID,
			Force: true,
		})
	}(container)

	signalchan := make(chan os.Signal, 1)
	signal.Notify(signalchan,
		syscall.SIGINT,
		syscall.SIGTERM,
		syscall.SIGQUIT)

	go func() {
		for {
			s := <-signalchan
			switch s {

			case syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT:
				jww.WARN.Println("SIGTERM/SIGINT/SIGQUIT detected, removing pending containers")
				client.RemoveContainer(docker.RemoveContainerOptions{
					ID:    container.ID,
					Force: true,
				})
			}
		}
	}()

	// writing without a reader will deadlock so write in a goroutine
	go func() {
		// it is important to close the writer or reading from the other end of the
		// pipe will never finish
		defer w.Close()
		err := client.ExportContainer(docker.ExportContainerOptions{ID: container.ID, OutputStream: w})
		if err != nil {
			jww.FATAL.Fatalln("Couldn't export container, sorry", err)
		}

	}()

	jww.INFO.Println("Extracting to", dirname)

	err = Untar(r, dirname, true)
	if err != nil {
		return fmt.Errorf("could not unpack to " + dirname)
	}
	err = prepareRootfs(dirname, fatal)

	return err
}

func prepareRootfs(dirname string, fatal bool) error {

	_, err := os.Stat(dirname + SEPARATOR + ".dockerenv")
	if err == nil {
		err = os.Remove(dirname + SEPARATOR + ".dockerenv")
		if err != nil {
			if fatal == true {
				return fmt.Errorf("could not remove docker env file")
			} else {
				jww.WARN.Println("error on remove .dockerenv, extracting anyway")
			}
		}
	}

	_, err = os.Stat(dirname + SEPARATOR + ".dockerinit")
	if err == nil {
		err = os.Remove(dirname + SEPARATOR + ".dockerinit")
		if err != nil {
			if fatal == true {
				return fmt.Errorf("could not remove docker init file")
			} else {
				jww.WARN.Println("error on remove .dockerinit, extracting anyway")
			}
		}
	}

	err = os.MkdirAll(dirname+SEPARATOR+"dev", 0751)
	if err != nil {
		if fatal == true {
			return fmt.Errorf("could not create dev folder")
		} else {
			jww.WARN.Println("could not create dev folder")
		}
	}

	// Google DNS as default
	d1 := []byte("nameserver 8.8.8.8\nnameserver 8.8.4.4\n")
	err = ioutil.WriteFile(dirname+SEPARATOR+"etc"+SEPARATOR+"resolv.conf", d1, 0644)
	if err != nil {
		if fatal == true {
			return fmt.Errorf("could not write resolv.conf file")
		} else {
			jww.WARN.Println("could not create resolv.conf file")
		}
	}

	return nil
}

// Untar just a wrapper around the docker functions
func Untar(in io.Reader, dest string, sameOwner bool) error {
	return archive.Untar(in, dest, &archive.TarOptions{
		NoLchown:        !sameOwner,
		ExcludePatterns: []string{"dev/"}, // prevent 'operation not permitted'
	})
}