diff --git a/pom.xml b/pom.xml index 2fdb894f9..33df66566 100644 --- a/pom.xml +++ b/pom.xml @@ -321,7 +321,7 @@ - -Dcryostat.discovery.jdp.enabled=true -Dcryostat.discovery.podman.enabled=true -XX:+FlightRecorder -XX:StartFlightRecording=filename=name=onstart,settings=default,disk=true,maxage=5m -Dcom.sun.management.jmxremote.autodiscovery=true -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=9091 -Dcom.sun.management.jmxremote.rmi.port=9091 -Djava.rmi.server.hostname=127.0.0.1 -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.local.only=false + -Dcryostat.discovery.jdp.enabled=true -Dcryostat.discovery.podman.enabled=true -XX:+FlightRecorder -XX:StartFlightRecording=filename/tmp,=name=onstart,settings=default,disk=true,maxage=5m -Dcom.sun.management.jmxremote.autodiscovery=true -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=9091 -Dcom.sun.management.jmxremote.rmi.port=9091 -Djava.rmi.server.hostname=127.0.0.1 -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.local.only=false diff --git a/schema/openapi.yaml b/schema/openapi.yaml index d8b24ed8c..ca6280b3a 100644 --- a/schema/openapi.yaml +++ b/schema/openapi.yaml @@ -24,6 +24,36 @@ components: type: string type: object type: object + Annotations_Flat: + properties: + cryostat: + additionalProperties: + type: string + type: object + platform: + additionalProperties: + type: string + type: object + type: object + ApplicationHealth: + properties: + build: + $ref: '#/components/schemas/BuildInfo' + cryostatVersion: + type: string + dashboardAvailable: + type: boolean + dashboardConfigured: + type: boolean + datasourceAvailable: + type: boolean + datasourceConfigured: + type: boolean + reportsAvailable: + type: boolean + reportsConfigured: + type: boolean + type: object ArchivedRecording: properties: archivedTime: @@ -54,7 +84,80 @@ components: $ref: '#/components/schemas/ArchivedRecording' type: array type: object - Data: + AuthResponse: + properties: + username: + type: string + type: object + BuildInfo: + properties: + git: + $ref: '#/components/schemas/GitInfo' + gitinfo: + $ref: '#/components/schemas/GitInfo' + type: object + ContentType: + enum: + - NONE + - BYTES + - TIMESTAMP + - MILLIS + - NANOS + - TICKS + - ADDRESS + - OS_THREAD + - JAVA_THREAD + - STACK_TRACE + - CLASS + - PERCENTAGE + type: string + Credential: + properties: + id: + format: int64 + type: integer + matchExpression: + $ref: '#/components/schemas/MatchExpression' + password: + pattern: \S + type: string + writeOnly: true + username: + pattern: \S + type: string + writeOnly: true + required: + - matchExpression + - username + - password + type: object + CredentialMatchResult: + properties: + id: + format: int64 + type: integer + matchExpression: + $ref: '#/components/schemas/MatchExpression' + targets: + items: + $ref: '#/components/schemas/Target' + type: array + type: object + CredentialTestResult: + enum: + - SUCCESS + - FAILURE + - NA + type: string + DashboardUrl: + properties: + grafanaDashboardUrl: + type: string + type: object + DatasourceUrl: + properties: + grafanaDatasourceUrl: + type: string type: object DiscoveryNode: properties: @@ -82,6 +185,28 @@ components: - nodeType - labels type: object + DiscoveryNode_Flat: + properties: + id: + format: int64 + type: integer + labels: + additionalProperties: + type: string + type: object + name: + pattern: \S + type: string + nodeType: + pattern: \S + type: string + target: + $ref: '#/components/schemas/Target_Flat' + required: + - name + - nodeType + - labels + type: object DiscoveryPlugin: properties: builtin: @@ -98,6 +223,22 @@ components: - id - realm type: object + DiscoveryPlugin_Flat: + properties: + builtin: + readOnly: true + type: boolean + callback: + format: uri + type: string + id: + $ref: '#/components/schemas/UUID_Flat' + realm: + $ref: '#/components/schemas/DiscoveryNode_Flat' + required: + - id + - realm + type: object Evaluation: properties: explanation: @@ -111,8 +252,63 @@ components: summary: type: string type: object + Event: + properties: + clazz: + type: string + description: + type: string + fields: + items: + $ref: '#/components/schemas/Field' + type: array + id: + type: string + location: + $ref: '#/components/schemas/Location' + methodDescriptor: + type: string + methodName: + type: string + name: + type: string + parameters: + items: + $ref: '#/components/schemas/MethodParameter' + type: array + path: + type: string + recordStackTrace: + type: boolean + returnValue: + $ref: '#/components/schemas/MethodReturnValue' + useRethrow: + type: boolean + type: object + Field: + properties: + contentType: + $ref: '#/components/schemas/ContentType' + converter: + type: string + description: + type: string + event: + $ref: '#/components/schemas/Event' + expression: + type: string + name: + type: string + relationKey: + type: string + type: object FileUpload: type: object + GitInfo: + properties: + hash: + type: string + type: object Instant: example: 2022-03-10T16:15:50Z format: date-time @@ -160,6 +356,12 @@ components: toDisk: type: boolean type: object + Location: + enum: + - ENTRY + - EXIT + - WRAP + type: string MatchExpression: properties: id: @@ -183,13 +385,6 @@ components: $ref: '#/components/schemas/Target' type: array type: object - Meta: - properties: - status: - type: string - type: - type: string - type: object Metadata: properties: expiry: @@ -199,6 +394,79 @@ components: type: string type: object type: object + MethodParameter: + properties: + contentType: + $ref: '#/components/schemas/ContentType' + converter: + type: string + description: + type: string + event: + $ref: '#/components/schemas/Event' + index: + format: int32 + type: integer + name: + type: string + relationKey: + type: string + type: object + MethodReturnValue: + properties: + contentType: + $ref: '#/components/schemas/ContentType' + converter: + type: string + description: + type: string + event: + $ref: '#/components/schemas/Event' + name: + type: string + relationKey: + type: string + type: object + PluginRegistration: + properties: + env: + additionalProperties: + type: string + type: object + id: + type: string + token: + type: string + type: object + ProbeResponse: + properties: + description: + type: string + name: + type: string + type: object + ProbeTemplate: + properties: + allowConverter: + type: boolean + allowToString: + type: boolean + classPrefix: + type: string + events: + items: + $ref: '#/components/schemas/Event' + type: array + fileName: + type: string + type: object + ProbeTemplateResponse: + properties: + description: + type: string + name: + type: string + type: object RecordingState: enum: - NEW @@ -211,9 +479,10 @@ components: properties: matchExpression: type: string - targets: + targetIds: items: - $ref: '#/components/schemas/Target' + format: int64 + type: integer type: array type: object Rule: @@ -322,6 +591,34 @@ components: - labels - annotations type: object + Target_Flat: + properties: + agent: + readOnly: true + type: boolean + alias: + pattern: \S + type: string + annotations: + $ref: '#/components/schemas/Annotations_Flat' + connectUrl: + format: uri + type: string + id: + format: int64 + type: integer + jvmId: + type: string + labels: + additionalProperties: + type: string + type: object + required: + - connectUrl + - alias + - labels + - annotations + type: object Template: properties: description: @@ -342,13 +639,10 @@ components: format: uuid pattern: '[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}' type: string - V2Response: - properties: - data: - $ref: '#/components/schemas/Data' - meta: - $ref: '#/components/schemas/Meta' - type: object + UUID_Flat: + format: uuid + pattern: '[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}' + type: string info: contact: email: cryostat-development@googlegroups.com @@ -362,39 +656,6 @@ info: version: 4.0.0-snapshot openapi: 3.0.3 paths: - /api/beta/credentials/{connectUrl}: - post: - parameters: - - in: path - name: connectUrl - required: true - schema: - type: string - requestBody: - content: - application/x-www-form-urlencoded: - schema: - properties: - password: - type: string - username: - type: string - type: object - responses: - "200": - content: - application/json: - schema: - $ref: '#/components/schemas/V2Response' - description: OK - "401": - description: Not Authorized - "403": - description: Not Allowed - security: - - SecurityScheme: [] - tags: - - Credential Check /api/beta/fs/recordings: get: responses: @@ -413,7 +674,7 @@ paths: security: - SecurityScheme: [] tags: - - Recordings + - Archived Recordings /api/beta/fs/recordings/{jvmId}: get: parameters: @@ -438,7 +699,7 @@ paths: security: - SecurityScheme: [] tags: - - Recordings + - Archived Recordings /api/beta/fs/recordings/{jvmId}/{filename}: delete: parameters: @@ -462,23 +723,23 @@ paths: security: - SecurityScheme: [] tags: - - Recordings - /api/beta/fs/recordings/{jvmId}/{filename}/upload: - post: + - Archived Recordings + /api/beta/recordings/{connectUrl}/{filename}: + delete: parameters: - in: path - name: filename + name: connectUrl required: true schema: type: string - in: path - name: jvmId + name: filename required: true schema: type: string responses: - "200": - description: OK + "204": + description: No Content "401": description: Not Authorized "403": @@ -486,17 +747,22 @@ paths: security: - SecurityScheme: [] tags: - - Recordings - /api/beta/matchExpressions: + - Archived Recordings + /api/beta/recordings/{jvmId}: get: - responses: - "200": - content: - application/json: - schema: + parameters: + - in: path + name: jvmId + required: true + schema: + type: string + responses: + "200": + content: + application/json: + schema: items: - additionalProperties: {} - type: object + $ref: '#/components/schemas/ArchivedRecording' type: array description: OK "401": @@ -506,20 +772,30 @@ paths: security: - SecurityScheme: [] tags: - - Match Expressions + - Archived Recordings post: + parameters: + - in: path + name: jvmId + required: true + schema: + type: string requestBody: content: - application/json: + application/x-www-form-urlencoded: schema: - $ref: '#/components/schemas/RequestData' + properties: + labels: + $ref: '#/components/schemas/JsonObject' + maxFiles: + format: int32 + type: integer + recording: + $ref: '#/components/schemas/FileUpload' + type: object responses: - "200": - content: - application/json: - schema: - $ref: '#/components/schemas/V2Response' - description: OK + "201": + description: Created "401": description: Not Authorized "403": @@ -527,8 +803,8 @@ paths: security: - SecurityScheme: [] tags: - - Match Expressions - /api/beta/matchExpressions/{id}: + - Archived Recordings + /api/v4/activedownload/{id}: get: parameters: - in: path @@ -540,33 +816,10 @@ paths: responses: "200": content: - application/json: + application/octet-stream: schema: - $ref: '#/components/schemas/MatchedExpression' - description: OK - "401": - description: Not Authorized - "403": - description: Not Allowed - security: - - SecurityScheme: [] - tags: - - Match Expressions - /api/beta/recordings/{connectUrl}/{filename}: - delete: - parameters: - - in: path - name: connectUrl - required: true - schema: - type: string - - in: path - name: filename - required: true - schema: - type: string - responses: - "200": + format: binary + type: string description: OK "401": description: Not Authorized @@ -575,46 +828,27 @@ paths: security: - SecurityScheme: [] tags: - - Recordings - /api/beta/recordings/{connectUrl}/{filename}/upload: + - Active Recordings Download + /api/v4/auth: post: - parameters: - - in: path - name: connectUrl - required: true - schema: - type: string - - in: path - name: filename - required: true - schema: - type: string responses: "200": + content: + application/json: + schema: + $ref: '#/components/schemas/AuthResponse' description: OK - "401": - description: Not Authorized - "403": - description: Not Allowed - security: - - SecurityScheme: [] tags: - - Recordings - /api/beta/recordings/{jvmId}: + - Auth + /api/v4/credentials: get: - parameters: - - in: path - name: jvmId - required: true - schema: - type: string responses: "200": content: application/json: schema: items: - $ref: '#/components/schemas/ArchivedRecording' + $ref: '#/components/schemas/CredentialMatchResult' type: array description: OK "401": @@ -624,62 +858,26 @@ paths: security: - SecurityScheme: [] tags: - - Recordings + - Credentials post: - parameters: - - in: path - name: jvmId - required: true - schema: - type: string requestBody: content: application/x-www-form-urlencoded: schema: properties: - labels: - $ref: '#/components/schemas/JsonObject' - maxFiles: - format: int32 - type: integer - recording: - $ref: '#/components/schemas/FileUpload' + matchExpression: + type: string + password: + type: string + username: + type: string type: object - responses: - "201": - description: Created - "401": - description: Not Authorized - "403": - description: Not Allowed - security: - - SecurityScheme: [] - tags: - - Recordings - /api/v1/grafana_dashboard_url: - get: - responses: - "200": - description: OK - tags: - - Health - /api/v1/grafana_datasource_url: - get: - responses: - "200": - description: OK - tags: - - Health - /api/v1/recordings: - get: responses: "200": content: application/json: schema: - items: - $ref: '#/components/schemas/ArchivedRecording' - type: array + $ref: '#/components/schemas/Credential' description: OK "401": description: Not Authorized @@ -688,25 +886,32 @@ paths: security: - SecurityScheme: [] tags: - - Recordings + - Credentials + /api/v4/credentials/test/{targetId}: post: + parameters: + - in: path + name: targetId + required: true + schema: + format: int64 + type: integer requestBody: content: application/x-www-form-urlencoded: schema: properties: - labels: - $ref: '#/components/schemas/JsonObject' - recording: - $ref: '#/components/schemas/FileUpload' + password: + type: string + username: + type: string type: object responses: "200": content: application/json: schema: - additionalProperties: {} - type: object + $ref: '#/components/schemas/CredentialTestResult' description: OK "401": description: Not Authorized @@ -715,15 +920,16 @@ paths: security: - SecurityScheme: [] tags: - - Recordings - /api/v1/recordings/{filename}: + - Credentials + /api/v4/credentials/{id}: delete: parameters: - in: path - name: filename + name: id required: true schema: - type: string + format: int64 + type: integer responses: "204": description: No Content @@ -734,18 +940,21 @@ paths: security: - SecurityScheme: [] tags: - - Recordings - /api/v1/reports/{recordingName}: + - Credentials get: - deprecated: true parameters: - in: path - name: recordingName + name: id required: true schema: - type: string + format: int64 + type: integer responses: "200": + content: + application/json: + schema: + $ref: '#/components/schemas/CredentialMatchResult' description: OK "401": description: Not Authorized @@ -754,11 +963,15 @@ paths: security: - SecurityScheme: [] tags: - - Reports - /api/v1/targets: + - Credentials + /api/v4/discovery: get: responses: "200": + content: + application/json: + schema: + $ref: '#/components/schemas/DiscoveryNode' description: OK "401": description: Not Authorized @@ -767,22 +980,19 @@ paths: security: - SecurityScheme: [] tags: - - Targets - /api/v1/targets/{connectUrl}/events: - get: - parameters: - - in: path - name: connectUrl - required: true - schema: - format: uri - type: string - - in: query - name: q - schema: - type: string + - Discovery + post: + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/JsonObject' responses: "200": + content: + application/json: + schema: + $ref: '#/components/schemas/PluginRegistration' description: OK "401": description: Not Authorized @@ -791,58 +1001,38 @@ paths: security: - SecurityScheme: [] tags: - - Events - /api/v1/targets/{connectUrl}/recordingOptions: - get: + - Discovery + /api/v4/discovery/{id}: + delete: parameters: - in: path - name: connectUrl + name: id required: true schema: - format: uri - type: string - responses: - "200": - description: OK - "401": - description: Not Authorized - "403": - description: Not Allowed - security: - - SecurityScheme: [] - tags: - - Recordings - patch: - parameters: - - in: path - name: connectUrl - required: true + $ref: '#/components/schemas/UUID' + - in: query + name: token schema: - format: uri type: string responses: - "200": - description: OK - "401": - description: Not Authorized - "403": - description: Not Allowed - security: - - SecurityScheme: [] + "204": + description: No Content tags: - - Recordings - /api/v1/targets/{connectUrl}/recordings: + - Discovery get: parameters: - in: path - name: connectUrl + name: id required: true schema: - format: uri + $ref: '#/components/schemas/UUID' + - in: query + name: token + schema: type: string responses: - "200": - description: OK + "204": + description: No Content "401": description: Not Authorized "403": @@ -850,118 +1040,45 @@ paths: security: - SecurityScheme: [] tags: - - Recordings + - Discovery post: parameters: - in: path - name: connectUrl - required: true - schema: - format: uri - type: string - responses: - "200": - description: OK - "401": - description: Not Authorized - "403": - description: Not Allowed - security: - - SecurityScheme: [] - tags: - - Recordings - /api/v1/targets/{connectUrl}/recordings/{recordingName}: - delete: - parameters: - - in: path - name: connectUrl - required: true - schema: - format: uri - type: string - - in: path - name: recordingName - required: true - schema: - type: string - responses: - "200": - description: OK - "401": - description: Not Authorized - "403": - description: Not Allowed - security: - - SecurityScheme: [] - tags: - - Recordings - patch: - parameters: - - in: path - name: connectUrl - required: true - schema: - format: uri - type: string - - in: path - name: recordingName + name: id required: true + schema: + $ref: '#/components/schemas/UUID' + - in: query + name: token schema: type: string requestBody: content: - text/plain: + application/json: schema: - type: string - responses: - "200": - description: OK - "401": - description: Not Authorized - "403": - description: Not Allowed - security: - - SecurityScheme: [] - tags: - - Recordings - /api/v1/targets/{connectUrl}/recordings/{recordingName}/upload: - post: - parameters: - - in: path - name: connectUrl - required: true - schema: - format: uri - type: string - - in: path - name: recordingName - required: true - schema: - type: string + items: + $ref: '#/components/schemas/DiscoveryNode' + type: array responses: - "200": - description: OK - "401": - description: Not Authorized - "403": - description: Not Allowed - security: - - SecurityScheme: [] + "201": + description: Created tags: - - Recordings - /api/v1/targets/{connectUrl}/snapshot: - post: + - Discovery + /api/v4/discovery_plugins: + get: parameters: - - in: path - name: connectUrl - required: true + - in: query + name: realm schema: - format: uri type: string responses: "200": content: - application/json: {} + application/json: + schema: + items: + $ref: '#/components/schemas/DiscoveryPlugin_Flat' + type: array description: OK "401": description: Not Authorized @@ -970,18 +1087,21 @@ paths: security: - SecurityScheme: [] tags: - - Recordings - /api/v1/targets/{connectUrl}/templates: + - Discovery + /api/v4/discovery_plugins/{id}: get: parameters: - in: path - name: connectUrl + name: id required: true schema: - format: uri - type: string + $ref: '#/components/schemas/UUID' responses: "200": + content: + application/json: + schema: + $ref: '#/components/schemas/DiscoveryPlugin' description: OK "401": description: Not Authorized @@ -990,28 +1110,24 @@ paths: security: - SecurityScheme: [] tags: - - Event Templates - /api/v1/targets/{connectUrl}/templates/{templateName}/type/{templateType}: + - Discovery + /api/v4/download/{encodedKey}: get: parameters: - in: path - name: connectUrl + name: encodedKey required: true schema: - format: uri type: string - - in: path - name: templateName - required: true + - in: query + name: f schema: type: string - - in: path - name: templateType - required: true - schema: - $ref: '#/components/schemas/TemplateType' responses: "200": + content: + application/json: + schema: {} description: OK "401": description: Not Authorized @@ -1020,23 +1136,15 @@ paths: security: - SecurityScheme: [] tags: - - Event Templates - /api/v1/targets/{targetId}/reports/{recordingName}: + - Archived Recordings + /api/v4/event_templates: get: - deprecated: true - parameters: - - in: path - name: recordingName - required: true - schema: - type: string - - in: path - name: targetId - required: true - schema: - type: string responses: "200": + content: + application/json: + schema: + $ref: '#/components/schemas/Template' description: OK "401": description: Not Authorized @@ -1045,8 +1153,7 @@ paths: security: - SecurityScheme: [] tags: - - Reports - /api/v1/templates: + - Event Templates post: requestBody: content: @@ -1058,6 +1165,10 @@ paths: type: object responses: "200": + content: + application/json: + schema: + $ref: '#/components/schemas/Template' description: OK "401": description: Not Authorized @@ -1067,7 +1178,7 @@ paths: - SecurityScheme: [] tags: - Event Templates - /api/v1/templates/{templateName}: + /api/v4/event_templates/{templateName}: delete: parameters: - in: path @@ -1076,8 +1187,8 @@ paths: schema: type: string responses: - "200": - description: OK + "204": + description: No Content "401": description: Not Authorized "403": @@ -1086,17 +1197,20 @@ paths: - SecurityScheme: [] tags: - Event Templates - /api/v2.1/auth: + /api/v4/grafana/{encodedKey}: post: + parameters: + - in: path + name: encodedKey + required: true + schema: + type: string responses: "200": - description: OK - tags: - - Auth - /api/v2.1/discovery: - get: - responses: - "200": + content: + text/plain: + schema: + type: string description: OK "401": description: Not Authorized @@ -1105,52 +1219,50 @@ paths: security: - SecurityScheme: [] tags: - - Discovery - /api/v2.1/logout: - post: + - Archived Recordings + /api/v4/grafana_dashboard_url: + get: responses: "200": + content: + application/json: + schema: + $ref: '#/components/schemas/DashboardUrl' description: OK tags: - - Auth - /api/v2.1/targets/{connectUrl}/templates/{templateName}/type/{templateType}: + - Health + /api/v4/grafana_datasource_url: get: - parameters: - - in: path - name: connectUrl - required: true - schema: - format: uri - type: string - - in: path - name: templateName - required: true - schema: - type: string - - in: path - name: templateType - required: true - schema: - $ref: '#/components/schemas/TemplateType' responses: "200": + content: + application/json: + schema: + $ref: '#/components/schemas/DatasourceUrl' description: OK - "401": - description: Not Authorized - "403": - description: Not Allowed - security: - - SecurityScheme: [] tags: - - Event Templates - /api/v2.2/credentials: + - Health + /api/v4/logout: + post: + responses: + "200": + content: + application/json: + schema: {} + description: OK + tags: + - Auth + /api/v4/matchExpressions: get: responses: "200": content: application/json: schema: - $ref: '#/components/schemas/V2Response' + items: + additionalProperties: {} + type: object + type: array description: OK "401": description: Not Authorized @@ -1159,23 +1271,20 @@ paths: security: - SecurityScheme: [] tags: - - Credentials + - Match Expressions post: requestBody: content: - application/x-www-form-urlencoded: + application/json: schema: - properties: - matchExpression: - type: string - password: - type: string - username: - type: string - type: object + $ref: '#/components/schemas/RequestData' responses: - "201": - description: Created + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/MatchedExpression' + description: OK "401": description: Not Authorized "403": @@ -1183,9 +1292,9 @@ paths: security: - SecurityScheme: [] tags: - - Credentials - /api/v2.2/credentials/{id}: - delete: + - Match Expressions + /api/v4/matchExpressions/{id}: + get: parameters: - in: path name: id @@ -1194,8 +1303,12 @@ paths: format: int64 type: integer responses: - "204": - description: No Content + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/MatchedExpression' + description: OK "401": description: Not Authorized "403": @@ -1203,87 +1316,64 @@ paths: security: - SecurityScheme: [] tags: - - Credentials + - Match Expressions + /api/v4/probes: get: - parameters: - - in: path - name: id - required: true - schema: - format: int64 - type: integer responses: "200": content: application/json: schema: - $ref: '#/components/schemas/V2Response' + items: + $ref: '#/components/schemas/ProbeTemplateResponse' + type: array description: OK - "401": - description: Not Authorized - "403": - description: Not Allowed - security: - - SecurityScheme: [] tags: - - Credentials - /api/v2.2/discovery: + - JMC Agent Templates post: requestBody: content: - application/json: + application/x-www-form-urlencoded: schema: - $ref: '#/components/schemas/JsonObject' + properties: + name: + type: string + probeTemplate: + $ref: '#/components/schemas/FileUpload' + type: object responses: "200": + content: + application/json: + schema: + $ref: '#/components/schemas/ProbeTemplate' description: OK - "401": - description: Not Authorized - "403": - description: Not Allowed - security: - - SecurityScheme: [] tags: - - Discovery - /api/v2.2/discovery/{id}: + - JMC Agent Templates + /api/v4/probes/{probeTemplateName}: delete: parameters: - in: path - name: id + name: probeTemplateName required: true - schema: - $ref: '#/components/schemas/UUID' - - in: query - name: token schema: type: string + responses: + "204": + description: No Content + tags: + - JMC Agent Templates + /api/v4/recordings: + get: responses: "200": content: application/json: schema: - additionalProperties: - additionalProperties: - type: string - type: object - type: object + items: + $ref: '#/components/schemas/ArchivedRecording' + type: array description: OK - tags: - - Discovery - get: - parameters: - - in: path - name: id - required: true - schema: - $ref: '#/components/schemas/UUID' - - in: query - name: token - schema: - type: string - responses: - "204": - description: No Content "401": description: Not Authorized "403": @@ -1291,43 +1381,26 @@ paths: security: - SecurityScheme: [] tags: - - Discovery + - Archived Recordings post: - parameters: - - in: path - name: id - required: true - schema: - $ref: '#/components/schemas/UUID' - - in: query - name: token - schema: - type: string requestBody: content: - application/json: + application/x-www-form-urlencoded: schema: - items: - $ref: '#/components/schemas/DiscoveryNode' - type: array + properties: + labels: + $ref: '#/components/schemas/JsonObject' + recording: + $ref: '#/components/schemas/FileUpload' + type: object responses: "200": content: application/json: schema: - additionalProperties: - additionalProperties: - type: string - type: object + additionalProperties: {} type: object description: OK - tags: - - Discovery - /api/v2.2/graphql: - get: - responses: - "200": - description: OK "401": description: Not Authorized "403": @@ -1335,11 +1408,18 @@ paths: security: - SecurityScheme: [] tags: - - Graph QL - post: + - Archived Recordings + /api/v4/recordings/{filename}: + delete: + parameters: + - in: path + name: filename + required: true + schema: + type: string responses: - "200": - description: OK + "204": + description: No Content "401": description: Not Authorized "403": @@ -1347,55 +1427,42 @@ paths: security: - SecurityScheme: [] tags: - - Graph QL - /api/v2/probes: + - Archived Recordings + /api/v4/reports/{encodedKey}: get: - responses: - "200": - description: OK - tags: - - JMC Agent - /api/v2/probes/{probeTemplateName}: - delete: parameters: - in: path - name: probeTemplateName - required: true - schema: - type: string - responses: - "200": - description: OK - tags: - - JMC Agent - post: - parameters: - - in: path - name: probeTemplateName + name: encodedKey required: true schema: type: string - requestBody: - content: - application/x-www-form-urlencoded: - schema: - properties: - probeTemplate: - $ref: '#/components/schemas/FileUpload' - type: object responses: "200": + content: + application/json: + schema: + additionalProperties: + $ref: '#/components/schemas/AnalysisResult' + type: object description: OK + "401": + description: Not Authorized + "403": + description: Not Allowed + security: + - SecurityScheme: [] tags: - - JMC Agent - /api/v2/rules: + - Reports + /api/v4/rules: get: responses: "200": content: application/json: schema: - $ref: '#/components/schemas/V2Response' + items: + $ref: '#/components/schemas/Rule' + type: array description: OK "401": description: Not Authorized @@ -1465,343 +1532,16 @@ paths: type: integer name: type: string - preservedArchives: - format: int32 - type: integer - type: object - responses: - "200": - content: - application/json: - schema: - $ref: '#/components/schemas/V2Response' - description: OK - "401": - description: Not Authorized - "403": - description: Not Allowed - security: - - SecurityScheme: [] - tags: - - Rules - /api/v2/rules/{name}: - delete: - parameters: - - in: path - name: name - required: true - schema: - type: string - - in: query - name: clean - schema: - type: boolean - responses: - "200": - content: - application/json: - schema: - $ref: '#/components/schemas/V2Response' - description: OK - "401": - description: Not Authorized - "403": - description: Not Allowed - security: - - SecurityScheme: [] - tags: - - Rules - get: - parameters: - - in: path - name: name - required: true - schema: - type: string - responses: - "200": - content: - application/json: - schema: - $ref: '#/components/schemas/V2Response' - description: OK - "401": - description: Not Authorized - "403": - description: Not Allowed - security: - - SecurityScheme: [] - tags: - - Rules - patch: - parameters: - - in: path - name: name - required: true - schema: - type: string - - in: query - name: clean - schema: - type: boolean - requestBody: - content: - application/json: - schema: - $ref: '#/components/schemas/JsonObject' - responses: - "200": - content: - application/json: - schema: - $ref: '#/components/schemas/V2Response' - description: OK - "401": - description: Not Authorized - "403": - description: Not Allowed - security: - - SecurityScheme: [] - tags: - - Rules - /api/v2/targets: - post: - parameters: - - in: query - name: dryrun - schema: - type: boolean - - in: query - name: storeCredentials - schema: - type: boolean - requestBody: - content: - application/json: - schema: - $ref: '#/components/schemas/Target' - application/x-www-form-urlencoded: - schema: - properties: - alias: - type: string - connectUrl: - format: uri - type: string - password: - type: string - username: - type: string - type: object - multipart/form-data: - schema: - properties: - alias: - type: string - connectUrl: - format: uri - type: string - password: - type: string - username: - type: string - type: object - responses: - "200": - description: OK - "401": - description: Not Authorized - "403": - description: Not Allowed - security: - - SecurityScheme: [] - tags: - - Custom Discovery - /api/v2/targets/{connectUrl}: - delete: - parameters: - - in: path - name: connectUrl - required: true - schema: - format: uri - type: string - responses: - "200": - description: OK - "401": - description: Not Authorized - "403": - description: Not Allowed - security: - - SecurityScheme: [] - tags: - - Custom Discovery - /api/v2/targets/{connectUrl}/events: - get: - parameters: - - in: path - name: connectUrl - required: true - schema: - format: uri - type: string - - in: query - name: q - schema: - type: string - responses: - "200": - content: - application/json: - schema: - $ref: '#/components/schemas/V2Response' - description: OK - "401": - description: Not Authorized - "403": - description: Not Allowed - security: - - SecurityScheme: [] - tags: - - Events - /api/v2/targets/{connectUrl}/probes: - delete: - parameters: - - in: path - name: connectUrl - required: true - schema: - format: uri - type: string - responses: - "200": - description: OK - tags: - - JMC Agent - get: - parameters: - - in: path - name: connectUrl - required: true - schema: - format: uri - type: string - responses: - "200": - description: OK - tags: - - JMC Agent - /api/v2/targets/{connectUrl}/probes/{probeTemplateName}: - post: - parameters: - - in: path - name: connectUrl - required: true - schema: - format: uri - type: string - - in: path - name: probeTemplateName - required: true - schema: - type: string - responses: - "200": - description: OK - tags: - - JMC Agent - /api/v2/targets/{connectUrl}/snapshot: - post: - parameters: - - in: path - name: connectUrl - required: true - schema: - format: uri - type: string - responses: - "200": - content: - application/json: {} - description: OK - "401": - description: Not Authorized - "403": - description: Not Allowed - security: - - SecurityScheme: [] - tags: - - Recordings - /api/v3/activedownload/{id}: - get: - parameters: - - in: path - name: id - required: true - schema: - format: int64 - type: integer - responses: - "200": - description: OK - "401": - description: Not Authorized - "403": - description: Not Allowed - security: - - SecurityScheme: [] - tags: - - Recordings - /api/v3/discovery: - get: - responses: - "200": - content: - application/json: - schema: - $ref: '#/components/schemas/DiscoveryNode' - description: OK - "401": - description: Not Authorized - "403": - description: Not Allowed - security: - - SecurityScheme: [] - tags: - - Discovery - /api/v3/discovery_plugins: - get: - parameters: - - in: query - name: realm - schema: - type: string - responses: - "200": - description: OK - "401": - description: Not Authorized - "403": - description: Not Allowed - security: - - SecurityScheme: [] - tags: - - Discovery - /api/v3/discovery_plugins/{id}: - get: - parameters: - - in: path - name: id - required: true - schema: - $ref: '#/components/schemas/UUID' + preservedArchives: + format: int32 + type: integer + type: object responses: "200": content: application/json: schema: - $ref: '#/components/schemas/DiscoveryPlugin' + $ref: '#/components/schemas/Rule' description: OK "401": description: Not Authorized @@ -1810,22 +1550,22 @@ paths: security: - SecurityScheme: [] tags: - - Discovery - /api/v3/download/{encodedKey}: - get: + - Rules + /api/v4/rules/{name}: + delete: parameters: - in: path - name: encodedKey + name: name required: true schema: type: string - in: query - name: f + name: clean schema: - type: string + type: boolean responses: - "200": - description: OK + "204": + description: No Content "401": description: Not Authorized "403": @@ -1833,17 +1573,20 @@ paths: security: - SecurityScheme: [] tags: - - Recordings - /api/v3/event_templates: + - Rules get: + parameters: + - in: path + name: name + required: true + schema: + type: string responses: "200": content: application/json: schema: - items: - $ref: '#/components/schemas/Template' - type: array + $ref: '#/components/schemas/Rule' description: OK "401": description: Not Authorized @@ -1852,60 +1595,29 @@ paths: security: - SecurityScheme: [] tags: - - Event Templates - post: - requestBody: - content: - application/x-www-form-urlencoded: - schema: - properties: - template: - $ref: '#/components/schemas/FileUpload' - type: object - responses: - "201": - description: Created - "401": - description: Not Authorized - "403": - description: Not Allowed - security: - - SecurityScheme: [] - tags: - - Event Templates - /api/v3/event_templates/{templateName}: - delete: + - Rules + patch: parameters: - in: path - name: templateName + name: name required: true schema: type: string - responses: - "204": - description: No Content - "401": - description: Not Authorized - "403": - description: Not Allowed - security: - - SecurityScheme: [] - tags: - - Event Templates - /api/v3/grafana/{encodedKey}: - post: - parameters: - - in: path - name: encodedKey - required: true + - in: query + name: clean schema: - type: string + type: boolean + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/JsonObject' responses: "200": content: - text/plain: + application/json: schema: - type: string + $ref: '#/components/schemas/Rule' description: OK "401": description: Not Authorized @@ -1914,86 +1626,73 @@ paths: security: - SecurityScheme: [] tags: - - Recordings - /api/v3/probes: + - Rules + /api/v4/targets: get: responses: "200": content: application/json: schema: - $ref: '#/components/schemas/V2Response' - description: OK - tags: - - JMC Agent - /api/v3/probes/{probeTemplateName}: - delete: - parameters: - - in: path - name: probeTemplateName - required: true - schema: - type: string - responses: - "200": + items: + $ref: '#/components/schemas/Target' + type: array description: OK + "401": + description: Not Authorized + "403": + description: Not Allowed + security: + - SecurityScheme: [] tags: - - JMC Agent + - Targets post: parameters: - - in: path - name: probeTemplateName - required: true + - in: query + name: dryrun schema: - type: string + type: boolean + - in: query + name: storeCredentials + schema: + type: boolean requestBody: content: + application/json: + schema: + $ref: '#/components/schemas/Target' application/x-www-form-urlencoded: schema: properties: - probeTemplate: - $ref: '#/components/schemas/FileUpload' + alias: + type: string + connectUrl: + format: uri + type: string + password: + type: string + username: + type: string + type: object + multipart/form-data: + schema: + properties: + alias: + type: string + connectUrl: + format: uri + type: string + password: + type: string + username: + type: string type: object - responses: - "200": - description: OK - tags: - - JMC Agent - /api/v3/reports/{encodedKey}: - get: - parameters: - - in: path - name: encodedKey - required: true - schema: - type: string - responses: - "200": - content: - application/json: - schema: - additionalProperties: - $ref: '#/components/schemas/AnalysisResult' - type: object - description: OK - "401": - description: Not Authorized - "403": - description: Not Allowed - security: - - SecurityScheme: [] - tags: - - Reports - /api/v3/targets: - get: responses: "200": content: application/json: schema: - items: - $ref: '#/components/schemas/Target' - type: array + $ref: '#/components/schemas/Target' description: OK "401": description: Not Authorized @@ -2002,8 +1701,8 @@ paths: security: - SecurityScheme: [] tags: - - Targets - /api/v3/targets/{id}: + - Custom Discovery + /api/v4/targets/{id}: delete: parameters: - in: path @@ -2013,8 +1712,8 @@ paths: format: int64 type: integer responses: - "200": - description: OK + "204": + description: No Content "401": description: Not Authorized "403": @@ -2046,7 +1745,7 @@ paths: - SecurityScheme: [] tags: - Targets - /api/v3/targets/{id}/event_templates: + /api/v4/targets/{id}/event_templates: get: parameters: - in: path @@ -2071,8 +1770,8 @@ paths: security: - SecurityScheme: [] tags: - - Event Templates - /api/v3/targets/{id}/event_templates/{templateType}/{templateName}: + - Target Event Templates + /api/v4/targets/{id}/event_templates/{templateType}/{templateName}: get: parameters: - in: path @@ -2093,6 +1792,10 @@ paths: $ref: '#/components/schemas/TemplateType' responses: "200": + content: + application/xml: + schema: + type: string description: OK "401": description: Not Authorized @@ -2101,8 +1804,8 @@ paths: security: - SecurityScheme: [] tags: - - Event Templates - /api/v3/targets/{id}/events: + - Target Event Templates + /api/v4/targets/{id}/events: get: parameters: - in: path @@ -2132,7 +1835,7 @@ paths: - SecurityScheme: [] tags: - Events - /api/v3/targets/{id}/probes: + /api/v4/targets/{id}/probes: delete: parameters: - in: path @@ -2142,10 +1845,10 @@ paths: format: int64 type: integer responses: - "200": - description: OK + "204": + description: No Content tags: - - JMC Agent + - JMC Agent Probes get: parameters: - in: path @@ -2159,11 +1862,13 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/V2Response' + items: + $ref: '#/components/schemas/ProbeResponse' + type: array description: OK tags: - - JMC Agent - /api/v3/targets/{id}/probes/{probeTemplateName}: + - JMC Agent Probes + /api/v4/targets/{id}/probes/{probeTemplateName}: post: parameters: - in: path @@ -2178,15 +1883,15 @@ paths: schema: type: string responses: - "200": - description: OK + "201": + description: Created tags: - - JMC Agent - /api/v3/targets/{id}/recordingOptions: + - JMC Agent Probes + /api/v4/targets/{targetId}/recordingOptions: get: parameters: - in: path - name: id + name: targetId required: true schema: format: int64 @@ -2206,11 +1911,11 @@ paths: security: - SecurityScheme: [] tags: - - Recordings + - Recording Options patch: parameters: - in: path - name: id + name: targetId required: true schema: format: int64 @@ -2242,12 +1947,12 @@ paths: security: - SecurityScheme: [] tags: - - Recordings - /api/v3/targets/{id}/recordings: + - Recording Options + /api/v4/targets/{targetId}/recordings: get: parameters: - in: path - name: id + name: targetId required: true schema: format: int64 @@ -2268,11 +1973,11 @@ paths: security: - SecurityScheme: [] tags: - - Recordings + - Active Recordings post: parameters: - in: path - name: id + name: targetId required: true schema: format: int64 @@ -2317,6 +2022,10 @@ paths: type: object responses: "200": + content: + application/json: + schema: + $ref: '#/components/schemas/LinkedRecordingDescriptor' description: OK "401": description: Not Authorized @@ -2325,21 +2034,25 @@ paths: security: - SecurityScheme: [] tags: - - Recordings - /api/v3/targets/{id}/snapshot: - post: + - Active Recordings + /api/v4/targets/{targetId}/recordings/{remoteId}: + delete: parameters: - in: path - name: id + name: remoteId + required: true + schema: + format: int64 + type: integer + - in: path + name: targetId required: true schema: format: int64 type: integer responses: - "200": - content: - application/json: {} - description: OK + "204": + description: No Content "401": description: Not Authorized "403": @@ -2347,9 +2060,8 @@ paths: security: - SecurityScheme: [] tags: - - Recordings - /api/v3/targets/{targetId}/recordings/{remoteId}: - delete: + - Active Recordings + get: parameters: - in: path name: remoteId @@ -2364,8 +2076,13 @@ paths: format: int64 type: integer responses: - "204": - description: No Content + "200": + content: + application/octet-stream: + schema: + format: binary + type: string + description: OK "401": description: Not Authorized "403": @@ -2373,7 +2090,7 @@ paths: security: - SecurityScheme: [] tags: - - Recordings + - Active Recordings patch: parameters: - in: path @@ -2407,8 +2124,8 @@ paths: security: - SecurityScheme: [] tags: - - Recordings - /api/v3/targets/{targetId}/recordings/{remoteId}/upload: + - Active Recordings + /api/v4/targets/{targetId}/recordings/{remoteId}/upload: post: parameters: - in: path @@ -2437,10 +2154,9 @@ paths: security: - SecurityScheme: [] tags: - - Recordings - /api/v3/targets/{targetId}/reports/{recordingId}: + - Active Recordings + /api/v4/targets/{targetId}/reports/{recordingId}: get: - deprecated: true parameters: - in: path name: recordingId @@ -2471,7 +2187,31 @@ paths: - SecurityScheme: [] tags: - Reports - /api/v3/tls/certs: + /api/v4/targets/{targetId}/snapshot: + post: + parameters: + - in: path + name: targetId + required: true + schema: + format: int64 + type: integer + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/LinkedRecordingDescriptor' + description: OK + "401": + description: Not Authorized + "403": + description: Not Allowed + security: + - SecurityScheme: [] + tags: + - Snapshots + /api/v4/tls/certs: get: responses: "200": @@ -2488,6 +2228,10 @@ paths: get: responses: "200": + content: + application/json: + schema: + $ref: '#/components/schemas/ApplicationHealth' description: OK tags: - Health diff --git a/schema/schema.graphql b/schema/schema.graphql index f471cf40e..0ee8fa1a6 100644 --- a/schema/schema.graphql +++ b/schema/schema.graphql @@ -52,12 +52,10 @@ type ArchivedRecording { archivedTime: BigInteger! doDelete: ArchivedRecording! doPutMetadata(metadataInput: MetadataLabelsInput): ArchivedRecording! - "URL for GET request to retrieve the JFR binary file content of this recording" downloadUrl: String jvmId: String metadata: Metadata name: String - "URL for GET request to retrieve a JSON formatted Automated Analysis Report of this recording" reportUrl: String size: BigInteger! } diff --git a/schema/update.bash b/schema/update.bash index 967bafc73..0fd286d32 100755 --- a/schema/update.bash +++ b/schema/update.bash @@ -1,8 +1,15 @@ #!/usr/bin/env bash +set -x + DIR="$(dirname "$(readlink -f "$0")")" -"${DIR}"/../mvnw -f "${DIR}/../pom.xml" -B -U clean compile test-compile +if ! command -v httpz && ! command -v wget; then + echo "No HTTPie or wget?" + exit 1 +fi + +"${DIR}"/../mvnw -f "${DIR}/../pom.xml" -B -U -Dspotless.check.skip clean compile test-compile "${DIR}"/../mvnw -f "${DIR}/../pom.xml" -B -U -Dmaven.test.skip -Dquarkus.quinoa=false -Dspotless.check.skip -Dquarkus.smallrye-openapi.info-title="Cryostat API" clean quarkus:dev & pid="$!" function cleanup() { @@ -16,12 +23,26 @@ while true; do if [ "${counter}" -gt 10 ]; then exit 1 fi - if wget --spider http://localhost:8181/health; then - break - else - counter=$((counter + 1)) - sleep "${2:-10}" + if command -v http; then + if http :8181/health/liveness; then + break + else + counter=$((counter + 1)) + sleep "${2:-10}" + fi + elif command -v wget; then + if wget --tries=1 --spider http://localhost:8181/health/liveness; then + break + else + counter=$((counter + 1)) + sleep "${2:-10}" + fi fi done -wget http://localhost:8181/api -O - | yq -P 'sort_keys(..)' > "${DIR}/openapi.yaml" -wget http://localhost:8181/api/v3/graphql/schema.graphql -O "${DIR}/schema.graphql" +if command -v http; then + http --pretty=format --body :8181/api | yq -P 'sort_keys(..)' > "${DIR}/openapi.yaml" + http --pretty=format --body :8181/api/v4/graphql/schema.graphql > "${DIR}/schema.graphql" +elif command -v wget; then + wget http://localhost:8181/api -O - | yq -P 'sort_keys(..)' > "${DIR}/openapi.yaml" + wget http://localhost:8181/api/v4/graphql/schema.graphql -O "${DIR}/schema.graphql" +fi diff --git a/src/main/java/io/cryostat/BuildInfo.java b/src/main/java/io/cryostat/BuildInfo.java index dce54b9fb..36343cb23 100644 --- a/src/main/java/io/cryostat/BuildInfo.java +++ b/src/main/java/io/cryostat/BuildInfo.java @@ -19,6 +19,7 @@ import java.io.InputStreamReader; import java.nio.charset.StandardCharsets; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; @@ -29,7 +30,7 @@ public class BuildInfo { private static final String RESOURCE_LOCATION = "META-INF/gitinfo"; - @Inject Logger logger; + @Inject @JsonIgnore Logger logger; private final GitInfo gitinfo = new GitInfo(); diff --git a/src/main/java/io/cryostat/ExceptionMappers.java b/src/main/java/io/cryostat/ExceptionMappers.java index 9a05221c7..03139f07b 100644 --- a/src/main/java/io/cryostat/ExceptionMappers.java +++ b/src/main/java/io/cryostat/ExceptionMappers.java @@ -35,6 +35,7 @@ import org.projectnessie.cel.tools.ScriptException; import software.amazon.awssdk.services.s3.model.NoSuchBucketException; import software.amazon.awssdk.services.s3.model.NoSuchKeyException; +import software.amazon.awssdk.services.s3.model.S3Exception; public class ExceptionMappers { @@ -56,6 +57,16 @@ public RestResponse mapNoSuchBucketException(NoSuchBucketException ex) { return RestResponse.status(HttpResponseStatus.BAD_GATEWAY.code()); } + @ServerExceptionMapper + public RestResponse mapS3Exception(S3Exception ex) { + logger.error(ex); + switch (ex.statusCode()) { + case 404: + return RestResponse.notFound(); + } + return RestResponse.status(HttpResponseStatus.BAD_GATEWAY.code()); + } + @ServerExceptionMapper public RestResponse mapConstraintViolationException(ConstraintViolationException ex) { logger.warn(ex); diff --git a/src/main/java/io/cryostat/Health.java b/src/main/java/io/cryostat/Health.java index 097efdf75..b9a867753 100644 --- a/src/main/java/io/cryostat/Health.java +++ b/src/main/java/io/cryostat/Health.java @@ -18,7 +18,6 @@ import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; -import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.concurrent.CompletableFuture; @@ -36,7 +35,6 @@ import jakarta.ws.rs.Path; import jakarta.ws.rs.Produces; import jakarta.ws.rs.core.MediaType; -import jakarta.ws.rs.core.Response; import org.apache.commons.lang3.StringUtils; import org.eclipse.microprofile.config.inject.ConfigProperty; import org.jboss.logging.Logger; @@ -72,7 +70,7 @@ class Health { @Blocking @Path("/health") @PermitAll - public Response health() { + public ApplicationHealth health() { CompletableFuture datasourceAvailable = new CompletableFuture<>(); CompletableFuture dashboardAvailable = new CompletableFuture<>(); CompletableFuture reportsAvailable = new CompletableFuture<>(); @@ -95,25 +93,15 @@ public Response health() { reportsAvailable.complete(true); } - return Response.ok( - Map.of( - "cryostatVersion", - String.format("v%s", version), - "build", - buildInfo, - "dashboardConfigured", - dashboardURL.isPresent(), - "dashboardAvailable", - dashboardAvailable.join(), - "datasourceConfigured", - datasourceURL.isPresent(), - "datasourceAvailable", - datasourceAvailable.join(), - "reportsConfigured", - reportsConfigured, - "reportsAvailable", - reportsAvailable.join())) - .build(); + return new ApplicationHealth( + String.format("v%s", version), + buildInfo, + dashboardURL.isPresent(), + dashboardAvailable.join(), + datasourceURL.isPresent(), + datasourceAvailable.join(), + reportsConfigured, + reportsAvailable.join()); } @GET @@ -128,23 +116,23 @@ public Response health() { public void liveness() {} @GET - @Path("/api/v1/grafana_dashboard_url") + @Path("/api/v4/grafana_dashboard_url") @PermitAll @Produces({MediaType.APPLICATION_JSON}) - public Response grafanaDashboardUrl() { + public DashboardUrl grafanaDashboardUrl() { String url = dashboardExternalURL.orElseGet( () -> dashboardURL.orElseThrow(() -> new BadRequestException())); - - return Response.ok(Map.of("grafanaDashboardUrl", url)).build(); + return new DashboardUrl(url); } @GET - @Path("/api/v1/grafana_datasource_url") + @Path("/api/v4/grafana_datasource_url") @PermitAll @Produces({MediaType.APPLICATION_JSON}) - public Response grafanaDatasourceUrl() { - return Response.ok(Map.of("grafanaDatasourceUrl", datasourceURL)).build(); + public DatasourceUrl grafanaDatasourceUrl() { + String url = datasourceURL.orElseThrow(() -> new BadRequestException()); + return new DatasourceUrl(url); } private void checkUri( @@ -180,4 +168,18 @@ private void checkUri( future.complete(false); } } + + static record ApplicationHealth( + String cryostatVersion, + BuildInfo build, + boolean dashboardConfigured, + boolean dashboardAvailable, + boolean datasourceConfigured, + boolean datasourceAvailable, + boolean reportsConfigured, + boolean reportsAvailable) {} + + static record DashboardUrl(String grafanaDashboardUrl) {} + + static record DatasourceUrl(String grafanaDatasourceUrl) {} } diff --git a/src/main/java/io/cryostat/JsonRequestFilter.java b/src/main/java/io/cryostat/JsonRequestFilter.java index c61651b27..6108380d0 100644 --- a/src/main/java/io/cryostat/JsonRequestFilter.java +++ b/src/main/java/io/cryostat/JsonRequestFilter.java @@ -39,11 +39,10 @@ public class JsonRequestFilter implements ContainerRequestFilter { static final Set disallowedFields = Set.of("id"); static final Set allowedPathPatterns = Set.of( - "/api/v2.2/discovery", - "/api/v2/rules/[\\w]+", - "/api/beta/matchExpressions", - "/api/v2.2/graphql", - "/api/v3/graphql"); + "/api/v4/discovery", + "/api/v4/rules/[\\w]+", + "/api/v4/matchExpressions", + "/api/v4/graphql"); private final Map compiledPatterns = new HashMap<>(); @Inject ObjectMapper objectMapper; diff --git a/src/main/java/io/cryostat/MessageCodecs.java b/src/main/java/io/cryostat/MessageCodecs.java index 81e7b1b5c..1ceb1ff62 100644 --- a/src/main/java/io/cryostat/MessageCodecs.java +++ b/src/main/java/io/cryostat/MessageCodecs.java @@ -15,7 +15,7 @@ */ package io.cryostat; -import io.cryostat.recordings.Recordings.LinkedRecordingDescriptor; +import io.cryostat.recordings.ActiveRecordings.LinkedRecordingDescriptor; import io.quarkus.runtime.StartupEvent; import io.quarkus.vertx.LocalEventBusCodec; diff --git a/src/main/java/io/cryostat/V2Response.java b/src/main/java/io/cryostat/V2Response.java deleted file mode 100644 index 6f0ccfd4f..000000000 --- a/src/main/java/io/cryostat/V2Response.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright The Cryostat Authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * 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. - */ -package io.cryostat; - -import java.util.Objects; - -import jakarta.annotation.Nullable; -import jakarta.ws.rs.core.Response; - -public record V2Response(Meta meta, Data data) { - public static V2Response json(Response.Status status, Object payload) { - Data data; - if (status.getFamily().equals(Response.Status.Family.CLIENT_ERROR) - || status.getFamily().equals(Response.Status.Family.SERVER_ERROR)) { - data = new ErrorData(payload); - } else { - data = new PayloadData(payload); - } - return new V2Response(new Meta("application/json", status.getReasonPhrase()), data); - } - - // FIXME the type and status should both come from an enum and be non-null - public record Meta(String type, String status) { - public Meta { - Objects.requireNonNull(type); - Objects.requireNonNull(status); - } - } - - interface Data {} - - public static class PayloadData implements Data { - @Nullable Object result; - - public PayloadData(Object payload) { - this.result = payload; - } - - public Object getResult() { - return result; - } - } - - public static class ErrorData implements Data { - String reason; - - public ErrorData(Object payload) { - if (payload instanceof Exception) { - this.reason = ((Exception) Objects.requireNonNull(payload)).getMessage(); - } else { - this.reason = Objects.requireNonNull(payload).toString(); - } - } - - public String getReason() { - return reason; - } - } -} diff --git a/src/main/java/io/cryostat/credentials/CredentialCheck.java b/src/main/java/io/cryostat/credentials/CredentialCheck.java deleted file mode 100644 index 62497cd42..000000000 --- a/src/main/java/io/cryostat/credentials/CredentialCheck.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright The Cryostat Authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * 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. - */ -package io.cryostat.credentials; - -import java.net.URI; -import java.net.URISyntaxException; -import java.util.Optional; - -import io.cryostat.V2Response; -import io.cryostat.targets.Target; -import io.cryostat.targets.TargetConnectionManager; - -import io.smallrye.common.annotation.Blocking; -import io.smallrye.mutiny.Uni; -import jakarta.annotation.security.RolesAllowed; -import jakarta.inject.Inject; -import jakarta.ws.rs.POST; -import jakarta.ws.rs.Path; -import jakarta.ws.rs.core.Response.Status; -import org.jboss.resteasy.reactive.RestForm; -import org.jboss.resteasy.reactive.RestPath; - -@Path("") -public class CredentialCheck { - - @Inject TargetConnectionManager connectionManager; - - @POST - @Blocking - @RolesAllowed("read") - @Path("/api/beta/credentials/{connectUrl}") - public Uni checkCredentialForTarget( - @RestPath String connectUrl, @RestForm String username, @RestForm String password) - throws URISyntaxException { - Target target = Target.getTargetByConnectUrl(new URI(connectUrl)); - return connectionManager - .executeDirect( - target, - Optional.empty(), - (conn) -> { - conn.connect(); - return CredentialTestResult.NA; - }) - .onFailure() - .recoverWithUni( - () -> { - Credential cred = new Credential(); - cred.username = username; - cred.password = password; - return connectionManager - .executeDirect( - target, - Optional.of(cred), - (conn) -> { - conn.connect(); - return CredentialTestResult.SUCCESS; - }) - .onFailure( - t -> - connectionManager.isJmxAuthFailure(t) - || connectionManager.isAgentAuthFailure( - t)) - .recoverWithItem(t -> CredentialTestResult.FAILURE); - }) - .map(r -> V2Response.json(Status.OK, r)); - } - - static enum CredentialTestResult { - SUCCESS, - FAILURE, - NA; - } -} diff --git a/src/main/java/io/cryostat/credentials/Credentials.java b/src/main/java/io/cryostat/credentials/Credentials.java index 4d81ab6d4..2eee81ed7 100644 --- a/src/main/java/io/cryostat/credentials/Credentials.java +++ b/src/main/java/io/cryostat/credentials/Credentials.java @@ -15,24 +15,30 @@ */ package io.cryostat.credentials; -import java.net.URI; -import java.util.HashMap; +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.Collection; import java.util.List; -import java.util.Map; import java.util.Objects; +import java.util.Optional; -import io.cryostat.V2Response; import io.cryostat.expressions.MatchExpression; import io.cryostat.expressions.MatchExpression.TargetMatcher; +import io.cryostat.targets.Target; +import io.cryostat.targets.TargetConnectionManager; +import io.smallrye.common.annotation.Blocking; +import io.smallrye.mutiny.Uni; import jakarta.annotation.security.RolesAllowed; import jakarta.inject.Inject; import jakarta.transaction.Transactional; import jakarta.ws.rs.DELETE; import jakarta.ws.rs.GET; +import jakarta.ws.rs.InternalServerErrorException; import jakarta.ws.rs.POST; import jakarta.ws.rs.Path; -import jakarta.ws.rs.core.Response; +import jakarta.ws.rs.core.Context; +import jakarta.ws.rs.core.UriInfo; import org.jboss.logging.Logger; import org.jboss.resteasy.reactive.RestForm; import org.jboss.resteasy.reactive.RestPath; @@ -40,44 +46,89 @@ import org.jboss.resteasy.reactive.RestResponse.ResponseBuilder; import org.projectnessie.cel.tools.ScriptException; -@Path("/api/v2.2/credentials") +@Path("/api/v4/credentials") public class Credentials { + @Inject TargetConnectionManager connectionManager; @Inject TargetMatcher targetMatcher; @Inject Logger logger; + @POST + @Blocking + @RolesAllowed("read") + @Path("/test/{targetId}") + public Uni checkCredentialForTarget( + @RestPath long targetId, @RestForm String username, @RestForm String password) + throws URISyntaxException { + Target target = Target.getTargetById(targetId); + return connectionManager + .executeDirect( + target, + Optional.empty(), + (conn) -> { + conn.connect(); + return CredentialTestResult.NA; + }) + .onFailure() + .recoverWithUni( + () -> { + Credential cred = new Credential(); + cred.username = username; + cred.password = password; + return connectionManager + .executeDirect( + target, + Optional.of(cred), + (conn) -> { + conn.connect(); + return CredentialTestResult.SUCCESS; + }) + .onFailure( + t -> + connectionManager.isJmxAuthFailure(t) + || connectionManager.isAgentAuthFailure( + t)) + .recoverWithItem(t -> CredentialTestResult.FAILURE); + }); + } + + @Blocking @GET @RolesAllowed("read") - public V2Response list() { - List credentials = Credential.listAll(); - return V2Response.json( - Response.Status.OK, - credentials.stream() - .map( - c -> { - try { - return Credentials.safeResult(c, targetMatcher); - } catch (ScriptException e) { - logger.warn(e); - return null; - } - }) - .filter(Objects::nonNull) - .toList()); + public List list() { + return Credential.listAll().stream() + .map( + c -> { + try { + return safeResult(c, targetMatcher); + } catch (ScriptException e) { + logger.warn(e); + return null; + } + }) + .filter(Objects::nonNull) + .toList(); } + @Blocking @GET @RolesAllowed("read") @Path("/{id}") - public V2Response get(@RestPath long id) throws ScriptException { - Credential credential = Credential.find("id", id).singleResult(); - return V2Response.json(Response.Status.OK, safeMatchedResult(credential, targetMatcher)); + public CredentialMatchResult get(@RestPath long id) { + try { + Credential credential = Credential.find("id", id).singleResult(); + return safeResult(credential, targetMatcher); + } catch (ScriptException e) { + logger.error("Error retrieving credential", e); + throw new InternalServerErrorException(e); + } } @Transactional @POST @RolesAllowed("write") - public RestResponse create( + public RestResponse create( + @Context UriInfo uriInfo, @RestForm String matchExpression, @RestForm String username, @RestForm String password) { @@ -88,7 +139,9 @@ public RestResponse create( credential.username = username; credential.password = password; credential.persist(); - return ResponseBuilder.created(URI.create("/api/v2.2/credentials/" + credential.id)) + return ResponseBuilder.created( + uriInfo.getAbsolutePathBuilder().path(Long.toString(credential.id)).build()) + .entity(credential) .build(); } @@ -100,33 +153,30 @@ public void delete(@RestPath long id) { Credential.find("id", id).singleResult().delete(); } - static Map notificationResult(Credential credential) throws ScriptException { - Map result = new HashMap<>(); - result.put("id", credential.id); - result.put("matchExpression", credential.matchExpression); + static CredentialMatchResult notificationResult(Credential credential) throws ScriptException { // TODO populating this on the credential post-persist hook leads to a database validation // error because the expression ends up getting defined twice with the same ID, somehow. // Populating this field with 0 means the UI is inaccurate when a new credential is first // defined, but after a refresh the data correctly updates. - result.put("numMatchingTargets", 0); - return result; + return new CredentialMatchResult(credential, List.of()); } - static Map safeResult(Credential credential, TargetMatcher matcher) + static CredentialMatchResult safeResult(Credential credential, TargetMatcher matcher) throws ScriptException { - Map result = new HashMap<>(); - result.put("id", credential.id); - result.put("matchExpression", credential.matchExpression); - result.put( - "numMatchingTargets", matcher.match(credential.matchExpression).targets().size()); - return result; + var matchedTargets = matcher.match(credential.matchExpression).targets(); + return new CredentialMatchResult(credential, matchedTargets); } - static Map safeMatchedResult(Credential credential, TargetMatcher matcher) - throws ScriptException { - Map result = new HashMap<>(); - result.put("matchExpression", credential.matchExpression); - result.put("targets", matcher.match(credential.matchExpression).targets()); - return result; + static record CredentialMatchResult( + long id, MatchExpression matchExpression, Collection targets) { + CredentialMatchResult(Credential credential, Collection targets) { + this(credential.id, credential.matchExpression, new ArrayList<>(targets)); + } + } + + static enum CredentialTestResult { + SUCCESS, + FAILURE, + NA; } } diff --git a/src/main/java/io/cryostat/discovery/CustomDiscovery.java b/src/main/java/io/cryostat/discovery/CustomDiscovery.java index 09d37d612..d53719053 100644 --- a/src/main/java/io/cryostat/discovery/CustomDiscovery.java +++ b/src/main/java/io/cryostat/discovery/CustomDiscovery.java @@ -26,7 +26,6 @@ import java.util.regex.Pattern; import io.cryostat.ConfigProperties; -import io.cryostat.V2Response; import io.cryostat.credentials.Credential; import io.cryostat.expressions.MatchExpression; import io.cryostat.targets.JvmIdException; @@ -40,12 +39,15 @@ import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; import jakarta.transaction.Transactional; +import jakarta.ws.rs.BadRequestException; import jakarta.ws.rs.Consumes; import jakarta.ws.rs.DELETE; +import jakarta.ws.rs.InternalServerErrorException; import jakarta.ws.rs.POST; import jakarta.ws.rs.Path; +import jakarta.ws.rs.core.Context; import jakarta.ws.rs.core.MediaType; -import jakarta.ws.rs.core.Response; +import jakarta.ws.rs.core.UriInfo; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.exception.ExceptionUtils; import org.eclipse.microprofile.config.inject.ConfigProperty; @@ -55,6 +57,7 @@ import org.jboss.resteasy.reactive.RestPath; import org.jboss.resteasy.reactive.RestQuery; import org.jboss.resteasy.reactive.RestResponse; +import org.jboss.resteasy.reactive.RestResponse.ResponseBuilder; @ApplicationScoped @Path("") @@ -74,38 +77,38 @@ public class CustomDiscovery { @Transactional(rollbackOn = {JvmIdException.class}) @POST - @Path("/api/v2/targets") + @Path("/api/v4/targets") @Consumes(MediaType.APPLICATION_JSON) @RolesAllowed("write") - public Response create( - Target target, @RestQuery boolean dryrun, @RestQuery boolean storeCredentials) { + public RestResponse create( + @Context UriInfo uriInfo, + Target target, + @RestQuery boolean dryrun, + @RestQuery boolean storeCredentials) { try { if (!uriUtil.validateUri(target.connectUrl)) { - return Response.status(Response.Status.BAD_REQUEST) - .entity( - String.format( - "The provided URI \"%s\" is unacceptable with the" - + " current URI range settings.", - target.connectUrl)) - .build(); + throw new BadRequestException( + String.format( + "The provided URI \"%s\" is unacceptable with the" + + " current URI range settings.", + target.connectUrl)); } // Continue with target creation if URI is valid... } catch (Exception e) { logger.error("Target validation failed", e); - return Response.status(Response.Status.BAD_REQUEST) - .entity(V2Response.json(Response.Status.BAD_REQUEST, e)) - .build(); + throw new BadRequestException("Target validation failed", e); } // TODO handle credentials embedded in JSON body - return doV2Create(target, Optional.empty(), dryrun, storeCredentials); + return doCreate(uriInfo, target, Optional.empty(), dryrun, storeCredentials); } @Transactional @POST - @Path("/api/v2/targets") + @Path("/api/v4/targets") @Consumes({MediaType.MULTIPART_FORM_DATA, MediaType.APPLICATION_FORM_URLENCODED}) @RolesAllowed("write") - public Response create( + public RestResponse createForm( + @Context UriInfo uriInfo, @RestForm URI connectUrl, @RestForm String alias, @RestForm String username, @@ -126,10 +129,11 @@ public Response create( credential.password = password; } - return doV2Create(target, Optional.ofNullable(credential), dryrun, storeCredentials); + return doCreate(uriInfo, target, Optional.ofNullable(credential), dryrun, storeCredentials); } - Response doV2Create( + RestResponse doCreate( + UriInfo uriInfo, Target target, Optional credential, boolean dryrun, @@ -137,21 +141,15 @@ Response doV2Create( try { target.connectUrl = sanitizeConnectUrl(target.connectUrl.toString()); if (!uriUtil.validateUri(target.connectUrl)) { - return Response.status(Response.Status.BAD_REQUEST) - .entity( - String.format( - "The provided URI \"%s\" is unacceptable with the" - + " current URI range settings.", - target.connectUrl)) - .build(); + throw new BadRequestException( + String.format( + "The provided URI \"%s\" is unacceptable with the" + + " current URI range settings.", + target.connectUrl)); } if (Target.find("connectUrl", target.connectUrl).singleResultOptional().isPresent()) { - return Response.status(Response.Status.BAD_REQUEST.getStatusCode()) - .entity( - V2Response.json( - Response.Status.BAD_REQUEST, "Duplicate connection URL")) - .build(); + throw new BadRequestException("Duplicate connection URL"); } try { @@ -166,14 +164,8 @@ Response doV2Create( .atMost(timeout); if (Target.find("jvmId", jvmId).count() > 0) { - return Response.status(Response.Status.BAD_REQUEST.getStatusCode()) - .entity( - V2Response.json( - Response.Status.BAD_REQUEST, - String.format( - "Target with JVM ID \"%s\" already discovered", - jvmId))) - .build(); + throw new BadRequestException( + String.format("Target with JVM ID \"%s\" already discovered", jvmId)); } } catch (Exception e) { logger.error("Target connection failed", e); @@ -185,15 +177,11 @@ Response doV2Create( : connectionManager.isServiceTypeFailure(e) ? "Unexpected service type on port" : "Target connection failed"; - return Response.status(Response.Status.BAD_REQUEST.getStatusCode()) - .entity(V2Response.json(Response.Status.BAD_REQUEST, msg)) - .build(); + throw new BadRequestException(msg, e); } if (dryrun) { - return Response.accepted() - .entity(V2Response.json(Response.Status.ACCEPTED, target)) - .build(); + return ResponseBuilder.accepted(target).build(); } target.persist(); @@ -212,48 +200,31 @@ Response doV2Create( node.persist(); realm.persist(); - return Response.created(URI.create("/api/v3/targets/" + target.id)) - .entity(V2Response.json(Response.Status.CREATED, target)) + return ResponseBuilder.created( + uriInfo.getAbsolutePathBuilder().path(Long.toString(target.id)).build()) + .entity(target) .build(); } catch (Exception e) { if (ExceptionUtils.indexOfType(e, ConstraintViolationException.class) >= 0) { logger.warn("Invalid target definition", e); - return Response.status(Response.Status.BAD_REQUEST.getStatusCode()) - .entity( - V2Response.json( - Response.Status.BAD_REQUEST, "Duplicate connection URL")) - .build(); + throw new BadRequestException("Duplicate connection URL", e); } logger.error("Unknown error", e); - return Response.serverError() - .entity(V2Response.json(Response.Status.INTERNAL_SERVER_ERROR, e)) - .build(); + throw new InternalServerErrorException(e); } } @Transactional @DELETE - @Path("/api/v2/targets/{connectUrl}") - @RolesAllowed("write") - public Response delete(@RestPath URI connectUrl) throws URISyntaxException { - Target target = Target.getTargetByConnectUrl(connectUrl); - return Response.status(RestResponse.Status.PERMANENT_REDIRECT) - .location(URI.create(String.format("/api/v3/targets/%d", target.id))) - .build(); - } - - @Transactional - @DELETE - @Path("/api/v3/targets/{id}") + @Path("/api/v4/targets/{id}") @RolesAllowed("write") - public Response delete(@RestPath long id) throws URISyntaxException { + public void delete(@RestPath long id) throws URISyntaxException { Target target = Target.find("id", id).singleResult(); DiscoveryNode realm = DiscoveryNode.getRealm(REALM).orElseThrow(); realm.children.remove(target.discoveryNode); target.discoveryNode.parent = null; realm.persist(); target.delete(); - return Response.noContent().build(); } private URI sanitizeConnectUrl(String in) throws URISyntaxException, MalformedURLException { diff --git a/src/main/java/io/cryostat/discovery/Discovery.java b/src/main/java/io/cryostat/discovery/Discovery.java index e45a28705..ad8bdb7db 100644 --- a/src/main/java/io/cryostat/discovery/Discovery.java +++ b/src/main/java/io/cryostat/discovery/Discovery.java @@ -37,6 +37,7 @@ import io.cryostat.targets.TargetConnectionManager; import io.cryostat.util.URIUtil; +import com.fasterxml.jackson.annotation.JsonView; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.nimbusds.jose.JOSEException; @@ -63,15 +64,12 @@ import jakarta.ws.rs.Produces; import jakarta.ws.rs.core.Context; import jakarta.ws.rs.core.MediaType; -import jakarta.ws.rs.core.Response; import jakarta.ws.rs.core.UriBuilder; import org.apache.commons.lang3.StringUtils; import org.eclipse.microprofile.config.inject.ConfigProperty; import org.jboss.logging.Logger; import org.jboss.resteasy.reactive.RestPath; import org.jboss.resteasy.reactive.RestQuery; -import org.jboss.resteasy.reactive.RestResponse; -import org.jboss.resteasy.reactive.RestResponse.ResponseBuilder; import org.quartz.Job; import org.quartz.JobBuilder; import org.quartz.JobDataMap; @@ -151,25 +149,16 @@ void onStop(@Observes ShutdownEvent evt) throws SchedulerException { } @GET - @Path("/api/v2.1/discovery") - @RolesAllowed("read") - public Response getv21() { - return Response.status(RestResponse.Status.PERMANENT_REDIRECT) - .location(URI.create("/api/v3/discovery")) - .build(); - } - - @GET - @Path("/api/v3/discovery") + @Path("/api/v4/discovery") @RolesAllowed("read") public DiscoveryNode get() { return DiscoveryNode.getUniverse(); } @GET - @Path("/api/v2.2/discovery/{id}") + @Path("/api/v4/discovery/{id}") @RolesAllowed("read") - public RestResponse checkRegistration( + public void checkRegistration( @Context RoutingContext ctx, @RestPath UUID id, @RestQuery String token) throws SocketException, UnknownHostException, @@ -179,17 +168,16 @@ public RestResponse checkRegistration( URISyntaxException { DiscoveryPlugin plugin = DiscoveryPlugin.find("id", id).singleResult(); jwtValidator.validateJwt(ctx, plugin, token, true); - return ResponseBuilder.ok().build(); } @Transactional @POST - @Path("/api/v2.2/discovery") + @Path("/api/v4/discovery") @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) @RolesAllowed("write") @SuppressFBWarnings("DLS_DEAD_LOCAL_STORE") - public Response register(@Context RoutingContext ctx, JsonObject body) + public PluginRegistration register(@Context RoutingContext ctx, JsonObject body) throws URISyntaxException, MalformedURLException, JOSEException, @@ -206,13 +194,11 @@ public Response register(@Context RoutingContext ctx, JsonObject body) // URI range validation if (!uriUtil.validateUri(callbackUri)) { - return Response.status(Response.Status.BAD_REQUEST) - .entity( - String.format( - "cryostat.target.callback of \"%s\" is unacceptable with the" - + " current URI range settings", - callbackUri)) - .build(); + throw new BadRequestException( + String.format( + "cryostat.target.callback of \"%s\" is unacceptable with the" + + " current URI range settings", + callbackUri)); } // TODO apply URI range validation to the remote address @@ -295,39 +281,21 @@ public Response register(@Context RoutingContext ctx, JsonObject body) String token = jwtFactory.createDiscoveryPluginJwt(plugin, remoteAddress, location); // TODO implement more generic env map passing by some platform detection - // strategy or - // generalized config properties + // strategy or generalized config properties var envMap = new HashMap(); String insightsProxy = System.getenv("INSIGHTS_PROXY"); if (StringUtils.isNotBlank(insightsProxy)) { envMap.put("INSIGHTS_SVC", insightsProxy); } - return Response.created(location) - .entity( - Map.of( - "meta", - Map.of( - "mimeType", "JSON", - "status", "OK"), - "data", - Map.of( - "result", - Map.of( - "id", - plugin.id.toString(), - "token", - token, - "env", - envMap)))) - .build(); + return new PluginRegistration(plugin.id.toString(), token, envMap); } @Transactional @POST - @Path("/api/v2.2/discovery/{id}") + @Path("/api/v4/discovery/{id}") @Consumes(MediaType.APPLICATION_JSON) @PermitAll - public Map> publish( + public void publish( @Context RoutingContext ctx, @RestPath UUID id, @RestQuery String token, @@ -351,22 +319,13 @@ public Map> publish( b.persist(); } plugin.persist(); - - return Map.of( - "meta", - Map.of( - "mimeType", "JSON", - "status", "OK"), - "data", - Map.of("result", plugin.id.toString())); } @Transactional @DELETE - @Path("/api/v2.2/discovery/{id}") + @Path("/api/v4/discovery/{id}") @PermitAll - public Map> deregister( - @Context RoutingContext ctx, @RestPath UUID id, @RestQuery String token) + public void deregister(@Context RoutingContext ctx, @RestPath UUID id, @RestQuery String token) throws SocketException, UnknownHostException, MalformedURLException, @@ -386,36 +345,23 @@ public Map> deregister( for (var key : jobKeys) { scheduler.deleteJob(key); } - plugin.delete(); - return Map.of( - "meta", - Map.of( - "mimeType", "JSON", - "status", "OK"), - "data", - Map.of("result", plugin.id.toString())); } @GET - @Path("/api/v3/discovery_plugins") + @JsonView(DiscoveryNode.Views.Flat.class) + @Path("/api/v4/discovery_plugins") @RolesAllowed("read") - public Response getPlugins(@RestQuery String realm) throws JsonProcessingException { + public List getPlugins(@RestQuery String realm) + throws JsonProcessingException { // TODO filter for the matching realm name within the DB query - List plugins = - DiscoveryPlugin.findAll().list().stream() - .filter(p -> StringUtils.isBlank(realm) || p.realm.name.equals(realm)) - .toList(); - return Response.ok() - .entity( - mapper.writerWithView(DiscoveryNode.Views.Flat.class) - .writeValueAsString(plugins)) - .type(MediaType.APPLICATION_JSON) - .build(); + return DiscoveryPlugin.findAll().list().stream() + .filter(p -> StringUtils.isBlank(realm) || p.realm.name.equals(realm)) + .toList(); } @GET - @Path("/api/v3/discovery_plugins/{id}") + @Path("/api/v4/discovery_plugins/{id}") @RolesAllowed("read") public DiscoveryPlugin getPlugin(@RestPath UUID id) throws JsonProcessingException { return DiscoveryPlugin.find("id", id).singleResult(); @@ -479,4 +425,6 @@ private InetAddress getRemoteAddress(RoutingContext ctx) { } return addr; } + + static record PluginRegistration(String id, String token, Map env) {} } diff --git a/src/main/java/io/cryostat/discovery/DiscoveryJwtFactory.java b/src/main/java/io/cryostat/discovery/DiscoveryJwtFactory.java index e033a1410..ffedc557f 100644 --- a/src/main/java/io/cryostat/discovery/DiscoveryJwtFactory.java +++ b/src/main/java/io/cryostat/discovery/DiscoveryJwtFactory.java @@ -55,7 +55,7 @@ public class DiscoveryJwtFactory { public static final String RESOURCE_CLAIM = "resource"; public static final String REALM_CLAIM = "realm"; - static final String DISCOVERY_API_PATH = "/api/v2.2/discovery/"; + static final String DISCOVERY_API_PATH = "/api/v4/discovery/"; @Inject JWSSigner signer; @Inject JWSVerifier verifier; diff --git a/src/main/java/io/cryostat/events/EventTemplates.java b/src/main/java/io/cryostat/events/EventTemplates.java index dade41164..5beac994b 100644 --- a/src/main/java/io/cryostat/events/EventTemplates.java +++ b/src/main/java/io/cryostat/events/EventTemplates.java @@ -16,17 +16,15 @@ package io.cryostat.events; import java.io.IOException; -import java.net.URI; import java.util.ArrayList; import java.util.List; +import io.cryostat.core.FlightRecorderException; import io.cryostat.core.templates.MutableTemplateService.InvalidXmlException; import io.cryostat.libcryostat.sys.FileSystem; import io.cryostat.libcryostat.templates.InvalidEventTemplateException; import io.cryostat.libcryostat.templates.Template; import io.cryostat.libcryostat.templates.TemplateType; -import io.cryostat.targets.Target; -import io.cryostat.util.HttpMimeType; import io.smallrye.common.annotation.Blocking; import jakarta.annotation.security.RolesAllowed; @@ -34,17 +32,20 @@ import jakarta.ws.rs.BadRequestException; import jakarta.ws.rs.DELETE; import jakarta.ws.rs.GET; +import jakarta.ws.rs.NotFoundException; import jakarta.ws.rs.POST; import jakarta.ws.rs.Path; -import jakarta.ws.rs.core.HttpHeaders; -import jakarta.ws.rs.core.Response; +import jakarta.ws.rs.core.Context; +import jakarta.ws.rs.core.UriInfo; +import org.apache.commons.lang3.StringUtils; import org.jboss.logging.Logger; import org.jboss.resteasy.reactive.RestForm; import org.jboss.resteasy.reactive.RestPath; import org.jboss.resteasy.reactive.RestResponse; +import org.jboss.resteasy.reactive.RestResponse.ResponseBuilder; import org.jboss.resteasy.reactive.multipart.FileUpload; -@Path("") +@Path("/api/v4/event_templates") public class EventTemplates { public static final Template ALL_EVENTS_TEMPLATE = @@ -62,140 +63,62 @@ public class EventTemplates { @Inject Logger logger; @GET - @Path("/api/v1/targets/{connectUrl}/templates") + @Blocking @RolesAllowed("read") - public Response listTemplatesV1(@RestPath URI connectUrl) throws Exception { - Target target = Target.getTargetByConnectUrl(connectUrl); - return Response.status(RestResponse.Status.PERMANENT_REDIRECT) - .location( - URI.create(String.format("/api/v3/targets/%d/event_templates", target.id))) - .build(); + public List