mirror of
https://github.com/balena-io/balena-cli.git
synced 2024-12-30 02:28:51 +00:00
Add support for the Opensource provisioning flow
Connects-to: #978 Change-type: major Depends-on: https://github.com/resin-io/resin-sdk/pull/594 HQ: https://github.com/resin-io/balena/pull/1140 Signed-off-by: Thodoris Greasidis <thodoris@resin.io>
This commit is contained in:
parent
13729ec4b6
commit
4d42f74c0c
@ -142,7 +142,7 @@ environment variable (in the same standard URL format).
|
||||
- [os versions <type>](#os-versions-type-)
|
||||
- [os download <type>](#os-download-type-)
|
||||
- [os build-config <image> <device-type>](#os-build-config-image-device-type-)
|
||||
- [os configure <image> [uuid] [deviceApiKey]](#os-configure-image-uuid-deviceapikey-)
|
||||
- [os configure <image>](#os-configure-image-)
|
||||
- [os initialize <image>](#os-initialize-image-)
|
||||
|
||||
- Config
|
||||
@ -965,13 +965,12 @@ show advanced configuration options
|
||||
|
||||
the path to the output JSON file
|
||||
|
||||
## os configure <image> [uuid] [deviceApiKey]
|
||||
## os configure <image>
|
||||
|
||||
Use this command to configure a previously downloaded operating system image for
|
||||
the specific device or for an application generally.
|
||||
|
||||
Calling this command without --version is not recommended, and may fail in
|
||||
future releases if the OS version cannot be inferred.
|
||||
Calling this command with the exact version number of the targeted image is required.
|
||||
|
||||
Note that device api keys are only supported on ResinOS 2.0.3+.
|
||||
|
||||
@ -1125,8 +1124,7 @@ show advanced commands
|
||||
|
||||
Use this command to generate a config.json for a device or application.
|
||||
|
||||
Calling this command without --version is not recommended, and may fail in
|
||||
future releases if the OS version cannot be inferred.
|
||||
Calling this command with the exact version number of the targeted image is required.
|
||||
|
||||
This is interactive by default, but you can do this automatically without interactivity
|
||||
by specifying an option for each question on the command line, if you know the questions
|
||||
|
@ -49,13 +49,17 @@ exports.optionalOsVersion =
|
||||
description: 'a resinOS version'
|
||||
parameter: 'version'
|
||||
|
||||
exports.osVersion = _.defaults
|
||||
required: 'You have to specify an exact os version'
|
||||
, exports.optionalOsVersion
|
||||
|
||||
exports.booleanDevice =
|
||||
signature: 'device'
|
||||
description: 'device'
|
||||
boolean: true
|
||||
alias: 'd'
|
||||
|
||||
exports.osVersion =
|
||||
exports.osVersionOrSemver =
|
||||
signature: 'version'
|
||||
description: """
|
||||
exact version number, or a valid semver range,
|
||||
|
@ -223,8 +223,7 @@ exports.generate =
|
||||
help: '''
|
||||
Use this command to generate a config.json for a device or application.
|
||||
|
||||
Calling this command without --version is not recommended, and may fail in
|
||||
future releases if the OS version cannot be inferred.
|
||||
Calling this command with the exact version number of the targeted image is required.
|
||||
|
||||
This is interactive by default, but you can do this automatically without interactivity
|
||||
by specifying an option for each question on the command line, if you know the questions
|
||||
@ -242,7 +241,7 @@ exports.generate =
|
||||
--network wifi --wifiSsid mySsid --wifiKey abcdefgh --appUpdatePollInterval 1
|
||||
'''
|
||||
options: [
|
||||
commandOptions.optionalOsVersion
|
||||
commandOptions.osVersion
|
||||
commandOptions.optionalApplication
|
||||
commandOptions.optionalDevice
|
||||
commandOptions.optionalDeviceApiKey
|
||||
|
@ -395,7 +395,7 @@ exports.init =
|
||||
commandOptions.optionalApplication
|
||||
commandOptions.yes
|
||||
commandOptions.advancedConfig
|
||||
_.assign({}, commandOptions.osVersion, { signature: 'os-version', parameter: 'os-version' })
|
||||
_.assign({}, commandOptions.osVersionOrSemver, { signature: 'os-version', parameter: 'os-version' })
|
||||
commandOptions.drive
|
||||
{
|
||||
signature: 'config'
|
||||
|
@ -96,7 +96,7 @@ exports.download =
|
||||
alias: 'o'
|
||||
required: 'You have to specify the output location'
|
||||
}
|
||||
commandOptions.osVersion
|
||||
commandOptions.osVersionOrSemver
|
||||
]
|
||||
action: (params, options, done) ->
|
||||
Promise = require('bluebird')
|
||||
@ -197,14 +197,13 @@ exports.buildConfig =
|
||||
.nodeify(done)
|
||||
|
||||
exports.configure =
|
||||
signature: 'os configure <image> [uuid] [deviceApiKey]'
|
||||
signature: 'os configure <image>'
|
||||
description: 'configure an os image'
|
||||
help: '''
|
||||
Use this command to configure a previously downloaded operating system image for
|
||||
the specific device or for an application generally.
|
||||
|
||||
Calling this command without --version is not recommended, and may fail in
|
||||
future releases if the OS version cannot be inferred.
|
||||
Calling this command with the exact version number of the targeted image is required.
|
||||
|
||||
Note that device api keys are only supported on ResinOS 2.0.3+.
|
||||
|
||||
@ -224,7 +223,7 @@ exports.configure =
|
||||
commandOptions.optionalApplication
|
||||
commandOptions.optionalDevice
|
||||
commandOptions.optionalDeviceApiKey
|
||||
commandOptions.optionalOsVersion
|
||||
commandOptions.osVersion
|
||||
{
|
||||
signature: 'config'
|
||||
description: 'path to the config JSON file, see `resin os build-config`'
|
||||
@ -232,7 +231,6 @@ exports.configure =
|
||||
}
|
||||
]
|
||||
action: (params, options, done) ->
|
||||
normalizeUuidProp(params)
|
||||
normalizeUuidProp(options, 'device')
|
||||
fs = require('fs')
|
||||
Promise = require('bluebird')
|
||||
@ -246,30 +244,20 @@ exports.configure =
|
||||
if _.filter([
|
||||
options.device
|
||||
options.application
|
||||
params.uuid
|
||||
]).length != 1
|
||||
patterns.exitWithExpectedError '''
|
||||
To configure an image, you must provide exactly one of:
|
||||
|
||||
* A device, with --device <uuid>
|
||||
* An application, with --app <appname>
|
||||
* [Deprecated] A device, passing its uuid directly on the command line
|
||||
|
||||
See the help page for examples:
|
||||
|
||||
$ resin help os configure
|
||||
'''
|
||||
if params.uuid
|
||||
console.warn(
|
||||
'Directly passing a UUID to `resin os configure` is deprecated, and will stop working in future.\n' +
|
||||
'Pass your device UUID with --device <uuid> instead.' +
|
||||
if params.deviceApiKey
|
||||
' Device api keys can be passed with --deviceApiKey.\n'
|
||||
else '\n'
|
||||
)
|
||||
|
||||
uuid = options.device || params.uuid
|
||||
deviceApiKey = options.deviceApiKey || params.deviceApiKey
|
||||
uuid = options.device
|
||||
deviceApiKey = options.deviceApiKey
|
||||
|
||||
console.info('Configuring operating system image')
|
||||
|
||||
|
@ -13,77 +13,64 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
import * as fs from 'fs';
|
||||
|
||||
import Promise = require('bluebird');
|
||||
import ResinSdk = require('resin-sdk');
|
||||
import deviceConfig = require('resin-device-config');
|
||||
import * as semver from 'resin-semver';
|
||||
|
||||
const resin = ResinSdk.fromSharedOptions();
|
||||
|
||||
function readRootCa(): Promise<string | void> {
|
||||
const caFile = process.env.NODE_EXTRA_CA_CERTS;
|
||||
if (!caFile) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
return Promise.fromCallback(cb =>
|
||||
fs.readFile(caFile, { encoding: 'utf8' }, cb),
|
||||
)
|
||||
.then(pem => Buffer.from(pem).toString('base64'))
|
||||
.catch({ code: 'ENOENT' }, () => {});
|
||||
}
|
||||
type ImgConfig = {
|
||||
applicationName: string;
|
||||
applicationId: number;
|
||||
deviceType: string;
|
||||
userId: number;
|
||||
username: string;
|
||||
appUpdatePollInterval: number;
|
||||
listenPort: number;
|
||||
vpnPort: number;
|
||||
apiEndpoint: string;
|
||||
vpnEndpoint: string;
|
||||
registryEndpoint: string;
|
||||
deltaEndpoint: string;
|
||||
pubnubSubscribeKey: string;
|
||||
pubnubPublishKey: string;
|
||||
mixpanelToken: string;
|
||||
wifiSsid?: string;
|
||||
wifiKey?: string;
|
||||
|
||||
// props for older OS versions
|
||||
connectivity?: string;
|
||||
files?: {
|
||||
[filepath: string]: string;
|
||||
};
|
||||
|
||||
// device specific config props
|
||||
deviceId?: number;
|
||||
uuid?: string;
|
||||
registered_at?: number;
|
||||
};
|
||||
|
||||
export function generateBaseConfig(
|
||||
application: ResinSdk.Application,
|
||||
options: { version?: string; appUpdatePollInterval?: number },
|
||||
) {
|
||||
if (options.appUpdatePollInterval) {
|
||||
options = {
|
||||
...options,
|
||||
appUpdatePollInterval: options.appUpdatePollInterval * 60 * 1000,
|
||||
};
|
||||
}
|
||||
options: { version: string; appUpdatePollInterval?: number },
|
||||
): Promise<ImgConfig> {
|
||||
options = {
|
||||
...options,
|
||||
appUpdatePollInterval: options.appUpdatePollInterval || 10,
|
||||
};
|
||||
|
||||
return Promise.props({
|
||||
userId: resin.auth.getUserId(),
|
||||
username: resin.auth.whoami(),
|
||||
apiUrl: resin.settings.get('apiUrl'),
|
||||
vpnUrl: resin.settings.get('vpnUrl'),
|
||||
registryUrl: resin.settings.get('registryUrl'),
|
||||
deltaUrl: resin.settings.get('deltaUrl'),
|
||||
apiConfig: resin.models.config.getAll(),
|
||||
rootCA: readRootCa().catch(() => {
|
||||
console.warn('Could not read root CA');
|
||||
}),
|
||||
}).then(results => {
|
||||
return deviceConfig.generate(
|
||||
{
|
||||
application,
|
||||
user: {
|
||||
id: results.userId,
|
||||
username: results.username,
|
||||
},
|
||||
endpoints: {
|
||||
api: results.apiUrl,
|
||||
vpn: results.vpnUrl,
|
||||
registry: results.registryUrl,
|
||||
delta: results.deltaUrl,
|
||||
},
|
||||
pubnub: results.apiConfig.pubnub,
|
||||
mixpanel: {
|
||||
token: results.apiConfig.mixpanelToken,
|
||||
},
|
||||
balenaRootCA: results.rootCA,
|
||||
},
|
||||
options,
|
||||
);
|
||||
const promise = resin.models.os.getConfig(application.app_name, options) as Promise<
|
||||
ImgConfig & { apiKey?: string; }
|
||||
>;
|
||||
return promise.tap(config => {
|
||||
// os.getConfig always returns a config for an app
|
||||
delete config.apiKey;
|
||||
});
|
||||
}
|
||||
|
||||
export function generateApplicationConfig(
|
||||
application: ResinSdk.Application,
|
||||
options: { version?: string },
|
||||
options: { version: string },
|
||||
) {
|
||||
return generateBaseConfig(application, options).tap(config => {
|
||||
if (semver.satisfies(options.version, '>=2.7.8')) {
|
||||
@ -97,7 +84,7 @@ export function generateApplicationConfig(
|
||||
export function generateDeviceConfig(
|
||||
device: ResinSdk.Device & { belongs_to__application: ResinSdk.PineDeferred },
|
||||
deviceApiKey: string | true | null,
|
||||
options: { version?: string },
|
||||
options: { version: string },
|
||||
) {
|
||||
return resin.models.application
|
||||
.get(device.belongs_to__application.__id)
|
||||
|
@ -39,8 +39,14 @@ export async function join(
|
||||
app.device_type = deviceType;
|
||||
}
|
||||
|
||||
logger.logDebug('Determining device OS version...');
|
||||
const deviceOsVersion = await getOsVersion(deviceIp);
|
||||
logger.logDebug(`Device OS version: ${deviceOsVersion}`);
|
||||
|
||||
logger.logDebug('Generating application config...');
|
||||
const config = await generateApplicationConfig(sdk, app);
|
||||
const config = await generateApplicationConfig(sdk, app, {
|
||||
version: deviceOsVersion,
|
||||
});
|
||||
logger.logDebug(`Using config: ${JSON.stringify(config, null, 2)}`);
|
||||
|
||||
logger.logDebug('Configuring...');
|
||||
@ -123,6 +129,15 @@ async function getDeviceType(deviceIp: string): Promise<string> {
|
||||
return match[1];
|
||||
}
|
||||
|
||||
async function getOsVersion(deviceIp: string): Promise<string> {
|
||||
const output = await execBuffered(deviceIp, 'cat /etc/os-release');
|
||||
const match = /^VERSION_ID="([^"]+)"$/m.exec(output);
|
||||
if (!match) {
|
||||
throw new Error('Failed to determine OS version ID');
|
||||
}
|
||||
return match[1];
|
||||
}
|
||||
|
||||
async function getOrSelectLocalDevice(deviceIp?: string): Promise<string> {
|
||||
if (deviceIp) {
|
||||
return deviceIp;
|
||||
@ -304,14 +319,21 @@ async function createApplication(
|
||||
});
|
||||
}
|
||||
|
||||
async function generateApplicationConfig(sdk: ResinSDK, app: Application) {
|
||||
async function generateApplicationConfig(
|
||||
sdk: ResinSDK,
|
||||
app: Application,
|
||||
options: { version: string },
|
||||
) {
|
||||
const form = await import('resin-cli-form');
|
||||
const { generateApplicationConfig: configGen } = await import('./config');
|
||||
|
||||
const manifest = await sdk.models.device.getManifestBySlug(app.device_type);
|
||||
const opts =
|
||||
manifest.options && manifest.options.filter(opt => opt.name !== 'network');
|
||||
const values = await form.run(opts);
|
||||
const values = {
|
||||
...(await form.run(opts)),
|
||||
...options,
|
||||
};
|
||||
|
||||
const config = await configGen(app, values);
|
||||
if (config.connectivity === 'connman') {
|
||||
|
Loading…
Reference in New Issue
Block a user