@cloud-cli/px 4.4.0 → 4.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +14 -0
- package/dist/index.js +45 -0
- package/dist/proxy-manager.d.ts +14 -0
- package/dist/proxy-manager.js +185 -0
- package/dist/types.d.ts +33 -0
- package/dist/types.js +1 -0
- package/package.json +2 -2
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { help, init } from "@cloud-cli/cli";
|
|
2
|
+
import { DomainAndTarget, DomainName, Proxy, WithOptionalProps } from "./types.js";
|
|
3
|
+
declare const _default: {
|
|
4
|
+
add(options: WithOptionalProps<Proxy>): Promise<Proxy>;
|
|
5
|
+
remove(options: WithOptionalProps<DomainAndTarget>): Promise<boolean>;
|
|
6
|
+
update(options: WithOptionalProps<Proxy>): Promise<Proxy>;
|
|
7
|
+
list(filters: Partial<Proxy>): Promise<Proxy[]>;
|
|
8
|
+
get(options: DomainName): Promise<Proxy[]>;
|
|
9
|
+
domains(): Promise<string[]>;
|
|
10
|
+
reload(): Promise<void>;
|
|
11
|
+
[init](): Promise<import("@cloud-cli/proxy").ProxyServer>;
|
|
12
|
+
[help](): string;
|
|
13
|
+
};
|
|
14
|
+
export default _default;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { help, init, logInfo } from "@cloud-cli/cli";
|
|
2
|
+
import { ProxyManager } from "./proxy-manager.js";
|
|
3
|
+
const manager = new ProxyManager();
|
|
4
|
+
export default {
|
|
5
|
+
async add(options) {
|
|
6
|
+
return manager.addProxy(options);
|
|
7
|
+
},
|
|
8
|
+
async remove(options) {
|
|
9
|
+
return manager.removeProxy(options);
|
|
10
|
+
},
|
|
11
|
+
async update(options) {
|
|
12
|
+
return await manager.updateProxy(options);
|
|
13
|
+
},
|
|
14
|
+
list(filters) {
|
|
15
|
+
return manager.getProxyList(filters);
|
|
16
|
+
},
|
|
17
|
+
get(options) {
|
|
18
|
+
return manager.getProxyListForDomain(options);
|
|
19
|
+
},
|
|
20
|
+
domains() {
|
|
21
|
+
return manager.getDomainList();
|
|
22
|
+
},
|
|
23
|
+
async reload() {
|
|
24
|
+
logInfo("Reloading proxy server");
|
|
25
|
+
return await manager.reload();
|
|
26
|
+
},
|
|
27
|
+
async [init]() {
|
|
28
|
+
await manager.reload();
|
|
29
|
+
return manager.server;
|
|
30
|
+
},
|
|
31
|
+
[help]() {
|
|
32
|
+
return `Manages reverse proxy entries
|
|
33
|
+
|
|
34
|
+
Available commands:
|
|
35
|
+
px.add --domain <domain> --target <url> [--cors] [--redirect] [--preserveHost] [--redirectUrl <url>] [--headers <headers>] [--authorization <value>] - Add an entry
|
|
36
|
+
px.remove --domain <domain> - Remove an entry
|
|
37
|
+
px.update --domain <domain> [proxy options] - Update an entry
|
|
38
|
+
px.list [--domain <domain>] [--target <url>] [proxy options] - List matching entries
|
|
39
|
+
px.get --domain <domain> - Get entries for a domain
|
|
40
|
+
px.domains - List configured domains
|
|
41
|
+
px.reload - Reload all proxy entries
|
|
42
|
+
|
|
43
|
+
Proxy options: domain, target, cors, redirect, preserveHost, redirectUrl, headers, authorization`;
|
|
44
|
+
},
|
|
45
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { ProxyServer } from '@cloud-cli/proxy';
|
|
2
|
+
import type { DockerContainer, DomainAndTarget, DomainName, Proxy, WithOptionalProps } from './types.js';
|
|
3
|
+
export declare class ProxyManager {
|
|
4
|
+
server: ProxyServer;
|
|
5
|
+
addProxy(properties: WithOptionalProps<Proxy>): Promise<Proxy>;
|
|
6
|
+
updateProxy(options: WithOptionalProps<Proxy>): Promise<Proxy>;
|
|
7
|
+
removeProxy(options: WithOptionalProps<DomainAndTarget>): Promise<boolean>;
|
|
8
|
+
getDomainList(): Promise<string[]>;
|
|
9
|
+
getProxyListWithContainers(): Promise<Proxy[]>;
|
|
10
|
+
getProxyList(filters?: Partial<Proxy>): Promise<Proxy[]>;
|
|
11
|
+
getProxyListForDomain(options: WithOptionalProps<DomainName>): Promise<Proxy[]>;
|
|
12
|
+
reload(): Promise<void>;
|
|
13
|
+
getRunningContainers(): Promise<DockerContainer[]>;
|
|
14
|
+
}
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import { getStorage, getConfig } from '@cloud-cli/cli';
|
|
2
|
+
import { ProxyEntry, ProxyServer, ProxySettings } from '@cloud-cli/proxy';
|
|
3
|
+
import { exec } from '@cloud-cli/exec';
|
|
4
|
+
const defaultOptions = {
|
|
5
|
+
httpPort: 80,
|
|
6
|
+
httpsPort: 443,
|
|
7
|
+
certsFolder: process.env.PX_CERTS_FOLDER || '/etc/letsencrypt/live',
|
|
8
|
+
};
|
|
9
|
+
const domainNotSpecifiedError = new Error('Domain not specified');
|
|
10
|
+
const targetNotSpecifiedError = new Error('Target not specified');
|
|
11
|
+
const moduleConfig = await getConfig('px', defaultOptions);
|
|
12
|
+
const { set, get, has, remove, getAll } = getStorage('px');
|
|
13
|
+
const settings = new ProxySettings({
|
|
14
|
+
certificatesFolder: moduleConfig.certsFolder,
|
|
15
|
+
certificateFile: 'fullchain.pem',
|
|
16
|
+
keyFile: 'privkey.pem',
|
|
17
|
+
httpPort: moduleConfig.httpPort,
|
|
18
|
+
httpsPort: moduleConfig.httpsPort,
|
|
19
|
+
});
|
|
20
|
+
const px = new ProxyServer(settings);
|
|
21
|
+
const emptyProxy = {
|
|
22
|
+
domain: '',
|
|
23
|
+
target: '',
|
|
24
|
+
cors: false,
|
|
25
|
+
preserveHost: true,
|
|
26
|
+
redirect: false,
|
|
27
|
+
redirectUrl: '',
|
|
28
|
+
headers: '',
|
|
29
|
+
authorization: '',
|
|
30
|
+
};
|
|
31
|
+
const readDomain = (options) => {
|
|
32
|
+
options.domain = options.domain || options.host || options._[0];
|
|
33
|
+
};
|
|
34
|
+
const numberRe = /^[0-9]+$/;
|
|
35
|
+
const readOption = value => {
|
|
36
|
+
switch (true) {
|
|
37
|
+
case value === 'true':
|
|
38
|
+
return true;
|
|
39
|
+
case value === 'false':
|
|
40
|
+
return false;
|
|
41
|
+
case numberRe.test(value):
|
|
42
|
+
return Number(value);
|
|
43
|
+
default:
|
|
44
|
+
return value;
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
function applyProperties(proxy, options) {
|
|
48
|
+
const properties = ['target', 'cors', 'redirect', 'redirectUrl', 'headers', 'authorization', 'preserveHost'];
|
|
49
|
+
for (const p of properties) {
|
|
50
|
+
if (p in options) {
|
|
51
|
+
proxy[p] = readOption(options[p]);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
export class ProxyManager {
|
|
56
|
+
constructor() {
|
|
57
|
+
this.server = px;
|
|
58
|
+
}
|
|
59
|
+
async addProxy(properties) {
|
|
60
|
+
readDomain(properties);
|
|
61
|
+
if (!properties.domain) {
|
|
62
|
+
throw domainNotSpecifiedError;
|
|
63
|
+
}
|
|
64
|
+
if (!properties.target && !properties.redirectUrl) {
|
|
65
|
+
throw targetNotSpecifiedError;
|
|
66
|
+
}
|
|
67
|
+
const proxy = { ...emptyProxy, domain: properties.domain };
|
|
68
|
+
applyProperties(proxy, properties);
|
|
69
|
+
set(properties.domain, proxy);
|
|
70
|
+
await this.reload();
|
|
71
|
+
return proxy;
|
|
72
|
+
}
|
|
73
|
+
async updateProxy(options) {
|
|
74
|
+
readDomain(options);
|
|
75
|
+
const domain = options.domain;
|
|
76
|
+
let proxy = get(domain);
|
|
77
|
+
if (!domain) {
|
|
78
|
+
throw domainNotSpecifiedError;
|
|
79
|
+
}
|
|
80
|
+
if (!proxy) {
|
|
81
|
+
proxy = { ...emptyProxy, domain };
|
|
82
|
+
}
|
|
83
|
+
applyProperties(proxy, options);
|
|
84
|
+
set(proxy.domain, proxy);
|
|
85
|
+
await this.reload();
|
|
86
|
+
return proxy;
|
|
87
|
+
}
|
|
88
|
+
async removeProxy(options) {
|
|
89
|
+
readDomain(options);
|
|
90
|
+
const host = options.domain;
|
|
91
|
+
if (!host) {
|
|
92
|
+
throw domainNotSpecifiedError;
|
|
93
|
+
}
|
|
94
|
+
if (has(host)) {
|
|
95
|
+
remove(host);
|
|
96
|
+
await this.reload();
|
|
97
|
+
return true;
|
|
98
|
+
}
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
async getDomainList() {
|
|
102
|
+
const proxies = await this.getProxyList();
|
|
103
|
+
return proxies.map((proxy) => proxy.domain);
|
|
104
|
+
}
|
|
105
|
+
async getProxyListWithContainers() {
|
|
106
|
+
const staticRoutes = getAll();
|
|
107
|
+
const containers = (await this.getRunningContainers()).map(readProxyFromContainer);
|
|
108
|
+
const containerDomains = containers.map(c => c.domain.split('/')[0]);
|
|
109
|
+
const list = [
|
|
110
|
+
...staticRoutes.filter(t => !containerDomains.includes(t.domain)),
|
|
111
|
+
...containers,
|
|
112
|
+
];
|
|
113
|
+
return list;
|
|
114
|
+
}
|
|
115
|
+
async getProxyList(filters = {}) {
|
|
116
|
+
const list = await this.getProxyListWithContainers();
|
|
117
|
+
const keys = Object.keys(filters);
|
|
118
|
+
if (!keys.length) {
|
|
119
|
+
return list;
|
|
120
|
+
}
|
|
121
|
+
return keys.reduce((list, key) => {
|
|
122
|
+
const filter = String(filters[key]).toLowerCase();
|
|
123
|
+
return list.filter((p) => String(p[key]).toLowerCase().includes(filter));
|
|
124
|
+
}, list);
|
|
125
|
+
}
|
|
126
|
+
async getProxyListForDomain(options) {
|
|
127
|
+
readDomain(options);
|
|
128
|
+
const all = await this.getProxyListWithContainers();
|
|
129
|
+
return all.filter((p) => p.domain.includes(options.domain));
|
|
130
|
+
}
|
|
131
|
+
async reload() {
|
|
132
|
+
const all = await this.getProxyListWithContainers();
|
|
133
|
+
px.reset();
|
|
134
|
+
for (const t of all) {
|
|
135
|
+
const [domain, path = ""] = t.domain.split("/");
|
|
136
|
+
px.add(new ProxyEntry({
|
|
137
|
+
domain,
|
|
138
|
+
path,
|
|
139
|
+
target: t.target,
|
|
140
|
+
redirectToHttps: t.redirect,
|
|
141
|
+
redirectToUrl: t.redirectUrl,
|
|
142
|
+
cors: t.cors,
|
|
143
|
+
headers: t.headers,
|
|
144
|
+
authorization: t.authorization,
|
|
145
|
+
preserveHost: t.preserveHost,
|
|
146
|
+
}));
|
|
147
|
+
}
|
|
148
|
+
px.start();
|
|
149
|
+
}
|
|
150
|
+
async getRunningContainers() {
|
|
151
|
+
const ps = await exec("docker", ["ps", "-aq"]);
|
|
152
|
+
const ids = ps.stdout.trim().split("\n");
|
|
153
|
+
const state = await exec("docker", ["inspect", ...ids]);
|
|
154
|
+
const json = JSON.parse(state.stdout);
|
|
155
|
+
return json
|
|
156
|
+
.map(readDockerContainer)
|
|
157
|
+
.filter(d => d.ports.length && d.labels.host);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
function readProxyFromContainer(c) {
|
|
161
|
+
const domain = [c.labels.host, c.labels.path].filter(Boolean).join("/");
|
|
162
|
+
const proxyOverrides = get(domain) || {};
|
|
163
|
+
return {
|
|
164
|
+
...emptyProxy,
|
|
165
|
+
...proxyOverrides,
|
|
166
|
+
domain,
|
|
167
|
+
target: `http://localhost:${c.ports[0].host}`,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
function readDockerContainer(d) {
|
|
171
|
+
const labels = Object.fromEntries(Object.entries(d.Config.Labels || {})
|
|
172
|
+
.filter(([key]) => key.startsWith("px:"))
|
|
173
|
+
.map(([key, value]) => [key.replace("px:", ""), value]));
|
|
174
|
+
return {
|
|
175
|
+
id: d.Id,
|
|
176
|
+
image: d.Image,
|
|
177
|
+
state: d.State.Status,
|
|
178
|
+
name: d.Name,
|
|
179
|
+
labels: labels,
|
|
180
|
+
ports: Object.entries(d.HostConfig.PortBindings || {}).map(([port, pb]) => ({
|
|
181
|
+
container: Number(port.replace(/\D+/, "")),
|
|
182
|
+
host: Number(pb[0].HostPort),
|
|
183
|
+
})),
|
|
184
|
+
};
|
|
185
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
interface OptionalProps {
|
|
2
|
+
host?: string;
|
|
3
|
+
_: string[];
|
|
4
|
+
}
|
|
5
|
+
export interface DomainName extends OptionalProps {
|
|
6
|
+
domain: string;
|
|
7
|
+
}
|
|
8
|
+
export interface DomainAndTarget extends DomainName {
|
|
9
|
+
target: string;
|
|
10
|
+
}
|
|
11
|
+
export interface Proxy {
|
|
12
|
+
domain: string;
|
|
13
|
+
target: string;
|
|
14
|
+
redirect: boolean;
|
|
15
|
+
preserveHost: boolean;
|
|
16
|
+
redirectUrl: string;
|
|
17
|
+
headers: string;
|
|
18
|
+
authorization: string;
|
|
19
|
+
cors: boolean;
|
|
20
|
+
}
|
|
21
|
+
export type WithOptionalProps<T extends object> = T & OptionalProps;
|
|
22
|
+
export interface DockerContainer {
|
|
23
|
+
id: string;
|
|
24
|
+
image: string;
|
|
25
|
+
name: string;
|
|
26
|
+
state: string;
|
|
27
|
+
ports: Array<{
|
|
28
|
+
host: number;
|
|
29
|
+
container: number;
|
|
30
|
+
}>;
|
|
31
|
+
labels: Record<string, string>;
|
|
32
|
+
}
|
|
33
|
+
export {};
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cloud-cli/px",
|
|
3
|
-
"version": "4.4.
|
|
3
|
+
"version": "4.4.1",
|
|
4
4
|
"description": "Reverse Proxy",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"prettier": "@cloud-cli/prettier-config",
|
|
9
9
|
"scripts": {
|
|
10
10
|
"test": "echo true",
|
|
11
|
-
"build": "rm -rf ./dist/* &&
|
|
11
|
+
"build": "rm -rf ./dist/* && npx tsc"
|
|
12
12
|
},
|
|
13
13
|
"keywords": [
|
|
14
14
|
"cloudy"
|