-
Notifications
You must be signed in to change notification settings - Fork 50
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Harmony 1681 - Service Image Tag Endpoint (#535)
* HARMONY-1681: Add endpoint for service-images * HARMONY-1681: Change service tag routes to only use token auth * HARMONY-1681: Rename routes to /service-image-tag * HARMONY-1681: Add documentation for service-image-tags endpoint. * HARMONY-1681: Add line in docs about requirement of bearer token * HARMONY-1681: Fix linter issues and add JSDocs * HARMONY-1681: Add info about tag requirements to docs * HARMONY-1599: Update dependencies. * HARMONY-1681: Make /service-image-tag GET routes available via OAUTH * HARMONY-1681: Add link to docker tag reqs in comment * HARMONY-1681: Add Batchee and Stitchee to expected test output for tag listings * HARMONY-1681: Prevent invalid env vars from causing crashes when handling service tag requests --------- Co-authored-by: Chris Durbin <christopher.d.durbin@nasa.gov>
- Loading branch information
1 parent
737b822
commit 6e9c8a1
Showing
11 changed files
with
662 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,160 @@ | ||
|
||
import { Response, NextFunction } from 'express'; | ||
import HarmonyRequest from '../models/harmony-request'; | ||
import { getEdlGroupInformation } from '../util/edl-api'; | ||
|
||
const harmonyTaskServices = [ | ||
'work-item-scheduler', | ||
'work-item-updater', | ||
'work-reaper', | ||
'work-failer', | ||
]; | ||
|
||
/** | ||
* Compute the map of services to tags. Harmony core services are excluded. | ||
* @returns The map of canonical service names to image tags. | ||
*/ | ||
function getImageTagMap(): {} { | ||
const imageMap = {}; | ||
for (const v of Object.keys(process.env)) { | ||
if (v.endsWith('_IMAGE')) { | ||
const serviceName = v.slice(0, -6).toLowerCase().replaceAll('_', '-'); | ||
// add in any services that are not Harmony core task services | ||
if (!harmonyTaskServices.includes(serviceName)) { | ||
const image = process.env[v]; | ||
const match = image.match(/.*:(.*)/); | ||
if (match) { | ||
const tag = match[1] || ''; | ||
imageMap[serviceName] = tag; | ||
} | ||
} | ||
} | ||
} | ||
|
||
return imageMap; | ||
} | ||
|
||
/** | ||
* Validate that the service exists | ||
* @param req - The request object | ||
* @param res - The response object - will be used to send an error if the validation fails | ||
* @returns A Promise containing `true` if the service exists, `false` otherwise | ||
*/ | ||
async function validateServiceExists( | ||
res: Response, service: string, | ||
): Promise<boolean> { | ||
const imageMap = getImageTagMap(); | ||
if (!imageMap[service]) { | ||
res.statusCode = 404; | ||
const message = `Service ${service} does not exist.\nThe existing services and their images are\n${JSON.stringify(imageMap, null, 2)}`; | ||
res.send(message); | ||
return false; | ||
} | ||
return true; | ||
} | ||
|
||
/** | ||
* Validate that the user is in the deployers or the admin group | ||
* @param req - The request object | ||
* @param res - The response object - will be used to send an error if the validation fails | ||
* @returns A Promise containing `true` if the user in in either group, `false` otherwise | ||
*/ | ||
async function validateUserIsInDeployerOrAdminGroup( | ||
req: HarmonyRequest, res: Response, | ||
): Promise<boolean> { | ||
const { isAdmin, isServiceDeployer } = await getEdlGroupInformation( | ||
req.user, req.context.logger, | ||
); | ||
|
||
if (!isServiceDeployer && !isAdmin) { | ||
res.statusCode = 403; | ||
res.send(`User ${req.user} is not in the service deployers or admin EDL groups`); | ||
return false; | ||
} | ||
return true; | ||
} | ||
|
||
/** | ||
* Verify that the given tag is valid. Send an error if it is not. | ||
* @param req - The request object | ||
* @param res - The response object - will be used to send an error if the validation fails | ||
* @returns a Promise containing `true` if the tag is valid, false if not | ||
*/ | ||
async function validateTag( | ||
req: HarmonyRequest, res: Response, | ||
): Promise<boolean> { | ||
const { tag } = req.body; | ||
// See https://docs.docker.com/engine/reference/commandline/image_tag/ | ||
const tagRegex = /^[a-zA-Z\d_][a-zA-Z\d\-_.]{0,127}$/; | ||
if (!tagRegex.test(tag)) { | ||
res.statusCode = 400; | ||
res.send('A tag name may contain lowercase and uppercase characters, digits, underscores, periods and dashes. A tag name may not start with a period or a dash and may contain a maximum of 128 characters.'); | ||
return false; | ||
} | ||
return true; | ||
} | ||
|
||
/** | ||
* Get a map of the canonical service names to their current tags | ||
* @param req - The request object | ||
* @param res - The response object | ||
* @param _next - The next middleware in the chain | ||
*/ | ||
export async function getServiceImageTags( | ||
req: HarmonyRequest, res: Response, _next: NextFunction, | ||
): Promise<void> { | ||
if (! await validateUserIsInDeployerOrAdminGroup(req, res)) return; | ||
|
||
const imageMap = getImageTagMap(); | ||
res.statusCode = 200; | ||
res.send(imageMap); | ||
} | ||
|
||
/** | ||
* Get the current image tag for the given service | ||
* @param req - The request object | ||
* @param res - The response object | ||
* @param _next - The next middleware in the chain | ||
*/ | ||
export async function getServiceImageTag( | ||
req: HarmonyRequest, res: Response, _next: NextFunction, | ||
): Promise<void> { | ||
if (! await validateUserIsInDeployerOrAdminGroup(req, res)) return; | ||
const { service } = req.params; | ||
if (! await validateServiceExists(res, service)) return; | ||
|
||
const imageTagMap = getImageTagMap(); | ||
const tag = imageTagMap[service]; | ||
res.statusCode = 200; | ||
res.send({ 'tag': tag }); | ||
} | ||
|
||
/** | ||
* Update the tag for the given service | ||
* | ||
* @param req - The request object | ||
* @param res - The response object | ||
* @param _next - The next middleware in the chain | ||
*/ | ||
export async function updateServiceImageTag( | ||
req: HarmonyRequest, res: Response, _next: NextFunction, | ||
): Promise<void> { | ||
if (! await validateUserIsInDeployerOrAdminGroup(req, res)) return; | ||
|
||
const { service } = req.params; | ||
if (! await validateServiceExists(res, service)) return; | ||
if (!req.body || !req.body.tag) { | ||
res.statusCode = 400; | ||
res.send('\'tag\' is a required body parameter'); | ||
return; | ||
} | ||
|
||
if (! await validateTag(req, res)) return; | ||
|
||
const { tag } = req.body; | ||
|
||
// TODO HARMONY-1701 run deployment script here | ||
|
||
res.statusCode = 201; | ||
res.send({ 'tag': tag }); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
### <a name="service-image-tags-details"></a> Managing Service Image Tags (Versions) | ||
|
||
Using the `service-image-tag` endpoint, service providers can manage the versions of their services deployed to an environment. Note that a user must be a member of either the EDL `Harmony Service Deployers` | ||
group or the EDL `Harmony Admin` group to access this endpoint, and requests to this endpoint _must_ include | ||
an EDL bearer token header, .e.g., `Authorization: Bearer <token>`. | ||
|
||
#### Get backend service tag (version) information for all services | ||
|
||
``` | ||
GET {{root}}/service-image-tag | ||
``` | ||
**Example {{exampleCounter}}** - Getting backend service image tags using the `service-image-tag` API | ||
|
||
The returned JSON response is a map of canonical service names to tags: | ||
|
||
```JSON | ||
{ | ||
"service-runner": "latest", | ||
"harmony-gdal-adapter": "latest", | ||
"hybig": "latest", | ||
"harmony-service-example": "latest", | ||
"harmony-netcdf-to-zarr": "latest", | ||
"harmony-regridder": "latest", | ||
"swath-projector": "latest", | ||
"hoss": "latest", | ||
"sds-maskfill": "latest", | ||
"trajectory-subsetter": "latest", | ||
"podaac-concise": "sit", | ||
"podaac-l2-subsetter": "sit", | ||
"podaac-ps3": "latest", | ||
"podaac-netcdf-converter": "latest", | ||
"query-cmr": "latest", | ||
"giovanni-adapter": "latest", | ||
"geoloco": "latest" | ||
} | ||
``` | ||
--- | ||
**Example {{exampleCounter}}** - Harmony `service-image-tags` response | ||
|
||
#### Get backend service tag (version) information for a specific service | ||
|
||
``` | ||
GET {{root}}/service-image-tag/#canonical-service-name | ||
``` | ||
**Example {{exampleCounter}}** - Getting a specific backend service image tag using the `service-image-tags` API | ||
|
||
The returned JSON response is a map with a single `tag` field: | ||
|
||
```JSON | ||
{ | ||
"tag": "1.2.3" | ||
} | ||
``` | ||
--- | ||
**Example {{exampleCounter}}** - Harmony `service-image-tags` response for a single service | ||
|
||
#### Update backend service tag (version) for a specific service | ||
|
||
``` | ||
PUT {{root}}/service-image-tag/#canonical-service-name | ||
``` | ||
**Example {{exampleCounter}}** - Updating a specific backend service image tag using the `service-image-tags` API | ||
|
||
The body of the `PUT` request should be a JSON object of the same form as the single service `GET` response in the | ||
example above: | ||
|
||
```JSON | ||
{ | ||
"tag": "new-version" | ||
} | ||
``` | ||
|
||
The returned JSON response is the same as the single service request above, indicating the new tag value | ||
|
||
```JSON | ||
{ | ||
"tag": "new-version" | ||
} | ||
``` | ||
--- | ||
**Example {{exampleCounter}}** - Harmony `service-image-tags` response for a updating a single service | ||
|
||
|
||
**Important** from the [Docker documentation](https://docs.docker.com/engine/reference/commandline/image_tag/): | ||
>A tag name may contain lowercase and uppercase characters, digits, underscores, periods and dashes. A tag name may not start with a period or a dash and may contain a maximum of 128 characters. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.