-
Notifications
You must be signed in to change notification settings - Fork 5.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: add support for refreshKeys #12156
feat: add support for refreshKeys #12156
Conversation
Changed Packages
|
Thanks for the contribution! |
await Promise.all( | ||
keys.map(k => { | ||
return tx<DbRefreshKeysRow>('refresh_keys') | ||
.insert({ | ||
entity_ref: stringifyEntityRef(k.entity), | ||
key: k.key, | ||
}) | ||
.onConflict(['entity_ref', 'key']) | ||
.ignore(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wasn't able to perform this with tx.batchInsert when I want to use an onConflict. Would it be better to check first if they exist and only do an insert if don't?
*/ | ||
|
||
exports.up = async function up(knex) { | ||
await knex.schema.createTable('refresh_keys', table => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure if this table should have a dedicated id field ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for this!
Not sure if I should comment on the RFC or the PRFC might be good to combine them next time 😅
const tx = txOpaque as Knex.Transaction; | ||
const { keys } = options; | ||
|
||
await Promise.all( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe we want to create a new set of refresh keys for the given entity in the database so that this method actually replacing the existing keys for that entity to not get into a situation where we potentially have an ever growing list of keys.
Given that this is probably better named setRefreshKeys
@@ -81,6 +81,14 @@ export type ReplaceUnprocessedEntitiesOptions = | |||
type: 'delta'; | |||
}; | |||
|
|||
export type RefreshKeyOptions = { | |||
keys: { key: String; entity: Entity }[]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should probably be entityRef
instead of a full entity.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could be, however in this case it would mean we'd need to emit the entityRefs from the processors, so the entityRef construction would happen inside all of the processors. With the current implementation they just emit an entity, and in the processing engine we can construct the refs based on the emitted entity.
I'm fine with both of them, maybe leaning towards the emission of entityRefs just for the sake of reducing the data thatis sent :D
Yeah I agree, I'll definietly combine them next time, it is a littlebit awkward this way. :/ |
@jhaals With this my intention was to understand the problem space and try to explore the way it can be done, if you think in a general sense it looks okey, I'd move forward with adding tests, and making sure it passes the ci and move it to a regular PR. |
f34043e
to
58ae6a5
Compare
Signed-off-by: Kiss Miklos <miklos@roadie.io>
Signed-off-by: Kiss Miklos <miklos@roadie.io>
Signed-off-by: Kiss Miklos <miklos@roadie.io>
Signed-off-by: Kiss Miklos <miklos@roadie.io>
Signed-off-by: Kiss Miklos <miklos@roadie.io>
Signed-off-by: Kiss Miklos <miklos@roadie.io>
Signed-off-by: Kiss Miklos <miklos@roadie.io>
Signed-off-by: Kiss Miklos <miklos@roadie.io>
Signed-off-by: Kiss Miklos <miklos@roadie.io>
Signed-off-by: Kiss Miklos <miklos@roadie.io>
Signed-off-by: Kiss Miklos <miklos@roadie.io>
Signed-off-by: Kiss Miklos <miklos@roadie.io>
6f915db
to
3125f54
Compare
Signed-off-by: Kiss Miklos <miklos@roadie.io>
Signed-off-by: Kiss Miklos <miklos@roadie.io>
Signed-off-by: Kiss Miklos <miklos@roadie.io>
Signed-off-by: Kiss Miklos <miklos@roadie.io>
processingResult.refresh( | ||
`url:${relativeUrl({ | ||
key: resolverKey, | ||
value: resolverValue, | ||
baseUrl: location.target, | ||
read, | ||
resolveUrl, | ||
})}`, | ||
), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is gonna work for a lot of resolver
implementations, but not all. I think it's worth an investigation into what it would mean to let the resolver
return or emit refresh keys. Think for example an OpenAPI-aware resolver that follows $ref
URLs that we then add to the set of refresh keys
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agree, that's gonna be more future-proof for sure! Let me see how would it look.
}, | ||
})) { | ||
emit(parseResult); | ||
emit( | ||
processingResult.refresh( | ||
`${LOCATION_TYPE}://${normalizedFilePath}`, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry for flip-flopping, but we're leaning back towards using the serialized location format for refresh keys after all. Feels like that's the more future proof design that will let us add new behavior without awkwardness.
Think for example a refresh key that simply references a GitHub repo, we'd probably do something like github-repo:https://github.com/backstage/backstage
for that since we need pretty much all parts of that URL anyway. If we just use the scheme it would need to either be the bare https://github.com/backstage/backstage
, but there's a potential risk for overlap there. Another more explicit option is then github-repo://github.com/backstage/backstage
, but that's where it starts getting real awkward, especially of we have values that don't really fit as URLs.
So with that, let's switch this back to file:<path>
for now, as in drop the //
😅
@@ -93,6 +93,8 @@ export class UrlReaderProcessor implements CatalogProcessor { | |||
value: parseResults as CatalogProcessorEntityResult[], | |||
}); | |||
} | |||
|
|||
emit(processingResult.refresh(`${location.type}:${location.target}`)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So right now leaning towards what's currently here, the same <type>:<target>
format as we use for locations.
Signed-off-by: Kiss Miklos <miklos@roadie.io>
Signed-off-by: Kiss Miklos <miklos@roadie.io>
Signed-off-by: Kiss Miklos <miklos@roadie.io>
Signed-off-by: Kiss Miklos <miklos@roadie.io>
Signed-off-by: Kiss Miklos <miklos@roadie.io>
@Rugvip Addressed the comments, opted to add the emit to the resolvers as the easiest solution right now. I can see some tests failed, but they pass locally, can it be that it is flaky? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Feeling we're getting pretty close to 😁
@@ -567,6 +578,7 @@ export type PlaceholderResolverParams = { | |||
baseUrl: string; | |||
read: PlaceholderResolverRead; | |||
resolveUrl: PlaceholderResolverResolveUrl; | |||
emit: CatalogProcessorEmit; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, think this makes sense! 👍
@freben could you have a look as well to make sure you think this fits alright with how the placeholder processor is put together?
I'm thinking we should also call out in the changeset that custom placeholder resolvers should be updated to emit refresh keys.
refresh(options: RefreshOptions): Promise<void>; | ||
refreshByRefreshKeys(options: RefreshByRefreshKeysOptions): Promise<void>; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What do you think about making this an addition to the existing RefreshOptions
rather than a new method?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we won't expose the refreshKeys on the DefualtRefreshService we won't need to add the options to the existing RefreshOptions either, so I'll hold on to this and do it based on how we decide.
async refreshByRefreshKeys(options: RefreshByRefreshKeysOptions) { | ||
await this.database.transaction(async tx => { | ||
await this.database.refreshByRefreshKeys(tx, options); | ||
}); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I feel we need to do something different here tbh, just passing through without authorization is pretty unexpected. What I'm thinking is that we'd keep refreshing by keys internal, so it's not a thing you can request via the API. The only way to refresh via keys would be some form of direct runtime access for example via the entity providers API.
Have you considered this step yet? didn't see it in the RFC. How do you end up wanting to pipe refresh data into the catalog? Maybe we could even skip this part in the PR and leave it to a followup
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yea, I agree my initial idea was to just expose it on the refresh service and have its own endpoint. I can accept that we do not want to do this.
The other thing that seems kinda easy is to add the refreshing to the EntityProvider, they have access to the DefaultProcessingDatabase so should be easy to expose a new method that calls the appropriate refresh function. However, I don't think EntityProviders should handle refreshes, for me they should care about creating/deleting the entities. So I'd probably not put this behaviour on to the EntityProviders.
I think refreshing belongs to the catalog. So I am thinking about something like that the CatalogBuilder could return a refresh function and then everyone could decide how they want to use that to refresh their stuff.
stg like:
export default async function createPlugin(
env: PluginEnvironment,
): Promise<Router> {
const builder = await CatalogBuilder.create(env);
builder.addProcessor(new ScaffolderEntitiesProcessor());
const { processingEngine, router, refresh } = await builder.build();
await processingEngine.start();
refresh(['refreshKey-1', 'refreshKey-2'])
return router;
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I actually removed the possibility to trigger refreshes via the RefreshService to be able to tackle it in a separate PR if we decide it that way.
Signed-off-by: Kiss Miklos <miklos@roadie.io>
Signed-off-by: Kiss Miklos <miklos@roadie.io>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice, thanks! 👍
Shall we as is and leave the triggering of refreshes as a followup?
I think the builder addition could work, especially for use-cases like your own. Looking at #11611 this is very likely to be an extension point though, so I almost doesn't matter how we implement it for now as it'll be migrated to that anyway.
I would like us to explore the EntityProvider approach too though, making it possible for a provider to trigger refreshes through the connection. Or maybe even a new form of abstraction like some form of refresh sources
I'd say so let's merge this one and I'll create a follow-up issue where we can discuss the different approaches for triggering the refreshes. I can explore both and create some PRs with the different approaches to discuss which fits better. |
🎉 |
Hey, I just made a Pull Request!
This is a potential implementation of the #12155 Nothing set in stone, would like some feedback on it!
✔️ Checklist
Signed-off-by
line in the message. (more info)