How it works
1. Reverse-Engineering the cache server
This is actually a pretty simple process. We just need to look at the requests that the GitHub Actions runner makes to the cache server and then replicate the api routes. We can use the source code of the official actions/cache
action to see how the requests are made.
GitHub also has very good documentation on how the cache keys are matched.
2. Getting the actions runner to use our cache server
The whole idea of creating a self-hosted cache server originated from the discovery that the official actions/cache
action uses an environment variable ACTIONS_CACHE_URL
to determine where to send cache requests. This means that we can simply set this environment variable to the base URL of our cache server and the runner will start using it ...right?
Well, not exactly. The actions/cache
action uses the ACTIONS_CACHE_URL
environment variable to determine the base URL of the cache server but we cannot overrid this environment variable in any way.
The default actions runner always overrides the ACTIONS_CACHE_URL
environment variable with an internal URL that points to the official GitHub cache server. This is the code that does it:
var systemConnection = ExecutionContext.Global.Endpoints.Single(x => string.Equals(x.Name, WellKnownServiceEndpointNames.SystemVssConnection, StringComparison.OrdinalIgnoreCase));
Environment["ACTIONS_RUNTIME_URL"] = systemConnection.Url.AbsoluteUri;
Environment["ACTIONS_RUNTIME_TOKEN"] = systemConnection.Authorization.Parameters[EndpointAuthorizationParameters.AccessToken];
if (systemConnection.Data.TryGetValue("CacheServerUrl", out var cacheUrl) && !string.IsNullOrEmpty(cacheUrl))
{
Environment["ACTIONS_CACHE_URL"] = cacheUrl;
}
if (systemConnection.Data.TryGetValue("GenerateIdTokenUrl", out var generateIdTokenUrl) && !string.IsNullOrEmpty(generateIdTokenUrl))
{
Environment["ACTIONS_ID_TOKEN_REQUEST_URL"] = generateIdTokenUrl;
Environment["ACTIONS_ID_TOKEN_REQUEST_TOKEN"] = systemConnection.Authorization.Parameters[EndpointAuthorizationParameters.AccessToken];
}
if (systemConnection.Data.TryGetValue("ResultsServiceUrl", out var resultsUrl) && !string.IsNullOrEmpty(resultsUrl))
{
Environment["ACTIONS_RESULTS_URL"] = resultsUrl;
}
The line Environment["ACTIONS_CACHE_URL"] = cacheUrl;
is the one that overrides the ACTIONS_CACHE_URL
environment variable with the internal URL.
To allow overriding the ACTIONS_CACHE_URL
environment variable, we need to modify the runner binary. This is a bit tricky because the runner is a compiled binary and we cannot simply modify the source code and recompile it. We need to modify the binary itself. So we did just that. We replaced the string ACTIONS_CACHE_URL
with ACTIONS_CACHE_ORL
(being careful to keep the same length) in the runner binary. This way, the runner will not override the ACTIONS_CACHE_URL
environment variable and we can set it to our cache server's base URL.