Passed
Push — master ( 956163...dedbc7 )
by Christian
03:02
created

MachineStoragePath::toString()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
rs 10
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, then 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->guardSymLink();
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
        $filesystem = $this->filesystem;
95
        $pathOnInstaller = $this->pathOnInstaller();
96
        $pathOnHostFileSystem = $this->pathOnHostFileSystem();
97
98
        if ($filesystem->exists($pathOnInstaller) && !$filesystem->exists("{$pathOnInstaller}/certs")) {
99
            throw new \Exception(
100
                "Error: Tried to create a directory named 'machine' in the directory from where you ".
101
                "executed Cocotte but it already exists and it is not a valid docker machine storage path."
102
            );
103
        }
104
        $filesystem->mkdir("{$pathOnInstaller}/certs");
105
        $filesystem->symlink($pathOnInstaller, $pathOnHostFileSystem);
106
    }
107
108
    private function guardSymLink(): void
109
    {
110
        if (!$this->filesystem->isLink($this->pathOnHostFileSystem())) {
111
            throw new \Exception(
112
                "Error: cannot symlink '{$this->pathOnInstaller()}' to '{$this->pathOnHostFileSystem()}' because ".
113
                "it is a real path on Cocotte filesystem. Start Cocotte from a different directory on your computer. ".
114
                "One that does not exist in the Cocotte filesystem.\n"
115
            );
116
        }
117
118
        $readlink = $this->filesystem->readlink($this->pathOnHostFileSystem());
119
        if ($readlink !== $this->pathOnInstaller()) {
120
            throw new \Exception(
121
                "Error: Cannot symlink '{$this->pathOnInstaller()}' to '{$this->pathOnHostFileSystem()}' because ".
122
                "it is already symlink which resolves to '{$readlink}'."
123
            );
124
        }
125
    }
126
}