update built-in objects in the database (#846)

Signed-off-by: Denis barbaron <denis.barbaron@orange.com>
This commit is contained in:
Denis Barbaron
2025-03-07 20:51:25 +01:00
committed by GitHub
parent 9de3828158
commit 4e8a5a1cef
3 changed files with 188 additions and 19 deletions

View File

@@ -202,6 +202,20 @@ You're now ready to start up STF itself:
stf local
```
Later, if you want to change the values of these built-in objects, for example to change the identity of the administrator user, you must follow the below instructions or you are likely to encounter data inconsistency issues:
1. stop the STF server (without stop the RethinkDB database)
2. It is recommended to make a backup of the database (in case of inconsistency problem during migration)
3. set environment variables to new desired values for built-in objects
4. if you change the administrator identity, make sure the user does not exist in the database yet, if it does you need to delete it first via the UI or RestFul API
5. run the `stf migrate` command
* If you get an STF error like `ERR/db:api..` (e.g. you tried to change the name of the current administrator or the new administrator already exists in the database), it means that no changes have been made to the database that remain consistent
* otherwise you get a STF message telling you the built-in objects have been updated successfully
6. Finally if all went well you are now ready to start STF itself:
```bash
stf local
```
After the [webpack](http://webpack.github.io/) build process has finished (which can take a small while) you should have your private STF running on [http://localhost:7100](http://localhost:7100). If you had devices connected before running the command, those devices should now be available for use. If not, you should see what went wrong from your console. Feel free to plug in or unplug any devices at any time.
Note that if you see your device ready to use but without a name or a proper image, we're probably missing the data for that model in [our device database](https://github.com/devicefarmer/stf-device-db). Everything should work fine either way.

View File

@@ -1,5 +1,5 @@
/**
* Copyright © 2019 contains code contributed by Orange SA, authors: Denis Barbaron - Licensed under the Apache license 2.0
* Copyright © 2019-2025 contains code contributed by Orange SA, authors: Denis Barbaron - Licensed under the Apache license 2.0
**/
module.exports.command = 'migrate'
@@ -23,20 +23,23 @@ module.exports.handler = function() {
return new Promise(function(resolve, reject) {
setTimeout(function() {
return dbapi.getGroupByIndex(apiutil.ROOT, 'privilege').then(function(group) {
// signatures of built-in objects are defined
const env = {
STF_ROOT_GROUP_NAME: group ? group.name : 'Common'
, STF_ADMIN_NAME: group ? group.owner.name : 'administrator'
, STF_ADMIN_EMAIL: group ? group.owner.email : 'administrator@fakedomain.com'
}
for (const i in env) {
if (process.env[i]) {
env[i] = process.env[i]
}
}
if (!group) {
const env = {
STF_ROOT_GROUP_NAME: 'Common'
, STF_ADMIN_NAME: 'administrator'
, STF_ADMIN_EMAIL: 'administrator@fakedomain.com'
}
for (const i in env) {
if (process.env[i]) {
env[i] = process.env[i]
}
}
// root group does not exist, so bootstrap is created
return dbapi.createBootStrap(env)
}
return group
// bootstrap is updated with new signatures
return dbapi.updateBootStrap(group, env)
})
.then(function() {
resolve(true)

View File

@@ -14,6 +14,15 @@ const uuid = require('uuid')
const apiutil = require('../util/apiutil')
const Promise = require('bluebird')
const _ = require('lodash')
const logger = require('../util/logger')
const log = logger.createLogger('db:api')
function getDevices() {
return db.run(r.table('devices'))
.then(function(cursor) {
return cursor.toArray()
})
}
dbapi.DuplicateSecondaryIndexError = function DuplicateSecondaryIndexError() {
Error.call(this)
@@ -35,6 +44,156 @@ dbapi.unlockBookingObjects = function() {
])
}
dbapi.updateBootStrap = function(rootGroup, env) {
const rootGroupNameHasChanged = rootGroup.name !== env.STF_ROOT_GROUP_NAME
const adminEmailHasChanged = rootGroup.owner.email !== env.STF_ADMIN_EMAIL
const adminNameHasChanged = rootGroup.owner.name !== env.STF_ADMIN_NAME
function createNewAdminUser() {
if (!adminEmailHasChanged) {
if (adminNameHasChanged) {
log.error('Forbidden (user name cannot be changed)')
return Promise.resolve(false)
}
return Promise.resolve(true)
}
return dbapi.createUser(env.STF_ADMIN_EMAIL,
env.STF_ADMIN_NAME,
'127.0.0.1').then(function(stats) {
if (!stats.inserted) {
log.error('Forbidden (user already exists)')
return false
}
log.info('Created (user name:%s email:%s)'
, env.STF_ADMIN_NAME
, env.STF_ADMIN_EMAIL)
return dbapi.loadUser(rootGroup.owner.email).then(function(oldAdminUser) {
return db.run(r.table('users').get(env.STF_ADMIN_EMAIL).update({
privilege: oldAdminUser.privilege
, groups: oldAdminUser.groups
, settings: oldAdminUser.settings
}))
})
})
.catch(function(err) {
log.error('Failed to create user')
return Promise.reject(err)
})
}
function updateDevicesForMigration() {
return getDevices().then(function(devices) {
return Promise.map(devices, function(device) {
return db.run(r.table('devices').get(device.serial).update({
group: {
name:
r.branch(
r.expr(rootGroupNameHasChanged)
.eq(true)
.and(r.row('group')('id')
.eq(rootGroup.id))
, env.STF_ROOT_GROUP_NAME
, r.row('group')('name'))
, originName:
r.branch(
r.expr(rootGroupNameHasChanged)
.eq(true)
.and(r.row('group')('origin')
.eq(rootGroup.id))
, env.STF_ROOT_GROUP_NAME
, r.row('group')('originName'))
, owner:
r.branch(
r.expr(adminEmailHasChanged)
.eq(true)
.and(r.row('group')('id')
.eq(rootGroup.id))
, {
name: env.STF_ADMIN_NAME
, email: env.STF_ADMIN_EMAIL
}
, r.row('group')('owner'))
}
}))
})
})
}
function updateGroupsForMigration() {
if (rootGroupNameHasChanged && !adminEmailHasChanged) {
return db.run(r.table('groups').get(rootGroup.id).update({
name: env.STF_ROOT_GROUP_NAME
}))
}
return dbapi.getGroups().then(function(groups) {
return Promise.map(groups, function(group) {
return db.run(r.table('groups').get(group.id).update({
name:
r.branch(
r.expr(rootGroupNameHasChanged)
.eq(true)
.and(r.row('id')
.eq(rootGroup.id))
, env.STF_ROOT_GROUP_NAME
, r.row('name'))
, owner:
r.branch(
r.expr(adminEmailHasChanged)
.eq(true)
.and(r.row('owner')('email')
.eq(rootGroup.owner.email))
, {
name: env.STF_ADMIN_NAME
, email: env.STF_ADMIN_EMAIL
}
, r.row('owner'))
, users:
r.branch(
r.expr(adminEmailHasChanged)
.eq(true)
, _.union([env.STF_ADMIN_EMAIL], _.difference(group.users, [rootGroup.owner.email]))
, r.row('users'))
}))
})
})
}
return createNewAdminUser().then(function(success) {
if (!success || !rootGroupNameHasChanged && !adminEmailHasChanged) {
return false
}
return updateGroupsForMigration().then(function() {
return updateDevicesForMigration()
})
.then(function() {
if (adminEmailHasChanged) {
return dbapi.removeUserAccessTokens(rootGroup.owner.email)
}
return true
})
.then(function() {
if (adminEmailHasChanged) {
return dbapi.deleteUser(rootGroup.owner.email)
}
return true
})
})
.then(function(success) {
if (success) {
log.info('Built-in objects have been updated successfully')
}
return success
})
.catch(function(err) {
log.error('Failed to update built-in objects, potential data consistency issue')
return Promise.reject(err)
})
}
dbapi.createBootStrap = function(env) {
const now = Date.now()
@@ -78,13 +237,6 @@ dbapi.createBootStrap = function(env) {
})
}
function getDevices() {
return db.run(r.table('devices'))
.then(function(cursor) {
return cursor.toArray()
})
}
function updateDevicesForMigration(group) {
return getDevices().then(function(devices) {
return Promise.map(devices, function(device) {