feat(backup/s3): add http and region parameters to S3 (#5658)
This commit is contained in:
parent
ffacc0d8d0
commit
c219ea06bf
@ -1,4 +1,5 @@
|
|||||||
import execa from 'execa'
|
import execa from 'execa'
|
||||||
|
import { parse } from 'xo-remote-parser'
|
||||||
|
|
||||||
import RemoteHandlerLocal from './local'
|
import RemoteHandlerLocal from './local'
|
||||||
import RemoteHandlerNfs from './nfs'
|
import RemoteHandlerNfs from './nfs'
|
||||||
@ -20,10 +21,7 @@ try {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const getHandler = (remote, ...rest) => {
|
export const getHandler = (remote, ...rest) => {
|
||||||
// FIXME: should be done in xo-remote-parser.
|
const Handler = HANDLERS[parse(remote.url).type]
|
||||||
const type = remote.url.split('://')[0]
|
|
||||||
|
|
||||||
const Handler = HANDLERS[type]
|
|
||||||
if (!Handler) {
|
if (!Handler) {
|
||||||
throw new Error('Unhandled remote type')
|
throw new Error('Unhandled remote type')
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import aws from '@sullux/aws-sdk'
|
import aws from '@sullux/aws-sdk'
|
||||||
import assert from 'assert'
|
import assert from 'assert'
|
||||||
|
import http from 'http'
|
||||||
import { parse } from 'xo-remote-parser'
|
import { parse } from 'xo-remote-parser'
|
||||||
|
|
||||||
import RemoteHandlerAbstract from './abstract'
|
import RemoteHandlerAbstract from './abstract'
|
||||||
@ -16,9 +17,8 @@ const IDEAL_FRAGMENT_SIZE = Math.ceil(MAX_OBJECT_SIZE / MAX_PARTS_COUNT) // the
|
|||||||
export default class S3Handler extends RemoteHandlerAbstract {
|
export default class S3Handler extends RemoteHandlerAbstract {
|
||||||
constructor(remote, _opts) {
|
constructor(remote, _opts) {
|
||||||
super(remote)
|
super(remote)
|
||||||
const { host, path, username, password } = parse(remote.url)
|
const { host, path, username, password, protocol, region } = parse(remote.url)
|
||||||
// https://www.zenko.io/blog/first-things-first-getting-started-scality-s3-server/
|
const params = {
|
||||||
this._s3 = aws({
|
|
||||||
accessKeyId: username,
|
accessKeyId: username,
|
||||||
apiVersion: '2006-03-01',
|
apiVersion: '2006-03-01',
|
||||||
endpoint: host,
|
endpoint: host,
|
||||||
@ -28,7 +28,16 @@ export default class S3Handler extends RemoteHandlerAbstract {
|
|||||||
httpOptions: {
|
httpOptions: {
|
||||||
timeout: 600000,
|
timeout: 600000,
|
||||||
},
|
},
|
||||||
}).s3
|
}
|
||||||
|
if (protocol === 'http') {
|
||||||
|
params.httpOptions.agent = new http.Agent()
|
||||||
|
params.sslEnabled = false
|
||||||
|
}
|
||||||
|
if (region !== undefined) {
|
||||||
|
params.region = region
|
||||||
|
}
|
||||||
|
|
||||||
|
this._s3 = aws(params).s3
|
||||||
|
|
||||||
const splitPath = path.split('/').filter(s => s.length)
|
const splitPath = path.split('/').filter(s => s.length)
|
||||||
this._bucket = splitPath.shift()
|
this._bucket = splitPath.shift()
|
||||||
|
@ -14,6 +14,7 @@
|
|||||||
- [Backup] Lock VM directory during backup to avoid race conditions (PR [#5746](https://github.com/vatesfr/xen-orchestra/pull/5746))
|
- [Backup] Lock VM directory during backup to avoid race conditions (PR [#5746](https://github.com/vatesfr/xen-orchestra/pull/5746))
|
||||||
- [XOA] Notify user when proxies need to be upgraded (PR [#5717](https://github.com/vatesfr/xen-orchestra/pull/5717))
|
- [XOA] Notify user when proxies need to be upgraded (PR [#5717](https://github.com/vatesfr/xen-orchestra/pull/5717))
|
||||||
- [Host/network] Identify the management network [#5731](https://github.com/vatesfr/xen-orchestra/issues/5731) (PR [#5743](https://github.com/vatesfr/xen-orchestra/pull/5743))
|
- [Host/network] Identify the management network [#5731](https://github.com/vatesfr/xen-orchestra/issues/5731) (PR [#5743](https://github.com/vatesfr/xen-orchestra/pull/5743))
|
||||||
|
- [Backup/S3] Support for HTTP protocol and choice of region (PR [#5658](https://github.com/vatesfr/xen-orchestra/pull/5658))
|
||||||
|
|
||||||
### Bug fixes
|
### Bug fixes
|
||||||
|
|
||||||
@ -44,6 +45,7 @@
|
|||||||
> In case of conflict, the highest (lowest in previous list) `$version` wins.
|
> In case of conflict, the highest (lowest in previous list) `$version` wins.
|
||||||
|
|
||||||
- xo-server-perf-alert patch
|
- xo-server-perf-alert patch
|
||||||
|
- xo-remote-parser minor
|
||||||
- @xen-orchestra/fs minor
|
- @xen-orchestra/fs minor
|
||||||
- @xen-orchestra/xapi patch
|
- @xen-orchestra/xapi patch
|
||||||
- @xen-orchestra/backups minor
|
- @xen-orchestra/backups minor
|
||||||
|
@ -37,9 +37,11 @@ export const parse = string => {
|
|||||||
object.domain = domain
|
object.domain = domain
|
||||||
object.username = username
|
object.username = username
|
||||||
object.password = password
|
object.password = password
|
||||||
} else if (type === 's3') {
|
} else if (type === 's3' || type === 's3+http') {
|
||||||
const parsed = new Url(string)
|
const parsed = new Url(string)
|
||||||
|
object.protocol = parsed.protocol === 's3:' ? 'https' : 'http'
|
||||||
object.type = 's3'
|
object.type = 's3'
|
||||||
|
object.region = parsed.hash.length === 0 ? undefined : parsed.hash.slice(1) // remove '#'
|
||||||
object.host = parsed.host
|
object.host = parsed.host
|
||||||
object.path = parsed.pathname
|
object.path = parsed.pathname
|
||||||
object.username = parsed.username
|
object.username = parsed.username
|
||||||
@ -48,7 +50,7 @@ export const parse = string => {
|
|||||||
return object
|
return object
|
||||||
}
|
}
|
||||||
|
|
||||||
export const format = ({ type, host, path, port, username, password, domain }) => {
|
export const format = ({ type, host, path, port, username, password, domain, protocol = type, region }) => {
|
||||||
type === 'local' && (type = 'file')
|
type === 'local' && (type = 'file')
|
||||||
let string = `${type}://`
|
let string = `${type}://`
|
||||||
if (type === 'nfs') {
|
if (type === 'nfs') {
|
||||||
@ -58,6 +60,7 @@ export const format = ({ type, host, path, port, username, password, domain }) =
|
|||||||
string += `${username}:${password}@${domain}\\\\${host}`
|
string += `${username}:${password}@${domain}\\\\${host}`
|
||||||
}
|
}
|
||||||
if (type === 's3') {
|
if (type === 's3') {
|
||||||
|
string = protocol === 'https' ? 's3://' : 's3+http://'
|
||||||
string += `${username}:${encodeURIComponent(password)}@${host}`
|
string += `${username}:${encodeURIComponent(password)}@${host}`
|
||||||
}
|
}
|
||||||
path = sanitizePath(path)
|
path = sanitizePath(path)
|
||||||
@ -68,5 +71,8 @@ export const format = ({ type, host, path, port, username, password, domain }) =
|
|||||||
path = `/${path}`
|
path = `/${path}`
|
||||||
}
|
}
|
||||||
string += path
|
string += path
|
||||||
|
if (type === 's3' && region !== undefined) {
|
||||||
|
string += `#${region}`
|
||||||
|
}
|
||||||
return string
|
return string
|
||||||
}
|
}
|
||||||
|
@ -48,10 +48,12 @@ const data = deepFreeze({
|
|||||||
string: 's3://AKIAS:XSuBupZ0mJlu%2B@s3-us-west-2.amazonaws.com/test-bucket/dir',
|
string: 's3://AKIAS:XSuBupZ0mJlu%2B@s3-us-west-2.amazonaws.com/test-bucket/dir',
|
||||||
object: {
|
object: {
|
||||||
type: 's3',
|
type: 's3',
|
||||||
|
protocol: 'https',
|
||||||
host: 's3-us-west-2.amazonaws.com',
|
host: 's3-us-west-2.amazonaws.com',
|
||||||
path: '/test-bucket/dir',
|
path: '/test-bucket/dir',
|
||||||
username: 'AKIAS',
|
username: 'AKIAS',
|
||||||
password: 'XSuBupZ0mJlu+',
|
password: 'XSuBupZ0mJlu+',
|
||||||
|
region: undefined,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@ -85,6 +87,18 @@ const parseData = deepFreeze({
|
|||||||
password: 'pas:sw@ord',
|
password: 'pas:sw@ord',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
'S3 with http and region': {
|
||||||
|
string: 's3+http://Administrator:password@192.168.100.225/bucket/dir#reg1',
|
||||||
|
object: {
|
||||||
|
type: 's3',
|
||||||
|
host: '192.168.100.225',
|
||||||
|
protocol: 'http',
|
||||||
|
path: '/bucket/dir',
|
||||||
|
region: 'reg1',
|
||||||
|
username: 'Administrator',
|
||||||
|
password: 'password',
|
||||||
|
},
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const formatData = deepFreeze({
|
const formatData = deepFreeze({
|
||||||
|
@ -580,7 +580,9 @@ const messages = {
|
|||||||
remoteSmbPlaceHolderAddressShare: '<address>\\\\<share>',
|
remoteSmbPlaceHolderAddressShare: '<address>\\\\<share>',
|
||||||
remoteSmbPlaceHolderOptions: 'Custom mount options',
|
remoteSmbPlaceHolderOptions: 'Custom mount options',
|
||||||
remoteS3PlaceHolderBucket: 'AWS S3 bucket name',
|
remoteS3PlaceHolderBucket: 'AWS S3 bucket name',
|
||||||
remoteS3PlaceHolderDirectory: 'directory',
|
remoteS3PlaceHolderDirectory: 'Directory',
|
||||||
|
remoteS3Region: 'Region, leave blank for default',
|
||||||
|
remoteS3TooltipProtocol: 'Check if you want HTTP instead of HTTPS',
|
||||||
remotePlaceHolderPassword: 'Password(fill to edit)',
|
remotePlaceHolderPassword: 'Password(fill to edit)',
|
||||||
|
|
||||||
// ------ New Storage -----
|
// ------ New Storage -----
|
||||||
|
@ -3,6 +3,7 @@ import ActionButton from 'action-button'
|
|||||||
import decorate from 'apply-decorators'
|
import decorate from 'apply-decorators'
|
||||||
import Icon from 'icon'
|
import Icon from 'icon'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
import Tooltip from 'tooltip'
|
||||||
import { addSubscriptions, resolveId } from 'utils'
|
import { addSubscriptions, resolveId } from 'utils'
|
||||||
import { alert, confirm } from 'modal'
|
import { alert, confirm } from 'modal'
|
||||||
import { createRemote, editRemote, subscribeRemotes } from 'xo'
|
import { createRemote, editRemote, subscribeRemotes } from 'xo'
|
||||||
@ -11,7 +12,7 @@ import { format } from 'xo-remote-parser'
|
|||||||
import { generateId, linkState } from 'reaclette-utils'
|
import { generateId, linkState } from 'reaclette-utils'
|
||||||
import { injectState, provideState } from 'reaclette'
|
import { injectState, provideState } from 'reaclette'
|
||||||
import { map, some, trimStart } from 'lodash'
|
import { map, some, trimStart } from 'lodash'
|
||||||
import { Password, Number } from 'form'
|
import { Password, Number, Toggle } from 'form'
|
||||||
import { SelectProxy } from 'select-objects'
|
import { SelectProxy } from 'select-objects'
|
||||||
|
|
||||||
const remoteTypes = {
|
const remoteTypes = {
|
||||||
@ -39,6 +40,8 @@ export default decorate([
|
|||||||
username: undefined,
|
username: undefined,
|
||||||
directory: undefined,
|
directory: undefined,
|
||||||
bucket: undefined,
|
bucket: undefined,
|
||||||
|
protocol: undefined,
|
||||||
|
region: undefined,
|
||||||
}),
|
}),
|
||||||
effects: {
|
effects: {
|
||||||
linkState,
|
linkState,
|
||||||
@ -60,6 +63,8 @@ export default decorate([
|
|||||||
proxyId = remote.proxy,
|
proxyId = remote.proxy,
|
||||||
type = remote.type,
|
type = remote.type,
|
||||||
username = remote.username,
|
username = remote.username,
|
||||||
|
protocol = remote.protocol || 'https',
|
||||||
|
region = remote.region,
|
||||||
} = state
|
} = state
|
||||||
let { path = remote.path } = state
|
let { path = remote.path } = state
|
||||||
if (type === 's3') {
|
if (type === 's3') {
|
||||||
@ -76,6 +81,8 @@ export default decorate([
|
|||||||
port: port || undefined,
|
port: port || undefined,
|
||||||
type,
|
type,
|
||||||
username,
|
username,
|
||||||
|
protocol,
|
||||||
|
region,
|
||||||
}),
|
}),
|
||||||
options: options !== '' ? options : null,
|
options: options !== '' ? options : null,
|
||||||
proxy: proxyId,
|
proxy: proxyId,
|
||||||
@ -133,6 +140,9 @@ export default decorate([
|
|||||||
setSecretKey(_, { target: { value } }) {
|
setSecretKey(_, { target: { value } }) {
|
||||||
this.state.password = value
|
this.state.password = value
|
||||||
},
|
},
|
||||||
|
setInsecure(_, value) {
|
||||||
|
this.state.protocol = value ? 'http' : 'https'
|
||||||
|
},
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
formId: generateId,
|
formId: generateId,
|
||||||
@ -149,6 +159,8 @@ export default decorate([
|
|||||||
name = remote.name || '',
|
name = remote.name || '',
|
||||||
options = remote.options || '',
|
options = remote.options || '',
|
||||||
password = remote.password || '',
|
password = remote.password || '',
|
||||||
|
protocol = remote.protocol || 'https',
|
||||||
|
region = remote.region || '',
|
||||||
parsedPath,
|
parsedPath,
|
||||||
path = parsedPath || '',
|
path = parsedPath || '',
|
||||||
parsedBucket = parsedPath != null && parsedPath.split('/')[0],
|
parsedBucket = parsedPath != null && parsedPath.split('/')[0],
|
||||||
@ -326,7 +338,12 @@ export default decorate([
|
|||||||
)}
|
)}
|
||||||
{type === 's3' && (
|
{type === 's3' && (
|
||||||
<fieldset className='form-group form-group'>
|
<fieldset className='form-group form-group'>
|
||||||
<div className='input-group '>
|
<div className='input-group form-group'>
|
||||||
|
<span className='input-group-addon'>
|
||||||
|
<Tooltip content={formatMessage(messages.remoteS3TooltipProtocol)}>
|
||||||
|
<Toggle iconSize={1} onChange={effects.setInsecure} value={protocol === 'http'} />
|
||||||
|
</Tooltip>
|
||||||
|
</span>
|
||||||
<input
|
<input
|
||||||
className='form-control'
|
className='form-control'
|
||||||
name='host'
|
name='host'
|
||||||
@ -338,7 +355,18 @@ export default decorate([
|
|||||||
value={host}
|
value={host}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className='input-group '>
|
<div className='input-group form-group'>
|
||||||
|
<input
|
||||||
|
className='form-control'
|
||||||
|
name='region'
|
||||||
|
onChange={effects.linkState}
|
||||||
|
pattern='[a-z0-9-]+'
|
||||||
|
placeholder={formatMessage(messages.remoteS3Region)}
|
||||||
|
type='text'
|
||||||
|
value={region}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className='input-group form-group'>
|
||||||
<input
|
<input
|
||||||
className='form-control'
|
className='form-control'
|
||||||
name='bucket'
|
name='bucket'
|
||||||
@ -363,7 +391,7 @@ export default decorate([
|
|||||||
value={directory}
|
value={directory}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className='input-group'>
|
<div className='input-group form-group'>
|
||||||
<input
|
<input
|
||||||
className='form-control'
|
className='form-control'
|
||||||
name='username'
|
name='username'
|
||||||
@ -374,14 +402,13 @@ export default decorate([
|
|||||||
value={username}
|
value={username}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className='input-group'>
|
<div className='input-group form-group'>
|
||||||
<input
|
<input
|
||||||
className='form-control'
|
className='form-control'
|
||||||
name='password'
|
name='password'
|
||||||
onChange={effects.setSecretKey}
|
onChange={effects.setSecretKey}
|
||||||
placeholder='Paste secret here to change it'
|
placeholder='Paste secret here to change it'
|
||||||
autoComplete='off'
|
autoComplete='off'
|
||||||
required
|
|
||||||
type='text'
|
type='text'
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
Loading…
Reference in New Issue
Block a user