1package houdini
2
3import (
4	"fmt"
5	"io"
6	"net/url"
7	"os"
8	"path/filepath"
9	"strings"
10	"sync"
11	"time"
12
13	"code.cloudfoundry.org/garden"
14	"github.com/charlievieth/fs"
15	"github.com/concourse/go-archive/tarfs"
16	"github.com/vito/houdini/process"
17)
18
19type UndefinedPropertyError struct {
20	Key string
21}
22
23func (err UndefinedPropertyError) Error() string {
24	return fmt.Sprintf("property does not exist: %s", err.Key)
25}
26
27type container struct {
28	spec garden.ContainerSpec
29
30	handle string
31
32	workDir   string
33	hasRootfs bool
34
35	properties  garden.Properties
36	propertiesL sync.RWMutex
37
38	env []string
39
40	processTracker process.ProcessTracker
41
42	graceTime  time.Duration
43	graceTimeL sync.RWMutex
44}
45
46func (backend *Backend) newContainer(spec garden.ContainerSpec, id string) (*container, error) {
47	var workDir string
48	var hasRootfs bool
49	if spec.RootFSPath != "" {
50		rootfsURI, err := url.Parse(spec.RootFSPath)
51		if err != nil {
52			return nil, err
53		}
54
55		switch rootfsURI.Scheme {
56		case "raw":
57			workDir = rootfsURI.Path
58			hasRootfs = true
59		default:
60			return nil, fmt.Errorf("unsupported rootfs uri (must be raw://): %s", spec.RootFSPath)
61		}
62	} else {
63		workDir = filepath.Join(backend.containersDir, id)
64
65		err := fs.MkdirAll(workDir, 0755)
66		if err != nil {
67			return nil, err
68		}
69	}
70
71	properties := spec.Properties
72	if properties == nil {
73		properties = garden.Properties{}
74	}
75
76	return &container{
77		spec: spec,
78
79		handle: spec.Handle,
80
81		workDir:   workDir,
82		hasRootfs: hasRootfs,
83
84		properties: properties,
85
86		env: spec.Env,
87
88		processTracker: process.NewTracker(),
89	}, nil
90}
91
92func (container *container) cleanup() error {
93	if !container.hasRootfs {
94		return fs.RemoveAll(container.workDir)
95	}
96
97	return nil
98}
99
100func (container *container) Handle() string {
101	return container.handle
102}
103
104func (container *container) Stop(kill bool) error {
105	return container.processTracker.Stop(kill)
106}
107
108func (container *container) Info() (garden.ContainerInfo, error) { return garden.ContainerInfo{}, nil }
109
110func (container *container) StreamIn(spec garden.StreamInSpec) error {
111	finalDestination := filepath.Join(container.workDir, filepath.FromSlash(spec.Path))
112
113	err := fs.MkdirAll(finalDestination, 0755)
114	if err != nil {
115		return err
116	}
117
118	err = tarfs.Extract(spec.TarStream, finalDestination)
119	if err != nil {
120		return err
121	}
122
123	return nil
124}
125
126func (container *container) StreamOut(spec garden.StreamOutSpec) (io.ReadCloser, error) {
127	if strings.HasSuffix(spec.Path, "/") {
128		spec.Path += "."
129	}
130
131	absoluteSource := container.workDir + string(os.PathSeparator) + filepath.FromSlash(spec.Path)
132
133	r, w := io.Pipe()
134
135	errs := make(chan error, 1)
136	go func() {
137		errs <- tarfs.Compress(w, filepath.Dir(absoluteSource), filepath.Base(absoluteSource))
138		_ = w.Close()
139	}()
140
141	return waitCloser{
142		ReadCloser: r,
143		wait:       errs,
144	}, nil
145}
146
147type waitCloser struct {
148	io.ReadCloser
149	wait <-chan error
150}
151
152func (c waitCloser) Close() error {
153	err := c.ReadCloser.Close()
154	if err != nil {
155		return err
156	}
157
158	return <-c.wait
159}
160
161func (container *container) LimitBandwidth(limits garden.BandwidthLimits) error { return nil }
162
163func (container *container) CurrentBandwidthLimits() (garden.BandwidthLimits, error) {
164	return garden.BandwidthLimits{}, nil
165}
166
167func (container *container) LimitCPU(limits garden.CPULimits) error { return nil }
168
169func (container *container) CurrentCPULimits() (garden.CPULimits, error) {
170	return garden.CPULimits{}, nil
171}
172
173func (container *container) LimitDisk(limits garden.DiskLimits) error { return nil }
174
175func (container *container) CurrentDiskLimits() (garden.DiskLimits, error) {
176	return garden.DiskLimits{}, nil
177}
178
179func (container *container) LimitMemory(limits garden.MemoryLimits) error { return nil }
180
181func (container *container) CurrentMemoryLimits() (garden.MemoryLimits, error) {
182	return garden.MemoryLimits{}, nil
183}
184
185func (container *container) NetIn(hostPort, containerPort uint32) (uint32, uint32, error) {
186	return 0, 0, nil
187}
188
189func (container *container) NetOut(garden.NetOutRule) error { return nil }
190
191func (container *container) BulkNetOut([]garden.NetOutRule) error { return nil }
192
193func (container *container) Run(spec garden.ProcessSpec, processIO garden.ProcessIO) (garden.Process, error) {
194	cmd, err := container.cmd(spec)
195	if err != nil {
196		return nil, err
197	}
198
199	return container.processTracker.Run(
200		spec.ID,
201		cmd,
202		processIO,
203		spec.TTY,
204	)
205}
206
207func (container *container) Attach(processID string, processIO garden.ProcessIO) (garden.Process, error) {
208	return container.processTracker.Attach(processID, processIO)
209}
210
211func (container *container) Property(name string) (string, error) {
212	container.propertiesL.RLock()
213	property, found := container.properties[name]
214	container.propertiesL.RUnlock()
215
216	if !found {
217		return "", UndefinedPropertyError{name}
218	}
219
220	return property, nil
221}
222
223func (container *container) SetProperty(name string, value string) error {
224	container.propertiesL.Lock()
225	container.properties[name] = value
226	container.propertiesL.Unlock()
227
228	return nil
229}
230
231func (container *container) RemoveProperty(name string) error {
232	container.propertiesL.Lock()
233	defer container.propertiesL.Unlock()
234
235	_, found := container.properties[name]
236	if !found {
237		return UndefinedPropertyError{name}
238	}
239
240	delete(container.properties, name)
241
242	return nil
243}
244
245func (container *container) Properties() (garden.Properties, error) {
246	return container.currentProperties(), nil
247}
248
249func (container *container) Metrics() (garden.Metrics, error) {
250	return garden.Metrics{}, nil
251}
252
253func (container *container) SetGraceTime(t time.Duration) error {
254	container.graceTimeL.Lock()
255	container.graceTime = t
256	container.graceTimeL.Unlock()
257	return nil
258}
259
260func (container *container) currentProperties() garden.Properties {
261	properties := garden.Properties{}
262
263	container.propertiesL.RLock()
264
265	for k, v := range container.properties {
266		properties[k] = v
267	}
268
269	container.propertiesL.RUnlock()
270
271	return properties
272}
273
274func (container *container) currentGraceTime() time.Duration {
275	container.graceTimeL.RLock()
276	defer container.graceTimeL.RUnlock()
277	return container.graceTime
278}
279