Adding integration with key vault

This commit is contained in:
mati108@gmail.com committed 2019-02-13 19:43:20 -03:00
1 parent 8c4377b197
commit 84ed1089ce
21 files changed
+209 -61

No files matched your search

+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@superalgos/operations-client",
"version": "0.0.2",
"version": "0.0.13",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@superalgos/operations-client",
"version": "0.0.13",
"version": "0.0.14",
"description": "Superalgos Operations client-side web app",
"main": "src/index.js",
"dependencies": {
+3 -1
View File
@@ -1,7 +1,9 @@
import clones from './clones';
import teams from './teams';
import keys from './keys';
export {
clones,
teams
teams,
keys
};
+15
View File
@@ -0,0 +1,15 @@
import gql from 'graphql-tag';
export const GET_ALL_KEYS = gql`
query {
keyVault_AvailableKeys {
id
key
description
}
}
`
export default {
GET_ALL_KEYS
};
+1
View File
@@ -30,5 +30,6 @@ export const clone = gql`
botAvatar
teamAvatar
processName
keyId
}
`;
-1
View File
@@ -3,7 +3,6 @@ const availableMonths = ["January", "February", "March", "April", "May", "June",
const tradingStartModes ={
live: "Live",
competition: "Competition",
backtest: "Backtest"
}
+4 -4
View File
@@ -12,10 +12,10 @@ import Home from './views/Home'
const App = () => (
<MuiPickersUtilsProvider utils={LuxonUtils}>
<Switch>
<Route exact path='/operations/' component={Home} />
<Route path='/operations/browse' component={BrowseClones} />
<Route path='/operations/history' component={HistoryClones} />
<Route path='/operations/add' component={AddClone} />
<Route exact path='/clones/' component={Home} />
<Route path='/clones/browse' component={BrowseClones} />
<Route path='/clones/history' component={HistoryClones} />
<Route path='/clones/add' component={AddClone} />
</Switch>
</MuiPickersUtilsProvider>
)
+71 -11
View File
@@ -1,6 +1,6 @@
import React, { Component } from 'react'
import { graphql, compose, Query } from 'react-apollo'
import { clones, teams } from '../../../GraphQL/Calls'
import { clones, teams, keys } from '../../../GraphQL/Calls'
import { DateTime } from 'luxon';
import TopBar from '../../BannerTopBar'
@@ -45,6 +45,7 @@ class AddClone extends Component {
runAsTeam: false,
teams: [],
teamId: '',
keyId: '',
// Indicator Bot
startYear: 2019,
@@ -60,7 +61,8 @@ class AddClone extends Component {
isNewCloneConfirmationOpen: false,
serverResponse: '',
serverError: false,
processNameError: false
processNameError: false,
keyIdError: false
}
}
@@ -230,6 +232,60 @@ class AddClone extends Component {
</React.Fragment>
}
{ this.state.mode == "live" &&
<React.Fragment>
<Typography className={classes.typography} variant='subtitle1' align='justify'>
Select one of your available Exchange API Keys to be used by this clone.
</Typography>
<Query
query={keys.GET_ALL_KEYS}
>
{({ loading, error, data }) => {
if (loading) return (
<TextField
select
label="Exchange API Key"
className={classNames(classes.margin, classes.textField, classes.form)}
value={this.state.keyId}
onChange={(e)=> this.setState({keyId:e.target.value})}
onBlur={(e)=>this.setState({keyIdError:false})}
error={this.state.keyIdError}
fullWidth
>
<MenuItem key='1' value='Loading keys...'>
'Loading keys...'
</MenuItem>
</TextField>
);
if (error) return `Error! ${error.message}`;
const list = data.keyVault_AvailableKeys.map((key) => (
<MenuItem key={key.id} value={key.id}>
{key.key}
</MenuItem>
));
return (
<TextField
select
label="Exchange API Key"
className={classNames(classes.margin, classes.textField, classes.form)}
value={this.state.keyId}
onChange={(e)=> this.setState({keyId:e.target.value})}
onBlur={(e)=>this.setState({keyIdError:false})}
error={this.state.keyIdError}
fullWidth
>
{ list }
</TextField>
);
}}
</Query>
</React.Fragment>
}
<Typography className={classes.typography} variant='subtitle1' align='justify'>
If Run as Team is selected, the clone will be taken from the
team respository instead of your own team member folder.
@@ -399,7 +455,8 @@ class AddClone extends Component {
</DialogContentText>
</DialogContent>
<DialogActions>
<Button onClick={this.handleNewCloneConfirmationClose} color="primary" autoFocus>
<Button onClick={this.handleNewCloneConfirmationClose}
variant='contained' color='secondary' autoFocus>
Ok
</Button>
</DialogActions>
@@ -431,7 +488,7 @@ class AddClone extends Component {
let serverResponse = await this.createCloneOnServer()
error = serverResponse.errors || !isDefined(serverResponse.data.operations_AddClone)
if(error){
this.state.serverResponse = serverResponse.errors[0].message
this.state.serverResponse = serverResponse.errors[0].message || serverResponse.errors[0]
this.state.serverError = true
}else{
this.state.serverResponse = "The new clone was sucessfully created."
@@ -453,15 +510,16 @@ class AddClone extends Component {
}
}
if(this.state.mode === "backtest"){
variables.clone.beginDatetime = this.state.beginDatetime.valueOf() / 1000|0
variables.clone.endDatetime = this.state.endDatetime.valueOf() / 1000|0
variables.clone.waitTime = this.state.waitTime
}
if(this.state.selectedBot.kind === "TRADER"){
variables.clone.runAsTeam = this.state.runAsTeam
variables.clone.processName = 'Trading-Process'
if(this.state.mode === "backtest"){
variables.clone.beginDatetime = this.state.beginDatetime.valueOf() / 1000|0
variables.clone.endDatetime = this.state.endDatetime.valueOf() / 1000|0
variables.clone.waitTime = this.state.waitTime
} else {
variables.clone.keyId = this.state.keyId
}
} else {
variables.clone.processName = this.state.processName
variables.clone.startYear = this.state.startYear
@@ -508,6 +566,7 @@ class AddClone extends Component {
processName: '',
teams: [],
teamId: '',
keyId: '',
//Error handlers
nameError: false,
@@ -517,7 +576,8 @@ class AddClone extends Component {
isNewCloneConfirmationOpen: false,
serverResponse: '',
serverError: false,
processNameError: false
processNameError: false,
keyIdError: false
})
};
+29
View File
@@ -2,3 +2,32 @@
## AA Operations Server
Server side functions support the business rules for the module.
## Available Methods
# Active clones List
- List active personal clones
- List active team clones (future release)
# History clone List
- List history personal clones
- List history team clones (future release)
# Get clone by id
- Get existing clones corresponding to the clone ids passed by parameter and returns:
- Clone id
- Team id
- Bot id
# Add new clones:
Receive a list of clones, create each of them and return the newly created clone.
A clone can be created:
- As a personal clone (run from the personal user folder)
- As a team clone (on competitions, run from the team folder)
Who can create each type:
- Personal clones are managed by the user who creates it, not visible for any other user.
- Team clones are managed by any ADMIN type team member (on the team member role attribute),
and all team admins will see it listed.
- Events Module:
At enroll time, the clone is created and the authId is sent
-1
View File
@@ -53,6 +53,5 @@ export class KubernateError extends Error {
message = 'OperationsModule Kubernate Error. ' + this.message
constructor(message){
super(message)
logger.error(this.stack)
}
}
+28
View File
@@ -0,0 +1,28 @@
import logger from '../config/logger'
import axios from 'axios'
const authorizeClon = (authorization, keyId, cloneId, releaseClon) => {
logger.debug('authorizeClon -> Entering function.')
return axios({
url: process.env.GATEWAY_ENDPOINT,
method: 'post',
data: {
query: `
mutation keyVault_AuthorizeClone($keyId: String!, $cloneId: String!, $releaseClon: Boolean!){
keyVault_AuthorizeClone(keyId: $keyId, cloneId: $cloneId, releaseClon:$releaseClon)
}
`,
variables: {
keyId: keyId,
cloneId: cloneId,
releaseClon: releaseClon
}
},
headers: {
authorization: authorization
}
})
}
export default authorizeClon
+2 -2
View File
@@ -1,8 +1,8 @@
import logger from '../config/logger'
import axios from 'axios'
const teamQuery = async(authorization, teamId) => {
logger.debug('GraphQL retrieving team information. %s', teamId)
const teamQuery = (authorization, teamId) => {
logger.debug('teamQuery -> Entering function')
return axios({
url: process.env.GATEWAY_ENDPOINT,
+5
View File
@@ -104,6 +104,11 @@ const createClone = async (clone) => {
"name": "WAIT_TIME",
"value": clone.waitTime.toString()
})
} else{
env.push({
"name": "KEY_ID",
"value": clone.keyId
})
}
} else if(clone.botType === INDICATOR || clone.botType === EXTRACTION ){
if(clone.mode !== NO_TIME){
+1
View File
@@ -23,6 +23,7 @@ const cloneSchema = new Schema({
type: String,
required: true,
},
keyId: String,
mode: {
type: String,
enum: CloneModeEnum,
+5 -5
View File
@@ -2,8 +2,8 @@ import logger from '../config/logger'
import { isDefined } from '../config/utils'
import { AutorizationError } from '../errors'
const cloneDetails = async(authId, team, clone) => {
logger.debug('Retrieving clone details.')
const cloneDetails = (authId, team, clone) => {
logger.debug('cloneDetails -> Entering function.')
if(!(authId === (process.env.AUTH_CLIENT_ID + '@clients') || isAuthorized(team, authId))){
logger.debug('User %s is not authorized to manage team %s.', authId, team.slug )
@@ -22,12 +22,12 @@ const cloneDetails = async(authId, team, clone) => {
clone.teamAvatar = team.profile.avatar
clone.userLoggedIn = team.members[0].member.alias //TODO change by context info if possible
logger.debug('Retrieving clone details complete.')
logger.debug('cloneDetails -> Retrieving clone details complete.')
return clone
};
function getSelectedBot(team, selectedBotId){
logger.debug('Getting selected bot.')
logger.debug('cloneDetails -> Getting selected bot.')
if(isDefined(team)){
for (var i = 0; i < team.fb.length; i++) {
if(team.fb[i].id === selectedBotId){
@@ -38,7 +38,7 @@ function getSelectedBot(team, selectedBotId){
}
function isAuthorized(team, authId){
logger.debug('Checking if the user is authorized by the teams module.')
logger.debug('cloneDetails -> Checking if the user is authorized by the teams module.')
let authorized = false
if(isDefined(team)){
for (var i = 0; i < team.members.length; i++) {
+34 -27
View File
@@ -1,23 +1,26 @@
import { CloneInputType } from '../types/input';
import { CloneType } from '../types';
import { Clone } from '../../models';
import { CloneModeEnum } from '../../enums/CloneMode';
import { BotTypesEnum } from '../../enums/BotTypes';
import { CloneInputType } from '../types/input'
import { CloneType } from '../types'
import { Clone } from '../../models'
import { CloneModeEnum } from '../../enums/CloneMode'
import { BotTypesEnum } from '../../enums/BotTypes'
import teamQuery from '../../graphQLCalls/teamQuery'
import authorizeClone from '../../graphQLCalls/authorizeClone'
import cloneDetails from '../cloneDetails'
import {
AuthentificationError,
WrongArgumentsError,
OperationsError
OperationsError,
AutorizationError
} from '../../errors'
import logger from '../../config/logger'
import createKubernetesClone from '../../kubernetes/createClone'
import { isDefined } from '../../config/utils'
import { LIVE } from '../../enums/CloneMode'
const args = {
clone: {type: CloneInputType }
clone: { type: CloneInputType }
}
const resolve = async(parent, { clone }, context) => {
@@ -27,40 +30,44 @@ const resolve = async(parent, { clone }, context) => {
throw new AuthentificationError()
}
if(!Object.values(CloneModeEnum).includes(clone.mode)){
if (!Object.values(CloneModeEnum).includes(clone.mode)) {
throw new WrongArgumentsError('The mode selected is not valid.')
}
if(!Object.values(BotTypesEnum).includes(clone.botType)){
if (!Object.values(BotTypesEnum).includes(clone.botType)) {
throw new WrongArgumentsError('The bot type selected is not valid.')
}
try{
if (clone.mode === LIVE && !isDefined(clone.keyId)) {
throw new WrongArgumentsError('The key was not provided to run the clone in Live mode.')
}
try {
let team = await teamQuery(context.authorization, clone.teamId)
clone = await cloneDetails(context.userId, team.data.data.teams_TeamById, clone)
clone.createDatetime = new Date().valueOf() / 1000|0
clone.createDatetime = new Date().valueOf() / 1000 | 0
clone.active = true
logger.debug('addClone -> Creating a new clone.')
let newClone = new Clone(clone)
newClone.authId = context.userId
clone.id = newClone._id
await createKubernetesClone(clone)
return new Promise((resolve, reject) => {
newClone.save((err) => {
if (err){
logger.error('addClone -> Error: %s', err.stack)
reject('There has been an error storing the clone.')
}
else {
//TODO transaction on database
logger.debug('addClone -> Save clone sucessful.')
resolve(newClone)
}
})
})
} catch(err) {
logger.debug('addClone -> Creating the clone on the Database.')
let savedClone = await newClone.save()
if (clone.mode === LIVE) {
logger.debug('addClone -> Authorizing clone to use the key.')
let response = await authorizeClone(context.authorization, clone.keyId, clone.id, false)
logger.debug('addClone -> Authorizing clone to use the key.')
if (response.data.data.keyVault_AuthorizeClone === clone.keyId) {
await createKubernetesClone(clone)
} else {
throw new AutorizationError()
}
}
logger.debug('addClone -> Clone created sucessfully.')
return savedClone
} catch (err) {
logger.error('addClone -> Error: %s', err.stack)
throw new OperationsError('There has been an error creating the clone.')
}
@@ -40,6 +40,9 @@ const resolve = async (parent, { id }, context) => {
let team = await teamQuery(context.authorization, clone.teamId)
clone = await cloneDetails(context.userId, team.data.data.teams_TeamById, clone)
logger.debug('removeClone -> Release the clone key.')
await authorizeClone(context.authorization, clone.keyId, clone.id, true)
logger.debug('removeClone -> Removing Clone from Kubernates.')
await removeKuberneteClone(clone)
@@ -13,6 +13,7 @@ import {
import { Clone } from '../../models'
import logger from '../../config/logger'
import teamQuery from '../../graphQLCalls/teamQuery'
import authorizeClone from '../../graphQLCalls/authorizeClone'
import cloneDetails from '../cloneDetails'
import removeKuberneteClone from '../../kubernetes/removeClone'
import { isDefined } from '../../config/utils'
@@ -55,9 +56,6 @@ const resolve = async (parent,
let team = await teamQuery(context.authorization, clone.teamId)
clone = await cloneDetails(context.userId, team.data.data.teams_TeamById, clone)
logger.debug('UpdateExecutionSummary -> Removing Clone from Kubernates.')
await removeKuberneteClone(clone)
const query = { _id: id }
const options = { new: true }
const update = {
+1 -2
View File
@@ -18,7 +18,7 @@ const args = {
}
const resolve = async(parent, { cloneIdList }, context) => {
logger.debug('Get Clones -> Entering Fuction. %j', cloneIdList)
logger.debug('Get Clones -> Entering Fuction.')
try{
let clones = []
for (var i = 0; i < cloneIdList.length; i++) {
@@ -44,7 +44,6 @@ const resolve = async(parent, { cloneIdList }, context) => {
id: cloneIdList[i]
})
}
}
return clones
+2 -1
View File
@@ -37,7 +37,8 @@ const CloneType = new GraphQLObjectType({
teamName: {type: GraphQLString},
botName: {type: GraphQLString},
botAvatar: {type: GraphQLString},
teamAvatar: {type: GraphQLString}
teamAvatar: {type: GraphQLString},
keyId: {type: GraphQLString}
})
})
export default CloneType
+2 -1
View File
@@ -23,7 +23,8 @@ const Type = new GraphQLInputObjectType({
month: { type: GraphQLInt },
interval: { type: GraphQLInt },
processName: { type: new GraphQLNonNull(GraphQLString) },
botType: { type: new GraphQLNonNull(GraphQLString) }
botType: { type: new GraphQLNonNull(GraphQLString) },
keyId: { type: GraphQLString }
})
});