Skip to content

Commit 392d5f0

Browse files
committed
Added docker resource
1 parent 195ce0a commit 392d5f0

File tree

7 files changed

+160
-11
lines changed

7 files changed

+160
-11
lines changed

scripts/init.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
tart clone ghcr.io/kevinwang5658/sonoma-codify:v0.0.3 codify-test-vm
2-
tart clone ghcr.io/kevinwang5658/sonoma-codify:v0.0.3 codify-sonoma
2+
# tart clone ghcr.io/kevinwang5658/sonoma-codify:v0.0.3 codify-sonoma

src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import { VscodeResource } from './resources/vscode/vscode.js';
3535
import { XcodeToolsResource } from './resources/xcode-tools/xcode-tools.js';
3636
import { MacportsResource } from './resources/macports/macports.js';
3737
import { Npm } from './resources/node/npm/npm.js';
38+
import { DockerResource } from './resources/docker/docker.js';
3839

3940
runPlugin(Plugin.create(
4041
'default',
@@ -74,5 +75,6 @@ runPlugin(Plugin.create(
7475
new PipSync(),
7576
new MacportsResource(),
7677
new Npm(),
78+
new DockerResource(),
7779
])
7880
)
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"$schema": "http://json-schema.org/draft-07/schema",
3+
"$id": "https://www.codifycli.com/docker.json",
4+
"title": "Docker resource",
5+
"type": "object",
6+
"description": "Installs docker.",
7+
"properties": {
8+
"acceptLicense": {
9+
"type": "boolean",
10+
"description": "Accepts the license agreement. Defaults to true"
11+
},
12+
"useCurrentUser": {
13+
"type": "boolean",
14+
"description": "Use the current user to install docker. Defaults to true"
15+
}
16+
},
17+
"additionalProperties": false
18+
}

src/resources/docker/docker.ts

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
import { CreatePlan, DestroyPlan, Resource, ResourceSettings, getPty } from 'codify-plugin-lib';
2+
import { StringIndexedObject } from 'codify-schemas';
3+
import fs from 'node:fs/promises';
4+
import os from 'node:os';
5+
import path from 'node:path';
6+
7+
import { SpawnStatus, codifySpawn } from '../../utils/codify-spawn.js';
8+
import { FileUtils } from '../../utils/file-utils.js';
9+
import { Utils } from '../../utils/index.js';
10+
import Schema from './docker-schema.json';
11+
12+
export interface DockerConfig extends StringIndexedObject {
13+
acceptLicense?: boolean;
14+
useCurrentUser?: boolean;
15+
}
16+
17+
const ARM_DOWNLOAD_LINK = 'https://desktop.docker.com/mac/main/arm64/Docker.dmg'
18+
const INTEL_DOWNLOAD_LINK = 'https://desktop.docker.com/mac/main/amd64/Docker.dmg'
19+
20+
export class DockerResource extends Resource<DockerConfig> {
21+
getSettings(): ResourceSettings<DockerConfig> {
22+
return {
23+
id: 'docker',
24+
schema: Schema,
25+
parameterSettings: {
26+
acceptLicense: {
27+
type: 'boolean',
28+
setting: true,
29+
default: true,
30+
},
31+
// version: {
32+
// type: 'version'
33+
// },
34+
useCurrentUser: {
35+
type: 'boolean',
36+
setting: true,
37+
default: true,
38+
}
39+
}
40+
};
41+
}
42+
43+
async refresh(): Promise<Partial<DockerConfig> | Partial<DockerConfig>[] | null> {
44+
const $ = getPty();
45+
46+
const versionResult = await $.spawnSafe('docker --version');
47+
if (versionResult.status === SpawnStatus.ERROR) {
48+
return null;
49+
}
50+
51+
const result: DockerConfig = {};
52+
53+
// TODO: support versioning in the future
54+
// const version = /Docker version (.*), build/.exec(versionResult.data)?.[1];
55+
// if (version && parameters.version) {
56+
// result.version = version;
57+
// }
58+
59+
return result;
60+
}
61+
62+
/**
63+
* References:
64+
* Blog about docker changes: https://dazwallace.wordpress.com/2022/12/02/changes-to-docker-desktop-for-mac/
65+
* Path: https://stackoverflow.com/questions/64009138/docker-command-not-found-when-running-on-mac
66+
* Issue: https://github.com/docker/for-mac/issues/6504
67+
* @param plan
68+
*/
69+
async create(plan: CreatePlan<DockerConfig>): Promise<void> {
70+
const downloadLink = await Utils.isArmArch() ? ARM_DOWNLOAD_LINK : INTEL_DOWNLOAD_LINK;
71+
72+
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'codify-docker'))
73+
await Utils.downloadUrlIntoFile(path.join(tmpDir, 'Docker.dmg'), downloadLink);
74+
const user = Utils.getUser();
75+
76+
try {
77+
await codifySpawn('hdiutil attach Docker.dmg', { cwd: tmpDir, requiresRoot: true })
78+
79+
console.log('Running Docker installer. This may take a couple of minutes to complete...')
80+
await codifySpawn(`/Volumes/Docker/Docker.app/Contents/MacOS/install ${plan.desiredConfig.acceptLicense ? '--accept-license' : ''} ${plan.desiredConfig.useCurrentUser ? `--user ${user}` : ''}`,
81+
{ requiresRoot: true }
82+
)
83+
await codifySpawn('hdiutil detach /Volumes/Docker', { cwd: tmpDir, requiresRoot: true })
84+
} finally {
85+
await fs.rm(tmpDir, { recursive: true, force: true })
86+
}
87+
88+
await codifySpawn('xattr -r -d com.apple.quarantine /Applications/Docker.app', { requiresRoot: true });
89+
await FileUtils.addPathToZshrc('/Applications/Docker.app/Contents/Resources/bin', false);
90+
}
91+
92+
async destroy(plan: DestroyPlan<DockerConfig>): Promise<void> {
93+
await codifySpawn('/Applications/Docker.app/Contents/MacOS/uninstall', { throws: false })
94+
await fs.rm(path.join(os.homedir(), 'Library/Group\\ Containers/group.com.docker'), { recursive: true, force: true });
95+
await fs.rm(path.join(os.homedir(), 'Library/Containers/com.docker.docker/Data'), { recursive: true, force: true });
96+
await fs.rm(path.join(os.homedir(), '.docker'), { recursive: true, force: true });
97+
await codifySpawn('rm -rf /Applications/Docker.app', { requiresRoot: true })
98+
99+
await FileUtils.removeLineFromZshrc('/Applications/Docker.app/Contents/Resources/bin')
100+
}
101+
102+
}

src/resources/node/npm/npm.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { CreatePlan, DestroyPlan, RefreshContext, Resource, ResourceSettings, getPty } from 'codify-plugin-lib';
1+
import { Resource, ResourceSettings, getPty } from 'codify-plugin-lib';
22
import { ResourceConfig } from 'codify-schemas';
33

44
import { NpmGlobalInstallParameter, NpmPackage } from './global-install.js';
@@ -22,7 +22,7 @@ export class Npm extends Resource<NpmConfig> {
2222
}
2323
}
2424

25-
async refresh(parameters: Partial<NpmConfig>, context: RefreshContext<NpmConfig>): Promise<Partial<NpmConfig> | Partial<NpmConfig>[] | null> {
25+
async refresh(parameters: Partial<NpmConfig>): Promise<Partial<NpmConfig> | Partial<NpmConfig>[] | null> {
2626
const pty = getPty();
2727

2828
const { status } = await pty.spawnSafe('which npm')
@@ -34,9 +34,9 @@ export class Npm extends Resource<NpmConfig> {
3434
}
3535

3636
// Npm gets created with NodeJS
37-
async create(plan: CreatePlan<NpmConfig>): Promise<void> {}
37+
async create(): Promise<void> {}
3838

3939
// Npm is destroyed with NodeJS
40-
destroy(plan: DestroyPlan<NpmConfig>): Promise<void> {}
40+
async destroy(): Promise<void> {}
4141

4242
}

src/utils/index.ts

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1-
import * as fs from 'node:fs/promises';
21
import * as fsSync from 'node:fs';
3-
4-
import { codifySpawn, SpawnStatus } from './codify-spawn.js';
5-
import { SpotlightKind, SpotlightUtils } from './spotlight-search.js';
2+
import * as fs from 'node:fs/promises';
3+
import os from 'node:os';
64
import path from 'node:path';
7-
import { finished } from 'node:stream/promises';
85
import { Readable } from 'node:stream';
6+
import { finished } from 'node:stream/promises';
7+
8+
import { SpawnStatus, codifySpawn } from './codify-spawn.js';
9+
import { SpotlightKind, SpotlightUtils } from './spotlight-search.js';
910

1011
export const Utils = {
1112
async findApplication(name: string): Promise<string[]> {
@@ -73,7 +74,7 @@ export const Utils = {
7374

7475
async isArmArch(): Promise<boolean> {
7576
const query = await codifySpawn('sysctl -n machdep.cpu.brand_string');
76-
return /M([0-9])/.test(query.data);
77+
return /M(\d)/.test(query.data);
7778
},
7879

7980
async isDirectoryOnPath(directory: string): Promise<boolean> {
@@ -108,4 +109,8 @@ export const Utils = {
108109
// Different type definitions here for readable stream (NodeJS vs DOM). Small hack to fix that
109110
await finished(Readable.fromWeb(body as never).pipe(ws));
110111
},
112+
113+
getUser(): string {
114+
return os.userInfo().username;
115+
}
111116
};

test/docker/docker.test.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { beforeEach, describe, expect, it } from 'vitest';
2+
import { PluginTester } from 'codify-plugin-test';
3+
import * as path from 'node:path';
4+
import cp from 'child_process';
5+
6+
describe('Test docker', async () => {
7+
const pluginPath = path.resolve('./src/index.ts');
8+
9+
it('Can install docker', { timeout: 300000 }, async () => {
10+
await PluginTester.fullTest(pluginPath, [
11+
{ type: 'docker' },
12+
], {
13+
validateApply: async () => {
14+
expect(() => cp.execSync('source ~/.zshrc; which aws;')).to.not.throw;
15+
16+
},
17+
validateDestroy: async () => {
18+
expect(() => cp.execSync('source ~/.zshrc; which aws;')).to.throw;
19+
}
20+
})
21+
})
22+
})

0 commit comments

Comments
 (0)