Skip to content

Commit

Permalink
feat: add updatestack cli command
Browse files Browse the repository at this point in the history
  • Loading branch information
mvayngrib committed Jul 9, 2018
1 parent c302c37 commit 77c8251
Show file tree
Hide file tree
Showing 10 changed files with 223 additions and 60 deletions.
8 changes: 1 addition & 7 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,7 @@ module.exports = {
"capitalized-comments": "off",
"class-methods-use-this": "warn",
"comma-dangle": "off",
"comma-spacing": [
"error",
{
"after": true,
"before": false
}
],
"comma-spacing": "off",
"comma-style": [
"error",
"last"
Expand Down
2 changes: 1 addition & 1 deletion npm-shrinkwrap.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion src/aws.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ export default function createAWSWrapper ({ env, logger }: {
...conf
})
} else {
if (env.TESTING && !services[lServiceName]) {
if (env.TESTING && !services[lServiceName] && lServiceName !== 'iot') {
// don't pretend to support it as this will result
// in calling the remote service!
return null
Expand Down
1 change: 1 addition & 0 deletions src/in-house-bot/commander.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ export const SUDO_ONLY_COMMANDS = [
'doctor',
'balance',
'reindex',
'updatestack',
// 'encryptbucket',
// 'enablebinary'
]
Expand Down
2 changes: 2 additions & 0 deletions src/in-house-bot/commands/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { command as setenvvar } from './setenvvar'
import { command as doctor } from './doctor'
import { command as balance } from './balance'
import { command as reindex } from './reindex'
import { command as updatestack } from './updatestack'

export {
help,
Expand Down Expand Up @@ -52,4 +53,5 @@ export {
doctor,
balance,
reindex,
updatestack,
}
24 changes: 24 additions & 0 deletions src/in-house-bot/commands/updatestack.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { ICommand, IDeploymentConf } from '../types'

export const command:ICommand = {
name: 'updatestack',
description: 'get a link to update your MyCloud',
examples: [
'/updatestack --template-url "<templateURL>"',
'/updatestack --template-url "<templateURL>" --notification-topics "<topic1,topic2,...>"',
],
exec: async ({ commander, req, ctx, args }) => {
if (!ctx.sudo) throw new Error('forbidden')

const { deployment } = commander
if (!deployment) {
throw new Error('"deployment" plugin not configured. Please add to plugins in bot.json')
}

const { templateUrl, notificationTopics } = args
await deployment.updateOwnStack({
templateUrl,
notificationTopics: notificationTopics.split(',').map(s => s.trim())
})
}
}
177 changes: 136 additions & 41 deletions src/in-house-bot/deployment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,12 @@ const TMP_SNS_TOPIC_TTL = unitToMillis.day
const LAUNCH_MESSAGE = 'Launch your Tradle MyCloud'
const ONLINE_MESSAGE = 'Your Tradle MyCloud is online!'
const CHILD_DEPLOYMENT = 'tradle.cloud.ChildDeployment'
const PARENT_DEPLOYMENT = 'tradle.cloud.ParentDeployment'
const CONFIGURATION = 'tradle.cloud.Configuration'
const AWS_REGION = 'tradle.cloud.AWSRegion'
const TMP_SNS_TOPIC = 'tradle.cloud.TmpSNSTopic'
const UPDATE_REQUEST = 'tradle.cloud.UpdateRequest'
const UPDATE_RESPONSE = 'tradle.cloud.UpdateResponse'
const UPDATE_REQUEST_TTL = 10 * unitToMillis.minute
const DEFAULT_LAUNCH_TEMPLATE_OPTS = {
template: 'action',
Expand Down Expand Up @@ -256,10 +259,10 @@ export class Deployment {
return {
template,
url: utils.getUpdateStackUrl({ stackId, templateUrl: url }),
snsTopic: await this.setupNotificationsForStack({
snsTopic: (await this.setupNotificationsForStack({
id: `${accountId}-${name}`,
type: StackOperationType.update
})
})).topic
}
}

Expand Down Expand Up @@ -298,16 +301,42 @@ export class Deployment {
})
}

public getParentDeployment = async (): Promise<ITradleObject> => {
return await this.bot.db.findOne({
orderBy: {
property: '_time',
desc: true
},
filter: {
EQ: {
[TYPE]: CONFIGURATION,
'childIdentity._permalink': await this.bot.getMyPermalink()
}
}
})
}

public reportLaunch = async ({ org, identity, referrerUrl, deploymentUUID }: {
org: IOrganization
identity: IIdentity
referrerUrl: string
deploymentUUID: string
}) => {
let saveParentDeployment
try {
await utils.runWithTimeout(() => this.bot.friends.load({ url: referrerUrl }), { millis: 10000 })
const friend = await utils.runWithTimeout(
() => this.bot.friends.load({ url: referrerUrl }),
{ millis: 20000 }
)

saveParentDeployment = this.saveParentDeployment({
friend,
apiUrl: referrerUrl,
childIdentity: identity
})
} catch (err) {
this.logger.error('failed to add referring MyCloud as friend', err)
saveParentDeployment = Promise.resolve()
}

const reportLaunchUrl = this.getReportLaunchUrl(referrerUrl)
Expand All @@ -325,6 +354,8 @@ export class Deployment {
Errors.rethrow(err, 'developer')
this.logger.error(`failed to notify referrer at: ${referrerUrl}`, err)
}

await saveParentDeployment
}

public receiveLaunchReport = async (report: ILaunchReportPayload) => {
Expand All @@ -347,9 +378,9 @@ export class Deployment {
})

await this.bot.draft({
type: CHILD_DEPLOYMENT,
resource: childDeployment
})
type: CHILD_DEPLOYMENT,
resource: childDeployment
})
.set({
apiUrl,
identity: friend.identity,
Expand All @@ -374,6 +405,21 @@ export class Deployment {
return resource.toJSON()
}

public saveParentDeployment = async ({ friend, childIdentity, apiUrl }: {
friend: ITradleObject
childIdentity: ITradleObject
apiUrl: string
}) => {
return await this.bot.draft({ type: PARENT_DEPLOYMENT })
.set({
childIdentity,
parentIdentity: friend.identity,
friend,
apiUrl
})
.signAndSave()
}

public notifyConfigurer = async ({ configurer, links }: {
links: IAppLinkSet
configurer: string
Expand Down Expand Up @@ -837,68 +883,89 @@ ${this.genUsageInstructions(links)}`
})
}

public requestUpdate = async ({ friend }: {
friend: ITradleObject
}) => {
const { bot } = this
const { env } = bot
const updateReq = bot.draft({ type: 'tradle.cloud.UpdateRequest' })
public requestUpdate = async () => {
const parent = await this.getParentDeployment()
return this.requestUpdateFromParent(parent)
}

public requestUpdateFromParent = async (parent: ITradleObject) => {
const updateReq = this.createUpdateRequestResource(parent)
await this.bot.send({
to: parent.parentIdentity._permalink,
object: updateReq
})
}

public createUpdateRequestResource = (parent: ITradleObject) => {
if (parent[TYPE] !== PARENT_DEPLOYMENT) {
throw new Errors.InvalidInput(`expected "parent" to be tradle.MyCloudFriend`)
}

const { parentIdentity } = parent
const { env } = this.bot
return this.bot.draft({ type: UPDATE_REQUEST })
.set({
service: env.SERVERLESS_SERVICE_NAME,
stage: env.SERVERLESS_STAGE,
region: env.AWS_REGION,
provider: friend.identity,
provider: parentIdentity,
})
.toJSON()

await bot.send({
to: friend.identity,
object: updateReq
})
}

public handleUpdateResponse = async (updateResponse: ITradleObject) => {
let req: ITradleObject
try {
req = await this.lookupUpdateRequest(updateResponse._author)
} catch (err) {
Errors.ignoreNotFound(err)
this.logger.warn('received stack update response...but no request was made, ignoring', {
from: updateResponse._author,
updateResponse: this.bot.buildStub(updateResponse)
})

throw err
public handleUpdateRequest = async ({ req, from }: {
req: ITradleObject
from: ITradleObject
}) => {
if (req._author !== buildResource.permalink(from)) {
throw new Errors.InvalidAuthor(`expected update request author to be the same identity as "from"`)
}

if (req._time + UPDATE_REQUEST_TTL > Date.now()) {
const msg = 'received update response for expired request, ignoring'
this.logger.warn(msg, {
from: updateResponse._author,
updateResponse: this.bot.buildStub(updateResponse)
})
const pkg = await this.genUpdatePackage({
createdBy: req._author
})

throw new Errors.Expired(msg)
}
const { snsTopic, url } = pkg
await this.bot.send({
to: req._author,
object: await this.bot.draft({ type: UPDATE_RESPONSE })
.set({
templateUrl: url,
notificationTopics: snsTopic,
request: req,
provider: from
})
.sign()
.then(r => r.toJSON())
})

return pkg
}

public handleUpdateResponse = async (updateResponse: ITradleObject) => {
await this._validateUpdateResponse(updateResponse)
const { templateUrl, notificationTopics } = updateResponse
await this.bot.lambdaUtils.invoke({
name: 'cli',
arg: `--template-url "${templateUrl}" --notification-topics "${notificationTopics.join(',')}"`,
// don't wait
arg: `--template-url "${templateUrl}" --notification-topics "${notificationTopics}"`,
// don't wait for this to finish
sync: false
})
}

public lookupUpdateRequest = async (providerPermalink: string) => {
if (!(typeof providerPermalink === 'string' && providerPermalink)) {
throw new Errors.InvalidInput('expected provider permalink')
}

return await this.bot.db.findOne({
orderBy: {
property: '_time',
desc: true
},
filter: {
EQ: {
[TYPE]: 'tradle.cloud.UpdateRequest',
[TYPE]: UPDATE_REQUEST,
'provider._permalink': providerPermalink
}
}
Expand Down Expand Up @@ -926,6 +993,34 @@ ${this.genUsageInstructions(links)}`
logger: bot.logger
})
}

private _validateUpdateResponse = async (updateResponse: ITradleObject) => {
const provider = updateResponse._author

let req: ITradleObject
try {
req = await this.lookupUpdateRequest(provider)
} catch (err) {
Errors.ignoreNotFound(err)
this.logger.warn('received stack update response...but no request was made, ignoring', {
from: provider,
updateResponse: this.bot.buildStub(updateResponse)
})

throw err
}

if (req._time + UPDATE_REQUEST_TTL < Date.now()) {
const msg = 'received update response for expired request, ignoring'
this.logger.warn(msg, {
from: provider,
updateResponse: this.bot.buildStub(updateResponse)
})

throw new Errors.Expired(msg)
}
}

}

export const createDeployment = (opts:DeploymentCtorOpts) => new Deployment(opts)
Expand Down
8 changes: 7 additions & 1 deletion src/in-house-bot/plugins/deployment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
IDeploymentConf,
IDeploymentPluginConf,
ITradleObject,
IPBReq,
Conf
} from '../types'

Expand Down Expand Up @@ -124,7 +125,12 @@ export const createPlugin:CreatePlugin<Deployment> = (components, { conf, logger
return {
api: deployment,
plugin: {
onFormsCollected
onFormsCollected,
'onmessage:tradle.cloud.UpdateRequest': (req: IPBReq) => deployment.handleUpdateRequest({
req: req.payload,
from: req.user
}),
'onmessage:tradle.cloud.UpdateResponse': (req: IPBReq) => deployment.handleUpdateResponse(req.payload),
}
}
}
Expand Down

0 comments on commit 77c8251

Please sign in to comment.