feat(backup/s3): add http and region parameters to S3 (#5658)

This commit is contained in:
Nicolas Raynaud 2021-04-28 11:30:23 +02:00 committed by GitHub
parent ffacc0d8d0
commit c219ea06bf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 75 additions and 17 deletions

View File

@ -1,4 +1,5 @@
import execa from 'execa'
import { parse } from 'xo-remote-parser'
import RemoteHandlerLocal from './local'
import RemoteHandlerNfs from './nfs'
@ -20,10 +21,7 @@ try {
}
export const getHandler = (remote, ...rest) => {
// FIXME: should be done in xo-remote-parser.
const type = remote.url.split('://')[0]
const Handler = HANDLERS[type]
const Handler = HANDLERS[parse(remote.url).type]
if (!Handler) {
throw new Error('Unhandled remote type')
}

View File

@ -1,5 +1,6 @@
import aws from '@sullux/aws-sdk'
import assert from 'assert'
import http from 'http'
import { parse } from 'xo-remote-parser'
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 {
constructor(remote, _opts) {
super(remote)
const { host, path, username, password } = parse(remote.url)
// https://www.zenko.io/blog/first-things-first-getting-started-scality-s3-server/
this._s3 = aws({
const { host, path, username, password, protocol, region } = parse(remote.url)
const params = {
accessKeyId: username,
apiVersion: '2006-03-01',
endpoint: host,
@ -28,7 +28,16 @@ export default class S3Handler extends RemoteHandlerAbstract {
httpOptions: {
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)
this._bucket = splitPath.shift()

View File

@ -14,6 +14,7 @@
- [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))
- [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
@ -44,6 +45,7 @@
> In case of conflict, the highest (lowest in previous list) `$version` wins.
- xo-server-perf-alert patch
- xo-remote-parser minor
- @xen-orchestra/fs minor
- @xen-orchestra/xapi patch
- @xen-orchestra/backups minor

View File

@ -37,9 +37,11 @@ export const parse = string => {
object.domain = domain
object.username = username
object.password = password
} else if (type === 's3') {
} else if (type === 's3' || type === 's3+http') {
const parsed = new Url(string)
object.protocol = parsed.protocol === 's3:' ? 'https' : 'http'
object.type = 's3'
object.region = parsed.hash.length === 0 ? undefined : parsed.hash.slice(1) // remove '#'
object.host = parsed.host
object.path = parsed.pathname
object.username = parsed.username
@ -48,7 +50,7 @@ export const parse = string => {
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')
let string = `${type}://`
if (type === 'nfs') {
@ -58,6 +60,7 @@ export const format = ({ type, host, path, port, username, password, domain }) =
string += `${username}:${password}@${domain}\\\\${host}`
}
if (type === 's3') {
string = protocol === 'https' ? 's3://' : 's3+http://'
string += `${username}:${encodeURIComponent(password)}@${host}`
}
path = sanitizePath(path)
@ -68,5 +71,8 @@ export const format = ({ type, host, path, port, username, password, domain }) =
path = `/${path}`
}
string += path
if (type === 's3' && region !== undefined) {
string += `#${region}`
}
return string
}

View File

@ -48,10 +48,12 @@ const data = deepFreeze({
string: 's3://AKIAS:XSuBupZ0mJlu%2B@s3-us-west-2.amazonaws.com/test-bucket/dir',
object: {
type: 's3',
protocol: 'https',
host: 's3-us-west-2.amazonaws.com',
path: '/test-bucket/dir',
username: 'AKIAS',
password: 'XSuBupZ0mJlu+',
region: undefined,
},
},
})
@ -85,6 +87,18 @@ const parseData = deepFreeze({
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({

View File

@ -580,7 +580,9 @@ const messages = {
remoteSmbPlaceHolderAddressShare: '<address>\\\\<share>',
remoteSmbPlaceHolderOptions: 'Custom mount options',
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)',
// ------ New Storage -----

View File

@ -3,6 +3,7 @@ import ActionButton from 'action-button'
import decorate from 'apply-decorators'
import Icon from 'icon'
import React from 'react'
import Tooltip from 'tooltip'
import { addSubscriptions, resolveId } from 'utils'
import { alert, confirm } from 'modal'
import { createRemote, editRemote, subscribeRemotes } from 'xo'
@ -11,7 +12,7 @@ import { format } from 'xo-remote-parser'
import { generateId, linkState } from 'reaclette-utils'
import { injectState, provideState } from 'reaclette'
import { map, some, trimStart } from 'lodash'
import { Password, Number } from 'form'
import { Password, Number, Toggle } from 'form'
import { SelectProxy } from 'select-objects'
const remoteTypes = {
@ -39,6 +40,8 @@ export default decorate([
username: undefined,
directory: undefined,
bucket: undefined,
protocol: undefined,
region: undefined,
}),
effects: {
linkState,
@ -60,6 +63,8 @@ export default decorate([
proxyId = remote.proxy,
type = remote.type,
username = remote.username,
protocol = remote.protocol || 'https',
region = remote.region,
} = state
let { path = remote.path } = state
if (type === 's3') {
@ -76,6 +81,8 @@ export default decorate([
port: port || undefined,
type,
username,
protocol,
region,
}),
options: options !== '' ? options : null,
proxy: proxyId,
@ -133,6 +140,9 @@ export default decorate([
setSecretKey(_, { target: { value } }) {
this.state.password = value
},
setInsecure(_, value) {
this.state.protocol = value ? 'http' : 'https'
},
},
computed: {
formId: generateId,
@ -149,6 +159,8 @@ export default decorate([
name = remote.name || '',
options = remote.options || '',
password = remote.password || '',
protocol = remote.protocol || 'https',
region = remote.region || '',
parsedPath,
path = parsedPath || '',
parsedBucket = parsedPath != null && parsedPath.split('/')[0],
@ -326,7 +338,12 @@ export default decorate([
)}
{type === 's3' && (
<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
className='form-control'
name='host'
@ -338,7 +355,18 @@ export default decorate([
value={host}
/>
</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
className='form-control'
name='bucket'
@ -363,7 +391,7 @@ export default decorate([
value={directory}
/>
</div>
<div className='input-group'>
<div className='input-group form-group'>
<input
className='form-control'
name='username'
@ -374,14 +402,13 @@ export default decorate([
value={username}
/>
</div>
<div className='input-group'>
<div className='input-group form-group'>
<input
className='form-control'
name='password'
onChange={effects.setSecretKey}
placeholder='Paste secret here to change it'
autoComplete='off'
required
type='text'
/>
</div>