-
Notifications
You must be signed in to change notification settings - Fork 1.3k
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
Implement a 'Use Last Successful Prebuild' workspace creation mode #13801
Conversation
started the job as gitpod-build-jx-incremental-workspace.1 because the annotations in the pull request description changed |
8ca52a6
to
e759051
Compare
895e314
to
cc36547
Compare
0d9dc26
to
13b6b6d
Compare
Okay, this early proof-of-concept should be ready for testing! 🎉 It adds a To get the button and test it:
tasks:
- init: sleep 240 && echo "$(date) prebuild commit $(git rev-parse HEAD)" >> /workspace/.init
command: cat /workspace/.init && echo "$(date) current commit $(git rev-parse HEAD)"
Note that this POC will not run any EDIT:
|
@jankeromnes A first test just worked nicely! 🧘 🚀 In my opinion we want to quickly be able to try this out in prod, internally. I suggest to do the following:
WDYT? Further things to follow up with (in later PRs):
|
76a302b
to
f45f889
Compare
Alrighty! Addressed most nits by refactoring all common incremental prebuild code into a Next steps will be to rebase & solve the merge conflicts, but other than that, I guess this is nearing completion. 🏁 EDIT: Done as well now ✅ |
eae904b
to
be559aa
Compare
be559aa
to
c4f7b40
Compare
Feedback from an customer conversation: Regarding the ability to choose to open a workspace from a previous prebuild:
For the functionality to explicitly start a workspace from a previous prebuild, I suggest we launch the workspace on the old prebuild without syncing the latest git context. And we show a message indicating such and that you are "x commits behind…". |
That's very good feedback, many thanks @mbrevoort for sourcing and sharing it! 💯
Interesting. I wonder how we can represent all this info in a short and sweet format. Does it all fit in a button? 🤔 Here is a very rough attempt at such an "enriched" button (sorry for the bad design): EDIT: Let's move the discussion to the follow-up issue #13944
Aha. This makes sense, is simple, and also quite risk-free. Maybe I can try to do something similar to #13768 and "force" the checked out commit to be the same as the old prebuild being used. 💭 EDIT: Follow-up issue #13945
Is this really useful? (Also, I wouldn't know where to show this -- e.g. in the terminal on start-up? 👀) My thinking is:
|
|
||
useEffect(() => { | ||
if (!user) return; | ||
(async () => { | ||
const featureFlags: FeatureFlagConfig = { | ||
persistent_volume_claim: { defaultValue: true, setter: setShowPersistentVolumeClaimUI }, | ||
usage_view: { defaultValue: false, setter: setShowUsageView }, | ||
showUseLastSuccessfulPrebuild: { defaultValue: false, setter: setShowUseLastSuccessfulPrebuild }, |
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's the current pattern we want to use for Feature Flag names? I thought it'd be snake_case
? But might be wrong! 🤔
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.
There is a debate (internal) about this currently 🙂
@@ -0,0 +1,175 @@ | |||
/** |
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.
@jankeromnes Any changes in this file? Or is it a "pure move"? 🤔
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 is a new file, composed of 3 pre-existing code sections. Here are the relevant diffs:
getCommitHistoryForContext diff
--- get-commit-history-1 2022-10-18 09:42:59.685989248 +0000
+++ get-commit-history-2 2022-10-18 09:41:19.713994670 +0000
@@ -1,18 +1,18 @@
const maxDepth = this.config.incrementalPrebuilds.commitHistory;
const hostContext = this.hostContextProvider.get(context.repository.host);
const repoProvider = hostContext?.services?.repositoryProvider;
-if (repoProvider) {
- prebuildContext.commitHistory = await repoProvider.getCommitHistory(
+if (!repoProvider) {
+ return {};
+}
+const history: WithCommitHistory = {};
+history.commitHistory = await repoProvider.getCommitHistory(
user,
context.repository.owner,
context.repository.name,
context.revision,
maxDepth,
);
- if (
- context.additionalRepositoryCheckoutInfo &&
- context.additionalRepositoryCheckoutInfo.length > 0
- ) {
+if (context.additionalRepositoryCheckoutInfo && context.additionalRepositoryCheckoutInfo.length > 0) {
const histories = context.additionalRepositoryCheckoutInfo.map(async (info) => {
const commitHistory = await repoProvider.getCommitHistory(
user,
@@ -26,5 +26,6 @@ if (repoProvider) {
commitHistory,
};
});
- prebuildContext.additionalRepositoryCommitHistories = await Promise.all(histories);
+ history.additionalRepositoryCommitHistories = await Promise.all(histories);
}
findGoodBaseForIncrementalBuild diff
--- find-good-base-1 2022-10-18 09:51:32.557961085 +0000
+++ find-good-base-2 2022-10-18 09:41:40.157993561 +0000
@@ -1,23 +1,23 @@
-if (context.commitHistory && context.commitHistory.length > 0) {
+if (!history.commitHistory || history.commitHistory.length < 1) {
+ return;
+}
+
+const { config } = await this.configProvider.fetchConfig({}, user, context);
+const imageSource = await this.imageSourceProvider.getImageSource({}, user, context, config);
+
// Note: This query returns only not-garbage-collected prebuilds in order to reduce cardinality
// (e.g., at the time of writing, the Gitpod repository has 16K+ prebuilds, but only ~300 not-garbage-collected)
- const recentPrebuilds = await this.db.trace({ span }).findPrebuildsWithWorkpace(commitContext.repository.cloneUrl);
- const loggedContext = filterForLogging(context);
+const recentPrebuilds = await this.workspaceDB.findPrebuildsWithWorkpace(context.repository.cloneUrl);
for (const recentPrebuild of recentPrebuilds) {
if (
- !(await this.isGoodBaseforIncrementalPrebuild(
- context,
+ await this.isGoodBaseforIncrementalBuild(
+ history,
config,
imageSource,
recentPrebuild.prebuild,
recentPrebuild.workspace,
- ))
+ )
) {
- log.debug({ userId: user.id }, "Not using incremental prebuild base", {
- candidatePrebuildId: recentPrebuild.prebuild.id,
- context: loggedContext,
- });
- continue;
- }
+ return recentPrebuild.prebuild;
}
}
isGoodBaseForIncrementalBuild diff
--- is-good-base-1 2022-10-18 09:44:50.477983240 +0000
+++ is-good-base-2 2022-10-18 09:42:00.273992470 +0000
@@ -1,7 +1,7 @@
-if (!context.commitHistory || context.commitHistory.length === 0) {
+if (!history.commitHistory || history.commitHistory.length === 0) {
return false;
}
-if (!CommitContext.is(candidate.context)) {
+if (!CommitContext.is(candidateWorkspace.context)) {
return false;
}
@@ -11,23 +11,26 @@ if (candidatePrebuild.state !== "availab
}
// we are only considering full prebuilds
-if (!!candidate.basedOnPrebuildId) {
+if (!!candidateWorkspace.basedOnPrebuildId) {
return false;
}
-const candidateCtx = candidate.context;
-if (candidateCtx.additionalRepositoryCheckoutInfo?.length !== context.additionalRepositoryCommitHistories?.length) {
+if (
+ candidateWorkspace.context.additionalRepositoryCheckoutInfo?.length !==
+ history.additionalRepositoryCommitHistories?.length
+) {
// different number of repos
return false;
}
-if (!context.commitHistory.some((sha) => sha === candidateCtx.revision)) {
+const candidateCtx = candidateWorkspace.context;
+if (!history.commitHistory.some((sha) => sha === candidateCtx.revision)) {
return false;
}
// check the commits are included in the commit history
-for (const subRepo of candidateCtx.additionalRepositoryCheckoutInfo || []) {
- const matchIngRepo = context.additionalRepositoryCommitHistories?.find(
+for (const subRepo of candidateWorkspace.context.additionalRepositoryCheckoutInfo || []) {
+ const matchIngRepo = history.additionalRepositoryCommitHistories?.find(
(repo) => repo.cloneUrl === subRepo.repository.cloneUrl,
);
if (!matchIngRepo || !matchIngRepo.commitHistory.some((sha) => sha === subRepo.revision)) {
@@ -36,10 +39,10 @@ for (const subRepo of candidateCtx.addit
}
// ensure the image source hasn't changed (skips older images)
-if (JSON.stringify(imageSource) !== JSON.stringify(candidate.imageSource)) {
+if (JSON.stringify(imageSource) !== JSON.stringify(candidateWorkspace.imageSource)) {
log.debug(`Skipping parent prebuild: Outdated image`, {
imageSource,
- parentImageSource: candidate.imageSource,
+ parentImageSource: candidateWorkspace.imageSource,
});
return false;
}
@@ -55,7 +58,7 @@ const filterPrebuildTasks = (tasks: Task
)
.filter((task) => Object.keys(task).length > 0);
const prebuildTasks = filterPrebuildTasks(config.tasks);
-const parentPrebuildTasks = filterPrebuildTasks(candidate.config.tasks);
+const parentPrebuildTasks = filterPrebuildTasks(candidateWorkspace.config.tasks);
if (JSON.stringify(prebuildTasks) !== JSON.stringify(parentPrebuildTasks)) {
log.debug(`Skipping parent prebuild: Outdated prebuild tasks`, {
prebuildTasks,
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.
Would have been great to see this diff surfaced in the PR, ideally by making the move a separate PR, or at least a separate commit. 😕
Only now I notice that fetchConfig
was pulled into findGoodBaseForIncrementalBuild
, which feels a bit strange, because now there is a disconnect between these two lines [1, 2]. 🤔
Something for a follow-up?
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 @geropl!
Would have been great to see this diff surfaced in the PR, ideally by making the move a separate PR, or at least a separate commit. 😕
True, I agree that code moves and code changes would ideally be made in separate commits. 💯 (But unfortunately, I squashed my commits, to make resolving some merge conflicts easier.)
Only now I notice that
fetchConfig
was pulled intofindGoodBaseForIncrementalBuild
, which feels a bit strange, because now there is a disconnect between these two lines [1, 2]. 🤔
Indeed, you're right about the fetchConfig
being moved. And this has the unfortunate consequence of calling fetchConfig
twice now [1, 2]. This could be resolved by allowing to pass an optional config
to findGoodBaseForIncrementalBuild
.
However, I don't understand what you mean by "disconnect". These two lines still do what was intended, right? (I.e., we fetch the config
for the current context, then we find a potentially older prebuild, but we "fix" the created workspace by forcing its config
back to the most recent state.)
FYI, this was done to fix a bug where, during incremental prebuilds, a prebuild somehow got a config
with an outdated command
, and the new workspace would run the outdated command
on start-up even though the .gitpod.yml
in the workspace and Git history both had the more recent command
.
I think that bug is still adequately handled in the new version of the code. Does this explanation resolve the "disconnect" you saw?
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.
With "disconnect" I mean that we should be using the exact same config
being used for the prebuild and for identifying the prebuild.
Now that we're doing it twice that might for some CommitContexts (e.g., PR/branch context) lead to situations where config is out of sync here. This a) makes it harder to reason about the code, and b) opens up possibility for current (or future) bugs.
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 explaining! I don't fully understand how the configs could get out-of-sync (because we only fetch a read-only config, i.e. a .gitpod.yml
from the repo, for the single context that we currently want to open, regardless of which prebuild or incremental prebuild we end up using -- we do occasionally fetch the same config multiple times, but we never modify it).
Maybe, going back to fetching it just once here, and passing it along to findGoodBaseForIncrementalBuild
would solve your concern? (I.e. then we keep exactly one config
object in this flow as before.)
Testing again. |
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.
c4f7b40
to
2eb936e
Compare
I guess this can be merged as-is, and I'm happy to make any adjustment follow-ups! We already have these two follow-up issues:
There are also two open discussions about feature flag capitalization, and incremental build logic diffs. But these discussions can continue async and possibly lead to new follow-up issues if needed. 🌴 Excited about moving this forward! 🛹 /unhold |
Description
Implement a 'Use Last Successful Prebuild' workspace creation mode, and companion button in the "Prebuild In Progress" screen.
Related Issue(s)
A first step towards #12582
How to test
.gitpod.yml
orDockerfile
here!)Release Notes
Documentation
Werft options:
If enabled this will build
install/preview
Valid options are
all
,workspace
,webapp
,ide