Skip to content

Commit 4e8a5a1

Browse files
authored
update built-in objects in the database (DeviceFarmer#846)
Signed-off-by: Denis barbaron <denis.barbaron@orange.com>
1 parent 9de3828 commit 4e8a5a1

3 files changed

Lines changed: 188 additions & 19 deletions

File tree

README.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,20 @@ You're now ready to start up STF itself:
202202
stf local
203203
```
204204

205+
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:
206+
1. stop the STF server (without stop the RethinkDB database)
207+
2. It is recommended to make a backup of the database (in case of inconsistency problem during migration)
208+
3. set environment variables to new desired values for built-in objects
209+
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
210+
5. run the `stf migrate` command
211+
* 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
212+
* otherwise you get a STF message telling you the built-in objects have been updated successfully
213+
6. Finally if all went well you are now ready to start STF itself:
214+
215+
```bash
216+
stf local
217+
```
218+
205219
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.
206220

207221
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.

lib/cli/migrate/index.js

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/**
2-
* Copyright © 2019 contains code contributed by Orange SA, authors: Denis Barbaron - Licensed under the Apache license 2.0
2+
* Copyright © 2019-2025 contains code contributed by Orange SA, authors: Denis Barbaron - Licensed under the Apache license 2.0
33
**/
44

55
module.exports.command = 'migrate'
@@ -23,20 +23,23 @@ module.exports.handler = function() {
2323
return new Promise(function(resolve, reject) {
2424
setTimeout(function() {
2525
return dbapi.getGroupByIndex(apiutil.ROOT, 'privilege').then(function(group) {
26-
if (!group) {
27-
const env = {
28-
STF_ROOT_GROUP_NAME: 'Common'
29-
, STF_ADMIN_NAME: 'administrator'
30-
, STF_ADMIN_EMAIL: 'administrator@fakedomain.com'
31-
}
32-
for (const i in env) {
33-
if (process.env[i]) {
34-
env[i] = process.env[i]
35-
}
26+
// signatures of built-in objects are defined
27+
const env = {
28+
STF_ROOT_GROUP_NAME: group ? group.name : 'Common'
29+
, STF_ADMIN_NAME: group ? group.owner.name : 'administrator'
30+
, STF_ADMIN_EMAIL: group ? group.owner.email : 'administrator@fakedomain.com'
31+
}
32+
for (const i in env) {
33+
if (process.env[i]) {
34+
env[i] = process.env[i]
3635
}
36+
}
37+
if (!group) {
38+
// root group does not exist, so bootstrap is created
3739
return dbapi.createBootStrap(env)
3840
}
39-
return group
41+
// bootstrap is updated with new signatures
42+
return dbapi.updateBootStrap(group, env)
4043
})
4144
.then(function() {
4245
resolve(true)

lib/db/api.js

Lines changed: 159 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,15 @@ const uuid = require('uuid')
1414
const apiutil = require('../util/apiutil')
1515
const Promise = require('bluebird')
1616
const _ = require('lodash')
17+
const logger = require('../util/logger')
18+
const log = logger.createLogger('db:api')
19+
20+
function getDevices() {
21+
return db.run(r.table('devices'))
22+
.then(function(cursor) {
23+
return cursor.toArray()
24+
})
25+
}
1726

1827
dbapi.DuplicateSecondaryIndexError = function DuplicateSecondaryIndexError() {
1928
Error.call(this)
@@ -35,6 +44,156 @@ dbapi.unlockBookingObjects = function() {
3544
])
3645
}
3746

47+
dbapi.updateBootStrap = function(rootGroup, env) {
48+
const rootGroupNameHasChanged = rootGroup.name !== env.STF_ROOT_GROUP_NAME
49+
const adminEmailHasChanged = rootGroup.owner.email !== env.STF_ADMIN_EMAIL
50+
const adminNameHasChanged = rootGroup.owner.name !== env.STF_ADMIN_NAME
51+
52+
function createNewAdminUser() {
53+
if (!adminEmailHasChanged) {
54+
if (adminNameHasChanged) {
55+
log.error('Forbidden (user name cannot be changed)')
56+
return Promise.resolve(false)
57+
}
58+
return Promise.resolve(true)
59+
}
60+
61+
return dbapi.createUser(env.STF_ADMIN_EMAIL,
62+
env.STF_ADMIN_NAME,
63+
'127.0.0.1').then(function(stats) {
64+
if (!stats.inserted) {
65+
log.error('Forbidden (user already exists)')
66+
return false
67+
}
68+
log.info('Created (user name:%s email:%s)'
69+
, env.STF_ADMIN_NAME
70+
, env.STF_ADMIN_EMAIL)
71+
72+
return dbapi.loadUser(rootGroup.owner.email).then(function(oldAdminUser) {
73+
return db.run(r.table('users').get(env.STF_ADMIN_EMAIL).update({
74+
privilege: oldAdminUser.privilege
75+
, groups: oldAdminUser.groups
76+
, settings: oldAdminUser.settings
77+
}))
78+
})
79+
})
80+
.catch(function(err) {
81+
log.error('Failed to create user')
82+
return Promise.reject(err)
83+
})
84+
}
85+
86+
function updateDevicesForMigration() {
87+
return getDevices().then(function(devices) {
88+
return Promise.map(devices, function(device) {
89+
return db.run(r.table('devices').get(device.serial).update({
90+
group: {
91+
name:
92+
r.branch(
93+
r.expr(rootGroupNameHasChanged)
94+
.eq(true)
95+
.and(r.row('group')('id')
96+
.eq(rootGroup.id))
97+
, env.STF_ROOT_GROUP_NAME
98+
, r.row('group')('name'))
99+
, originName:
100+
r.branch(
101+
r.expr(rootGroupNameHasChanged)
102+
.eq(true)
103+
.and(r.row('group')('origin')
104+
.eq(rootGroup.id))
105+
, env.STF_ROOT_GROUP_NAME
106+
, r.row('group')('originName'))
107+
, owner:
108+
r.branch(
109+
r.expr(adminEmailHasChanged)
110+
.eq(true)
111+
.and(r.row('group')('id')
112+
.eq(rootGroup.id))
113+
, {
114+
name: env.STF_ADMIN_NAME
115+
, email: env.STF_ADMIN_EMAIL
116+
}
117+
, r.row('group')('owner'))
118+
}
119+
}))
120+
})
121+
})
122+
}
123+
124+
function updateGroupsForMigration() {
125+
if (rootGroupNameHasChanged && !adminEmailHasChanged) {
126+
return db.run(r.table('groups').get(rootGroup.id).update({
127+
name: env.STF_ROOT_GROUP_NAME
128+
}))
129+
}
130+
131+
return dbapi.getGroups().then(function(groups) {
132+
return Promise.map(groups, function(group) {
133+
return db.run(r.table('groups').get(group.id).update({
134+
name:
135+
r.branch(
136+
r.expr(rootGroupNameHasChanged)
137+
.eq(true)
138+
.and(r.row('id')
139+
.eq(rootGroup.id))
140+
, env.STF_ROOT_GROUP_NAME
141+
, r.row('name'))
142+
, owner:
143+
r.branch(
144+
r.expr(adminEmailHasChanged)
145+
.eq(true)
146+
.and(r.row('owner')('email')
147+
.eq(rootGroup.owner.email))
148+
, {
149+
name: env.STF_ADMIN_NAME
150+
, email: env.STF_ADMIN_EMAIL
151+
}
152+
, r.row('owner'))
153+
, users:
154+
r.branch(
155+
r.expr(adminEmailHasChanged)
156+
.eq(true)
157+
, _.union([env.STF_ADMIN_EMAIL], _.difference(group.users, [rootGroup.owner.email]))
158+
, r.row('users'))
159+
}))
160+
})
161+
})
162+
}
163+
164+
return createNewAdminUser().then(function(success) {
165+
if (!success || !rootGroupNameHasChanged && !adminEmailHasChanged) {
166+
return false
167+
}
168+
169+
return updateGroupsForMigration().then(function() {
170+
return updateDevicesForMigration()
171+
})
172+
.then(function() {
173+
if (adminEmailHasChanged) {
174+
return dbapi.removeUserAccessTokens(rootGroup.owner.email)
175+
}
176+
return true
177+
})
178+
.then(function() {
179+
if (adminEmailHasChanged) {
180+
return dbapi.deleteUser(rootGroup.owner.email)
181+
}
182+
return true
183+
})
184+
})
185+
.then(function(success) {
186+
if (success) {
187+
log.info('Built-in objects have been updated successfully')
188+
}
189+
return success
190+
})
191+
.catch(function(err) {
192+
log.error('Failed to update built-in objects, potential data consistency issue')
193+
return Promise.reject(err)
194+
})
195+
}
196+
38197
dbapi.createBootStrap = function(env) {
39198
const now = Date.now()
40199

@@ -78,13 +237,6 @@ dbapi.createBootStrap = function(env) {
78237
})
79238
}
80239

81-
function getDevices() {
82-
return db.run(r.table('devices'))
83-
.then(function(cursor) {
84-
return cursor.toArray()
85-
})
86-
}
87-
88240
function updateDevicesForMigration(group) {
89241
return getDevices().then(function(devices) {
90242
return Promise.map(devices, function(device) {

0 commit comments

Comments
 (0)