Skip to content
This repository was archived by the owner on Feb 12, 2024. It is now read-only.

Commit 05824f7

Browse files
committed
feat: config profile
1 parent 6c72e5c commit 05824f7

File tree

8 files changed

+266
-3
lines changed

8 files changed

+266
-3
lines changed

package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@
7575
"execa": "^1.0.0",
7676
"form-data": "^2.3.3",
7777
"hat": "0.0.3",
78-
"interface-ipfs-core": "~0.104.0",
78+
"interface-ipfs-core": "ipfs/interface-js-ipfs-core#feat/profiles",
7979
"ipfsd-ctl": "~0.42.0",
8080
"libp2p-websocket-star": "~0.10.2",
8181
"ncp": "^2.0.0",
@@ -120,7 +120,7 @@
120120
"ipfs-bitswap": "~0.24.1",
121121
"ipfs-block": "~0.8.1",
122122
"ipfs-block-service": "~0.15.1",
123-
"ipfs-http-client": "^32.0.0",
123+
"ipfs-http-client": "ipfs/js-ipfs-http-client#feat/profile",
124124
"ipfs-http-response": "~0.3.0",
125125
"ipfs-mfs": "~0.11.4",
126126
"ipfs-multipart": "~0.1.0",
@@ -142,6 +142,7 @@
142142
"is-pull-stream": "~0.0.0",
143143
"is-stream": "^2.0.0",
144144
"iso-url": "~0.4.6",
145+
"jsondiffpatch": "~0.3.11",
145146
"just-flatten-it": "^2.1.0",
146147
"just-safe-set": "^2.1.0",
147148
"kind-of": "^6.0.2",

src/cli/commands/config/profile.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
'use strict'
2+
3+
module.exports = {
4+
command: 'profile <command>',
5+
6+
description: 'Interact with config profiles.',
7+
8+
builder (yargs) {
9+
return yargs
10+
.commandDir('profile')
11+
},
12+
13+
handler (argv) {
14+
}
15+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
'use strict'
2+
3+
const JSONDiff = require('jsondiffpatch')
4+
5+
module.exports = {
6+
command: 'apply <profile>',
7+
8+
describe: 'Apply profile to config',
9+
10+
builder: {
11+
'dry-run': {
12+
type: 'boolean',
13+
describe: 'print difference between the current config and the config that would be generated.'
14+
}
15+
},
16+
17+
handler (argv) {
18+
argv.resolve((async () => {
19+
const ipfs = await argv.getIpfs()
20+
const diff = await ipfs.config.profile(argv.profile, { dryRun: argv.dryRun })
21+
const delta = JSONDiff.diff(diff.oldCfg, diff.newCfg)
22+
return JSONDiff.formatters.console.format(delta, diff.oldCfg)
23+
})())
24+
}
25+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
'use strict'
2+
3+
const { profiles } = require('../../../../core/components/config')
4+
5+
module.exports = {
6+
command: 'ls',
7+
8+
describe: 'List available config profiles',
9+
10+
builder: {},
11+
12+
handler (argv) {
13+
argv.resolve((async () => {
14+
return profiles.map(p => p.name + ':\n ' + p.description).join('\n')
15+
})())
16+
}
17+
}

src/core/components/config.js

Lines changed: 90 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
'use strict'
22

33
const promisify = require('promisify-es6')
4+
const defaultConfig = require('../runtime/config-nodejs.js')()
45

56
module.exports = function config (self) {
67
return {
@@ -17,6 +18,94 @@ module.exports = function config (self) {
1718
}),
1819
replace: promisify((config, callback) => {
1920
self._repo.config.set(config, callback)
20-
})
21+
}),
22+
profile: promisify(applyProfile)
23+
}
24+
25+
async function applyProfile (profileName, opts, callback) {
26+
if (typeof opts === 'function') {
27+
callback = opts
28+
opts = {}
29+
}
30+
const { dryRun } = opts
31+
32+
const profile = profiles.find(p => p.name === profileName)
33+
if (!profile) {
34+
return callback(new Error(`No profile with name '${profileName}' exists`))
35+
}
36+
37+
try {
38+
const oldCfg = await self.config.get()
39+
const newCfg = JSON.parse(JSON.stringify(oldCfg)) // clone
40+
profile.transform(newCfg)
41+
if (!dryRun) {
42+
await self.config.replace(newCfg)
43+
}
44+
45+
// Scrub private key from output
46+
delete oldCfg.Identity.PrivKey
47+
delete newCfg.Identity.PrivKey
48+
49+
callback(null, { oldCfg, newCfg })
50+
} catch (err) {
51+
callback(new Error(`Could not apply profile '${profileName}' to config: ${err.message}`))
52+
}
2153
}
2254
}
55+
56+
const profiles = [{
57+
name: 'server',
58+
description: 'Disables local host discovery - recommended when running IPFS on machines with public IPv4 addresses.',
59+
transform: (config) => {
60+
config.Discovery.MDNS.Enabled = false
61+
config.Discovery.webRTCStar.Enabled = false
62+
}
63+
}, {
64+
name: 'local-discovery',
65+
description: 'Enables local host discovery - inverse of "server" profile.',
66+
transform: (config) => {
67+
config.Discovery.MDNS.Enabled = true
68+
config.Discovery.webRTCStar.Enabled = true
69+
}
70+
}, {
71+
name: 'test',
72+
description: 'Reduces external interference of IPFS daemon - for running the daemon in test environments.',
73+
transform: (config) => {
74+
config.Addresses.API = defaultConfig.Addresses.API ? '/ip4/127.0.0.1/tcp/0' : ''
75+
config.Addresses.Gateway = defaultConfig.Addresses.Gateway ? '/ip4/127.0.0.1/tcp/0' : ''
76+
config.Addresses.Swarm = defaultConfig.Addresses.Swarm.length ? ['/ip4/127.0.0.1/tcp/0'] : []
77+
config.Bootstrap = []
78+
config.Discovery.MDNS.Enabled = false
79+
config.Discovery.webRTCStar.Enabled = false
80+
}
81+
}, {
82+
name: 'default-networking',
83+
description: 'Restores default network settings - inverse of "test" profile.',
84+
transform: (config) => {
85+
console.log('applying default-networking')
86+
console.log('setting to', defaultConfig.Addresses)
87+
config.Addresses.API = defaultConfig.Addresses.API
88+
config.Addresses.Gateway = defaultConfig.Addresses.Gateway
89+
config.Addresses.Swarm = defaultConfig.Addresses.Swarm
90+
config.Bootstrap = defaultConfig.Bootstrap
91+
config.Discovery.MDNS.Enabled = defaultConfig.Discovery.MDNS.Enabled
92+
config.Discovery.webRTCStar.Enabled = defaultConfig.Discovery.webRTCStar.Enabled
93+
}
94+
}, {
95+
name: 'lowpower',
96+
description: 'Reduces daemon overhead on the system - recommended for low power systems.',
97+
transform: (config) => {
98+
config.Swarm = config.Swarm || {}
99+
config.Swarm.ConnMgr = config.Swarm.ConnMgr || {}
100+
config.Swarm.ConnMgr.LowWater = 20
101+
config.Swarm.ConnMgr.HighWater = 40
102+
}
103+
}, {
104+
name: 'default-power',
105+
description: 'Inverse of "lowpower" profile.',
106+
transform: (config) => {
107+
config.Swarm = defaultConfig.Swarm
108+
}
109+
}]
110+
111+
module.exports.profiles = profiles

src/http/api/resources/config.js

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ const log = debug('ipfs:http-api:config')
77
log.error = debug('ipfs:http-api:config:error')
88
const multipart = require('ipfs-multipart')
99
const Boom = require('boom')
10+
const Joi = require('@hapi/joi')
11+
const { profiles } = require('../../../core/components/config')
1012

1113
exports.getOrSet = {
1214
// pre request handler that parses the args and returns `key` & `value` which are assigned to `request.pre.args`
@@ -160,3 +162,39 @@ exports.replace = {
160162
return h.response()
161163
}
162164
}
165+
166+
exports.profile = {
167+
validate: {
168+
query: Joi.object().keys({
169+
'dry-run': Joi.boolean().default(false)
170+
}).unknown()
171+
},
172+
173+
// pre request handler that parses the args and returns `profile` which is assigned to `request.pre.args`
174+
parseArgs: async function (request, h) {
175+
if (!request.query.arg) {
176+
throw Boom.badRequest("Argument 'profile' is required")
177+
}
178+
179+
if (!profiles.find(p => p.name === request.query.arg)) {
180+
throw Boom.badRequest("Argument 'profile' is not a valid profile name")
181+
}
182+
183+
return { profile: request.query.arg }
184+
},
185+
186+
handler: async function (request, h) {
187+
const { ipfs } = request.server.app
188+
const { profile } = request.pre.args
189+
const dryRun = request.query['dry-run']
190+
191+
let diff
192+
try {
193+
diff = await ipfs.config.profile(profile, { dryRun })
194+
} catch (err) {
195+
throw Boom.boomify(err, { message: 'Failed to apply profile' })
196+
}
197+
198+
return h.response({ OldCfg: diff.oldCfg, NewCfg: diff.newCfg })
199+
}
200+
}

src/http/api/routes/config.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,5 +31,16 @@ module.exports = [
3131
]
3232
},
3333
handler: resources.config.replace.handler
34+
},
35+
{
36+
method: '*',
37+
path: '/api/v0/config/profile/apply',
38+
options: {
39+
pre: [
40+
{ method: resources.config.profile.parseArgs, assign: 'args' }
41+
],
42+
validate: resources.config.profile.validate
43+
},
44+
handler: resources.config.profile.handler
3445
}
3546
]

test/cli/config.js

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ chai.use(dirtyChai)
88
const fs = require('fs')
99
const path = require('path')
1010
const runOnAndOff = require('../utils/on-and-off')
11+
const defaultConfig = require('../../src/core/runtime/config-nodejs')()
1112

1213
describe('config', () => runOnAndOff((thing) => {
1314
let ipfs
@@ -97,4 +98,70 @@ describe('config', () => runOnAndOff((thing) => {
9798
restoreConfig()
9899
})
99100
})
101+
102+
describe('profile', function () {
103+
this.timeout(40 * 1000)
104+
105+
beforeEach(() => restoreConfig())
106+
after(() => restoreConfig())
107+
108+
it('server / local-discovery', async () => {
109+
await ipfs('config profile apply server')
110+
const updated = updatedConfig()
111+
expect(updated.Discovery.MDNS.Enabled).to.equal(false)
112+
expect(updated.Discovery.webRTCStar.Enabled).to.equal(false)
113+
114+
await ipfs('config profile apply local-discovery')
115+
const reversed = updatedConfig()
116+
expect(reversed.Discovery.MDNS.Enabled).to.equal(true)
117+
expect(reversed.Discovery.webRTCStar.Enabled).to.equal(true)
118+
})
119+
120+
it('test / default-networking', async () => {
121+
await ipfs('config profile apply test')
122+
const updated = updatedConfig()
123+
expect(updated.Addresses.API).to.equal('/ip4/127.0.0.1/tcp/0')
124+
expect(updated.Addresses.Gateway).to.equal('/ip4/127.0.0.1/tcp/0')
125+
expect(updated.Addresses.Swarm).to.eql(['/ip4/127.0.0.1/tcp/0'])
126+
expect(updated.Bootstrap).to.eql([])
127+
expect(updated.Discovery.MDNS.Enabled).to.equal(false)
128+
expect(updated.Discovery.webRTCStar.Enabled).to.equal(false)
129+
130+
await ipfs('config profile apply default-networking')
131+
const reversed = updatedConfig()
132+
expect(reversed.Addresses.API).to.equal(defaultConfig.Addresses.API)
133+
expect(reversed.Addresses.Gateway).to.equal(defaultConfig.Addresses.Gateway)
134+
expect(reversed.Addresses.Swarm).to.eql(defaultConfig.Addresses.Swarm)
135+
expect(reversed.Bootstrap).to.eql(defaultConfig.Bootstrap)
136+
expect(reversed.Discovery.MDNS.Enabled).to.equal(true)
137+
expect(reversed.Discovery.webRTCStar.Enabled).to.equal(true)
138+
})
139+
140+
it('lowpower / default-power', async () => {
141+
await ipfs('config profile apply lowpower')
142+
const updated = updatedConfig()
143+
expect(updated.Swarm.ConnMgr.LowWater).to.equal(20)
144+
expect(updated.Swarm.ConnMgr.HighWater).to.equal(40)
145+
146+
await ipfs('config profile apply default-power')
147+
const reversed = updatedConfig()
148+
expect(reversed.Swarm.ConnMgr.LowWater).to.equal(defaultConfig.Swarm.ConnMgr.LowWater)
149+
expect(reversed.Swarm.ConnMgr.HighWater).to.equal(defaultConfig.Swarm.ConnMgr.HighWater)
150+
})
151+
152+
it('--dry-run causes no change', async () => {
153+
await ipfs('config profile apply --dry-run=true server')
154+
const after = updatedConfig()
155+
expect(after.Discovery.MDNS.Enabled).to.equal(defaultConfig.Discovery.MDNS.Enabled)
156+
157+
await ipfs('config profile apply --dry-run=false server')
158+
const updated = updatedConfig()
159+
expect(updated.Discovery.MDNS.Enabled).to.equal(false)
160+
})
161+
162+
it('Private key does not appear in output', async () => {
163+
const out = await ipfs('config profile apply server')
164+
expect(out).not.includes('PrivKey')
165+
})
166+
})
100167
}))

0 commit comments

Comments
 (0)