Passed
Push — master ( 417ecd...901a9f )
by Christian
02:13
created

MachineStoragePath::guardSymLinkIsNotAUnixStandardPath()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 10
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 7
nc 2
nop 0
dl 0
loc 10
rs 9.4285
c 0
b 0
f 0
1
<?php declare(strict_types=1);
2
3
namespace Cocotte\Machine;
4
5
use Assert\Assertion;
6
use Cocotte\Environment\LazyEnvironmentValue;
7
use Cocotte\Environment\LazyLoadAware;
8
use Cocotte\Filesystem\Filesystem;
9
use Cocotte\Shell\Env;
10
11
class MachineStoragePath implements LazyEnvironmentValue, LazyLoadAware
12
{
13
    const MACHINE_STORAGE_PATH = 'MACHINE_STORAGE_PATH';
14
    const INPUT_OPTION = 'machine-storage-path';
15
16
    /**
17
     * @var string
18
     */
19
    private $value;
20
21
    /**
22
     * @var Filesystem
23
     */
24
    private $filesystem;
25
26
    public function __construct(string $value, Filesystem $filesystem)
27
    {
28
        Assertion::notEmpty($value, "The machine storage path is empty.");
29
        Assertion::regex(
30
            $value,
31
            '/^[^$"\']+$/',
32
            "The machine storage path '{$value}' contains dollar signs, single quotes or double quotes."
33
        );
34
        Assertion::true(
35
            $filesystem->isAbsolutePath($value),
36
            "Machine storage path '{$value}' is not an absolute path"
37
        );
38
        $this->filesystem = $filesystem;
39
        $this->value = $value;
40
    }
41
42
    public function toString(): string
43
    {
44
        return $this->value;
45
    }
46
47
    public function __toString()
48
    {
49
        return $this->toString();
50
    }
51
52
    public function onLazyLoad(Env $env): void
53
    {
54
        $env->put(self::MACHINE_STORAGE_PATH, $this->toString());
55
        $this->symLink();
56
    }
57
58
    /**
59
     * This is crooked but it serves our purpose:
60
     * Create a path on installer identical to the storage path on host.
61
     * Because docker machine stores an absolute path in its config files and
62
     * we want it to work outside of the installer afterwards.
63
     * This solution is preferred to editing the json config files after machine creation and
64
     * every time docker machine would rewrite it.
65
     *
66
     * @throws \Exception
67
     */
68
    private function symLink()
69
    {
70
        // Unlikely but if Cocotte is run from a root /host directory, the we don't need a sym link.
71
        if ($this->pathOnInstaller() === $this->pathOnHostFileSystem()) {
72
            return;
73
        }
74
75
        if (!$this->filesystem->exists($this->pathOnHostFileSystem())) {
76
            $this->createSymLink();
77
        } else {
78
            $this->guardSymLinkIsNotAUnixStandardPath();
79
        }
80
    }
81
82
    private function pathOnHostFileSystem(): string
83
    {
84
        return $this->toString();
85
    }
86
87
    private function pathOnInstaller(): string
88
    {
89
        return "/host/machine";
90
    }
91
92
    private function createSymLink(): void
93
    {
94
        if (is_dir($this->pathOnInstaller()) && !is_dir("{$this->pathOnInstaller()}/certs")) {
95
            throw new \Exception(
96
                "Error: Tried to create a directory named 'machine' in the directory from where you ".
97
                "executed Cocotte but it already exists and it is not a valid docker machine storage path."
98
            );
99
        }
100
        $this->filesystem->mkdir("{$this->pathOnInstaller()}/certs");
101
        $this->filesystem->symlink($this->pathOnInstaller(), $this->pathOnHostFileSystem());
102
    }
103
104
    private function guardSymLinkIsNotAUnixStandardPath(): void
105
    {
106
        if (
107
            !is_link($this->pathOnHostFileSystem()) ||
108
            $this->filesystem->readlink($this->pathOnHostFileSystem()) !== $this->pathOnInstaller()
109
        ) {
110
            throw new \Exception(
111
                "Error: cannot symlink '{$this->pathOnInstaller()}' to '{$this->pathOnHostFileSystem()}' because it is a real ".
112
                "path on Cocotte filesystem. Start Cocotte from a different directory on your computer. One ".
113
                "that does not exist in the Filesystem Hierarchy Standard of a UNIX-like operating system\n"
114
            );
115
        }
116
    }
117
}