-
Notifications
You must be signed in to change notification settings - Fork 147
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 artifact_proxy
server
#222
Merged
Merged
Changes from 6 commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
6b9ee32
feat(artifact_proxy): create artifact proxy server
eseidel 89d41f3
chore: update README
felangel f2ab822
more cleanup
felangel d104f26
formatting
felangel 7bf0a8c
update config
felangel 0541629
ci: add deploy workflow
felangel ad8686c
apply feedback
felangel 2565d55
apply more feedback
felangel File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
name: Deploy Artifacts Proxy | ||
|
||
on: | ||
workflow_dispatch: | ||
push: | ||
branches: | ||
- main | ||
paths: | ||
- .github/workflows/deploy_artifacts_proxy.yaml | ||
- packages/artifacts_proxy/**" | ||
|
||
env: | ||
PROJECT_ID: code-push-prod | ||
SERVICE: artifacts-proxy | ||
REGION: us-central1 | ||
|
||
jobs: | ||
deploy: | ||
runs-on: ubuntu-latest | ||
|
||
defaults: | ||
run: | ||
working-directory: packages/artifacts_proxy | ||
|
||
name: ☁️ Artifacts Proxy | ||
|
||
steps: | ||
- name: 📚 Git Checkout | ||
uses: actions/checkout@v3 | ||
with: | ||
submodules: recursive | ||
|
||
- uses: dart-lang/setup-dart@v1 | ||
|
||
- name: Setup Cloud SDK | ||
uses: google-github-actions/[email protected] | ||
with: | ||
project_id: ${{ env.PROJECT_ID }} | ||
service_account_key: ${{ secrets.CLOUD_RUN_SA_PROD }} | ||
export_default_credentials: true | ||
|
||
- name: Authorize Docker Push | ||
run: gcloud auth configure-docker | ||
|
||
- name: Build and Push Container | ||
run: |- | ||
docker build -t gcr.io/${{ env.PROJECT_ID }}/${{ env.SERVICE }}:${{ github.sha }} . | ||
docker push gcr.io/${{ env.PROJECT_ID }}/${{ env.SERVICE }}:${{ github.sha }} | ||
|
||
- name: Deploy to Cloud Run | ||
id: deploy | ||
uses: google-github-actions/[email protected] | ||
with: | ||
service: ${{ env.SERVICE }} | ||
image: gcr.io/${{ env.PROJECT_ID }}/${{ env.SERVICE }}:${{ github.sha }} | ||
region: ${{ env.REGION }} | ||
|
||
- name: Show Output | ||
run: echo ${{ steps.deploy.outputs.url }} | ||
|
||
- name: Ping | ||
run: curl "${{ steps.deploy.outputs.url }}" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
# https://dart.dev/guides/libraries/private-files | ||
# Created by `dart pub` | ||
.dart_tool/ | ||
|
||
# Used for local testing of artifact_proxy. | ||
artifacts/ | ||
felangel marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
# Test related files | ||
coverage/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
# Official Dart image: https://hub.docker.com/_/dart | ||
# Specify the Dart SDK base image version using dart:<version> (ex: dart:2.12) | ||
FROM dart:stable AS build | ||
|
||
# Resolve app dependencies. | ||
WORKDIR /app | ||
|
||
# Copy app source code and AOT compile it. | ||
COPY . . | ||
felangel marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
# Ensure packages are still up-to-date if anything has changed | ||
RUN dart pub get --offline | ||
RUN dart compile exe bin/server.dart -o bin/server | ||
|
||
# Build minimal serving image from AOT-compiled `/server` and required system | ||
# libraries and configuration files stored in `/runtime/` from the build stage. | ||
FROM scratch | ||
COPY --from=build /runtime/ / | ||
COPY --from=build /app/bin/server /app/bin/ | ||
|
||
# Start server. | ||
EXPOSE 8080 | ||
CMD ["/app/bin/server"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
# Artifact Proxy | ||
|
||
This is a tool for proxying Flutter artifacts from a derived Flutter engine | ||
revision back to the base Flutter engine revision. This is useful for | ||
when you need to modify _some_ of the Flutter artifacts but not all of them. | ||
|
||
This is a development tool which map requests to Google | ||
Storage (either Shorebird's bucket or the official Flutter buckets). | ||
|
||
## Usage | ||
|
||
Uses `config.yaml` to configure the engine revisions and artifact overrides. | ||
|
||
```bash | ||
# Run locally with hot-reload enabled. | ||
dart --enable-vm-service run bin/server.dart --watch | ||
``` | ||
|
||
And then in a separate terminal: | ||
|
||
``` | ||
FLUTTER_STORAGE_BASE_URL=http://localhost:8080 flutter precache -a | ||
``` | ||
|
||
You should use a separate checkout of Flutter when running this, so you don't | ||
poison the cache of your main Flutter checkout. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
include: package:very_good_analysis/analysis_options.4.0.0.yaml |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
// ignore_for_file: avoid_print | ||
|
||
import 'dart:io'; | ||
|
||
import 'package:args/args.dart'; | ||
import 'package:artifact_proxy/artifact_proxy.dart'; | ||
import 'package:shelf/shelf.dart'; | ||
import 'package:shelf/shelf_io.dart' as shelf_io; | ||
import 'package:shelf_hotreload/shelf_hotreload.dart'; | ||
import 'package:yaml/yaml.dart'; | ||
|
||
Future<void> main(List<String> args) async { | ||
final parser = ArgParser() | ||
..addOption( | ||
'config', | ||
defaultsTo: 'config.yaml', | ||
help: 'Path to config file.', | ||
) | ||
..addFlag( | ||
felangel marked this conversation as resolved.
Show resolved
Hide resolved
|
||
'record', | ||
help: 'Record requests into config file.', | ||
) | ||
..addFlag( | ||
'watch', | ||
help: 'Whether to watch for changes and hot-reload.', | ||
); | ||
|
||
final results = parser.parse(args); | ||
|
||
if (results.rest.isNotEmpty) { | ||
print(parser.usage); | ||
exit(1); | ||
} | ||
|
||
final shouldWatch = results['watch'] as bool; | ||
final configPath = results['config'] as String; | ||
final config = loadYaml(File(configPath).readAsStringSync()) as Map; | ||
final handler = artifactProxyHandler(config: config); | ||
final ip = InternetAddress.anyIPv6; | ||
felangel marked this conversation as resolved.
Show resolved
Hide resolved
|
||
final port = int.parse(Platform.environment['PORT'] ?? '8080'); | ||
felangel marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
// Hot-reload is enabled when the `--watch` flag is passed. | ||
if (shouldWatch) return withHotreload(() => serve(handler, ip, port)); | ||
|
||
await serve(handler, ip, port); | ||
} | ||
|
||
Future<HttpServer> serve(Handler proxy, InternetAddress ip, int port) async { | ||
const pipeline = Pipeline(); | ||
final handler = pipeline.addMiddleware(logRequests()).addHandler(proxy); | ||
final server = await shelf_io.serve(handler, ip, port); | ||
print('Serving at http://localhost:${server.port}'); | ||
server.autoCompress = true; | ||
return server; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
engine_mappings: | ||
79f4c5321a581f580a9bda01ec372cbf4a53aa53: | ||
flutter_engine_revision: 9aa7816315095c86410527932918c718cb35e7d6 | ||
shorebird_storage_bucket: download.shorebird.dev | ||
felangel marked this conversation as resolved.
Show resolved
Hide resolved
|
||
shorebird_artifact_overrides: | ||
felangel marked this conversation as resolved.
Show resolved
Hide resolved
|
||
# android-x64 artifacts.zip | ||
- flutter_infra_release/flutter/$engine/android-x64-release/artifacts.zip | ||
felangel marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
# embedding release | ||
- download.flutter.io/io/flutter/flutter_embedding_release/1.0.0-$engine/flutter_embedding_release-1.0.0-$engine.pom | ||
- download.flutter.io/io/flutter/flutter_embedding_release/1.0.0-$engine/flutter_embedding_release-1.0.0-$engine.jar | ||
|
||
# arm64_v8a release | ||
- download.flutter.io/io/flutter/arm64_v8a_release/1.0.0-$engine/arm64_v8a_release-1.0.0-$engine.pom | ||
- download.flutter.io/io/flutter/arm64_v8a_release/1.0.0-$engine/arm64_v8a_release-1.0.0-$engine.jar | ||
|
||
# armeabi_v7a release | ||
- download.flutter.io/io/flutter/armeabi_v7a_release/1.0.0-$engine/armeabi_v7a_release-1.0.0-$engine.pom | ||
- download.flutter.io/io/flutter/armeabi_v7a_release/1.0.0-$engine/armeabi_v7a_release-1.0.0-$engine.jar | ||
|
||
# x86_64 release | ||
- download.flutter.io/io/flutter/x86_64_release/1.0.0-$engine/x86_64_release-1.0.0-$engine.pom | ||
- download.flutter.io/io/flutter/x86_64_release/1.0.0-$engine/x86_64_release-1.0.0-$engine.jar |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
// ignore_for_file: avoid_print | ||
|
||
import 'package:collection/collection.dart'; | ||
import 'package:shelf/shelf.dart'; | ||
|
||
/// A [Handler] that proxies artifact requests to the correct location. | ||
/// This is determined based on the [config]. | ||
Handler artifactProxyHandler({required Map<dynamic, dynamic> config}) { | ||
final engineMappings = config['engine_mappings'] as Map; | ||
final shorebirdEngineRevisions = engineMappings.keys.cast<String>(); | ||
|
||
return (Request request) { | ||
final path = request.url.path; | ||
final shorebirdEngineRevision = shorebirdEngineRevisions.firstWhereOrNull( | ||
path.contains, | ||
felangel marked this conversation as resolved.
Show resolved
Hide resolved
|
||
); | ||
|
||
final normalizedPath = shorebirdEngineRevision != null | ||
? path.replaceAll(shorebirdEngineRevision, r'$engine') | ||
: path; | ||
|
||
if (shorebirdEngineRevision == null) { | ||
final location = getFlutterArtifactLocation(artifactPath: normalizedPath); | ||
print('No engine revision detected, forwarding to: $location'); | ||
return Response.found(location); | ||
} | ||
|
||
final engineMapping = engineMappings[shorebirdEngineRevision] as Map; | ||
final shorebirdOverrides = | ||
engineMapping['shorebird_artifact_overrides'] as List; | ||
final flutterEngineRevision = | ||
engineMapping['flutter_engine_revision'] as String; | ||
final shorebirdStorageBucket = | ||
engineMapping['shorebird_storage_bucket'] as String; | ||
felangel marked this conversation as resolved.
Show resolved
Hide resolved
|
||
final shouldOverride = shorebirdOverrides.contains(normalizedPath); | ||
|
||
if (shouldOverride) { | ||
final location = getShorebirdArtifactLocation( | ||
artifactPath: normalizedPath, | ||
engine: shorebirdEngineRevision, | ||
bucket: shorebirdStorageBucket, | ||
); | ||
print('Shorebird artifact detected, forwarding to: $location'); | ||
return Response.found(location); | ||
} | ||
|
||
final location = getFlutterArtifactLocation( | ||
artifactPath: normalizedPath, | ||
engine: flutterEngineRevision, | ||
); | ||
print('Flutter artifact detected, forwarding to: $location'); | ||
return Response.found(location); | ||
}; | ||
} | ||
|
||
/// Returns the location of the artifact at [artifactPath] using the | ||
/// specified [engine] revision for original Flutter artifacts. | ||
String getFlutterArtifactLocation({ | ||
required String artifactPath, | ||
String? engine, | ||
}) { | ||
final adjustedPath = engine != null | ||
? artifactPath.replaceAll(r'$engine', engine) | ||
: artifactPath; | ||
|
||
final isChromeInfra = adjustedPath.contains('flutter_infra_release/cipd'); | ||
|
||
if (isChromeInfra) { | ||
felangel marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return adjustedPath.replaceAll( | ||
'flutter_infra_release/cipd', | ||
'https://chrome-infra-packages.appspot.com/dl', | ||
); | ||
} | ||
|
||
return 'https://storage.googleapis.com/$adjustedPath'; | ||
} | ||
|
||
/// Returns the location of the artifact at [artifactPath] using the | ||
/// specified [engine] revision for Shorebird artifacts. | ||
String getShorebirdArtifactLocation({ | ||
required String artifactPath, | ||
required String engine, | ||
required String bucket, | ||
}) { | ||
final adjustedPath = artifactPath.replaceAll(r'$engine', engine); | ||
return 'https://storage.googleapis.com/$bucket/$adjustedPath'; | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
It scares me ever so slightly that we're hosting this in our public repo. Any commit reviewed to artifacts_proxy could end up directly in prod... Which I guess is good? Just a bit scary to have it so little interaction to get to prod.
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.
Yeah we can make this a manual trigger only if you prefer. Ideally we make the CI checks as robust as we need to give us the confidence to deploy automatically 😄