Skip to content

Commit d68c953

Browse files
authored
Merge pull request #197 from github/feature/release-asset-filtering
2 parents 43f0b20 + 62358b9 commit d68c953

10 files changed

Lines changed: 805 additions & 10 deletions

File tree

‎.gitignore‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@
22
/codeql-action-sync
33
/dist/
44
/pkged.go
5+
/release/

‎README.md‎

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ If your GitHub Enterprise Server instance is on a completely isolated network wh
1919
From a machine with access to both GitHub.com and GitHub Enterprise Server use the `./codeql-action-sync sync` command to copy the CodeQL Action and bundles.
2020

2121
**Required Arguments:**
22-
* `--destination-url` - The URL of the GitHub Enterprise Server instance to push the Action to.
22+
* `--destination-url` - The root URL of the GitHub Enterprise Server instance, for example `https://github.example.com`. Do not include an API path such as `/api/v3`, a query, a fragment, or surrounding whitespace.
2323
* `--destination-token` - A [Personal Access Token](https://docs.github.com/en/enterprise/user/github/authenticating-to-github/creating-a-personal-access-token) for the destination GitHub Enterprise Server instance. If the destination repository is in an organization that does not yet exist or that you are not an owner of, your token will need to have the `site_admin` scope in order to create the organization or update the repository in it. The organization can also be created manually or an existing organization that you own can be used, in which case the `repo` and `workflow` scopes are sufficient. The token can also be provided by setting the `CODEQL_ACTION_SYNC_TOOL_DESTINATION_TOKEN` environment variable.
2424

2525
**Optional Arguments:**
@@ -29,20 +29,64 @@ From a machine with access to both GitHub.com and GitHub Enterprise Server use t
2929
* `--actions-admin-user` - The name of the Actions admin user, which will be used if you are updating the bundled CodeQL Action. If not specified `actions-admin` will be used.
3030
* `--force` - By default the tool will not overwrite existing repositories. Providing this flag will allow it to.
3131
* `--push-ssh` - Push Git contents over SSH rather than HTTPS. To use this option you must have SSH access to your GitHub Enterprise instance configured.
32+
* `--include-platforms` - Only download release assets for the listed platforms. Valid values are `linux64`, `linux-arm64`, `osx64`, and `win64`.
33+
* `--exclude-platforms` - Download release assets for every platform except those listed. This cannot be used with `--include-platforms`.
34+
* `--bundle-archive-format` - Only download CodeQL bundles in the selected format. Valid values are `tar.gz` and `tar.zst`. Requires explicit `tools` URLs in CodeQL workflows; see [Archive format restrictions](#archive-format-restrictions).
35+
36+
Platform lists are comma-separated. For example, the following syncs Linux x64 and Windows assets, omits the combined all-platform bundle, and downloads CodeQL bundles only as gzip archives. Use it only with workflows configured as described in [Archive format restrictions](#archive-format-restrictions):
37+
38+
```shell
39+
./codeql-action-sync sync \
40+
--destination-url https://github.example.com \
41+
--include-platforms linux64,win64 \
42+
--bundle-archive-format tar.gz
43+
```
44+
45+
To sync every platform except macOS:
46+
47+
```shell
48+
./codeql-action-sync sync \
49+
--destination-url https://github.example.com \
50+
--exclude-platforms osx64
51+
```
52+
53+
With none of these flags, the tool continues to copy every release asset. Platform filters also apply to platform-specific checksums, language bundles, and update-job proxies. Non-archive metadata is retained, while the combined all-platform CodeQL bundle is omitted when a platform filter is active. The archive format flag applies only to CodeQL bundles; update-job proxies retain their published format. If a required platform bundle is not published in the requested format, the command fails rather than silently falling back. When an archive format is specified, every explicitly included platform must publish a primary bundle in that format, even if the release has no assets for that platform at all.
54+
55+
These flags limit new downloads and uploads. They do not delete assets copied to GitHub Enterprise Server by an earlier sync.
56+
57+
#### Archive format restrictions
58+
59+
**Warning:** `--bundle-archive-format` controls which bundles are copied, not which archive the CodeQL Action requests. The Action normally chooses gzip on Windows and chooses zstd on Linux/macOS when the CLI and runner support it. If that format was excluded, the Action may try downloading it from GitHub.com, which fails on air-gapped runners. Leave this flag unset to preserve automatic archive selection.
60+
61+
When restricting the format, set the CodeQL init step's `tools` input to an explicit URL for a retained bundle on your destination server. For example, a Linux x64 job using a gzip-only sync:
62+
63+
```yaml
64+
- uses: github/codeql-action/init@v4
65+
with:
66+
languages: javascript
67+
tools: https://github.example.com/github/codeql-action/releases/download/<bundle-tag>/codeql-bundle-linux64.tar.gz
68+
```
69+
70+
Replace `<bundle-tag>` with a synced release tag compatible with your Action version, and adjust the host, repository, platform, and archive format for each job. The explicit URL pins the bundle version, so update it when upgrading the bundle. Ensure each runner can extract the selected archive format.
3271

3372
### I don't have a machine that can access both GitHub.com and GitHub Enterprise Server.
3473
From a machine with access to GitHub.com use the `./codeql-action-sync pull` command to download a copy of the CodeQL Action and bundles to a local folder.
3574

3675
**Optional Arguments:**
3776
* `--cache-dir` - The directory in which to store data downloaded from GitHub.com. If not specified a directory next to the sync tool will be used.
3877
* `--source-token` - A token to access the API of GitHub.com. This is normally not required, but can be provided if you have issues with API rate limiting. The token does not need to have any scopes.
78+
* `--include-platforms` - Only download release assets for the listed platforms. Valid values are `linux64`, `linux-arm64`, `osx64`, and `win64`.
79+
* `--exclude-platforms` - Download release assets for every platform except those listed. This cannot be used with `--include-platforms`.
80+
* `--bundle-archive-format` - Only download CodeQL bundles in the selected format. Valid values are `tar.gz` and `tar.zst`. Requires explicit `tools` URLs in CodeQL workflows; see [Archive format restrictions](#archive-format-restrictions).
81+
82+
The filtering semantics are the same as for `sync` above. Reusing a cache with different filters removes now-excluded local assets before the cache can be pushed, including assets from historical releases no longer referenced by the current Action defaults. Historical release metadata and matching assets are retained.
3983

4084
Next copy the sync tool and cache directory to another machine which has access to GitHub Enterprise Server.
4185

4286
Now use the `./codeql-action-sync push` command to upload the CodeQL Action and bundles to GitHub Enterprise Server.
4387

4488
**Required Arguments:**
45-
* `--destination-url` - The URL of the GitHub Enterprise Server instance to push the Action to.
89+
* `--destination-url` - The root URL of the GitHub Enterprise Server instance, for example `https://github.example.com`. Do not include an API path such as `/api/v3`, a query, a fragment, or surrounding whitespace.
4690
* `--destination-token` - A [Personal Access Token](https://docs.github.com/en/enterprise/user/github/authenticating-to-github/creating-a-personal-access-token) for the destination GitHub Enterprise Server instance. If the destination repository is in an organization that does not yet exist or that you are not an owner of, your token will need to have the `site_admin` scope in order to create the organization or update the repository in it. The organization can also be created manually or an existing organization that you own can be used, in which case the `repo` and `workflow` scopes are sufficient. The token can also be provided by setting the `CODEQL_ACTION_SYNC_TOOL_DESTINATION_TOKEN` environment variable.
4791

4892
**Optional Arguments:**

‎cmd/pull.go‎

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,16 @@ var pullCmd = &cobra.Command{
1313
RunE: func(cmd *cobra.Command, args []string) error {
1414
version.LogVersion()
1515
cacheDirectory := cachedirectory.NewCacheDirectory(rootFlags.cacheDir)
16-
return pull.Pull(cmd.Context(), cacheDirectory, pullFlags.sourceToken, pullFlags.sourceURL)
16+
return pull.Pull(cmd.Context(), cacheDirectory, pullFlags.sourceToken, pullFlags.sourceURL, pullFlags.includePlatforms, pullFlags.excludePlatforms, pullFlags.bundleArchiveFormat)
1717
},
1818
}
1919

2020
type pullFlagFields struct {
21-
sourceToken string
22-
sourceURL string
21+
sourceToken string
22+
sourceURL string
23+
includePlatforms []string
24+
excludePlatforms []string
25+
bundleArchiveFormat string
2326
}
2427

2528
var pullFlags = pullFlagFields{}
@@ -28,4 +31,7 @@ func (f *pullFlagFields) Init(cmd *cobra.Command) {
2831
cmd.Flags().StringVar(&f.sourceToken, "source-token", "", "A token to access the API of GitHub.com. This is normally not required, but can be provided if you have issues with API rate limiting.")
2932
cmd.Flags().StringVar(&f.sourceURL, "source-url", "", "Use a custom Git URL for fetching the Action repository contents from. The CodeQL bundles will still be fetched from GitHub.com.")
3033
cmd.Flags().MarkHidden("source-url")
34+
cmd.Flags().StringSliceVar(&f.includePlatforms, "include-platforms", nil, "Only download release assets for these platforms: linux64, linux-arm64, osx64, win64.")
35+
cmd.Flags().StringSliceVar(&f.excludePlatforms, "exclude-platforms", nil, "Download release assets for every platform except these: linux64, linux-arm64, osx64, win64.")
36+
cmd.Flags().StringVar(&f.bundleArchiveFormat, "bundle-archive-format", "", "Only download CodeQL bundles in this archive format: tar.gz or tar.zst. Requires an explicit tools URL in CodeQL workflows.")
3137
}

‎cmd/sync.go‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,12 @@ var syncCmd = &cobra.Command{
1313
Short: "Sync the CodeQL Action from GitHub to a GitHub Enterprise Server installation.",
1414
RunE: func(cmd *cobra.Command, args []string) error {
1515
version.LogVersion()
16+
err := push.ValidateArguments(pushFlags.destinationURL, pushFlags.destinationToken, pushFlags.destinationRepository)
17+
if err != nil {
18+
return err
19+
}
1620
cacheDirectory := cachedirectory.NewCacheDirectory(rootFlags.cacheDir)
17-
err := pull.Pull(cmd.Context(), cacheDirectory, pullFlags.sourceToken, pullFlags.sourceURL)
21+
err = pull.Pull(cmd.Context(), cacheDirectory, pullFlags.sourceToken, pullFlags.sourceURL, pullFlags.includePlatforms, pullFlags.excludePlatforms, pullFlags.bundleArchiveFormat)
1822
if err != nil {
1923
return err
2024
}

‎internal/pull/pull.go‎

Lines changed: 65 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ type pullService struct {
4242
gitCloneURL string
4343
githubDotComClient *github.Client
4444
sourceToken string
45+
assetFilter releaseAssetFilter
4546
}
4647

4748
func (pullService *pullService) pullGit(fresh bool) error {
@@ -185,6 +186,9 @@ func (pullService *pullService) findRelevantReleases() ([]string, error) {
185186

186187
func (pullService *pullService) pullReleases() error {
187188
log.Debug("Pulling CodeQL bundles...")
189+
if pullService.assetFilter.bundleArchiveFormat != "" {
190+
log.Warnf("--bundle-archive-format %s does not change the CodeQL Action's automatic archive selection. Configure the CodeQL init step's tools input with an explicit URL for a synced bundle; otherwise air-gapped runners may attempt to download an excluded format from GitHub.com.", pullService.assetFilter.bundleArchiveFormat)
191+
}
188192
relevantReleases, err := pullService.findRelevantReleases()
189193
if err != nil {
190194
return err
@@ -196,6 +200,11 @@ func (pullService *pullService) pullReleases() error {
196200
if err != nil {
197201
return githubapiutil.EnrichResponseError(response, err, "Error loading CodeQL release information.")
198202
}
203+
selectedAssets, skippedAssets, err := pullService.assetFilter.selectAssets(releaseTag, release.Assets)
204+
if err != nil {
205+
return err
206+
}
207+
logAssetSelection(releaseTag, len(release.Assets), len(selectedAssets), skippedAssets)
199208
err = os.MkdirAll(pullService.cacheDirectory.ReleasePath(releaseTag), 0755)
200209
if err != nil {
201210
return errors.Wrap(err, "Error creating releases directory.")
@@ -214,7 +223,24 @@ func (pullService *pullService) pullReleases() error {
214223
if err != nil {
215224
return errors.Wrap(err, "Error creating assets directory.")
216225
}
217-
for _, asset := range release.Assets {
226+
selectedAssetNames := map[string]bool{}
227+
for _, asset := range selectedAssets {
228+
selectedAssetNames[asset.GetName()] = true
229+
}
230+
cachedAssets, err := ioutil.ReadDir(assetsPath)
231+
if err != nil {
232+
return errors.Wrap(err, "Error reading cached release assets.")
233+
}
234+
for _, cachedAsset := range cachedAssets {
235+
if !selectedAssetNames[cachedAsset.Name()] {
236+
log.Debugf("Removing filtered cached asset %s...", cachedAsset.Name())
237+
err = os.RemoveAll(pullService.cacheDirectory.AssetPath(releaseTag, cachedAsset.Name()))
238+
if err != nil {
239+
return errors.Wrap(err, "Error removing filtered cached asset.")
240+
}
241+
}
242+
}
243+
for _, asset := range selectedAssets {
218244
log.Debugf("Downloading asset %s...", asset.GetName())
219245
downloadPath := pullService.cacheDirectory.AssetPath(releaseTag, asset.GetName())
220246
downloadPathStat, err := os.Stat(downloadPath)
@@ -257,11 +283,46 @@ func (pullService *pullService) pullReleases() error {
257283
}
258284
}
259285
}
286+
return pullService.pruneFilteredCachedAssets()
287+
}
288+
289+
func (pullService *pullService) pruneFilteredCachedAssets() error {
290+
if !pullService.assetFilter.filtersPlatforms() && pullService.assetFilter.bundleArchiveFormat == "" {
291+
return nil
292+
}
293+
cachedReleases, err := ioutil.ReadDir(pullService.cacheDirectory.ReleasesPath())
294+
if os.IsNotExist(err) {
295+
return nil
296+
}
297+
if err != nil {
298+
return errors.Wrap(err, "Error reading cached releases.")
299+
}
300+
for _, release := range cachedReleases {
301+
cachedAssets, err := ioutil.ReadDir(pullService.cacheDirectory.AssetsPath(release.Name()))
302+
if err != nil {
303+
return errors.Wrapf(err, "Error reading cached release assets for %s.", release.Name())
304+
}
305+
for _, asset := range cachedAssets {
306+
reason := pullService.assetFilter.exclusionReason(classifyReleaseAsset(asset.Name()))
307+
if reason == "" {
308+
continue
309+
}
310+
log.Debugf("Removing cached asset %s from release %s: %s.", asset.Name(), release.Name(), reason)
311+
err = os.RemoveAll(pullService.cacheDirectory.AssetPath(release.Name(), asset.Name()))
312+
if err != nil {
313+
return errors.Wrapf(err, "Error removing filtered cached asset %s from release %s.", asset.Name(), release.Name())
314+
}
315+
}
316+
}
260317
return nil
261318
}
262319

263-
func Pull(ctx context.Context, cacheDirectory cachedirectory.CacheDirectory, sourceToken string, sourceURL string) error {
264-
err := cacheDirectory.CheckOrCreateVersionFile(true, version.Version())
320+
func Pull(ctx context.Context, cacheDirectory cachedirectory.CacheDirectory, sourceToken string, sourceURL string, includePlatforms []string, excludePlatforms []string, bundleArchiveFormat string) error {
321+
assetFilter, err := newReleaseAssetFilter(includePlatforms, excludePlatforms, bundleArchiveFormat)
322+
if err != nil {
323+
return err
324+
}
325+
err = cacheDirectory.CheckOrCreateVersionFile(true, version.Version())
265326
if err != nil {
266327
return err
267328
}
@@ -288,6 +349,7 @@ func Pull(ctx context.Context, cacheDirectory cachedirectory.CacheDirectory, sou
288349
gitCloneURL: sourceURL,
289350
githubDotComClient: github.NewClient(tokenClient),
290351
sourceToken: sourceToken,
352+
assetFilter: assetFilter,
291353
}
292354

293355
err = pullService.pullGit(false)

0 commit comments

Comments
 (0)