Upgrading¶
The release notes list the breaking changes of each release, e.g., of 0.4.0, and link the upgrade steps on this page. Upgrades of Helm releases are described in Deployment. This page describes how to upgrade, and how existing setups and data are affected.
The steps add up: when skipping versions, apply the sections of all versions in
between, starting with the oldest, which is the lowest on this page. Where
sections advise different rollouts, follow the strictest (e.g., upgrade with the
app scaled to 0 replicas). 1.0.0rc2 starts with a fresh database (see From
1.0.0rc1): when upgrading to it or later, skip what the sections
below say about stored data (e.g., fixes with mongosh or migrations of stored
subjects). Their steps for the config, the deployment and clients still apply.
From 1.0.0rc1¶
This version needs a fresh database: data is not kept, and there is no migration. The fields that the registry adds to the API and stores moved to camelCase (see API style), so this version can't read what earlier versions stored. It refuses to start if it finds such data or indexes (also after only emptying the collections), naming the collections. This also holds for upgrades from any earlier version:
- Scale the app to 0 replicas.
- Point it to an empty database (e.g., a new volume), or delete the database
serviceStore. Back up what you want to keep first, and register services, service types and roles again through the API. - Upgrade the app and the clients together, e.g., cloud-registry-ui to its 1.0.0-rc2.
Going back to 1.0.0rc1 needs its database, as it was.
Outgoing requests: the registry now fetches service info from public
addresses when users import it and for
syncs, so it needs outgoing connections, on top of those to
the token issuers; nothing changes if they are blocked, except that imports fail and
synced services become out-of-sync. It adds the collections
service_info_fetches and notification_dismissals, and the policy rules
import-service-info, owners-sync-services, maintainers-sync-services,
get-service-sync, list-syncs, list-notifications and
dismiss-notifications, which overlays that give policies as a list must include.
Sync is available by default, so owners can turn it on for
their services: each worker then runs a background thread, which queries the database
once a minute and fetches service info, by default every 24 hours per service (the
index it uses is created on startup); set
custom.endpoints.services.service_info.sync: false to prevent
it. The Helm chart has an optional policy
for the app's connections.
FOCA 0.21: the app now builds on FOCA 0.21 (see FOCA's migration guide), whose stricter request checks change some responses, so check clients that rely on them:
- Query parameters that an operation doesn't declare now return
400 Bad Requeston all operations, includingGET /services,GET /service-infoandPOST /services/types, which ignored them. createdAtandupdatedAtthat aren't date-times now return400 Bad Requestfor all users, instead of being ignored for users other than admins.- Some
400 Bad Requestresponses have anotherdetail: URLs that aren't valid URIs (without scheme, or with whitespace) get FOCA's message, which quotes the value (and is logged atDEBUGsince FOCA 0.22, see below), so don't send secrets in those fields, and unpaired surrogates in JSON bodies get one that doesn't name the field. - FOCA checks the formats of responses too, so stored
date-timeandurivalues must be valid, or reads of the service andGET /servicesreturn500. Requests can't store such values, and this version needs a fresh database, but if data got in around the API, the app logs an error on startup naming the services: correct or delete them in the database.
FOCA 0.22: the app now builds on FOCA 0.22 (see FOCA's migration
guide). Requests that
fail the validation of the API specification keep the validator's reason as
detail (exceptions.validation_reasons). Where the invalid fields are known,
their responses gain the members type (FOCA's problem type, a URL ending in
/problems/invalid-request/), code (invalid-request) and errors, which
lists them; bodies that aren't JSON or have unpaired surrogates get none of
these. The logs change, so check log checks and alerts that rely on them:
- Requests (
Incoming request) and denials of access control (Access denied) name the route of the request (e.g.,/ga4gh/registry/v1/services/{serviceId}) instead of its path; the path is logged atDEBUG. - Rejected requests are logged by the locations and codes of their invalid
fields (e.g.,
Request rejected: #/url (invalid-format)) instead of the validator's reason (otherwise by the mappeddetail, e.g.,The request is malformed.); the reason is logged atDEBUGonly, so rejected values are no longer logged atINFO. - FOCA warns once per worker of what it deprecates ahead of its release on ASGI, e.g., of the werkzeug classes in the app's exception map.
Invalid fields: requests that the registry's own checks reject (e.g., URLs
with user info or schemes their field doesn't allow, environments, unregistered
types, identifiers that can't be stored in roles) now get the same 400 Bad
Request as those that fail the schema validation: all invalid fields in
errors, each with a JSON Pointer (pointer) or parameter name
(parameter), a stable code and a message, with the problem type and code
invalid-request (see Errors). Their detail lists the
locations and messages of all invalid fields (e.g., '#/url': Must use one of
these schemes: 'https'.) instead of describing the first one. Clients that
show or parse detail should show the messages of errors at their fields
instead.
Service URLs: a url with leading or trailing spaces now returns
400 Bad Request instead of being stored, so remove the spaces from what you
register again.
API: fields and query parameters change their names, without aliases. Keys
of the config stay snake_case.
| Where | Before | Now |
|---|---|---|
GET /service-info |
registration_rules |
registrationRules |
| its rules | url_schemes |
urlSchemes |
| its rules | service_types |
serviceTypes |
| its rules | max_unused_service_types |
maxUnusedServiceTypes |
| its rules | max_services_per_user |
maxServicesPerUser |
/service-info/listing |
service_id |
serviceId |
| permissions | user_id |
userId |
GET /me |
user_id |
userId |
GET /me |
admin_available |
adminAvailable |
GET /me |
may_add_service_types |
mayAddServiceTypes |
GET /me |
service_types_pending |
serviceTypesPending |
GET /me, roles in services |
service_id |
serviceId |
GET /approvals |
service_types |
serviceTypes |
| invitations | service_id |
serviceId |
| invitations | created_by |
createdBy |
| invitations | created_at |
createdAt |
| invitations | expires_at |
expiresAt |
invitations, accepted |
user_id |
userId |
invitations, accepted |
accepted_at |
acceptedAt |
POST /invitations/accept |
service_id |
serviceId |
GET /services/<id>/invitations, query |
include_accepted |
includeAccepted |
| verifications | verified_at |
verifiedAt |
GET /verifications |
service_id |
serviceId |
The paths of users' roles change too, as they collided with those of groups, which API clients and overlays with their own policy rules must follow:
- API:
PUTandDELETE /services/<id>/permissions/<user-id>move to/services/<id>/permissions/users/<user-id>. There is no alias: the former paths are not found (404 Not Found)./services/<id>/permissions/groups/<group>andGET /services/<id>/permissionsstay. Users calledgroups, or whose identifiers start withgroups/, were refused and can now be granted roles and accept invitations; the namespaces of issuers (security.auth.issuers) may now start withgroups/. - Policies: the rule
owners-change-permissionsmoves from/ga4gh/registry/v1/services/:id/permissions/*to.../permissions/users/*, and no longer covers the roles of groups;owners-change-group-permissions(.../permissions/groups/*) keeps its path and is now the only rule for them. Overlays that only set rules tonullneed no change, but note thatowners-change-group-permissions: nullnow stops owners from changing roles of groups, which needed a change of both rules before. Overlays that redefineowners-change-permissions, or givepoliciesas a list, must move their rules:.../permissions/*still lets owners change roles of users, but also of groups: change it to.../permissions/users/*. The app warns on startup if the rule of groups is changed or removed, but such a rule is still configured..../permissions/:userand rules per namespace (e.g.,.../permissions/https:/*) don't match the new paths, so that owners get403 Forbidden: change them to.../permissions/users/*(or.../permissions/users/https:/*). The app warns on startup if owners can create invitations, but no rule lets them grant roles to users.
FOCA 0.20: the app now builds on FOCA 0.20 (see FOCA's migration
guide), whose image has
no pip. Images derived from the app's image that run pip install must use uv
instead, mounted from a pinned stage, as the image has none either (see the
guide). The app has no gateway plugins, so the plugin store and its secret
don't apply. The Helm chart is now on foca-common 0.4.0, which checks audiences:
the chart no longer accepts an invalid global config.security.auth.audience if
each issuer has its own, which it used to ignore. New values are optional:
cloudRegistry.startupProbe, cloudRegistry.readinessProbe and
cloudRegistry.livenessProbe replace the default probes of the app's container.
Environment of the service info: the app now refuses to start if
service_info.service.environment isn't in
custom.endpoints.services.environments (see Service
environments). The defaults pass
(dev, which the default list has), as does an empty list or a service info
without environment, so check this only if you changed one of the two: in
that case, add the value to the list, or set the environment to a listed value,
before upgrading. Otherwise, the pods of this version don't start, and the error
names both settings.
Listing the registry itself: admins can now add the registry to its own services (see Listing the registry itself). Nothing changes until they do.
Invitation labels: invitations can have a label
(see Invitations). Only invitations with a
label have the field in responses, so clients that reject unknown fields of
invitations must accept label.
Expired invitations: those that weren't accepted are now kept (see Invitations):
GET /services/<id>/invitationslists them, and no longer lists accepted invitations unless asked (?includeAccepted=true). All invitations have a newstatus(open,expiredoraccepted). Clients that look for acceptances in the list must ask for them, and clients that show all listed invitations as open must checkstatus.DELETE /services/<id>/invitations/<invitation-id>removes expired ones, which used to be404 Not Found. Their links still can't be accepted (404).- The new option
custom.access_control.invitations.keep_expired_days(default: 30, at most 365) sets when they are removed automatically.0removes them when they expire, as before. Check the default against your privacy statement: until then, invitations that weren't accepted keep their labels, which may name people, and who created them and when (createdBy,createdAs,createdAt). With the defaults, that is up to 37 days after they were created (expiry_days+keep_expired_days; before, 7), and current owners see the creators. - Revoking a user's roles on a service now also deletes the invitations to it
that the user created and nobody accepted, also those that stayed valid because
they were created as an admin or through a group, and with them
createdBy,createdAsandcreatedAt. Accepted ones keep these fields until they expire. max_per_servicestill counts the same: invitations that haven't expired, open or accepted.- The app sets the TTL index of the collection
invitationswhen it starts and the value differs, so that its database user needs to be able to drop and create indexes (asreadWritecan).
From 0.6.0¶
Data is kept. Owners can invite users to their services by link (see Invitations):
- The app stores invitations in the new collection
invitations, whose indexes it creates on startup (among them TTL indexes, with which MongoDB deletes expired invitations). - The base config has three new rules (
owners-manage-invitations,owners-revoke-invitations,accept-invitationsinsecurity.access_control.policies). Overlays that give the policies as a list, rather than changing single rules by name, must add them, or the new endpoints return403 Forbidden. - Deployments that stopped owners from granting roles, by removing or
restricting
owners-change-permissionsby name (see Requiring login for reads), inherit the rules of invitations, with which owners can grantownerandmaintaineragain: setowners-manage-invitationsandowners-revoke-invitationstonullin the same overlay. The app warns on startup if owners can create invitations, but no rule lets them grant roles to users. - The new options
custom.access_control.invitations(expiry_days, at most 3650, andmax_per_service) have defaults (see Invitations).
Users other than admins may now register at most 50 services each
(custom.access_control.max_services_per_user; see Services per
user): check how many services your
users registered, and raise the limit, or set null to turn it off, in the
same rollout if needed. Users who registered more keep their services, but
can't register others until they are below the limit. After the upgrade,
list them, e.g., with mongosh (see MongoDB; with
your limit instead of 50):
The app counts each service for the user who registered it, stored with the
service from now on. On startup, it adds that user to the services stored
before: the only user who is owner of the service (services with no or
several such owners, e.g., owned by a group, count for nobody), and counts the
services per user (new collection service_counts, and a new index of
services). Restart the app once after the rollout (or upgrade with the app
scaled to 0 replicas), so that services registered, updated or deleted by
pods of 0.6.0 during the rollout are counted correctly.
Services that admins registered before the upgrade have the admin as their only owner, so they count for the admin, unlike those that admins register in admin mode from now on. The app can't tell admins apart on startup (they are admins through the groups in their tokens), so if an admin registered many services (e.g., imported them), make these count for nobody after the upgrade (with the admin's user identifier), and restart the app, so that it corrects the counters:
db.getSiblingDB("serviceStore").services.updateMany(
{_createdBy: "<admin's user identifier>"},
{$set: {_createdBy: null}},
)
Registrations and updates (POST /services, PUT /services/{serviceId})
with organization fields other than name and url are now rejected (400
Bad Request; see Registrations). Clients that send
further fields in organization must drop them. Services stored with such
fields are still served; the app warns of them on startup (see Stored
services).
With the Helm chart, the default cloudRegistry.image is now fully
qualified, docker.io/elixircloud/cloud-registry (see
Image): adapt image allow-lists, admission policies
(e.g., of Kyverno or Gatekeeper) and registry mirror rules that match the
short name elixircloud/cloud-registry, or pods may be rejected, or pull
past the mirror, after helm upgrade.
From 0.5.0¶
Data is kept. The app now builds on FOCA 0.18, which provides several of its settings and checks; change the config before upgrading:
- Public URL:
custom.endpoints.serviceis removed: set FOCA'sserver.public_urlto the absolute URL of the app as clients see it, including any path prefix of a proxy (formerlyapi_path), e.g.,https://registry.example.org/instead ofurl_prefix: https,external_host: registry.example.organdexternal_port: 443(see Public URLs). The app refuses to start without it, and FOCA refuses URLs with a query or fragment. With the Helm chart,hostNamekeeps working (https://<hostName>/); setpublicUrlfor anything else (see Deployment). - Admins required:
custom.access_control.require_adminmoves to FOCA'ssecurity.access_control.require_admin(truein the base config, as before); the old key is rejected. The other settings ofcustom.access_control(service_types,max_unused_service_types) stay. - Gunicorn: the image loads FOCA's settings (
foca.gunicorn_config);cloud_registry.gunicorn_configno longer exists. The settings are the same; custom commands that load the old module must load FOCA's. - Admin mode: admins act as admins only in requests that ask for it
(header
X-Request-Roles: admin; see Admin mode). Tell the maintainers of clients and scripts that need admin rights to send the header, or remove the opt-in (security.access_control.opt_in_roles: []); for web UIs, see Admin mode. InGET /me,adminis now true only in admin mode; clients decide whether to offer the switch by the new fieldadmin_available. FOCA refuses configured rules that grant the roleadmin(security.access_control.policies) on startup. Stored ones (e.g., granted via the permissions API) still apply, without admin mode, and FOCA warns of them when policies are first loaded. Revoke them (DELETE /admin/access-control/permissions/<rule-id>, in admin mode), and add the users to an admin group instead. During a rolling update, pods of the previous version still grant admin rights without the header, and may store new admin grants via the permissions API: if admin mode must hold from the start, stop them before the new pods serve requests (e.g., scale the app to 0 replicas, then upgrade), or revoke stored admin grants once the rollout has finished. - Owners' roles of groups: the policy rule
owners-change-permissionsnow covers/ga4gh/registry/v1/services/:id/permissions/*(before:.../permissions/:user), as user identifiers may contain slashes (with several token issuers), so it also covers the roles of groups (.../permissions/groups/<group>). Overlays that restrict owners' roles of groups (e.g.,owners-change-group-permissions: null) must now also changeowners-change-permissions, or owners can grant and revoke roles of groups again: remove it, or redefine it for users only (see Requiring login for reads, also for the startup warning). Later versions move the roles of users (see From 1.0.0rc1), so that the rules no longer overlap.
Noticeable, but no action needed:
- Startup checks of the group settings, of the admins and of the service
info are FOCA's, with FOCA's log messages (other wording and loggers):
- FOCA refuses group mappings without a group claim
(
security.access_control.claim_groups) and group claims with surrounding whitespace with aValueError(before: aRuntimeErrorof the app), and doesn't warn if no group claim is set at all. - FOCA's admin check also counts configured rules
(
security.access_control.policies) that grant the roleadmin(only without admin mode, which refuses such rules), and its warning without admins no longer names what nobody can do in the mode of service types (see Service types). - FOCA warns of values of the service info in reserved example domains
(e.g.,
id,organization.url,contactUrlanddocumentationUrl), and doesn't checkorganization.name.
- FOCA refuses group mappings without a group claim
(
- Requests rejected by the validation of the API specification still get
the reason as
detail, now from FOCA (exceptions.validation_details); the reasons are logged by FOCA. Responses that fail the validation of the API specification (e.g., of stored services that are invalid) are now logged by Connexion (ERROR, with the response), as FOCA only filters the log records of rejected requests.
From 0.4.0¶
Data is kept. Before upgrading:
-
Check the environments of the stored services, e.g., with
mongosh(see MongoDB for access to the database of a Helm deployment):Registrations may only use the allowed environments now, by default
prod,test,devandstaging. To keep other values, allow them incustom.endpoints.services.environmentsin the same rollout; otherwise, services with other values are served, but can only be updated with an allowed value (see Stored services). 2. Add the rulesadd-service-typesandlist-approvalsto overlays that give the policies as a list (see Service types). 3. Tell clients that the registry sets the timestamps now: those that users other than admins send are ignored. 4. Users other than admins may add at most 10 types that no service has (custom.access_control.max_unused_service_types; see Service types); change the limit in the same rollout if needed, and rate-limitPOST /services/typesandPOST /servicesat the ingress (see Rate limits). 5. Overlays that set their ownx-bearerInfoFunc(inapi.specs.registry.add_security_fields) keep working, but tokens sent with a public read that the overlay's function rejects are then validated twice. The base config now usescloud_registry.ga4gh.registry.permissions.validate_bearer_token, a thin wrapper around FOCA'svalidate_token(); drop the setting from the overlay, or point it to that function (see Authentication).
Then upgrade, and restart the app once after the rollout (or upgrade with
the app scaled to 0 replicas), so that it stores the types of the services
that pods of 0.4.0 registered during the rollout (see Service
types). On startup, the app also creates its new indexes
and reconciles which services are pending; nothing needs to be done for
them. Switch to the mode moderated (see Service
types) only after that: while pods of 0.4.0
still register services, a user's pending type can hide another user's
service that a pod of 0.4.0 registered with the same type, until an admin
approves the type.
From versions before 0.4.0¶
Redeploy with a fresh install and an empty database; don't migrate the data. The previously deployed version (FOCA 0.12) accepted tokens of any issuer and had no ownership checks, so anyone with any OpenID Connect token could create, change or delete entries: its data can't be trusted. In the new version, services stored before have no owner, so only admins could manage them.
- Optionally, export the registered services as a reference for their
operators (
GET /ga4gh/registry/v1/services). - Uninstall the release (
helm uninstall <release> -n <namespace>; this deletes the MongoDB volume and data). Releases of the legacy charts can't be upgraded anyway: the chart renames its resources and changes the pod selectors. - Create a Secret with the MongoDB credentials (MongoDB requires authentication now; see Authentication).
- Write the values file of the deployment (see
Configuration of the Helm chart). The
identity provider is deployment configuration now, without defaults (see
Deployment configuration):
hostName: the public host name (required);config.security.auth.issuers: the trusted token issuers, i.e., your identity provider (required; the base config trusts none);config.security.auth.audience: the client IDs of the apps that call the registry, e.g., its front end (required);config.security.access_control.claim_groups: the claim with the groups of users, also inconfig.security.auth.userinfo_claimsif the identity provider releases it via user info only (required with group mappings);config.security.access_control.group_mapping: the admin group mapped to the roleadmin(required, unlessconfig.security.access_control.require_adminisfalse; before 0.6.0,config.custom.access_control.require_admin);mongodb.auth.existingSecret: the name of the Secret;config.service_info.service: the service info of the deployment (e.g.,id,name,organization,contactUrlandenvironment: prod; the defaults changed, see the configuration changes in the release notes, so set the previousid,ELIXIR_CLOUD_SERVICE_REGISTRY_1, to keep it for clients), which is no longer set via the API;- remove
cloud_registry.appName(usefullnameOverrideto choose the resource names) andapiServer, and move any changes made to theapp-configConfigMap toconfig; - rename
host_nametohostNameandcloud_registrytocloudRegistry(rendering fails while the old keys are set), andmongodb.volumeSizetomongodb.persistence.size(rendering fails withvolumeSize; see Persistence); - optionally, enable the app's NetworkPolicy
(
cloudRegistry.networkPolicy.enabled: true), withcloudRegistry.networkPolicy.ingressFromset to the pods of the cluster's ingress controller or Gateway (there are no default peers; see Network policies). MongoDB's NetworkPolicy is enabled by default and only lets the app's pods connect (see Network policies).
- Install the chart attached to the release (
cloud-registry-1.0.0-rc2.tgz):helm install <release> cloud-registry-1.0.0-rc2.tgz -n <namespace> -f values-prod.yaml. The chart version goes from 4.0.0 (the last chart version in the repository) back to the app version, which it now follows; that's no problem for a fresh install. - Ask the operators to register their services again (
POST /services); they become their owners. Admins verify them (see Verified services).
To keep the data anyway, dump and restore it as described in From the legacy
charts: the app then adds keys to the
stored services on startup and refuses to start while some of them have
invalid URLs or are duplicates (see Stored services), and
admins have to assign owners (PUT
/services/{serviceId}/permissions/users/{userId}).
Docker Compose setups keep the data in a named volume now; see Docker Compose data to keep using the former bind mount.
Docker Compose data¶
Earlier versions kept the data in ../data/cloud-registry/db. To keep using
it, bind-mount it in a docker-compose.override.yaml:
Public URLs¶
Previously, custom.endpoints.service.api_path held the API base path itself
(e.g., ga4gh/registry/v1); remove that value when upgrading (see Public
URLs).
Service info¶
Previously, service info could be set via POST /service-info and was stored
in the database. That endpoint is gone, and stored service info is no longer
served (the service_info collection can be dropped): put it in the config
when upgrading (see Service info).
Service types¶
Previously, service types were not stored: GET /services/types listed the
types of the registered services. Now they are stored (see Service
types). On startup, the app stores the types of the
registered services, so GET /services/types lists the same types as
before; afterwards, it also lists types that no service has (yet), and types
stay registered when the services that had them change or are deleted
(admins remove them). Types of stored services that aren't objects with the
strings group, artifact and version are skipped. Services registered by
pods of the previous version during a rolling update don't have their types
stored until the app restarts: restart it once after the rollout (e.g.,
kubectl rollout restart deployment/<name> -n <namespace>).
Overlays that give the policies (security.access_control.policies) as a
list must add the new rules add-service-types and list-approvals (see
config.yaml), or users can't add types with POST /services/types or list
pending approvals with GET /approvals.
Several token issuers¶
Trusting a second token issuer (see Several token issuers) namespaces the identifiers of users by issuer, also those of the issuer trusted so far, unless its subjects are kept bare. Stored identifiers of its users (their roles on services, roles granted via the permissions API, the service types they added, the services they registered (for the limit of services per user), their display names, and the invitations they created or accepted) then no longer match them (e.g., open invitations of owners are deleted when they are accepted, as their creators seem to be no longer owners):
- Recommended: keep the subjects of the issuer trusted so far bare
(
namespace: ""), so that nothing needs to be migrated. Its users keep their identifiers, and those of the new issuer are namespaced. - Otherwise: after the config change, namespace the stored identifiers
of the issuer trusted so far with the migration of the image, with the
app's new config (e.g., in a pod of the app, whose deployment name
depends on the release;
--dry-runonly counts them, and changes nothing in the database, whereas the migration itself sets up the app as on startup, so run it with the image of the deployed version). A dry run counts what is stored, so its numbers can differ: configured rules are those that the running app stored. It doesn't check the config as startup does, either. The migration, also with--dry-run, refuses to run on a database of a version before 1.0.0rc2, while the config trusts a single issuer, or keeps the subjects of an issuer bare:
kubectl exec deploy/cloud-registry -- \
python -m cloud_registry.namespace_subjects https://login.example.org/oidc/
The argument is the namespace of the issuer trusted so far (by default,
the issuer). Its users lack their roles from the rollout until the
migration, so run it right after the rollout, while users don't change
roles or accept invitations (e.g., announce a short maintenance window):
roles revoked during the migration may be restored by it, invitations
accepted during it may be deleted or stay claimed although their role
wasn't granted (create new ones then), and requests that store display
names may fail (clients can retry them). Running app instances
load the changed rules on their next request. Configured rules that name
bare users (security.access_control.policies) are counted, but not
changed: namespace them in the config. The migration can be run again
(namespaced identifiers are kept).
Stored services¶
This section describes services stored by versions up to 1.0.0rc1. From 1.0.0rc2, which starts with a fresh database, the app no longer adds keys or creators to stored services on startup, and no longer warns of invalid URLs or further fields of organizations. It still warns of URL schemes and environments that are not allowed, e.g., after the config changed.
Services stored before the registry set the timestamps
(createdAt, updatedAt) keep those they were registered with, which
their owners could set to any value (before timestamps were checked, also
to values that aren't RFC 3339 date-times), until they are updated: then
updatedAt is set, and createdAt is kept if it is an RFC 3339 date-time
(services without one don't get one). Admins can correct them via the API
(PUT /services/{serviceId}); as their timestamps are checked, they send a
valid createdAt, or none, when they update services with invalid ones.
Services stored before the environments were
checked may have other values (e.g.,
production); they are served as they are, but can only be updated with an
allowed value (or without environment). Allow their values in
custom.endpoints.services.environments to keep them.
Services stored before the length limits were introduced may have longer values; they can't be updated unchanged, only with the values shortened.
Services stored before organization fields other than name and url were
rejected may have further fields in organization,
of any size; they are still served, but can only be updated without them. The
app warns of them on startup, naming them. Have their owners (or admins)
update them via the API (PUT /services/{serviceId}), or remove the fields
in the database, e.g., with mongosh (see MongoDB),
which keeps only name and url, and only changes the service if its
organization is an object with both:
const services = db.getSiblingDB("serviceStore").services
const organizationValid = {
$expr: {$eq: [{$type: "$organization"}, "object"]},
"organization.name": {$type: "string"},
"organization.url": {$type: "string"},
}
services.updateOne({id: "<id>", ...organizationValid}, [
{$set: {organization: {$arrayToObject: {$filter: {
input: {$objectToArray: "$organization"},
cond: {$in: ["$$this.k", ["name", "url"]]},
}}}}},
])
Services whose organization lacks name or url (or isn't an object, or
has values that aren't strings; only data written outside the API, which has
always required both) aren't served: with response validation on
(validate_responses in the base config), GET /services/{serviceId}
returns 500 Internal Server Error for them, and so does the whole list,
GET /services, until both are set. The app doesn't warn of them on
startup, and the example above doesn't match them (matchedCount: 0), so
that it doesn't leave an organization of {}. List them, and set both
fields (or delete the services); the update only changes services that are
still broken (e.g., not fixed via the API meanwhile), and keeps the other
fields of organizations that are objects:
const organizationBroken = {$nor: [organizationValid]}
services.find(organizationBroken, {_id: 0, id: 1, organization: 1})
services.updateOne({id: "<id>", ...organizationBroken}, [
{$set: {organization: {$mergeObjects: [
{$cond: [{$eq: [{$type: "$organization"}, "object"]}, "$organization", {}]},
{name: "<name>", url: "<URL>"},
]}}},
])
Then clean up their further fields as above.
Services stored before the check of duplicate
services get their key on startup; only services
without a key are checked again. If some of them have invalid URLs, or the same
URL and API type as another one, the app refuses to start and names all of
them. Invalid URLs include some that were accepted before: URLs with schemes
other than http and https, whitespace or control characters, malformed
percent-encodings (also in queries and fragments), characters that URLs only
allow percent-encoded (e.g., <, |, \), hosts with percent-encodings or
empty labels (e.g., a..org), IPv4 addresses not in dotted-decimal form (e.g.,
127.1), or port 0. Fix or delete
them in the database, e.g., with mongosh (see
MongoDB for access to the database of a Helm
deployment):
db.getSiblingDB("serviceStore").services.deleteOne({id: "<id>"})
db.getSiblingDB("serviceStore").services.updateOne({id: "<id>"}, {$set: {url: "<URL>"}})
Then start the app again; versions up to 1.0.0rc1 add the missing keys.
Services that already have a key (stored by dev images built before URLs
were checked, or before the stricter checks above) are not checked again: if
their URLs are now invalid, they are still served, but can only be updated
with a valid URL, and registrations of the same service with a validly spelled
URL may not be detected as duplicates. Versions up to 1.0.0rc1 warn of them on startup,
naming them; update or delete them as above.
Services stored with http URLs (accepted before https became the only
allowed scheme by default) are still
served, but can only be updated with an allowed scheme; the app warns of them
on startup, naming them. Have their owners (or admins) update them with an
https URL via the API (PUT /services/{serviceId}), which also updates
their keys, or delete them; or allow http (not recommended, as clients
would send access tokens unencrypted).