17
17
18
18
import { Args , Command } from '@oclif/core' ;
19
19
import { ExpectedError } from '../../errors' ;
20
- import { getBalenaSdk , stripIndent } from '../../utils/lazy' ;
20
+ import { getBalenaSdk , getCliForm , stripIndent } from '../../utils/lazy' ;
21
+ import {
22
+ formatDuration ,
23
+ intervalToDuration ,
24
+ isValid ,
25
+ parseISO ,
26
+ } from 'date-fns' ;
27
+
28
+ // In days
29
+ const durations = [ 1 , 7 , 30 , 90 ] ;
21
30
22
31
async function isLoggedInWithJwt ( ) {
23
32
const balena = getBalenaSdk ( ) ;
@@ -41,23 +50,83 @@ export default class GenerateCmd extends Command {
41
50
This key can be used to log into the CLI using 'balena login --token <key>',
42
51
or to authenticate requests to the API with an 'Authorization: Bearer <key>' header.
43
52
` ;
44
- public static examples = [ '$ balena api-key generate "Jenkins Key"' ] ;
53
+ public static examples = [
54
+ '$ balena api-key generate "Jenkins Key"' ,
55
+ '$ balena api-key generate "Jenkins Key" 2025-10-30' ,
56
+ '$ balena api-key generate "Jenkins Key" never' ,
57
+ ] ;
45
58
46
59
public static args = {
47
60
name : Args . string ( {
48
61
description : 'the API key name' ,
49
62
required : true ,
50
63
} ) ,
64
+ expiryDate : Args . string ( {
65
+ description :
66
+ 'the expiry date of the API key as an ISO date string, or "never" for no expiry' ,
67
+ } ) ,
51
68
} ;
52
69
53
70
public static authenticated = true ;
54
71
55
72
public async run ( ) {
56
73
const { args : params } = await this . parse ( GenerateCmd ) ;
57
74
75
+ let expiryDateResponse : string | number | undefined = params . expiryDate ;
58
76
let key ;
59
77
try {
60
- key = await getBalenaSdk ( ) . models . apiKey . create ( params . name ) ;
78
+ if ( ! expiryDateResponse ) {
79
+ expiryDateResponse = await getCliForm ( ) . ask ( {
80
+ message : 'Please pick an expiry date for the API key' ,
81
+ type : 'list' ,
82
+ choices : [ ...durations , 'custom' , 'never' ] . map ( ( duration ) => ( {
83
+ name :
84
+ duration === 'never'
85
+ ? 'No expiration'
86
+ : typeof duration === 'number'
87
+ ? formatDuration (
88
+ intervalToDuration ( {
89
+ start : 0 ,
90
+ end : duration * 24 * 60 * 60 * 1000 ,
91
+ } ) ,
92
+ )
93
+ : 'Custom expiration' ,
94
+ value : duration ,
95
+ } ) ) ,
96
+ } ) ;
97
+ }
98
+ let expiryDate : Date | null ;
99
+ if ( expiryDateResponse === 'never' ) {
100
+ expiryDate = null ;
101
+ } else if ( expiryDateResponse === 'custom' ) {
102
+ do {
103
+ expiryDate = parseISO (
104
+ await getCliForm ( ) . ask ( {
105
+ message :
106
+ 'Please enter an expiry date for the API key as an ISO date string' ,
107
+ type : 'input' ,
108
+ } ) ,
109
+ ) ;
110
+ if ( ! isValid ( expiryDate ) ) {
111
+ console . error ( 'Invalid date format' ) ;
112
+ }
113
+ } while ( ! isValid ( expiryDate ) ) ;
114
+ } else if ( typeof expiryDateResponse === 'string' ) {
115
+ expiryDate = parseISO ( expiryDateResponse ) ;
116
+ if ( ! isValid ( expiryDate ) ) {
117
+ throw new Error (
118
+ 'Invalid date format, please use a valid ISO date string' ,
119
+ ) ;
120
+ }
121
+ } else {
122
+ expiryDate = new Date (
123
+ Date . now ( ) + expiryDateResponse * 24 * 60 * 60 * 1000 ,
124
+ ) ;
125
+ }
126
+ key = await getBalenaSdk ( ) . models . apiKey . create ( {
127
+ name : params . name ,
128
+ expiryDate : expiryDate === null ? null : expiryDate . toISOString ( ) ,
129
+ } ) ;
61
130
} catch ( e ) {
62
131
if ( e . name === 'BalenaNotLoggedIn' ) {
63
132
if ( await isLoggedInWithJwt ( ) ) {
0 commit comments