Skip to content

Commit e9b3f26

Browse files
committed
New nextflow auth status command
Signed-off-by: Phil Ewels <[email protected]>
1 parent 582d70b commit e9b3f26

File tree

1 file changed

+169
-0
lines changed

1 file changed

+169
-0
lines changed

modules/nextflow/src/main/groovy/nextflow/cli/CmdAuth.groovy

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ class CmdAuth extends CmdBase implements UsageAware {
6666
commands.add(new LoginCmd())
6767
commands.add(new LogoutCmd())
6868
commands.add(new ConfigCmd())
69+
commands.add(new StatusCmd())
6970
}
7071

7172
void usage() {
@@ -1019,4 +1020,172 @@ class CmdAuth extends CmdBase implements UsageAware {
10191020
result << ''
10201021
}
10211022
}
1023+
1024+
class StatusCmd implements SubCmd {
1025+
1026+
@Override
1027+
String getName() { 'status' }
1028+
1029+
@Override
1030+
void apply(List<String> args) {
1031+
if (args.size() > 0) {
1032+
throw new AbortOperationException("Too many arguments for status command")
1033+
}
1034+
1035+
def config = readConfig()
1036+
1037+
println "Nextflow Seqera Platform authentication status"
1038+
println ""
1039+
1040+
// API endpoint
1041+
def endpointInfo = getConfigValue(config, 'tower.endpoint', 'TOWER_API_ENDPOINT', 'https://api.cloud.seqera.io')
1042+
println "API endpoint: ${endpointInfo.value} (${endpointInfo.source})"
1043+
1044+
// Access token status
1045+
def tokenInfo = getConfigValue(config, 'tower.accessToken', 'TOWER_ACCESS_TOKEN')
1046+
if (tokenInfo.value) {
1047+
println "Access token: yes (${tokenInfo.source})"
1048+
} else {
1049+
println "Access token: no"
1050+
}
1051+
1052+
// Monitoring enabled
1053+
def enabledInfo = getConfigValue(config, 'tower.enabled', null, 'false')
1054+
def enabledValue = enabledInfo.value?.toString()?.toLowerCase() in ['true', '1', 'yes'] ? 'Yes' : 'No'
1055+
println "Local workflow monitoring enabled: ${enabledValue} (${enabledInfo.source ?: 'default'})"
1056+
1057+
// Default workspace
1058+
def workspaceInfo = getConfigValue(config, 'tower.workspaceId', 'TOWER_WORKFLOW_ID')
1059+
if (workspaceInfo.value) {
1060+
// Try to get workspace name from API if we have a token
1061+
def workspaceName = null
1062+
if (tokenInfo.value) {
1063+
workspaceName = getWorkspaceNameFromApi(tokenInfo.value as String, endpointInfo.value as String, workspaceInfo.value as String)
1064+
}
1065+
1066+
if (workspaceName) {
1067+
println "Default workspace: ${workspaceName} [${workspaceInfo.value}] (${workspaceInfo.source})"
1068+
} else {
1069+
println "Default workspace: ${workspaceInfo.value} (${workspaceInfo.source})"
1070+
}
1071+
} else {
1072+
println "Default workspace: Personal workspace (default)"
1073+
}
1074+
1075+
println ""
1076+
println "System health status"
1077+
1078+
// API connection check
1079+
def apiConnectionOk = checkApiConnection(endpointInfo.value as String)
1080+
println "API connection: ${apiConnectionOk ? 'OK' : 'ERROR'}"
1081+
1082+
// Authentication check
1083+
if (tokenInfo.value) {
1084+
try {
1085+
def userInfo = callUserInfoApi(tokenInfo.value as String, endpointInfo.value as String)
1086+
def currentUser = userInfo.userName
1087+
println "Authentication: OK (${currentUser})"
1088+
} catch (Exception e) {
1089+
println "Authentication: ERROR"
1090+
}
1091+
} else {
1092+
println "Authentication: ERROR (no token)"
1093+
}
1094+
}
1095+
1096+
private String shortenPath(String path) {
1097+
def userHome = System.getProperty('user.home')
1098+
if (path.startsWith(userHome)) {
1099+
return '~' + path.substring(userHome.length())
1100+
}
1101+
return path
1102+
}
1103+
1104+
private Map getConfigValue(Map config, String configKey, String envVarName, String defaultValue = null) {
1105+
def configValue = config[configKey]
1106+
def envValue = System.getenv(envVarName)
1107+
def effectiveValue = configValue ?: envValue ?: defaultValue
1108+
1109+
def source = null
1110+
if (configValue) {
1111+
source = shortenPath(getConfigFile().toString())
1112+
} else if (envValue) {
1113+
source = "env var \$${envVarName}"
1114+
} else if (defaultValue) {
1115+
source = "default"
1116+
}
1117+
1118+
return [
1119+
value: effectiveValue,
1120+
source: source,
1121+
fromConfig: configValue != null,
1122+
fromEnv: envValue != null,
1123+
isDefault: !configValue && !envValue
1124+
]
1125+
}
1126+
1127+
private String getWorkspaceNameFromApi(String accessToken, String endpoint, String workspaceId) {
1128+
try {
1129+
// Get user info to get user ID
1130+
def userInfo = callUserInfoApi(accessToken, endpoint)
1131+
def userId = userInfo.id as String
1132+
1133+
// Get workspaces for the user
1134+
def workspacesUrl = "${endpoint}/user/${userId}/workspaces"
1135+
def connection = new URL(workspacesUrl).openConnection() as HttpURLConnection
1136+
connection.requestMethod = 'GET'
1137+
connection.connectTimeout = 10000 // 10 second timeout
1138+
connection.readTimeout = 10000
1139+
connection.setRequestProperty('Authorization', "Bearer ${accessToken}")
1140+
1141+
if (connection.responseCode != 200) {
1142+
return null
1143+
}
1144+
1145+
def response = connection.inputStream.text
1146+
def json = new groovy.json.JsonSlurper().parseText(response) as Map
1147+
def orgsAndWorkspaces = json.orgsAndWorkspaces as List
1148+
1149+
// Find the workspace with matching ID
1150+
def workspace = orgsAndWorkspaces.find { ((Map)it).workspaceId?.toString() == workspaceId }
1151+
if (workspace) {
1152+
def ws = workspace as Map
1153+
return "${ws.orgName} / ${ws.workspaceFullName}"
1154+
}
1155+
1156+
return null
1157+
} catch (Exception e) {
1158+
return null
1159+
}
1160+
}
1161+
1162+
private boolean checkApiConnection(String endpoint) {
1163+
try {
1164+
def serviceInfoUrl = "${endpoint}/service-info"
1165+
def connection = new URL(serviceInfoUrl).openConnection() as HttpURLConnection
1166+
connection.requestMethod = 'GET'
1167+
connection.connectTimeout = 10000 // 10 second timeout
1168+
connection.readTimeout = 10000
1169+
1170+
return connection.responseCode == 200
1171+
} catch (Exception e) {
1172+
return false
1173+
}
1174+
}
1175+
1176+
1177+
@Override
1178+
void usage(List<String> result) {
1179+
result << 'Show authentication status and configuration'
1180+
result << "Usage: nextflow auth $name".toString()
1181+
result << ''
1182+
result << 'This command shows:'
1183+
result << ' - Authentication status (yes/no) and source'
1184+
result << ' - API endpoint and source'
1185+
result << ' - Monitoring enabled status and source'
1186+
result << ' - Default workspace and source'
1187+
result << ' - System health status (API connection and authentication)'
1188+
result << ''
1189+
}
1190+
}
10221191
}

0 commit comments

Comments
 (0)