diff --git a/CHANGELOG.unreleased.md b/CHANGELOG.unreleased.md
index e6fd7326260..3607079bef6 100644
--- a/CHANGELOG.unreleased.md
+++ b/CHANGELOG.unreleased.md
@@ -11,6 +11,7 @@
- [Jobs] Ability to copy a job ID (PR [#5951](https://github.com/vatesfr/xen-orchestra/pull/5951))
- [Host/advanced] Add button to enable/disable the host (PR [#5952](https://github.com/vatesfr/xen-orchestra/pull/5952))
- [VM/export] Ability to copy the export URL (PRĀ [#5948](https://github.com/vatesfr/xen-orchestra/pull/5948))
+- [Servers] Ability to use an HTTP proxy between XO and a server
### Bug fixes
@@ -42,8 +43,10 @@
- xo-server-netbox patch
- vhd-lib minor
+- xen-api minor
- @xen-orchestra/backup minor
- @xen-orchestra/proxy minor
- vhd-cli minor
+- xapi-explore-sr minor
- xo-server patch
- xo-web minor
diff --git a/packages/xapi-explore-sr/src/index.js b/packages/xapi-explore-sr/src/index.js
index 0664a720811..71d9eb008a3 100755
--- a/packages/xapi-explore-sr/src/index.js
+++ b/packages/xapi-explore-sr/src/index.js
@@ -3,6 +3,7 @@
import archy from 'archy'
import chalk from 'chalk'
import execPromise from 'exec-promise'
+import firstDefined from '@xen-orchestra/defined'
import humanFormat from 'human-format'
import pw from 'pw'
import { createClient } from 'xen-api'
@@ -69,11 +70,13 @@ execPromise(async args => {
url = required('Host URL'),
user = required('Host user'),
password = await askPassword('Host password'),
+ httpProxy = firstDefined(process.env.http_proxy, process.env.HTTP_PROXY),
] = args
const xapi = createClient({
allowUnauthorized: true,
auth: { user, password },
+ httpProxy,
readOnly: true,
url,
watchEvents: false,
diff --git a/packages/xen-api/README.md b/packages/xen-api/README.md
index 6c4ef643a09..00b6d2577b7 100644
--- a/packages/xen-api/README.md
+++ b/packages/xen-api/README.md
@@ -52,6 +52,7 @@ Options:
- `auth`: credentials used to sign in (can also be specified in the URL)
- `readOnly = false`: if true, no methods with side-effects can be called
- `callTimeout`: number of milliseconds after which a call is considered failed (can also be a map of timeouts by methods)
+- `httpProxy`: URL of the HTTP/HTTPS proxy used to reach the host, can include credentials
```js
// Force connection.
diff --git a/packages/xen-api/src/index.js b/packages/xen-api/src/index.js
index 0a9a3122877..2cdaffd9c72 100644
--- a/packages/xen-api/src/index.js
+++ b/packages/xen-api/src/index.js
@@ -115,6 +115,7 @@ export class Xapi extends EventEmitter {
}
this._allowUnauthorized = opts.allowUnauthorized
+ this._httpProxy = opts.httpProxy
this._setUrl(url)
this._connected = new Promise(resolve => {
@@ -851,6 +852,7 @@ export class Xapi extends EventEmitter {
rejectUnauthorized: !this._allowUnauthorized,
},
url,
+ httpProxy: this._httpProxy,
})
this._url = url
}
diff --git a/packages/xen-api/src/transports/json-rpc.js b/packages/xen-api/src/transports/json-rpc.js
index 1bda4d36136..7703433f51f 100644
--- a/packages/xen-api/src/transports/json-rpc.js
+++ b/packages/xen-api/src/transports/json-rpc.js
@@ -1,4 +1,5 @@
import httpRequestPlus from 'http-request-plus'
+import ProxyAgent from 'proxy-agent'
import { format, parse } from 'json-rpc-protocol'
import XapiError from '../_XapiError'
@@ -6,7 +7,11 @@ import XapiError from '../_XapiError'
import UnsupportedTransport from './_UnsupportedTransport'
// https://github.com/xenserver/xenadmin/blob/0df39a9d83cd82713f32d24704852a0fd57b8a64/XenModel/XenAPI/Session.cs#L403-L433
-export default ({ secureOptions, url }) => {
+export default ({ secureOptions, url, httpProxy }) => {
+ let agent
+ if (httpProxy !== undefined) {
+ agent = new ProxyAgent(httpProxy)
+ }
return (method, args) =>
httpRequestPlus
.post(url, {
@@ -17,6 +22,7 @@ export default ({ secureOptions, url }) => {
'Content-Type': 'application/json',
},
path: '/jsonrpc',
+ agent,
})
.readAll('utf8')
.then(
diff --git a/packages/xen-api/src/transports/xml-rpc-json.js b/packages/xen-api/src/transports/xml-rpc-json.js
index 3e098dcad72..40d483e320e 100644
--- a/packages/xen-api/src/transports/xml-rpc-json.js
+++ b/packages/xen-api/src/transports/xml-rpc-json.js
@@ -1,5 +1,6 @@
import { createClient, createSecureClient } from 'xmlrpc'
import { promisify } from 'promise-toolbox'
+import ProxyAgent from 'proxy-agent'
import XapiError from '../_XapiError'
@@ -70,10 +71,15 @@ const parseResult = result => {
throw new UnsupportedTransport()
}
-export default ({ secureOptions, url: { hostname, port, protocol } }) => {
+export default ({ secureOptions, url: { hostname, port, protocol }, httpProxy }) => {
const secure = protocol === 'https:'
+ let agent
+ if (httpProxy !== undefined) {
+ agent = new ProxyAgent(httpProxy)
+ }
const client = (secure ? createSecureClient : createClient)({
...(secure ? secureOptions : undefined),
+ agent,
host: hostname,
path: '/json',
port,
diff --git a/packages/xen-api/src/transports/xml-rpc.js b/packages/xen-api/src/transports/xml-rpc.js
index 884e0267309..f312d66e9d7 100644
--- a/packages/xen-api/src/transports/xml-rpc.js
+++ b/packages/xen-api/src/transports/xml-rpc.js
@@ -1,5 +1,6 @@
import { createClient, createSecureClient } from 'xmlrpc'
import { promisify } from 'promise-toolbox'
+import ProxyAgent from 'proxy-agent'
import XapiError from '../_XapiError'
@@ -30,10 +31,15 @@ const parseResult = result => {
return result.Value
}
-export default ({ secureOptions, url: { hostname, port, protocol } }) => {
+export default ({ secureOptions, url: { hostname, port, protocol, httpProxy } }) => {
const secure = protocol === 'https:'
+ let agent
+ if (httpProxy !== undefined) {
+ agent = new ProxyAgent(httpProxy)
+ }
const client = (secure ? createSecureClient : createClient)({
...(secure ? secureOptions : undefined),
+ agent,
host: hostname,
port,
})
diff --git a/packages/xo-server/src/api/server.mjs b/packages/xo-server/src/api/server.mjs
index a9c68dc9a7e..194f3240396 100644
--- a/packages/xo-server/src/api/server.mjs
+++ b/packages/xo-server/src/api/server.mjs
@@ -36,6 +36,10 @@ add.params = {
optional: true,
type: 'boolean',
},
+ httpProxy: {
+ optional: true,
+ type: 'string',
+ },
}
// -------------------------------------------------------------------
@@ -104,6 +108,10 @@ set.params = {
optional: true,
type: 'boolean',
},
+ httpProxy: {
+ optional: true,
+ type: ['string', 'null'],
+ },
}
// -------------------------------------------------------------------
diff --git a/packages/xo-server/src/xo-mixins/xen-servers.mjs b/packages/xo-server/src/xo-mixins/xen-servers.mjs
index 2f7c8f76775..d61cdaeeb90 100644
--- a/packages/xo-server/src/xo-mixins/xen-servers.mjs
+++ b/packages/xo-server/src/xo-mixins/xen-servers.mjs
@@ -94,7 +94,7 @@ export default class {
// TODO: disconnect servers on stop.
}
- async registerXenServer({ allowUnauthorized = false, host, label, password, readOnly = false, username }) {
+ async registerXenServer({ allowUnauthorized = false, host, label, password, readOnly = false, username, httpProxy }) {
// FIXME: We are storing passwords which is bad!
// Could we use tokens instead?
// TODO: use plain objects
@@ -102,6 +102,7 @@ export default class {
allowUnauthorized,
enabled: true,
host,
+ httpProxy,
label: label || undefined,
password,
readOnly,
@@ -119,11 +120,18 @@ export default class {
}
}
- async updateXenServer(id, { allowUnauthorized, enabled, error, host, label, password, readOnly, username }) {
+ async updateXenServer(
+ id,
+ { allowUnauthorized, enabled, error, host, label, password, readOnly, username, httpProxy }
+ ) {
const server = await this._getXenServer(id)
const xapi = this._xapis[id]
const requireDisconnected =
- allowUnauthorized !== undefined || host !== undefined || password !== undefined || username !== undefined
+ allowUnauthorized !== undefined ||
+ host !== undefined ||
+ password !== undefined ||
+ username !== undefined ||
+ httpProxy !== undefined
if (requireDisconnected && xapi !== undefined && xapi.status !== 'disconnected') {
throw new Error('this entry require disconnecting the server to update it')
@@ -153,6 +161,10 @@ export default class {
server.set('allowUnauthorized', allowUnauthorized)
}
+ if (httpProxy !== undefined) {
+ // if value is null, pass undefined to the model , so it will delete this optionnal property from the Server object
+ server.set('httpProxy', httpProxy === null ? undefined : httpProxy)
+ }
await this._servers.update(server)
}
@@ -288,6 +300,7 @@ export default class {
readOnly: server.readOnly,
...config.get('xapiOptions'),
+ httpProxy: server.httpProxy,
guessVhdSizeOnImport: config.get('guessVhdSizeOnImport'),
auth: {
diff --git a/packages/xo-web/src/common/intl/messages.js b/packages/xo-web/src/common/intl/messages.js
index 7589102c0c2..178a3a0a47e 100644
--- a/packages/xo-web/src/common/intl/messages.js
+++ b/packages/xo-web/src/common/intl/messages.js
@@ -1826,6 +1826,8 @@ const messages = {
serverEnabled: 'Enabled',
serverDisabled: 'Disabled',
serverDisable: 'Disable server',
+ serverHttpProxy: ' HTTP proxy URL',
+ serverHttpProxyPlaceHolder: ' HTTP proxy URL',
// ----- Copy VM -----
copyVm: 'Copy VM',
diff --git a/packages/xo-web/src/common/xo/index.js b/packages/xo-web/src/common/xo/index.js
index 7d61cc100f7..2acd1ab9cd6 100644
--- a/packages/xo-web/src/common/xo/index.js
+++ b/packages/xo-web/src/common/xo/index.js
@@ -560,10 +560,11 @@ export const exportConfig = () =>
// Server ------------------------------------------------------------
-export const addServer = (host, username, password, label, allowUnauthorized) =>
+export const addServer = (host, username, password, label, allowUnauthorized, httpProxy) =>
_call('server.add', {
allowUnauthorized,
host,
+ httpProxy,
label,
password,
username,
diff --git a/packages/xo-web/src/xo-app/settings/servers/index.js b/packages/xo-web/src/xo-app/settings/servers/index.js
index b3a4bb35a0b..651cebe6e62 100644
--- a/packages/xo-web/src/xo-app/settings/servers/index.js
+++ b/packages/xo-web/src/xo-app/settings/servers/index.js
@@ -132,6 +132,18 @@ const COLUMNS = [
itemRenderer: ({ poolId }) => poolId !== undefined && ,
name: _('pool'),
},
+ {
+ itemRenderer: (server, formatMessage) => (
+ editServer(server, { httpProxy: httpProxy || null })}
+ placeholder={formatMessage(messages.serverHttpProxyPlaceHolder)}
+ />
+ ),
+ name: _('serverHttpProxy'),
+ sortCriteria: _ => _.httpProxy,
+ },
]
const INDIVIDUAL_ACTIONS = [
{
@@ -152,13 +164,13 @@ export default class Servers extends Component {
}
_addServer = async () => {
- const { label, host, password, username, allowUnauthorized } = this.state
-
- await addServer(host, username, password, label, allowUnauthorized)
+ const { label, host, password, username, allowUnauthorized, httpProxy } = this.state
+ await addServer(host, username, password, label, allowUnauthorized, httpProxy)
this.setState({
allowUnauthorized: false,
host: '',
+ httpProxy: '',
label: '',
password: '',
username: '',
@@ -227,6 +239,15 @@ export default class Servers extends Component {
{' '}
+
+
+
{' '}
{_('serverConnect')}