├── .github
└── workflows
│ └── scorecard.yml
├── .gitignore
├── .style.yapf
├── CONTRIBUTING.md
├── LICENSE
├── Makefile
├── README.md
├── SECURITY.md
├── docs
├── SCHEMA.md
├── configuration.md
├── java.md
├── node-js.md
├── python.md
└── troubleshooting.md
├── pylintrc
├── samples
├── README.md
├── app_engine_flexible_prerequisites.md
├── app_engine_standard_prerequisites.md
├── gce_samples_prerequisites.md
├── java
│ ├── appengine-flexible
│ │ ├── README.md
│ │ ├── helloworld-springboot
│ │ │ ├── README.md
│ │ │ ├── pom.xml
│ │ │ └── src
│ │ │ │ ├── main
│ │ │ │ ├── appengine
│ │ │ │ │ └── app.yaml
│ │ │ │ ├── docker
│ │ │ │ │ └── Dockerfile
│ │ │ │ ├── java
│ │ │ │ │ └── com
│ │ │ │ │ │ └── example
│ │ │ │ │ │ └── java
│ │ │ │ │ │ ├── HelloController.java
│ │ │ │ │ │ └── HelloWorldApplication.java
│ │ │ │ └── resources
│ │ │ │ │ └── application.properties
│ │ │ │ └── test
│ │ │ │ └── java
│ │ │ │ └── com
│ │ │ │ └── example
│ │ │ │ └── java
│ │ │ │ └── HelloControllerTest.java
│ │ └── helloworld
│ │ │ ├── README.md
│ │ │ ├── pom.xml
│ │ │ └── src
│ │ │ └── main
│ │ │ ├── appengine
│ │ │ └── app.yaml
│ │ │ ├── docker
│ │ │ └── Dockerfile
│ │ │ └── java
│ │ │ └── com
│ │ │ └── example
│ │ │ └── flexible
│ │ │ └── helloworld
│ │ │ └── HelloServlet.java
│ ├── appengine-java11
│ │ ├── README.md
│ │ ├── appengine-simple-jetty-main
│ │ │ ├── README.md
│ │ │ ├── pom.xml
│ │ │ └── src
│ │ │ │ └── main
│ │ │ │ └── java
│ │ │ │ └── com
│ │ │ │ └── example
│ │ │ │ └── appengine
│ │ │ │ └── demo
│ │ │ │ └── jettymain
│ │ │ │ └── Main.java
│ │ ├── helloworld-servlet
│ │ │ ├── README.md
│ │ │ ├── pom.xml
│ │ │ └── src
│ │ │ │ └── main
│ │ │ │ ├── appengine
│ │ │ │ └── app.yaml
│ │ │ │ └── java
│ │ │ │ └── com
│ │ │ │ └── example
│ │ │ │ └── appengine
│ │ │ │ └── HelloAppEngine.java
│ │ └── simple-server
│ │ │ ├── .gitignore
│ │ │ ├── Main.java
│ │ │ ├── README.md
│ │ │ └── app.yaml
│ ├── appengine-java8
│ │ ├── README.md
│ │ └── helloworld
│ │ │ ├── README.md
│ │ │ ├── pom.xml
│ │ │ └── src
│ │ │ ├── main
│ │ │ ├── java
│ │ │ │ └── com
│ │ │ │ │ └── example
│ │ │ │ │ └── appengine
│ │ │ │ │ └── java8
│ │ │ │ │ └── HelloAppEngine.java
│ │ │ └── webapp
│ │ │ │ ├── WEB-INF
│ │ │ │ └── appengine-web.xml
│ │ │ │ └── index.jsp
│ │ │ └── test
│ │ │ └── java
│ │ │ └── com
│ │ │ └── example
│ │ │ └── appengine
│ │ │ └── java8
│ │ │ └── HelloAppEngineTest.java
│ └── gce
│ │ ├── README.md
│ │ ├── config
│ │ └── base
│ │ │ ├── etc
│ │ │ └── java-util-logging.properties
│ │ │ ├── modules
│ │ │ └── gce.mod
│ │ │ └── resources
│ │ │ └── jetty-logging.properties
│ │ ├── deploy.sh
│ │ ├── pom.xml
│ │ ├── src
│ │ ├── main
│ │ │ ├── appengine
│ │ │ │ └── app.yaml
│ │ │ ├── java
│ │ │ │ └── com
│ │ │ │ │ └── example
│ │ │ │ │ └── getstarted
│ │ │ │ │ └── basicactions
│ │ │ │ │ └── HelloworldController.java
│ │ │ └── webapp
│ │ │ │ └── WEB-INF
│ │ │ │ └── web.xml
│ │ └── test
│ │ │ └── java
│ │ │ └── com
│ │ │ └── example
│ │ │ └── getstarted
│ │ │ └── basicactions
│ │ │ └── UserJourneyTestIT.java
│ │ ├── startup-script.sh
│ │ └── teardown.sh
├── node-js
│ ├── appengine-flexible
│ │ ├── README.md
│ │ ├── app.js
│ │ ├── app.yaml
│ │ └── package.json
│ ├── appengine-standard
│ │ ├── README.md
│ │ ├── app.js
│ │ ├── app.yaml
│ │ └── package.json
│ └── gce
│ │ ├── README.md
│ │ ├── app.js
│ │ ├── package.json
│ │ └── startup-script.sh
└── python
│ ├── appengine-flexible
│ ├── README.md
│ ├── app.yaml
│ ├── main.py
│ └── requirements.txt
│ ├── appengine-standard
│ ├── README.md
│ ├── app.yaml
│ ├── main.py
│ └── requirements.txt
│ └── gce
│ ├── README.md
│ ├── add-google-cloud-ops-agent-repo.sh
│ ├── deploy.sh
│ ├── main.py
│ ├── procfile
│ ├── python-app.conf
│ ├── requirements.txt
│ ├── startup-script.sh
│ └── teardown.sh
├── setup.cfg
├── setup.py
├── snapshot_dbg_cli
├── COMMAND_REFERENCE.md
├── __init__.py
├── __main__.py
├── breakpoint_utils.py
├── cli.py
├── cli_common_arguments.py
├── cli_run.py
├── cli_services.py
├── cli_version.py
├── data_formatter.py
├── debuggee_utils.py
├── delete_breakpoints.py
├── delete_debuggees_command.py
├── delete_logpoints_command.py
├── delete_snapshots_command.py
├── exceptions.py
├── firebase_management_rest_service.py
├── firebase_rtdb_rest_service.py
├── firebase_types.py
├── gcloud_cli_service.py
├── get_logpoint_command.py
├── get_snapshot_command.py
├── http_service.py
├── init_command.py
├── list_debuggees_command.py
├── list_logpoints_command.py
├── list_snapshots_command.py
├── permissions_rest_service.py
├── set_logpoint_command.py
├── set_snapshot_command.py
├── snapshot_debugger_rtdb_service.py
├── snapshot_debugger_schema.py
├── snapshot_parser.py
├── status_message.py
├── time_utils.py
├── user_input.py
└── user_output.py
├── snapshot_dbg_cli_tests
├── __init__.py
├── test_breakpoint_utils.py
├── test_cli_common_arguments.py
├── test_cli_run.py
├── test_cli_services.py
├── test_cli_version.py
├── test_data_formatter.py
├── test_debuggee_utils.py
├── test_delete_debuggees_command.py
├── test_delete_logpoints_command.py
├── test_delete_snapshots_command.py
├── test_firebase_management_rest_service.py
├── test_firebase_rtdb_rest_service.py
├── test_firebase_types.py
├── test_gcloud_cli_service.py
├── test_get_logpoint_command.py
├── test_get_snapshot_command.py
├── test_http_service.py
├── test_init.py
├── test_init_command.py
├── test_list_debuggees_command.py
├── test_list_logpoints_command.py
├── test_list_snapshots_command.py
├── test_permissions_rest_service.py
├── test_set_logpoint_command.py
├── test_set_snapshot_command.py
├── test_snapshot_debugger_rtdb_service.py
├── test_snapshot_debugger_schema.py
├── test_snapshot_parser.py
├── test_status_message.py
├── test_time_utils.py
├── test_user_input.py
└── test_user_output.py
└── snapshot_dbg_extension
├── .gitignore
├── .vscodeignore
├── CHANGELOG.md
├── LICENSE
├── README.md
├── media
├── expr.svg
└── icon-512.png
├── package.json
├── src
├── adapter.ts
├── breakpoint.ts
├── breakpointManager.ts
├── debugUtil.ts
├── debuggeePicker.ts
├── expressionsPrompter.ts
├── extension.ts
├── gcloudCredential.ts
├── ideBreakpoints.ts
├── initialActiveBreakpoints.ts
├── lineMappings.ts
├── logLevelPicker.ts
├── logpointMessage.ts
├── snapshotPicker.ts
├── statusMessage.ts
├── test
│ ├── runTest.ts
│ └── suite
│ │ ├── extension.test.ts
│ │ └── index.ts
├── userPreferences.ts
├── util.ts
└── whenClauseContextUtil.ts
└── tsconfig.json
/.github/workflows/scorecard.yml:
--------------------------------------------------------------------------------
1 | # This workflow uses actions that are not certified by GitHub. They are provided
2 | # by a third-party and are governed by separate terms of service, privacy
3 | # policy, and support documentation.
4 |
5 | name: Scorecard supply-chain security
6 | on:
7 | # For Branch-Protection check. Only the default branch is supported. See
8 | # https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection
9 | branch_protection_rule:
10 | # To guarantee Maintained check is occasionally updated. See
11 | # https://github.com/ossf/scorecard/blob/main/docs/checks.md#maintained
12 | schedule:
13 | - cron: '38 17 * * 4'
14 | push:
15 | branches: [ "main" ]
16 |
17 | # Declare default permissions as read only.
18 | permissions: read-all
19 |
20 | jobs:
21 | analysis:
22 | name: Scorecard analysis
23 | runs-on: ubuntu-latest
24 | permissions:
25 | # Needed to upload the results to code-scanning dashboard.
26 | security-events: write
27 | # Needed to publish results and get a badge (see publish_results below).
28 | id-token: write
29 | # Uncomment the permissions below if installing in a private repository.
30 | # contents: read
31 | # actions: read
32 |
33 | steps:
34 | - name: "Checkout code"
35 | uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # v3.1.0
36 | with:
37 | persist-credentials: false
38 |
39 | - name: "Run analysis"
40 | uses: ossf/scorecard-action@e38b1902ae4f44df626f11ba0734b14fb91f8f86 # v2.1.2
41 | with:
42 | results_file: results.sarif
43 | results_format: sarif
44 | # (Optional) "write" PAT token. Uncomment the `repo_token` line below if:
45 | # - you want to enable the Branch-Protection check on a *public* repository, or
46 | # - you are installing Scorecard on a *private* repository
47 | # To create the PAT, follow the steps in https://github.com/ossf/scorecard-action#authentication-with-pat.
48 | # repo_token: ${{ secrets.SCORECARD_TOKEN }}
49 |
50 | # Public repositories:
51 | # - Publish results to OpenSSF REST API for easy access by consumers
52 | # - Allows the repository to include the Scorecard badge.
53 | # - See https://github.com/ossf/scorecard-action#publishing-results.
54 | # For private repositories:
55 | # - `publish_results` will always be set to `false`, regardless
56 | # of the value entered here.
57 | publish_results: true
58 |
59 | # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
60 | # format to the repository Actions tab.
61 | - name: "Upload artifact"
62 | uses: actions/upload-artifact@3cea5372237819ed00197afe530f5a7ea3e805c8 # v3.1.0
63 | with:
64 | name: SARIF file
65 | path: results.sarif
66 | retention-days: 5
67 |
68 | # Upload the results to GitHub's code scanning dashboard.
69 | - name: "Upload to code-scanning"
70 | uses: github/codeql-action/upload-sarif@17573ee1cc1b9d061760f3a006fc4aac4f944fd5 # v2.2.4
71 | with:
72 | sarif_file: results.sarif
73 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | __pycache__/
2 | *.egg-info/
3 | dist/
4 | env/
5 | *.class
6 | *.jar
7 | *.so
8 | .gcloudignore
9 |
10 | # maven
11 | target/
12 |
13 | # npm
14 | node_modules/
15 | package-lock.json
16 |
--------------------------------------------------------------------------------
/.style.yapf:
--------------------------------------------------------------------------------
1 | [style]
2 | based_on_style = yapf
3 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # How to Contribute
2 |
3 | We'd love to accept your patches and contributions to this project. There are
4 | just a few small guidelines you need to follow.
5 |
6 | ## Contributor License Agreement
7 |
8 | Contributions to this project must be accompanied by a Contributor License
9 | Agreement (CLA). You (or your employer) retain the copyright to your
10 | contribution; this simply gives us permission to use and redistribute your
11 | contributions as part of the project. Head over to
12 | to see your current agreements on file or
13 | to sign a new one.
14 |
15 | You generally only need to submit a CLA once, so if you've already submitted one
16 | (even if it was for a different project), you probably don't need to do it
17 | again.
18 |
19 | ## Code Reviews
20 |
21 | All submissions, including submissions by project members, require review. We
22 | use GitHub pull requests for this purpose. Consult
23 | [GitHub Help](https://help.github.com/articles/about-pull-requests/) for more
24 | information on using pull requests.
25 |
26 | ## CLI coding style
27 |
28 | The CLI python code follows the [Google Python Style
29 | Guide](https://google.github.io/styleguide/pyguide.html) with one exception:
30 |
31 | - 2 spaces for indentation rather than 4.
32 |
33 | It also uses the [YAPF](https://github.com/google/yapf) formatter, which should
34 | be run on any code changes.
35 |
36 | ## Community Guidelines
37 |
38 | This project follows
39 | [Google's Open Source Community Guidelines](https://opensource.google/conduct/).
40 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | .PHONY: test
2 |
3 | test:
4 | python3 -m unittest discover -s snapshot_dbg_cli_tests -t .
5 |
--------------------------------------------------------------------------------
/SECURITY.md:
--------------------------------------------------------------------------------
1 | # Security Policy
2 |
3 | To report a security issue, please use [https://g.co/vulnz](https://g.co/vulnz).
4 |
5 | We use g.co/vulnz for our intake, and do coordination and disclosure here on
6 | GitHub (including using GitHub Security Advisory).
7 |
8 | The Google Security Team will respond within 5 working days of your report on
9 | g.co/vulnz.
10 |
--------------------------------------------------------------------------------
/docs/java.md:
--------------------------------------------------------------------------------
1 | # Java Snapshot Debugger Agent
2 |
3 | This section contains information for integrating and configuring the Java
4 | Snapshot Debugger agent in different environments. Full configuration
5 | information for the agent can be found at [Java Agent Documentation][java-agent]
6 |
7 | ## Samples
8 |
9 | See [samples/java][java-samples] for working examples of installing and
10 | configuring the Java agent across different Google Cloud environments.
11 |
12 | [java-agent]: https://github.com/GoogleCloudPlatform/cloud-debug-java/blob/main/README.md
13 | [java-samples]: https://github.com/GoogleCloudPlatform/snapshot-debugger/tree/main/samples/java
14 |
15 | ## Running locally
16 |
17 | It's also possible to run things outside of a Google Cloud environment, such
18 | as locally. Here are some notes for doing so, which involves generating a
19 | service account key so the agents are able to read/write from the Firebase RTDB
20 | backend.
21 |
22 | ### Download service account credentials from Firebase.
23 |
24 | 1. Navigate to your project in the Firebase console service account page.
25 | Replace `PROJECT_ID` with your project’s ID.
26 |
27 | ```
28 | https://console.firebase.google.com/project/PROJECT_ID/settings/serviceaccounts/adminsdk
29 | ```
30 |
31 | 2. Click **Generate new private key** and save the key locally.
32 |
33 | ### Install and configure the agent
34 |
35 | 1. Download the pre-built agent package:
36 |
37 | ```
38 | # Create a directory for the Debugger. Add and unzip the agent in the directory.
39 | sudo sh -c "mkdir /opt/cdbg && wget -qO- https://github.com/GoogleCloudPlatform/cloud-debug-java/releases/latest/download/cdbg_java_agent_gce.tar.gz | tar xvz -C /opt/cdbg"
40 | ```
41 |
42 | 2. Add the agent to your Java invocation:
43 |
44 | _(If you are using Tomcat or Jetty, see the [Application
45 | Servers](https://github.com/GoogleCloudPlatform/cloud-debug-java#application-servers)
46 | section of the agent documentation for extra information.)_
47 |
48 | ```
49 | # Start the agent when the app is deployed.
50 | java -agentpath:/opt/cdbg/cdbg_java_agent.so \
51 | -Dcom.google.cdbg.module=MODULE \
52 | -Dcom.google.cdbg.version=VERSION \
53 | -Dcom.google.cdbg.agent.use_firebase=True \
54 | -Dcom.google.cdbg.auth.serviceaccount.jsonfile=PATH-TO-KEY-FILE
55 | -jar PATH_TO_JAR_FILE
56 | ```
57 |
58 | Where:
59 | * `MODULE` is a name for your app, such as MyApp, Backend, or Frontend.
60 | * `VERSION` is a version, such as v1.0, build_147, or v20170714.
61 | * `PATH-TO-KEY-FILE` is the path to your Firebase private key.
62 | * `PATH_TO_JAR_FILE` is the relative path to the app's JAR file. e.g.,: ~/myapp.jar.
63 |
--------------------------------------------------------------------------------
/docs/node-js.md:
--------------------------------------------------------------------------------
1 | # Node.js Snapshot Debugger Agent
2 |
3 | This section contains information for integrating and configuring the Node.js
4 | Snapshot Debugger agent in different environments. Full configuration
5 | information for the agent can be found at [Node.js Agent
6 | Documentation][nodejs-agent]
7 |
8 | ## Samples
9 |
10 | See [samples/node-js][nodejs-samples] for working examples of installing and
11 | configuring the Node.js agent across different Google Cloud environments.
12 |
13 | [nodejs-agent]: https://github.com/googleapis/cloud-debug-nodejs/blob/main/README.md
14 | [nodejs-samples]: https://github.com/GoogleCloudPlatform/snapshot-debugger/tree/main/samples/node-js
15 |
16 | ## Running locally
17 |
18 | It's also possible to run things outside of a Google Cloud environment, such
19 | as locally. Here are some notes for doing so, which involves generating a
20 | service account key so the agents are able to read/write from the Firebase RTDB
21 | backend.
22 |
23 | ### Download service account credentials from Firebase.
24 |
25 | 1. Navigate to your project in the Firebase console service account page.
26 | Replace `PROJECT_ID` with your project’s ID.
27 |
28 | ```
29 | https://console.firebase.google.com/project/PROJECT_ID/settings/serviceaccounts/adminsdk
30 | ```
31 |
32 | 2. Click **Generate new private key** and save the key locally.
33 |
34 | ### Install and configure the agent
35 |
36 | 1. Use [npm](https://www.npmjs.com/) to install the package:
37 |
38 | ```
39 | npm install --save @google-cloud/debug-agent
40 | ```
41 |
42 | 2. Configure and enable the agent at the top of your app's main script or entry
43 | point (but after @google/cloud-trace if you are also using it).
44 |
45 | ```
46 | require('@google-cloud/debug-agent').start({
47 | useFirebase: true,
48 | firebaseKeyPath: 'PATH-TO-KEY-FILE',
49 | // Specify this if you are the Spark billing plan and are using the
50 | // default RTDB instance.
51 | // firebaseDbUrl: 'https://RTDB-NAME-default-rtdb.firebaseio.com',
52 | serviceContext: {
53 | service: 'SERVICE',
54 | version: 'VERSION',
55 | }
56 | });
57 | ```
58 |
59 | Where:
60 | * `PATH-TO-KEY-FILE` is the path to your Firebase private key.
61 | * `RTDB-NAME` is the name of your Firebase database.
62 | * `SERVICE` is a name for your app, such as `MyApp`, `Backend`, or `Frontend`.
63 | * `VERSION` is a version, such as `v1.0`, `build_147`, or `v20170714`.
64 |
65 |
--------------------------------------------------------------------------------
/docs/python.md:
--------------------------------------------------------------------------------
1 | # Python Snapshot Debugger Agent
2 |
3 | This section contains information for integrating and configuring the Python
4 | Snapshot Debugger agent in different environments. Full configuration
5 | information for the agent can be found at [Python Agent
6 | Documentation][python-agent]
7 |
8 | ## Samples
9 |
10 | See [samples/python][python-samples] for working examples of installing and
11 | configuring the Python agent across different Google Cloud environments.
12 |
13 | [python-agent]: https://github.com/GoogleCloudPlatform/cloud-debug-python/blob/main/README.md
14 | [python-samples]: https://github.com/GoogleCloudPlatform/snapshot-debugger/tree/main/samples/python
15 |
16 | ## Running locally
17 |
18 | It's also possible to run things outside of a Google Cloud environment, such
19 | as locally. Here are some notes for doing so, which involves generating a
20 | service account key so the agents are able to read/write from the Firebase RTDB
21 | backend.
22 |
23 | ### Download service account credentials from Firebase.
24 |
25 | 1. Navigate to your project in the Firebase console service account page.
26 | Replace `PROJECT_ID` with your project’s ID.
27 |
28 | ```
29 | https://console.firebase.google.com/project/PROJECT_ID/settings/serviceaccounts/adminsdk
30 | ```
31 |
32 | 2. Click **Generate new private key** and save the key locally.
33 |
34 | ### Install and configure the agent
35 |
36 | 1. Download the Debugger agent.
37 |
38 | The easiest way to install the Python Debugger is with
39 | [pip](https://pypi.org/project/pip/)
40 |
41 | ```
42 | pip install google-python-cloud-debugger
43 | ```
44 |
45 | 2. Add the following lines as early as possible in your initialization code, such as in your main function, or in manage.py when using the Django web framework.
46 |
47 | ```
48 | try:
49 | import googleclouddebugger
50 | googleclouddebugger.enable(
51 | use_firebase=True,
52 | module='[MODULE]',
53 | version='[VERSION]',
54 | service_account_json_file='[PATH-TO-KEY-FILE]'
55 | )
56 | except ImportError:
57 | pass
58 | ```
59 |
60 | Where:
61 | * `MODULE` is a name for your app, such as MyApp, Backend, or Frontend.
62 | * `VERSION` is a version, such as v1.0, build_147, or v20170714.
63 | * `PATH-TO-KEY-FILE` is the path to your Firebase private key.
64 |
--------------------------------------------------------------------------------
/docs/troubleshooting.md:
--------------------------------------------------------------------------------
1 | # Snapshot Debugger Troubleshooting
2 |
3 | If you run into problems with Snapshot Debugger, file an
4 | [issue](https://github.com/GoogleCloudPlatform/snapshot-debugger/issues).
5 |
6 | ## Your project doesn’t show up when enabling Firebase
7 |
8 | ### Symptom
9 |
10 | Your project id is not auto populated and is not present in the project dropdown
11 | when you try to [Enable Firebase for your GCP Project][enable-firebase]
12 |
13 | ### Resolution
14 |
15 | Check if the Firebase Management API is already enabled, as if it is, that
16 | interferes with the process. Try using the following link to disable the
17 | Firebase Management API, then go back and follow the steps in the [Enable
18 | Firebase for your GCP Project][enable-firebase]
19 |
20 | ```
21 | https://console.developers.google.com/apis/api/firebase.googleapis.com?project=PROJECT_ID
22 | ```
23 |
24 | Where PROJECT_ID is your project ID.
25 |
26 | ## Your database in not displayed in the Firebase Console
27 |
28 | ### Symptom
29 |
30 | A blank screen is shown when attempting to view database contents in Firebase
31 | Console's Realtime Database section. The project is using the Blaze pricing
32 | plan.
33 |
34 | ### Resolution
35 |
36 | Rerun the init command to find the database's url. Use that url to view the
37 | database's contents. See [Blaze plan RTDB setup][blaze-plan-setup] for
38 | details, as noted there, it is safe to run the `init` command multiple times to
39 | view your database's information.
40 |
41 | The database's url should resemble `https://DATABASE_NAME.firebaseio.com`,
42 | which should redirect to
43 | `https://console.firebase.google.com/project/PROJECT_ID/database/DATABASE_NAME/data`
44 | for the Firebase Console view.
45 |
46 | [enable-firebase]: https://github.com/GoogleCloudPlatform/snapshot-debugger/blob/main/README.md#enable-firebase-for-your-google-cloud-project
47 | [blaze-plan-setup]: https://github.com/GoogleCloudPlatform/snapshot-debugger/blob/main/README.md#blaze-plan-rtdb-setup
48 |
49 | ## Your Debuggee Is Not Found
50 |
51 | ### Symptom
52 |
53 | When you run the `snapshot-dbg-cli list_debuggees` command, a debuggee you
54 | expect to be present is not shown.
55 |
56 | ### Resolution
57 |
58 | There are a variety of potential causes for this, run through the following:
59 |
60 | #### Wake your application up
61 |
62 | For environments such as App Engine Standard, your application may not actually
63 | be running. This can be the case if one of the following holds:
64 |
65 | * You have just deployed your application and it has not yet received a request
66 | * Enough time has passed since it last serviced a request, as a result the
67 | instance count may have been scaled to 0
68 |
69 | Simply hitting your application's endpoint will cause your application to run,
70 | and should cause the debuggee to be listed by `snapshot-dbg-cli list_debuggees`.
71 |
72 | #### Ensure the required access scopes are in place
73 |
74 | Ensure the required access scopes are configured for your environment so that
75 | the Snapshot Debugger Agent is able to communicate with the Firebase RTDB
76 | backend. See [Required Access Scopes][access-scopes] for more information.
77 |
78 | #### Ensure the service account has the required permissions
79 |
80 | Ensure the service account running your service has the required permissions.
81 | See [Required Permissions][required-permissions] for more information. If you
82 | set the permission, wait a few minutes before checking to see if the debuggee
83 | now appears.
84 |
85 | [access-scopes]: https://github.com/GoogleCloudPlatform/snapshot-debugger/blob/main/docs/configuration.md#access-scopes
86 | [required-permissions]: https://github.com/GoogleCloudPlatform/snapshot-debugger/blob/main/docs/configuration.md#service-account-permissions
87 |
--------------------------------------------------------------------------------
/samples/README.md:
--------------------------------------------------------------------------------
1 | # Snapshot Debugger Samples
2 |
3 | This directory contains working examples of using the Snapshot Debugger with
4 | Java, Python and Node.js applications across different Google Cloud
5 | environments.
6 |
7 | The accompanying README files in the subdirectories contain the relevant
8 | information for each sample.
9 |
10 | For more information on the Snapshot Debugger see [Snapshot
11 | Debugger](https://github.com/GoogleCloudPlatform/snapshot-debugger)
12 |
13 | For more information on the agents, as well as how to run them locally, see the
14 | agent specific documentation:
15 |
16 | * [Java](https://github.com/GoogleCloudPlatform/snapshot-debugger/blob/main/docs/java.md)
17 | * [Node.js](https://github.com/GoogleCloudPlatform/snapshot-debugger/blob/main/docs/node-js.md)
18 | * [Python](https://github.com/GoogleCloudPlatform/snapshot-debugger/blob/main/docs/python.md)
19 |
--------------------------------------------------------------------------------
/samples/app_engine_flexible_prerequisites.md:
--------------------------------------------------------------------------------
1 | # Prerequisite Steps For The App Engine Flexible Samples
2 |
3 | ## Project Setup
4 |
5 | Follow the instructions
6 | [here](https://cloud.google.com/appengine/docs/flexible/managing-projects-apps-billing)
7 | to create/confgure your App Engine enabled project.
8 |
9 | ## Install the Snapshot Debugger CLI and enable Firebase
10 |
11 | Follow the instructions beginning at [Before you
12 | begin](../README.md#before-you-begin) through to and including [Enable
13 | Firebase for your Google Cloud
14 | Project](../README.md#enable-firebase-for-your-google-cloud-project) to
15 | get the Snapshot Debugger CLI installed and your project configured to use
16 | Firebase.
17 |
18 | ## Configure Service Account Roles
19 |
20 | The application running on App Engine will need to read/write from the Firebase
21 | RTDB instance. For it to do that, the service account running the application
22 | will need the `roles/firebasedatabase.admin` role. In addition, service accounts
23 | running applications in the App Engine Flexible environment require the
24 | `roles/storage.objectViewer` and `roles/appengineflex.serviceAgent` roles.
25 |
26 | * **Determine the Service Account Email Address**
27 |
28 | Here it is assumed the default App Engine service account will be used. If
29 | you have provided special configuration to use a different service account,
30 | use the email address of that service account.
31 |
32 | ```
33 | gcloud iam service-accounts list
34 | ```
35 |
36 | Identify the email of the entry with a display name resembling `App Engine
37 | default service account`.
38 |
39 | * **Add the roles**
40 |
41 | ```
42 | gcloud projects add-iam-policy-binding [YOUR PROJECT ID] \
43 | --member=serviceAccount:[SERVICE ACCOUNT EMAIL ADDRESS] \
44 | --role=roles/firebasedatabase.admin
45 |
46 | gcloud projects add-iam-policy-binding [YOUR PROJECT ID] \
47 | --member=serviceAccount:[SERVICE ACCOUNT EMAIL ADDRESS] \
48 | --role=roles/storage.objectViewer
49 |
50 | gcloud projects add-iam-policy-binding [YOUR PROJECT ID] \
51 | --member=serviceAccount:[SERVICE ACCOUNT EMAIL ADDRESS] \
52 | --role=roles/appengineflex.serviceAgent
53 | ```
54 |
--------------------------------------------------------------------------------
/samples/app_engine_standard_prerequisites.md:
--------------------------------------------------------------------------------
1 | # Prerequisite Steps For The App Engine Standard Samples
2 |
3 | ## Project Setup
4 |
5 | Follow the appropriate instructions for your target language to create/confgure
6 | your App Engine enabled project.
7 |
8 | * [Java](https://cloud.google.com/appengine/docs/standard/java-gen2/building-app/creating-project)
9 | * [Java 8](https://cloud.google.com/appengine/docs/legacy/standard/java/console)
10 | * [Node.js](https://cloud.google.com/appengine/docs/standard/nodejs/building-app/creating-project)
11 | * [Python](https://cloud.google.com/appengine/docs/standard/python3/building-app/creating-gcp-project)
12 |
13 | ## Install the Snapshot Debugger CLI and enable Firebase
14 |
15 | Follow the instructions beginning at [Before you
16 | begin](../README.md#before-you-begin) through to and including [Enable
17 | Firebase for your Google Cloud
18 | Project](../README.md#enable-firebase-for-your-google-cloud-project) to
19 | get the Snapshot Debugger CLI installed and your project configured to use
20 | Firebase.
21 |
22 | ## Configure Service Account Role
23 |
24 | The application running on App Engine will need to read/write from the Firebase
25 | RTDB instance. For it to do that, the service account running the application
26 | will need the `roles/firebasedatabase.admin` role.
27 |
28 | * **Determine the Service Account Email Address**
29 |
30 | Here it is assumed the default App Engine service account will be used. If
31 | you have provided special configuration to use a different service account,
32 | use the email address of that service account.
33 |
34 | ```
35 | gcloud iam service-accounts list
36 | ```
37 |
38 | Identify the email of the entry with a display name resembling `App Engine
39 | default service account`.
40 |
41 | * **Add the role**
42 |
43 | ```
44 | gcloud projects add-iam-policy-binding [YOUR PROJECT ID] \
45 | --member=serviceAccount:[SERVICE ACCOUNT EMAIL ADDRESS] \
46 | --role=roles/firebasedatabase.admin
47 | ```
48 |
--------------------------------------------------------------------------------
/samples/gce_samples_prerequisites.md:
--------------------------------------------------------------------------------
1 | # Prerequisite Steps For The GCE Samples
2 |
3 | ## Create a Project in the Google Cloud Platform Console
4 |
5 | If you haven't already created a project, create one now. Projects enable you to
6 | manage all Google Cloud Platform resources for your app, including deployment,
7 | access control, billing, and services.
8 |
9 | 1. Open the [Cloud Platform Console][cloud-console].
10 | 1. In the drop-down menu at the top, select **Create a project**.
11 | 1. Give your project a name.
12 | 1. Make a note of the project ID, which might be different from the project
13 | name. The project ID is used in commands and in configurations.
14 |
15 | [cloud-console]: https://console.cloud.google.com/
16 |
17 | ## Enable Compute Engine API
18 |
19 | The Compute Engine API (compute.googleapis.com) must be enabled.
20 |
21 | 1. Check if it's already enabled.
22 |
23 | ```
24 | gcloud services list --enabled | grep compute.googleapis.com
25 | ```
26 |
27 | If the entry was found, it is enabled. If not, proceed to step 2 to enable
28 | it.
29 |
30 | 2. Enable the API if it is not already renabled.
31 |
32 | ```
33 | gcloud services enable compute.googleapis.com
34 | ```
35 |
36 | ## Install the Snapshot Debugger CLI and enable Firebase
37 |
38 | Follow the instructions beginning at [Before you
39 | begin](../README.md#before-you-begin) through to and including [Enable
40 | Firebase for your Google Cloud
41 | Project](../README.md#enable-firebase-for-your-google-cloud-project) to
42 | get the Snapshot Debugger CLI installed and your project configured to use
43 | Firebase.
44 |
45 | ## Configure Service Account Role
46 |
47 | The application running on GCE will need to read/write from the Firebase RTDB
48 | instance. For it to do that, the service account running the GCE instance will
49 | need the `roles/firebasedatabase.admin` role.
50 |
51 | * **Determine the Service Account Email Address**
52 |
53 | Here it is assumed the default GCE service account will be used when the
54 | instance gets created.
55 |
56 | ```
57 | gcloud iam service-accounts list
58 | ```
59 |
60 | Identify the email of the entry with a display name resembling `Compute
61 | Engine default service account`.
62 |
63 | * **Add the role**
64 |
65 | ```
66 | gcloud projects add-iam-policy-binding [YOUR PROJECT ID] \
67 | --member=serviceAccount:[SERVICE ACCOUNT EMAIL ADDRESS] \
68 | --role=roles/firebasedatabase.admin
69 | ```
70 |
--------------------------------------------------------------------------------
/samples/java/appengine-flexible/README.md:
--------------------------------------------------------------------------------
1 | # Snapshot Debugger Examples for Google App Engine Flexible Environment
2 |
3 | > **NOTE**
4 | > This file was copied from
5 | > [java-docs-samples/appengine-flexible/README.md](https://github.com/GoogleCloudPlatform/java-docs-samples/blob/main/flexible/README.md)
6 | > and modified for the Snapshot Debugger samples here.
7 |
8 | ## Prerequisites
9 |
10 | ### Setup the Project and the Snapshot Debugger CLI
11 |
12 | 1. Perform all [Prerequisite Steps](../../app_engine_flexible_prerequisites.md)
13 |
14 | ### Download Maven
15 |
16 | Some of these samples use the [Apache Maven][maven] build system. Before getting
17 | started, be sure to [download][maven-download] and [install][maven-install] it.
18 | When you use Maven as described here, it will automatically download the needed
19 | client libraries.
20 |
21 | [maven]: https://maven.apache.org
22 | [maven-download]: https://maven.apache.org/download.cgi
23 | [maven-install]: https://maven.apache.org/install.html
24 |
25 | ### Install Google Cloud `gcloud` CLI App Engine extension for Java
26 |
27 | 1. `gcloud` should already have been installed as part of an earlier step, if
28 | not [install][install-gcloud] and [initialize][initialize-gcloud] the Google
29 | Cloud CLI.
30 | 1. Install the [gcloud component][managing-components] that includes the App
31 | Engine extension for Java.
32 |
33 | If you used the `apt` or `yum` package managers to install the gcloud CLI,
34 | [use those same package managers to install the gcloud component][external-package-managers].
35 |
36 | Otherwise, use the following command:
37 |
38 | ```
39 | gcloud components install app-engine-java
40 | ```
41 |
42 | [install-gcloud]: https://cloud.google.com/sdk/docs/install
43 | [initialize-gcloud]: https://cloud.google.com/sdk/docs/initializing
44 | [managing-components]: https://cloud.google.com/sdk/docs/managing-components
45 | [external-package-managers]: https://cloud.google.com/sdk/docs/components#external_package_managers
46 |
47 | ### Google Cloud Shell, Open JDK 8 setup:
48 |
49 | If running in the Google Cloud Shell, to switch to an Open JDK 8 you can use:
50 |
51 | ```
52 | sudo update-alternatives --config java
53 | # And select the usr/lib/jvm/java-8-openjdk-amd64/jre/bin/java version.
54 | # Also, set the JAVA_HOME variable for Maven to pick the correct JDK:
55 | export JAVA_HOME=/usr/lib/jvm/java-8-openjdk-amd64
56 | ```
57 |
58 | ### Available Runtimes
59 |
60 | There are two Google provided Java runtimes available:
61 | - [The Java 8 / Jetty 9 runtime](https://cloud.google.com/appengine/docs/flexible/java/dev-jetty9)
62 | - Provides OpenJDK 8 and Eclipse Jetty 9 with support for the Java Servlet 3.1
63 | Specification.
64 | - [The Java 8 runtime](https://cloud.google.com/appengine/docs/flexible/java/dev-java-only)
65 | - Does not include any web-serving framework. The only requirement is that
66 | your app should listen and respond on port 8080.
67 |
68 | ### Runtime Customization and JAVA_USER_OPTS
69 |
70 | The runtime customization feature is used in the samples to add the Snapshot
71 | Debugger agent. In addition, the JAVA_USER_OPTS environment variable is used to
72 | set the `-agentpath` JVM option to load the agent. See the links below for more
73 | information on these options:
74 |
75 | - The Java 8 / Jetty 9 Runtime
76 | - [Customization](https://cloud.google.com/appengine/docs/flexible/java/dev-jetty9#customize)
77 | - [Jetty 9 runtime environment variables](https://cloud.google.com/appengine/docs/flexible/java/dev-jetty9#variables)
78 | - [Java 8 runtime environment variables are additionally available](https://cloud.google.com/appengine/docs/flexible/java/dev-java-only#variables)
79 | - The Java 8 Runtime
80 | - [Customization](https://cloud.google.com/appengine/docs/flexible/java/dev-java-only#customizing)
81 | - [Available environment variables](https://cloud.google.com/appengine/docs/flexible/java/dev-java-only#variables)
82 |
83 | **Sample applications**:
84 | - [helloworld](helloworld) uses the Java 8 / Jetty 9 runtime
85 | - [helloworld-springboot](helloworld-springboot) uses the Java 8 Runtime
86 |
87 |
--------------------------------------------------------------------------------
/samples/java/appengine-flexible/helloworld-springboot/README.md:
--------------------------------------------------------------------------------
1 | # Spring Boot based Hello World app
2 |
3 | > **Note**
4 | > This example was copied from
5 | > [flexible/helloworld-springboot](https://github.com/GoogleCloudPlatform/java-docs-samples/tree/main/flexible/helloworld-springboot)
6 | > and modified for Snapshot Debugger Java agent use.
7 |
8 |
9 | ## Setup
10 |
11 | See [Prerequisites](../README.md#Prerequisites).
12 |
13 | Ensure your current working directory is
14 | `samples/java/appengine-flexible/helloworld-springboot`, as all following instructions
15 | assumes this.
16 |
17 | ## Examine the `app.yaml` and `Dockerfile` files
18 |
19 | This example customizes the Java 8 / Jetty 9 runtime by explicitly providing a
20 | Dockerfile. The purpose of the Dockerfile is to add the Snapshot Debugger Java
21 | agent to the runtime. The app.yaml file provides configuration information to
22 | specify a custom runtime is being used and to configure the loading of the
23 | agent.
24 |
25 | app.yaml:
26 |
27 | https://github.com/GoogleCloudPlatform/snapshot-debugger/blob/ed767fe00d5b1006ff60ac9c4bf57668b774962f/samples/java/appengine-flexible/helloworld-springboot/src/main/appengine/app.yaml#L15-L34
28 |
29 | Dockerfile:
30 |
31 | https://github.com/GoogleCloudPlatform/snapshot-debugger/blob/ed767fe00d5b1006ff60ac9c4bf57668b774962f/samples/java/appengine-flexible/helloworld-springboot/src/main/docker/Dockerfile#L1-L15
32 |
33 | ## Deploy to App Engine Flexible
34 |
35 | Deploy the app with the following:
36 |
37 | ```
38 | mvn clean package appengine:deploy
39 | ```
40 |
41 | Early on in the output, log lines resembling the following will be emitted, make
42 | note of them:
43 |
44 | ```
45 | [INFO] GCLOUD: target service: [default]
46 | [INFO] GCLOUD: target version: [20221125t224954]
47 | [INFO] GCLOUD: target url: [https://PROJECT_ID.REGION_ID.r.appspot.com]
48 | ```
49 |
50 | The service and version will be used to identify your debuggee ID.
51 |
52 | ## Navigate To Your App
53 |
54 | This will ensure the app is running correctly. The URL should be provided in the
55 | `target url` output of the previous step.
56 |
57 | ## Determine the Debuggee ID
58 |
59 | Based on the service and version you'll be able to identify your debuggee ID
60 | based on the output from the `list_debuggees` command.
61 |
62 | ```
63 | snapshot-dbg-cli list_debuggees
64 | ```
65 |
66 | The output will resemble the following. The first column will contain an entry
67 | ` - `, which in this case is `default - 20221125t224954`.
68 |
69 | ```
70 | Name ID Description Last Active Status
71 | ------------------------- ---------- ------------------------------------------------ -------------------- ------
72 | default - 20221125t224954 d-62259477 my-project-id-20221125t224954-448130606220701265 2022-11-25T22:50:00Z ACTIVE
73 | ```
74 |
75 | The debuggee ID in this case is `d-62259477`. Using this ID you may now run
76 | through an [Example workflow](../../../../README.md#example-workflow).
77 |
78 | E.g.
79 | * Use the `set_snapshot` CLI command to set a snapshot at
80 | `HelloController.java:26`. Note the returned breakpoint ID.
81 | * Navigate to your application using the `target url` shown in the `mvn clean
82 | package appengine:deploy` output. This will trigger the breakpoint and
83 | collect the snapshot.
84 | * Use the `get_snapshot` CLI command to retrieve the snapshot using the
85 | breakpoint ID created with the `set_snapshot` command.
86 |
--------------------------------------------------------------------------------
/samples/java/appengine-flexible/helloworld-springboot/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 | 4.0.0
19 | com.example.java
20 | helloworld-springboot
21 | 0.0.1-SNAPSHOT
22 | helloworld-springboot
23 |
24 |
28 |
29 | com.google.cloud.samples
30 | shared-configuration
31 | 1.2.0
32 |
33 |
34 |
35 | 1.8
36 | 1.8
37 |
38 |
39 |
40 |
41 | org.springframework.boot
42 | spring-boot-starter-web
43 | 2.6.6
44 |
45 |
46 | org.springframework.boot
47 | spring-boot-starter-actuator
48 | 2.6.6
49 |
50 |
51 | org.springframework.boot
52 | spring-boot-starter-test
53 | 2.6.6
54 | test
55 |
56 |
57 | org.junit.vintage
58 | junit-vintage-engine
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 | org.springframework.boot
68 | spring-boot-maven-plugin
69 | 2.6.6
70 |
71 |
72 |
73 | repackage
74 |
75 |
76 |
77 |
78 |
79 | com.google.cloud.tools
80 | appengine-maven-plugin
81 | 2.4.2
82 |
83 |
84 | GCLOUD_CONFIG
85 |
86 | GCLOUD_CONFIG
87 |
88 |
89 |
90 |
91 |
92 |
--------------------------------------------------------------------------------
/samples/java/appengine-flexible/helloworld-springboot/src/main/appengine/app.yaml:
--------------------------------------------------------------------------------
1 | # Copyright 2015 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 | #
15 | runtime: custom
16 | env: flex
17 |
18 | env_variables:
19 | # Prevents the runtime from loading the current Cloud Debugger agent which is
20 | # present in the base runtime and enabled by default. Loading this agent
21 | # would conflict with the Snapshot Debugger agent. Eventually once the Cloud
22 | # Debugger has been turned down the base image will no longer contain the
23 | # agent and this won't be necessary.
24 | DBG_ENABLE: false
25 |
26 | # The contents of JAVA_USER_OPTS are added to the java command line. To note,
27 | # in this case the Java agent is able to find the class files (enabling it to
28 | # set breakpoints) without any extra configuration. This is because the
29 | # jetty.base property gets set and the root.war file is extracted to
30 | # 'jetty.base'/webapps/root. There are times however you may need to also
31 | # configure cdbg_extra_class_path if the agent cannot find the class files,
32 | # see https://github.com/GoogleCloudPlatform/cloud-debug-java#extra-classpath
33 | # for more information.
34 | JAVA_USER_OPTS: -agentpath:/opt/cdbg/cdbg_java_agent.so=--log_dir=/var/log/app_engine,--use-firebase=true
35 |
36 | handlers:
37 | - url: /.*
38 | script: this field is required, but ignored
39 |
40 | # Set scaling to 1 to minimize resources for the example. This setting should
41 | # be revisited for production use.
42 | manual_scaling:
43 | instances: 1
44 |
--------------------------------------------------------------------------------
/samples/java/appengine-flexible/helloworld-springboot/src/main/docker/Dockerfile:
--------------------------------------------------------------------------------
1 | # We are customizing the Java 8 Runtime
2 | # See https://cloud.google.com/appengine/docs/flexible/java/dev-java-only#customizing
3 | # for more information on these first two lines.
4 | FROM gcr.io/google-appengine/openjdk:8
5 | COPY ./helloworld-springboot-0.0.1-SNAPSHOT.jar $APP_DESTINATION
6 |
7 | # Until the Cloud Debugger has been fully turned down, the rutime image will
8 | # continue to contain the current Cloud Debugger Java agent located in
9 | # /opt/cdbg. Here we remove it if it exists.
10 | RUN rm -rf /opt/cdbg
11 |
12 | # Add the Snapshot Debugger Java Agent
13 | ADD https://github.com/GoogleCloudPlatform/cloud-debug-java/releases/latest/download/cdbg_java_agent_gce.tar.gz /opt/cdbg/
14 | RUN tar Cxfvz /opt/cdbg /opt/cdbg/cdbg_java_agent_gce.tar.gz --no-same-owner \
15 | && rm /opt/cdbg/cdbg_java_agent_gce.tar.gz
16 |
17 |
--------------------------------------------------------------------------------
/samples/java/appengine-flexible/helloworld-springboot/src/main/java/com/example/java/HelloController.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.example.java;
18 |
19 | import org.springframework.web.bind.annotation.RequestMapping;
20 | import org.springframework.web.bind.annotation.RestController;
21 |
22 | @RestController
23 | public class HelloController {
24 | @RequestMapping("/")
25 | String index() {
26 | return "Hello World!";
27 | }
28 |
29 | /**
30 | * (Optional) App Engine health check endpoint mapping.
31 | *
32 | * @see
34 | * If your app does not handle health checks, a HTTP 404 response is interpreted as a
35 | * successful reply.
36 | */
37 | @RequestMapping("/_ah/health")
38 | public String healthy() {
39 | // Message body required though ignored
40 | return "Still surviving.";
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/samples/java/appengine-flexible/helloworld-springboot/src/main/java/com/example/java/HelloWorldApplication.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.example.java;
18 |
19 | import org.springframework.boot.SpringApplication;
20 | import org.springframework.boot.autoconfigure.SpringBootApplication;
21 |
22 | @SpringBootApplication
23 | public class HelloWorldApplication {
24 |
25 | public static void main(String[] args) {
26 | SpringApplication.run(HelloWorldApplication.class, args);
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/samples/java/appengine-flexible/helloworld-springboot/src/main/resources/application.properties:
--------------------------------------------------------------------------------
1 | # Copyright 2015 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
--------------------------------------------------------------------------------
/samples/java/appengine-flexible/helloworld-springboot/src/test/java/com/example/java/HelloControllerTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.example.java;
18 |
19 | import static org.hamcrest.Matchers.equalTo;
20 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
21 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
22 |
23 | import org.junit.jupiter.api.Test;
24 | import org.springframework.beans.factory.annotation.Autowired;
25 | import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
26 | import org.springframework.boot.test.context.SpringBootTest;
27 | import org.springframework.http.MediaType;
28 | import org.springframework.test.web.servlet.MockMvc;
29 | import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
30 |
31 | @SpringBootTest
32 | @AutoConfigureMockMvc
33 | public class HelloControllerTest {
34 |
35 | @Autowired private MockMvc mvc;
36 |
37 | @Test
38 | public void getHello() throws Exception {
39 | mvc.perform(MockMvcRequestBuilders.get("/").accept(MediaType.APPLICATION_JSON))
40 | .andExpect(status().isOk())
41 | .andExpect(content().string(equalTo("Hello World!")));
42 | }
43 |
44 | @Test
45 | public void getHealth() throws Exception {
46 | mvc.perform(MockMvcRequestBuilders.get("/_ah/health").accept(MediaType.APPLICATION_JSON))
47 | .andExpect(status().isOk())
48 | .andExpect(content().string(equalTo("Still surviving.")));
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/samples/java/appengine-flexible/helloworld/README.md:
--------------------------------------------------------------------------------
1 | # Appengine Helloworld sample for Google App Engine Flexible
2 |
3 | > **Note**
4 | > This example was copied from
5 | > [flexible/helloworld](https://github.com/GoogleCloudPlatform/java-docs-samples/tree/main/flexible/helloworld)
6 | > and modified for Snapshot Debugger Java agent use.
7 |
8 | ## Setup
9 |
10 | See [Prerequisites](../README.md#Prerequisites).
11 |
12 | Ensure your current working directory is
13 | `samples/java/appengine-flexible/helloworld`, as all following instructions
14 | assumes this.
15 |
16 | ## Examine the `app.yaml` and `Dockerfile` files
17 |
18 | This example customizes the Java 8 / Jetty 9 runtime by explicitly providing a
19 | Dockerfile. The purpose of the Dockerfile is to add the Snapshot Debugger Java
20 | agent to the runtime. The app.yaml file provides configuration information to
21 | specify a custom runtime is being used and to configure the loading of the
22 | agent.
23 |
24 | app.yaml:
25 |
26 | https://github.com/GoogleCloudPlatform/snapshot-debugger/blob/ef597d47dadff1921dbf5a00a459d72acf1886c3/samples/java/appengine-flexible/helloworld/src/main/appengine/app.yaml#L15-L40
27 |
28 | Dockerfile:
29 |
30 | https://github.com/GoogleCloudPlatform/snapshot-debugger/blob/0868fe074c9504c9c226c93baa4fa9249afc8c68/samples/java/appengine-flexible/helloworld/src/main/docker/Dockerfile#L1-L15
31 |
32 | ## Deploy to App Engine Flexible
33 |
34 | Deploy the app with the following:
35 |
36 | ```
37 | mvn clean package appengine:deploy
38 | ```
39 |
40 | Early on in the output, log lines resembling the following will be emitted, make
41 | note of them:
42 |
43 | ```
44 | [INFO] GCLOUD: target service: [default]
45 | [INFO] GCLOUD: target version: [20221125t154414]
46 | [INFO] GCLOUD: target url: [https://PROJECT_ID.REGION_ID.r.appspot.com]
47 | ```
48 |
49 | The service and version will be used to identify your debuggee ID.
50 |
51 | ## Navigate To Your App
52 |
53 | This will ensure the app is running correctly. The URL should be provided in the
54 | `target url` output of the previous step.
55 |
56 | ## Determine the Debuggee ID
57 |
58 | Based on the service and version you'll be able to identify your debuggee ID
59 | based on the output from the `list_debuggees` command.
60 |
61 | ```
62 | snapshot-dbg-cli list_debuggees
63 | ```
64 |
65 | The output will resemble the following. The first column will contain an entry
66 | ` - `, which in this case is `default - 20221125t154414`.
67 |
68 | ```
69 | Name ID Description Last Active Status
70 | -------------------------- --------- ------------------------------------------------ -------------------- ------
71 | default - 20221125t154414 d-85ad2c65 my-project-id-20221125t154414-448123750866017122 2022-11-25T15:45:00Z ACTIVE
72 | ```
73 |
74 | The debuggee ID in this case is `d-85ad2c65`. Using this ID you may now run
75 | through an [Example workflow](../../../../README.md#example-workflow).
76 |
77 | E.g.
78 | * Use the `set_snapshot` CLI command to set a snapshot at
79 | `HelloServlet.java:34`. Note the returned breakpoint ID.
80 | * Navigate to your application using the `target url` shown in the `mvn clean
81 | package appengine:deploy` output. This will trigger the breakpoint and
82 | collect the snapshot.
83 | * Use the `get_snapshot` CLI command to retrieve the snapshot using the
84 | breakpoint ID created with the `set_snapshot` command.
85 |
--------------------------------------------------------------------------------
/samples/java/appengine-flexible/helloworld/pom.xml:
--------------------------------------------------------------------------------
1 |
16 |
17 | 4.0.0
18 | war
19 | 1.0-SNAPSHOT
20 | com.example.flexible
21 | flexible-helloworld
22 |
23 |
27 |
28 | com.google.cloud.samples
29 | shared-configuration
30 | 1.2.0
31 |
32 |
33 |
34 | 1.8
35 | 1.8
36 |
37 | false
38 |
39 | 2.4.3
40 | 9.4.44.v20210927
41 |
42 |
43 |
44 |
45 |
46 |
47 | javax.servlet
48 | javax.servlet-api
49 | 3.1.0
50 | jar
51 | provided
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 | ${project.build.directory}/${project.build.finalName}/WEB-INF/classes
60 |
61 |
62 |
63 | org.apache.maven.plugins
64 | maven-war-plugin
65 | 3.3.2
66 |
67 |
68 |
69 | com.google.cloud.tools
70 | appengine-maven-plugin
71 | ${appengine.maven.plugin}
72 |
73 | GCLOUD_CONFIG
74 | GCLOUD_CONFIG
75 |
76 |
77 |
78 |
79 | org.eclipse.jetty
80 | jetty-maven-plugin
81 | ${jetty}
82 |
83 |
84 |
85 |
86 |
87 |
--------------------------------------------------------------------------------
/samples/java/appengine-flexible/helloworld/src/main/appengine/app.yaml:
--------------------------------------------------------------------------------
1 | # Copyright 2016 Google Inc.
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | # 'custom` indicates a custom runtime will be used and a Dockerfile will be
16 | # provided to create it.
17 | runtime: custom
18 | env: flex
19 |
20 | # To note, all available environment variables for the Java 8 / Jetty 9 image
21 | # can be found at the following two locations:
22 | # - https://cloud.google.com/appengine/docs/flexible/java/dev-jetty9#variables
23 | # - https://cloud.google.com/appengine/docs/flexible/java/dev-java-only#variables
24 | env_variables:
25 | # Prevents the runtime from loading the current Cloud Debugger agent which is
26 | # present in the base runtime and enabled by default. Loading this agent
27 | # would conflict with the Snapshot Debugger agent. Eventually once the Cloud
28 | # Debugger has been turned down the base image will no longer contain the
29 | # agent and this won't be necessary.
30 | DBG_ENABLE: false
31 |
32 | # The contents of JAVA_USER_OPTS are added to the java command line. To note,
33 | # in this case the Java agent is able to find the class files (enabling it to
34 | # set breakpoints) without any extra configuration. This is because the
35 | # jetty.base property gets set and the root.war file is extracted to
36 | # 'jetty.base'/webapps/root. There are times however you may need to also
37 | # configure cdbg_extra_class_path if the agent cannot find the class files,
38 | # see https://github.com/GoogleCloudPlatform/cloud-debug-java#extra-classpath
39 | # for more information.
40 | JAVA_USER_OPTS: -agentpath:/opt/cdbg/cdbg_java_agent.so=--log_dir=/var/log/app_engine,--use-firebase=true
41 |
42 | handlers:
43 | - url: /.*
44 | script: this field is required, but ignored
45 |
46 | # Set scaling to 1 to minimize resources for the example. This setting should
47 | # be revisited for production use.
48 | manual_scaling:
49 | instances: 1
50 |
--------------------------------------------------------------------------------
/samples/java/appengine-flexible/helloworld/src/main/docker/Dockerfile:
--------------------------------------------------------------------------------
1 | # We are customizing the Java 8 / Jetty 9 Runtime
2 | # See https://cloud.google.com/appengine/docs/flexible/java/dev-jetty9#customize
3 | # for more information on these first two lines.
4 | FROM gcr.io/google-appengine/jetty
5 | COPY ./flexible-helloworld-1.0-SNAPSHOT.war $APP_DESTINATION
6 |
7 | # Until the Cloud Debugger has been fully turned down, the rutime image will
8 | # continue to contain the current Cloud Debugger Java agent located in
9 | # /opt/cdbg. Here we remove it if it exists.
10 | RUN rm -rf /opt/cdbg
11 |
12 | # Add the Snapshot Debugger Java Agent
13 | ADD https://github.com/GoogleCloudPlatform/cloud-debug-java/releases/latest/download/cdbg_java_agent_gce.tar.gz /opt/cdbg/
14 | RUN tar Cxfvz /opt/cdbg /opt/cdbg/cdbg_java_agent_gce.tar.gz --no-same-owner \
15 | && rm /opt/cdbg/cdbg_java_agent_gce.tar.gz
16 |
--------------------------------------------------------------------------------
/samples/java/appengine-flexible/helloworld/src/main/java/com/example/flexible/helloworld/HelloServlet.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 Google Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.example.flexible.helloworld;
18 |
19 | // [START gae_flex_servlet]
20 | import java.io.IOException;
21 | import java.io.PrintWriter;
22 | import javax.servlet.annotation.WebServlet;
23 | import javax.servlet.http.HttpServlet;
24 | import javax.servlet.http.HttpServletRequest;
25 | import javax.servlet.http.HttpServletResponse;
26 |
27 | @WebServlet(name = "helloworld", value = "")
28 | @SuppressWarnings("serial")
29 | public class HelloServlet extends HttpServlet {
30 |
31 | @Override
32 | public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
33 | PrintWriter out = resp.getWriter();
34 | out.println("Hello, world - App Engine Flexible");
35 | }
36 | }
37 | // [END gae_flex_servlet]
38 |
--------------------------------------------------------------------------------
/samples/java/appengine-java11/appengine-simple-jetty-main/README.md:
--------------------------------------------------------------------------------
1 | # Embedded Jetty Server for Google App Engine Standard with Java 11
2 |
3 | > **Note**
4 | > This directory was copied from [appengine-java11/appengine-simple-jetty-main](https://github.com/GoogleCloudPlatform/java-docs-samples/tree/main/appengine-java11/appengine-simple-jetty-main).
5 |
6 | For the Java 11 runtime, your application must have a `Main` class that starts a
7 | web server. This sample is a shared artifact that provides a `Main` class to
8 | instantiate an HTTP server to run an embedded web application `WAR` file.
9 |
10 | ## Install the dependency
11 |
12 | This sample is used as a dependency and must be installed locally:
13 |
14 | ```
15 | mvn install
16 | ```
17 |
18 | ## Using the dependency
19 |
20 | See [`helloworld-servlet`](../helloworld-servlet) for a complete example.
21 |
22 | Your project's `pom.xml` needs to be updated accordingly:
23 |
24 | - Add the `appengine-simple-jetty-main` dependency:
25 |
26 | ```
27 |
28 | com.example.appengine.demo
29 | simple-jetty-main
30 | 1
31 | provided
32 |
33 | ```
34 |
35 | - On deployment, the App Engine runtime uploads files located in
36 | `${build.directory}/appengine-staging`. Add the `maven-dependency-plugin` to
37 | the build in order to copy dependencies to the correct folder:
38 |
39 | ```
40 |
41 | org.apache.maven.plugins
42 | maven-dependency-plugin
43 | 3.1.1
44 |
45 |
46 | copy
47 | prepare-package
48 |
49 | copy-dependencies
50 |
51 |
52 |
53 | ${project.build.directory}/appengine-staging
54 |
55 |
56 |
57 |
58 |
59 | ```
60 |
61 | See [`helloworld-servlet`](../helloworld-servlet) for how to write your
62 | `app.yaml` file to use the dependency by specifying the entry point.
63 |
--------------------------------------------------------------------------------
/samples/java/appengine-java11/appengine-simple-jetty-main/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 4.0.0
4 | com.example.appengine.demo
5 | simple-jetty-main
6 | simplejettymain-j11
7 | 1
8 | jar
9 |
10 |
14 |
15 | com.google.cloud.samples
16 | shared-configuration
17 | 1.2.0
18 |
19 |
20 |
21 | UTF-8
22 | 11
23 | 11
24 |
25 |
26 |
27 |
28 |
29 |
30 | org.eclipse.jetty
31 | jetty-server
32 | 9.4.51.v20230217
33 |
34 |
35 | org.eclipse.jetty
36 | jetty-webapp
37 | 9.4.44.v20210927
38 | jar
39 |
40 |
41 | org.eclipse.jetty
42 | jetty-util
43 | 9.4.44.v20210927
44 |
45 |
46 | org.eclipse.jetty
47 | jetty-annotations
48 | 9.4.43.v20210629
49 | jar
50 |
51 |
52 |
53 | org.eclipse.jetty
54 | apache-jsp
55 | 9.4.44.v20210927
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 | org.codehaus.mojo
66 | exec-maven-plugin
67 | 3.0.0
68 |
69 |
70 |
71 | java
72 |
73 |
74 |
75 |
76 | com.example.appengine.demo.jettymain.Main
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
--------------------------------------------------------------------------------
/samples/java/appengine-java11/appengine-simple-jetty-main/src/main/java/com/example/appengine/demo/jettymain/Main.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.example.appengine.demo.jettymain;
18 |
19 | // [START gae_java11_server]
20 | import org.eclipse.jetty.server.Server;
21 | import org.eclipse.jetty.webapp.Configuration.ClassList;
22 | import org.eclipse.jetty.webapp.WebAppContext;
23 |
24 | /**
25 | * Simple Jetty Main that can execute a directory containing an exploded webapp
26 | * when passed as an argument.
27 | */
28 | public class Main {
29 |
30 | public static void main(String[] args) throws Exception {
31 | if (args.length != 1) {
32 | System.err.println("Usage: need a relative path to the war file to execute");
33 | System.exit(1);
34 | }
35 | System.setProperty("org.eclipse.jetty.util.log.class", "org.eclipse.jetty.util.log.StrErrLog");
36 | System.setProperty("org.eclipse.jetty.LEVEL", "INFO");
37 |
38 | // Create a basic Jetty server object that will listen on port defined by
39 | // the PORT environment variable when present, otherwise on 8080.
40 | int port = Integer.parseInt(System.getenv().getOrDefault("PORT", "8080"));
41 | Server server = new Server(port);
42 |
43 | // The WebAppContext is the interface to provide configuration for a web
44 | // application. In this example, the context path is being set to "/" so
45 | // it is suitable for serving root context requests. It is expecting the
46 | // first argument passed in is the directory containing an exploded
47 | // webapp.
48 | WebAppContext webapp = new WebAppContext(args[0], "/");
49 | ClassList classlist = ClassList.setServerDefault(server);
50 |
51 | // Enable Annotation Scanning.
52 | classlist.addBefore(
53 | "org.eclipse.jetty.webapp.JettyWebXmlConfiguration",
54 | "org.eclipse.jetty.annotations.AnnotationConfiguration");
55 |
56 | // Set the the WebAppContext as the ContextHandler for the server.
57 | server.setHandler(webapp);
58 |
59 | // Start the server! By using the server.join() the server thread will
60 | // join with the current thread. See
61 | // "http://docs.oracle.com/javase/1.5.0/docs/api/java/lang/Thread.html#join()"
62 | // for more details.
63 | server.start();
64 | server.join();
65 | }
66 | }
67 | // [END gae_java11_server]
68 |
--------------------------------------------------------------------------------
/samples/java/appengine-java11/helloworld-servlet/src/main/appengine/app.yaml:
--------------------------------------------------------------------------------
1 | # Copyright 2019 Google LLC
2 | # Licensed under the Apache License, Version 2.0 (the "License");
3 | # you may not use this file except in compliance with the License.
4 | # You may obtain a copy of the License at
5 | #
6 | # http://www.apache.org/licenses/LICENSE-2.0
7 | #
8 | # Unless required by applicable law or agreed to in writing, software
9 | # distributed under the License is distributed on an "AS IS" BASIS,
10 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 | # See the License for the specific language governing permissions and
12 | # limitations under the License.
13 |
14 | runtime: java11
15 | entrypoint: 'java -agentpath:/workspace/cdbg/cdbg_java_agent.so=--use-firebase=true,--cdbg_extra_class_path=/workspace/helloworld/WEB-INF/classes:/workspace/helloworld/WEB-INF/lib -cp "*" com.example.appengine.demo.jettymain.Main /workspace/helloworld'
16 |
--------------------------------------------------------------------------------
/samples/java/appengine-java11/helloworld-servlet/src/main/java/com/example/appengine/HelloAppEngine.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.example.appengine;
18 |
19 | import java.io.IOException;
20 | import javax.servlet.annotation.WebServlet;
21 | import javax.servlet.http.HttpServlet;
22 | import javax.servlet.http.HttpServletRequest;
23 | import javax.servlet.http.HttpServletResponse;
24 |
25 | @WebServlet(name = "HelloAppEngine", value = "/hello")
26 | public class HelloAppEngine extends HttpServlet {
27 |
28 | @Override
29 | public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
30 |
31 | response.setContentType("text/html");
32 | response.getWriter().println("
Hello, Google App Engine with Java 11!
");
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/samples/java/appengine-java11/simple-server/.gitignore:
--------------------------------------------------------------------------------
1 | cdbg/*
2 |
--------------------------------------------------------------------------------
/samples/java/appengine-java11/simple-server/Main.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import com.sun.net.httpserver.HttpServer;
18 | import java.io.IOException;
19 | import java.io.OutputStream;
20 | import java.net.InetSocketAddress;
21 |
22 | public class Main {
23 |
24 | public static void main(String[] args) throws IOException {
25 | // Create an instance of HttpServer bound to port defined by the
26 | // PORT environment variable when present, otherwise on 8080.
27 | int port = Integer.parseInt(System.getenv().getOrDefault("PORT", "8080"));
28 | HttpServer server = HttpServer.create(new InetSocketAddress(port), 0);
29 |
30 | // Set root URI path.
31 | server.createContext("/", (var t) -> {
32 | byte[] response = "Hello World from Google App Engine Java 11.".getBytes();
33 | t.sendResponseHeaders(200, response.length);
34 | try (OutputStream os = t.getResponseBody()) {
35 | os.write(response);
36 | }
37 | });
38 |
39 | // Start the server.
40 | server.start();
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/samples/java/appengine-java11/simple-server/README.md:
--------------------------------------------------------------------------------
1 | # Using the Java Agent on a Simple One File Server App in GAE Standard Java 11
2 |
3 | > **Note**
4 | > This example was copied from
5 | > [appengine-java11/custom-entrypoint](https://github.com/GoogleCloudPlatform/java-docs-samples/blob/main/appengine-java11/custom-entrypoint)
6 | > and modified for Snapshot Debugger Java agent use.
7 |
8 |
9 | ## Setup
10 | See [Prerequisites](../README.md#Prerequisites).
11 |
12 | Ensure your current working directory is
13 | `samples/java/appengine-java11/simple-server`, as all following instructions
14 | assumes this.
15 |
16 | ## Compile the Source With Full Debug Information Enabled
17 |
18 | The `-g` option here is what enables full debug information. Without it
19 | information on method arguments and local variables would not be available. The
20 | `-source` and `-target` options are to ensure it will run with the Java 11
21 | runtime.
22 |
23 | ```
24 | javac -source 11 -target 11 -g Main.java
25 | ```
26 |
27 | ## Install the Snapshot Debugger Java Agent
28 |
29 | ```
30 | mkdir cdbg
31 | wget -qO- https://github.com/GoogleCloudPlatform/cloud-debug-java/releases/latest/download/cdbg_java_agent_gce.tar.gz | tar xvz -C cdbg
32 | ```
33 |
34 | ## Deploy to App Engine Standard
35 |
36 | Examine the app.yaml contents, which provides a custom entry point that
37 | specifies the `-agentpath` java option to load the agent:
38 |
39 | https://github.com/GoogleCloudPlatform/snapshot-debugger/blob/ef7db1b29937f1f6e140d69214aadf51bddb3770/samples/java/appengine-java11/simple-server/app.yaml#L15-L16
40 |
41 | Deploy the app with the following:
42 |
43 | ```
44 | gcloud app deploy
45 | ```
46 |
47 | Make note of the following output entries, which should resemble the following:
48 |
49 | ```
50 | [...snip]
51 | target service: [default]
52 | target version: [20221122t161333]
53 | target url: [https://.appspot.com]
54 | [...snip]
55 | ```
56 |
57 | The service and version will be used to identify your debuggee ID.
58 |
59 | ## Navigate To Your App
60 |
61 | This will ensure the app is run and is required as your app will not be
62 | debuggable until after the first request has been received. The URL should be
63 | provided in the `target url` output of the previous step.
64 |
65 | ## Determine the Debuggee ID
66 |
67 | Based on the service and version you'll be able to identify your debuggee ID
68 | based on the output from the `list_debuggees` command.
69 |
70 | ```
71 | snapshot-dbg-cli list_debuggees
72 | ```
73 |
74 | The output will resemble the following. The first column will contain an entry
75 | ` - `, which in this case is `default - 20221117t213436`.
76 |
77 | ```
78 | Name ID Description Last Active Status
79 | ------------------------- ---------- ------------------------------------------------ -------------------- ------
80 | default - 20221122t161333 d-ad4829f7 my-project-id-20221122t161333-448054658875855015 2022-11-22T16:14:00Z ACTIVE
81 | ```
82 |
83 | The debuggee ID in this case is `d-ad4829f7`. Using this ID you may now run
84 | through an [Example workflow](../../../../README.md#example-workflow).
85 |
86 | E.g.
87 | * Use the `set_snapshot` CLI command to set a snapshot at `Main.java:33`. Note
88 | the returned breakpoint ID.
89 | * Navigate to your application using the `target url` shown in the `gcloud
90 | app deploy` output. This will trigger the breakpoint and collect the snapshot.
91 | * Use the `get_snapshot` CLI command to retrieve the snapshot using the
92 | breakpoint ID created with the `set_snapshot` command.
93 |
94 | ## Troubleshooting
95 |
96 | ### Can't see Debuggee (via list_debuggees)
97 |
98 | Be sure you navidate to `target url` to wake the application up. In App
99 | Engine Standard the newly deployed application will not actually run until it
100 | receives a request.
101 |
--------------------------------------------------------------------------------
/samples/java/appengine-java11/simple-server/app.yaml:
--------------------------------------------------------------------------------
1 | # Copyright 2019 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | runtime: java11
16 | entrypoint: java -agentpath:/workspace/cdbg/cdbg_java_agent.so=--use-firebase=true Main
17 |
--------------------------------------------------------------------------------
/samples/java/appengine-java8/helloworld/src/main/java/com/example/appengine/java8/HelloAppEngine.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017 Google Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.example.appengine.java8;
18 |
19 | import com.google.appengine.api.utils.SystemProperty;
20 | import java.io.IOException;
21 | import java.util.Properties;
22 | import javax.servlet.annotation.WebServlet;
23 | import javax.servlet.http.HttpServlet;
24 | import javax.servlet.http.HttpServletRequest;
25 | import javax.servlet.http.HttpServletResponse;
26 |
27 | // With @WebServlet annotation the webapp/WEB-INF/web.xml is no longer required.
28 | @WebServlet(name = "HelloAppEngine", value = "/hello")
29 | public class HelloAppEngine extends HttpServlet {
30 |
31 | @Override
32 | public void doGet(HttpServletRequest request, HttpServletResponse response)
33 | throws IOException {
34 | Properties properties = System.getProperties();
35 |
36 | response.setContentType("text/plain");
37 | response.getWriter().println("Hello App Engine - Standard using "
38 | + SystemProperty.version.get() + " Java "
39 | + properties.get("java.specification.version"));
40 | }
41 |
42 | public static String getInfo() {
43 | return "Version: " + System.getProperty("java.version")
44 | + " OS: " + System.getProperty("os.name")
45 | + " User: " + System.getProperty("user.name");
46 | }
47 |
48 | }
49 |
--------------------------------------------------------------------------------
/samples/java/appengine-java8/helloworld/src/main/webapp/WEB-INF/appengine-web.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
19 | java8
20 | true
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/samples/java/appengine-java8/helloworld/src/main/webapp/index.jsp:
--------------------------------------------------------------------------------
1 |
2 |
3 | <%--
4 | ~ Copyright 2017 Google Inc.
5 | ~
6 | ~ Licensed under the Apache License, Version 2.0 (the "License"); you
7 | ~ may not use this file except in compliance with the License. You may
8 | ~ obtain a copy of the License at
9 | ~
10 | ~ http://www.apache.org/licenses/LICENSE-2.0
11 | ~
12 | ~ Unless required by applicable law or agreed to in writing, software
13 | ~ distributed under the License is distributed on an "AS IS" BASIS,
14 | ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
15 | ~ implied. See the License for the specific language governing
16 | ~ permissions and limitations under the License.
17 | --%>
18 |
19 | <%@ page contentType="text/html;charset=UTF-8" language="java" %>
20 | <%@ page import="com.example.appengine.java8.HelloAppEngine" %>
21 |
22 |
23 |
24 | Hello App Engine Standard Java 8
25 |
26 |
27 |
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/samples/java/appengine-java8/helloworld/src/test/java/com/example/appengine/java8/HelloAppEngineTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017 Google Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.example.appengine.java8;
18 |
19 | import static com.google.common.truth.Truth.assertThat;
20 | import static org.mockito.Mockito.when;
21 |
22 | import com.google.appengine.tools.development.testing.LocalServiceTestHelper;
23 | import java.io.PrintWriter;
24 | import java.io.StringWriter;
25 | import javax.servlet.http.HttpServletRequest;
26 | import javax.servlet.http.HttpServletResponse;
27 | import org.junit.After;
28 | import org.junit.Before;
29 | import org.junit.Test;
30 | import org.junit.runner.RunWith;
31 | import org.junit.runners.JUnit4;
32 | import org.mockito.Mock;
33 | import org.mockito.MockitoAnnotations;
34 |
35 | /**
36 | * Unit tests for {@link HelloAppEngine}.
37 | */
38 | @RunWith(JUnit4.class)
39 | public class HelloAppEngineTest {
40 | private static final String FAKE_URL = "fake.fk/hello";
41 | // Set up a helper so that the ApiProxy returns a valid environment for local testing.
42 | private final LocalServiceTestHelper helper = new LocalServiceTestHelper();
43 |
44 | @Mock private HttpServletRequest mockRequest;
45 | @Mock private HttpServletResponse mockResponse;
46 | private StringWriter responseWriter;
47 | private HelloAppEngine servletUnderTest;
48 |
49 | @Before
50 | public void setUp() throws Exception {
51 | MockitoAnnotations.initMocks(this);
52 | helper.setUp();
53 |
54 | // Set up some fake HTTP requests
55 | when(mockRequest.getRequestURI()).thenReturn(FAKE_URL);
56 |
57 | // Set up a fake HTTP response.
58 | responseWriter = new StringWriter();
59 | when(mockResponse.getWriter()).thenReturn(new PrintWriter(responseWriter));
60 |
61 | servletUnderTest = new HelloAppEngine();
62 | }
63 |
64 | @After public void tearDown() {
65 | helper.tearDown();
66 | }
67 |
68 | @Test
69 | public void doGetWritesResponse() throws Exception {
70 | servletUnderTest.doGet(mockRequest, mockResponse);
71 |
72 | // We expect our hello world response.
73 | assertThat(responseWriter.toString())
74 | .contains("Hello App Engine - Standard ");
75 | }
76 |
77 | @Test
78 | public void helloInfoTest() {
79 | String result = HelloAppEngine.getInfo();
80 | assertThat(result)
81 | .containsMatch("^Version:\\s+.+OS:\\s+.+User:\\s");
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/samples/java/gce/config/base/etc/java-util-logging.properties:
--------------------------------------------------------------------------------
1 | # Copyright 2019 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | # A default java.util.logging configuration.
16 | #
17 |
18 | handlers=java.util.logging.ConsoleHandler,java.util.logging.FileHandler
19 |
20 | java.util.logging.FileHandler.level=INFO
21 | #java.util.logging.FileHandler.formatter=
22 | #java.util.logging.FileHandler.pattern=/opt/jetty/logs/app.%g.log.json
23 | java.util.logging.FileHandler.limit=104857600
24 | java.util.logging.FileHandler.count=3
25 |
26 | # Set the default logging level for all loggers to INFO
27 | .level=INFO
28 |
29 | # Override root level
30 | com.foo.level=FINE
31 |
32 | # Override parent's level
33 | com.foo.bar.level=SEVERE
34 |
--------------------------------------------------------------------------------
/samples/java/gce/config/base/modules/gce.mod:
--------------------------------------------------------------------------------
1 | # Copyright 2019 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 | #
15 | # GCE Module
16 | #
17 |
18 | [depend]
19 | resources
20 | server
21 |
22 | [optional]
23 |
24 | [ini-template]
25 |
26 | ## Google Defaults
27 | jetty.httpConfig.outputAggregationSize=32768
28 | jetty.httpConfig.headerCacheSize=512
29 |
30 | jetty.httpConfig.sendServerVersion=true
31 | jetty.httpConfig.sendDateHeader=false
32 |
33 | #gae.httpPort=80
34 | #gae.httpsPort=443
35 |
--------------------------------------------------------------------------------
/samples/java/gce/config/base/resources/jetty-logging.properties:
--------------------------------------------------------------------------------
1 | # Copyright 2019 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | # Direct Jetty logging to JavaUtilLog
16 | # see etc/java-util-logging.properties
17 | org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.JavaUtilLog
18 |
--------------------------------------------------------------------------------
/samples/java/gce/deploy.sh:
--------------------------------------------------------------------------------
1 | # Copyright 2019 Google LLC All Rights Reserved.
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | set -ex
16 |
17 | MY_INSTANCE_NAME="my-app-instance"
18 | ZONE=us-central1-a
19 |
20 | gcloud compute instances create $MY_INSTANCE_NAME \
21 | --image-family=debian-10 \
22 | --image-project=debian-cloud \
23 | --machine-type=g1-small \
24 | --scopes userinfo-email,cloud-platform,https://www.googleapis.com/auth/firebase.database \
25 | --metadata-from-file startup-script=startup-script.sh \
26 | --zone $ZONE \
27 | --tags http-server
28 |
29 | gcloud compute firewall-rules create default-allow-http-80 \
30 | --allow tcp:80 \
31 | --source-ranges 0.0.0.0/0 \
32 | --target-tags http-server \
33 | --description "Allow port 80 access to http-server"
34 |
--------------------------------------------------------------------------------
/samples/java/gce/src/main/appengine/app.yaml:
--------------------------------------------------------------------------------
1 | # Copyright 2019 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | # [START runtime]
16 | runtime: java
17 | env: flex
18 |
19 | handlers:
20 | - url: /.*
21 | script: this field is required, but ignored
22 |
23 | # [START env_variables]
24 | env_variables: # Logging options
25 | JAVA_OPTS: >-
26 | -D.level=INFO
27 | # [END env_variables]
28 | # [END runtime]
29 |
30 | runtime_config: # Optional
31 | jdk: openjdk8
32 | server: jetty9
33 |
--------------------------------------------------------------------------------
/samples/java/gce/src/main/java/com/example/getstarted/basicactions/HelloworldController.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.example.getstarted.basicactions;
18 |
19 | import java.io.IOException;
20 | import javax.servlet.annotation.WebServlet;
21 | import javax.servlet.http.HttpServlet;
22 | import javax.servlet.http.HttpServletRequest;
23 | import javax.servlet.http.HttpServletResponse;
24 |
25 | @WebServlet(value = "/")
26 | public class HelloworldController extends HttpServlet {
27 |
28 | @Override
29 | public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
30 | resp.getWriter().write("Hello world - GCE!");
31 | resp.setStatus(HttpServletResponse.SC_OK);
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/samples/java/gce/src/main/webapp/WEB-INF/web.xml:
--------------------------------------------------------------------------------
1 |
2 |
14 |
15 |
16 |
20 |
21 |
22 | /
23 |
24 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/samples/java/gce/src/test/java/com/example/getstarted/basicactions/UserJourneyTestIT.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.example.getstarted.basicactions;
18 |
19 | import static org.junit.Assert.assertTrue;
20 |
21 | import org.junit.After;
22 | import org.junit.Before;
23 | import org.junit.BeforeClass;
24 | import org.junit.Ignore;
25 | import org.junit.Test;
26 | import org.junit.runner.RunWith;
27 | import org.junit.runners.JUnit4;
28 | import org.openqa.selenium.WebDriver;
29 | import org.openqa.selenium.chrome.ChromeDriverService;
30 | import org.openqa.selenium.chrome.ChromeOptions;
31 | import org.openqa.selenium.remote.RemoteWebDriver;
32 | import org.openqa.selenium.remote.service.DriverService;
33 |
34 | @RunWith(JUnit4.class)
35 | @SuppressWarnings("checkstyle:AbbreviationAsWordInName")
36 | public class UserJourneyTestIT {
37 | private static DriverService service;
38 | private WebDriver driver;
39 |
40 | @BeforeClass
41 | public static void setupClass() throws Exception {
42 | service = ChromeDriverService.createDefaultService();
43 | service.start();
44 | }
45 |
46 | @Before
47 | public void setup() {
48 | driver = new RemoteWebDriver(service.getUrl(), new ChromeOptions());
49 | }
50 |
51 | @After
52 | public void tearDown() {
53 | driver.quit();
54 | }
55 |
56 | @Test
57 | @Ignore("b/138123046")
58 | public void userJourney() {
59 | driver.get("http://localhost:8080");
60 |
61 | try {
62 | assertTrue(driver.getPageSource().contains("Hello world - GCE!"));
63 | } catch (Exception e) {
64 | System.err.println(driver.getPageSource());
65 | throw e;
66 | }
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/samples/java/gce/teardown.sh:
--------------------------------------------------------------------------------
1 | #! /bin/bash
2 |
3 | # Copyright 2019 Google LLC All Rights Reserved.
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License");
6 | # you may not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 |
17 | set -x
18 |
19 | MY_INSTANCE_NAME="my-app-instance"
20 | ZONE=us-central1-a
21 |
22 | gcloud compute instances delete $MY_INSTANCE_NAME \
23 | --zone=$ZONE --delete-disks=all
24 |
25 | gcloud compute firewall-rules delete default-allow-http-80
26 |
--------------------------------------------------------------------------------
/samples/node-js/appengine-flexible/README.md:
--------------------------------------------------------------------------------
1 | # Snapshot Debugger Examples for Node.js in the App Engine flexible environment
2 |
3 | NOTE: This sample application was copied from
4 | [nodejs-docs-samples/appengine/hello-world/flexible][sample-source]
5 | and modified for the Snapshot Debugger samples here.
6 |
7 |
8 | This is the sample application for the
9 | [Quickstart for Node.js in the App Engine flexible environment](https://cloud.google.com/appengine/docs/flexible/nodejs/quickstart)
10 | tutorial found in the [Google App Engine Node.js flexible environment](https://cloud.google.com/appengine/docs/flexible/nodejs)
11 | documentation.
12 |
13 | * [Setup](#setup)
14 | * [Deploying to App Engine](#deploying-to-app-engine)
15 |
16 | ## Setup
17 |
18 | Before you can run or deploy the sample, you need to do the following:
19 |
20 | 1. Perform all [Prerequisite Steps](../../app_engine_flexible_prerequisites.md)
21 | 1. Install dependencies:
22 |
23 | npm install
24 |
25 | ## Deploying to App Engine
26 |
27 | The following code changes have been made to enable the Snapshot Debugger:
28 |
29 | https://github.com/GoogleCloudPlatform/snapshot-debugger/blob/43428608e9fd262cd3a4d48c151b6577a8abd5e1/samples/node-js/appengine-flexible/package.json#L18-L21
30 |
31 | https://github.com/GoogleCloudPlatform/snapshot-debugger/blob/43428608e9fd262cd3a4d48c151b6577a8abd5e1/samples/node-js/appengine-flexible/app.js#L17-L20
32 |
33 | Then deploy to App Engine as usual:
34 |
35 | gcloud app deploy
36 |
37 | ## Navigate To Your App
38 |
39 | This will ensure the app is run and is required as your app will not be
40 | debuggable until after the first request has been received. The URL should be
41 | provided in the `target url` output of the previous step.
42 |
43 | ## Determine the Debuggee ID
44 |
45 | Based on the service and version you'll be able to identify your debuggee ID
46 | based on the output from the `list_debuggees` command.
47 |
48 | ```
49 | snapshot-dbg-cli list_debuggees
50 | ```
51 |
52 | The output will resemble the following. The first column will contain an entry
53 | ` - `, which in this case is `default - 20221117t213436`.
54 |
55 | ```
56 | Name ID Description Last Active Status
57 | ------------------------- ---------- ----------------------------------- -------------------- ------
58 | default - 20221122t161333 d-ad4829f7 node app.js version:20221122t161333 2022-11-22T16:15:00Z ACTIVE
59 | ```
60 |
61 | The debuggee ID in this case is `d-ad4829f7`. Using this ID you may now run
62 | through an [Example workflow](../../../README.md#example-workflow).
63 |
64 | E.g.
65 | * Use the `set_snapshot` CLI command to set a snapshot at `app.js:28`.
66 | Note the returned breakpoint ID.
67 | * Navigate to your application using the `target url` shown in the
68 | `gcloud app deploy` output. This will trigger the breakpoint and
69 | collect the snapshot.
70 | * Use the `get_snapshot` CLI command to retrieve the snapshot using the
71 | breakpoint ID created with the `set_snapshot` command.
72 |
73 | [sample-source]: https://github.com/GoogleCloudPlatform/nodejs-docs-samples/blob/main/appengine/hello-world/flexible
74 |
75 |
--------------------------------------------------------------------------------
/samples/node-js/appengine-flexible/app.js:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Google LLC
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | 'use strict';
16 |
17 | require('@google-cloud/debug-agent').start({
18 | useFirebase: true,
19 | allowExpressions: true,
20 | });
21 |
22 | const express = require('express');
23 |
24 | const app = express();
25 |
26 | app.get('/', (req, res) => {
27 | res.status(200).send('Hello, world!').end();
28 | });
29 |
30 | // Start the server
31 | const PORT = parseInt(process.env.PORT) || 8080;
32 | app.listen(PORT, () => {
33 | console.log(`App listening on port ${PORT}`);
34 | console.log('Press Ctrl+C to quit.');
35 | });
36 |
37 | module.exports = app;
38 |
--------------------------------------------------------------------------------
/samples/node-js/appengine-flexible/app.yaml:
--------------------------------------------------------------------------------
1 | # Copyright 2017, Google, Inc.
2 | # Licensed under the Apache License, Version 2.0 (the "License");
3 | # you may not use this file except in compliance with the License.
4 | # You may obtain a copy of the License at
5 | #
6 | # http://www.apache.org/licenses/LICENSE-2.0
7 | #
8 | # Unless required by applicable law or agreed to in writing, software
9 | # distributed under the License is distributed on an "AS IS" BASIS,
10 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 | # See the License for the specific language governing permissions and
12 | # limitations under the License.
13 |
14 | runtime: nodejs
15 | env: flex
16 |
17 | # This sample incurs costs to run on the App Engine flexible environment.
18 | # The settings below are to reduce costs during testing and are not appropriate
19 | # for production use. For more information, see:
20 | # https://cloud.google.com/appengine/docs/flexible/nodejs/configuring-your-app-with-app-yaml
21 | manual_scaling:
22 | instances: 1
23 | resources:
24 | cpu: 1
25 | memory_gb: 0.5
26 | disk_size_gb: 10
27 |
28 |
--------------------------------------------------------------------------------
/samples/node-js/appengine-flexible/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "appengine-hello-world",
3 | "description": "Simple Hello World Node.js sample for Google App Engine Flexible Environment.",
4 | "version": "0.0.2",
5 | "private": true,
6 | "license": "Apache-2.0",
7 | "author": "Google Inc.",
8 | "repository": {
9 | "type": "git",
10 | "url": "https://github.com/GoogleCloudPlatform/nodejs-docs-samples.git"
11 | },
12 | "engines": {
13 | "node": "=v16.19.0"
14 | },
15 | "scripts": {
16 | "start": "node app.js"
17 | },
18 | "dependencies": {
19 | "express": "^4.17.1",
20 | "@google-cloud/debug-agent": "^7.2.1"
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/samples/node-js/appengine-standard/README.md:
--------------------------------------------------------------------------------
1 | # Snapshot Debugger Examples for Node.js in the App Engine standard environment
2 |
3 | NOTE: This sample application was copied from
4 | [nodejs-docs-samples/appengine/hello-world/standard][sample-source]
5 | and modified for the Snapshot Debugger samples here.
6 |
7 |
8 | This is the sample application for the
9 | [Quickstart for Node.js in the App Engine standard environment](https://cloud.google.com/appengine/docs/standard/nodejs/quickstart)
10 | tutorial found in the
11 | [Google App Engine Node.js standard environment](https://cloud.google.com/appengine/docs/standard/nodejs)
12 | documentation.
13 |
14 | * [Setup](#setup)
15 | * [Deploying to App Engine](#deploying-to-app-engine)
16 |
17 | ## Setup
18 |
19 | Before you can run or deploy the sample, you need to do the following:
20 |
21 | 1. Perform all [Prerequisite Steps](../../app_engine_standard_prerequisites.md)
22 | 1. Install dependencies:
23 |
24 | npm install
25 |
26 | ## Deploying to App Engine
27 |
28 | The following code changes have been made to enable the Snapshot Debugger:
29 |
30 | https://github.com/GoogleCloudPlatform/snapshot-debugger/blob/c7cc477b9a66541f9b6804f0b6ba215547901afc/samples/node-js/appengine-standard/package.json#L18-L21
31 |
32 | https://github.com/GoogleCloudPlatform/snapshot-debugger/blob/c7cc477b9a66541f9b6804f0b6ba215547901afc/samples/node-js/appengine-standard/app.js#L17-L20
33 |
34 | Then deploy to App Engine as usual:
35 |
36 | gcloud app deploy
37 |
38 |
39 | ## Navigate To Your App
40 |
41 | This will ensure the app is run and is required as your app will not be
42 | debuggable until after the first request has been received. The URL should be
43 | provided in the `target url` output of the previous step.
44 |
45 | ## Determine the Debuggee ID
46 |
47 | Based on the service and version you'll be able to identify your debuggee ID
48 | based on the output from the `list_debuggees` command.
49 |
50 | ```
51 | snapshot-dbg-cli list_debuggees
52 | ```
53 |
54 | The output will resemble the following. The first column will contain an entry
55 | ` - `, which in this case is `default - 20221117t213436`.
56 |
57 | ```
58 | Name ID Description Last Active Status
59 | ------------------------- ---------- ----------------------------------- -------------------- ------
60 | default - 20221122t161333 d-ad4829f7 node app.js version:20221122t161333 2022-11-22T16:15:00Z ACTIVE
61 | ```
62 |
63 | The debuggee ID in this case is `d-ad4829f7`. Using this ID you may now run
64 | through an [Example workflow](../../../README.md#example-workflow).
65 |
66 | E.g.
67 | * Use the `set_snapshot` CLI command to set a snapshot at `app.js:28`.
68 | Note the returned breakpoint ID.
69 | * Navigate to your application using the `target url` shown in the
70 | `gcloud app deploy` output. This will trigger the breakpoint and
71 | collect the snapshot.
72 | * Use the `get_snapshot` CLI command to retrieve the snapshot using the
73 | breakpoint ID created with the `set_snapshot` command.
74 |
75 | [sample-source]: https://github.com/GoogleCloudPlatform/nodejs-docs-samples/tree/main/appengine/hello-world/standard
76 |
77 |
--------------------------------------------------------------------------------
/samples/node-js/appengine-standard/app.js:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Google LLC
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | 'use strict';
16 |
17 | require('@google-cloud/debug-agent').start({
18 | useFirebase: true,
19 | allowExpressions: true,
20 | });
21 |
22 | const express = require('express');
23 |
24 | const app = express();
25 |
26 | app.get('/', (req, res) => {
27 | res.status(200).send('Hello, world!').end();
28 | });
29 |
30 | // Start the server
31 | const PORT = parseInt(process.env.PORT) || 8080;
32 | app.listen(PORT, () => {
33 | console.log(`App listening on port ${PORT}`);
34 | console.log('Press Ctrl+C to quit.');
35 | });
36 |
37 | module.exports = app;
38 |
--------------------------------------------------------------------------------
/samples/node-js/appengine-standard/app.yaml:
--------------------------------------------------------------------------------
1 | # Copyright 2017, Google, Inc.
2 | # Licensed under the Apache License, Version 2.0 (the "License");
3 | # you may not use this file except in compliance with the License.
4 | # You may obtain a copy of the License at
5 | #
6 | # http://www.apache.org/licenses/LICENSE-2.0
7 | #
8 | # Unless required by applicable law or agreed to in writing, software
9 | # distributed under the License is distributed on an "AS IS" BASIS,
10 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 | # See the License for the specific language governing permissions and
12 | # limitations under the License.
13 |
14 | runtime: nodejs16
15 |
--------------------------------------------------------------------------------
/samples/node-js/appengine-standard/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "appengine-hello-world",
3 | "description": "Simple Hello World Node.js sample for Google App Engine Standard Environment.",
4 | "version": "0.0.2",
5 | "private": true,
6 | "license": "Apache-2.0",
7 | "author": "Google Inc.",
8 | "repository": {
9 | "type": "git",
10 | "url": "https://github.com/GoogleCloudPlatform/nodejs-docs-samples.git"
11 | },
12 | "engines": {
13 | "node": ">=14.0.0"
14 | },
15 | "scripts": {
16 | "start": "node app.js"
17 | },
18 | "dependencies": {
19 | "express": "^4.17.1",
20 | "@google-cloud/debug-agent": "^7.2.1"
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/samples/node-js/gce/app.js:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Google LLC
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | 'use strict';
16 |
17 | require('@google-cloud/debug-agent').start({
18 | useFirebase: true,
19 | allowExpressions: true,
20 | serviceContext: {
21 | service: 'gce-nodejs-sample',
22 | version: 'v1'
23 | },
24 | });
25 |
26 | const express = require('express');
27 |
28 | const app = express();
29 |
30 | app.get('/', (req, res) => {
31 | res.status(200).send('Hello, world!').end();
32 | });
33 |
34 | // Start the server
35 | const PORT = process.env.PORT || 8080;
36 | app.listen(PORT, () => {
37 | console.log(`App listening on port ${PORT}`);
38 | console.log('Press Ctrl+C to quit.');
39 | });
40 |
41 | module.exports = app;
42 |
--------------------------------------------------------------------------------
/samples/node-js/gce/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "gce-hello-world",
3 | "description": "Simple Hello World Node.js sample for Google Compute Engine.",
4 | "version": "0.0.1",
5 | "private": true,
6 | "license": "Apache-2.0",
7 | "author": "Google Inc.",
8 | "repository": {
9 | "type": "git",
10 | "url": "https://github.com/GoogleCloudPlatform/nodejs-getting-started.git"
11 | },
12 | "engines": {
13 | "node": ">=12"
14 | },
15 | "scripts": {
16 | "start": "node app.js"
17 | },
18 | "dependencies": {
19 | "express": "^4.16.3",
20 | "@google-cloud/debug-agent": "^7.2.1"
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/samples/node-js/gce/startup-script.sh:
--------------------------------------------------------------------------------
1 | #! /bin/bash
2 | # Copyright 2019, Google, Inc.
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | set -e
16 | set -v
17 |
18 |
19 | # Talk to the metadata server to get the project id
20 | PROJECTID=$(curl -s "http://metadata.google.internal/computeMetadata/v1/project/project-id" -H "Metadata-Flavor: Google")
21 | echo ${PROJECTID}
22 |
23 | # Install logging monitor. The monitor will automatically pick up logs sent to
24 | # syslog.
25 | curl -s "https://storage.googleapis.com/signals-agents/logging/google-fluentd-install.sh" | bash
26 | service google-fluentd restart &
27 |
28 | # Install dependencies from apt
29 | apt-get update
30 | apt-get install -yq ca-certificates git build-essential supervisor
31 |
32 | # Install nodejs
33 | mkdir /opt/nodejs
34 | curl https://nodejs.org/dist/v16.15.0/node-v16.15.0-linux-x64.tar.gz | tar xzf - -C /opt/nodejs --strip-components=1
35 | ln -s /opt/nodejs/bin/node /usr/bin/node
36 | ln -s /opt/nodejs/bin/npm /usr/bin/npm
37 |
38 | # Get the application source code from the Github Repository.
39 | # git requires $HOME and it's not set during the startup script.
40 | export HOME=/root
41 | git config --global credential.helper gcloud.sh
42 | git clone https://github.com/GoogleCloudPlatform/snapshot-debugger /opt/app/new-repo
43 |
44 | # Install app dependencies
45 | cd /opt/app/new-repo/samples/node-js/gce
46 | echo "about to install application dependencies"
47 | npm install
48 | # FAILING HERE?
49 |
50 | # Create a nodeapp user. The application will run as this user.
51 | useradd -m -d /home/nodeapp nodeapp
52 | chown -R nodeapp:nodeapp /opt/app
53 |
54 | # Configure supervisor to run the node app.
55 | cat >/etc/supervisor/conf.d/node-app.conf << EOF
56 | [program:nodeapp]
57 | directory=/opt/app/new-repo/samples/node-js/gce
58 | command=npm start
59 | autostart=true
60 | autorestart=true
61 | user=nodeapp
62 | environment=HOME="/home/nodeapp",USER="nodeapp",NODE_ENV="production"
63 | stdout_logfile=syslog
64 | stderr_logfile=syslog
65 | EOF
66 |
67 | supervisorctl reread
68 | supervisorctl update
69 |
70 | # Application should now be running under supervisor
71 | # [END startup]
72 |
--------------------------------------------------------------------------------
/samples/python/appengine-flexible/README.md:
--------------------------------------------------------------------------------
1 | # Snapshot Debugger Examples for Python in the App Engine flexible environment
2 |
3 | NOTE: This sample application was copied from
4 | [python-docs-samples/appengine/hello-world/flexible][sample-source]
5 | and modified for the Snapshot Debugger samples here.
6 |
7 |
8 | This is the sample application for the
9 | [Quickstart for Python in the App Engine flexible environment](https://cloud.google.com/appengine/docs/flexible/python/quickstart)
10 | tutorial found in the [Google App Engine Python flexible environment](https://cloud.google.com/appengine/docs/flexible/python)
11 | documentation.
12 |
13 | * [Setup](#setup)
14 | * [Deploying to App Engine](#deploying-to-app-engine)
15 |
16 | ## Setup
17 |
18 | Before you can run or deploy the sample, you need to do the following:
19 |
20 | 1. Perform all [Prerequisite Steps](../../app_engine_flexible_prerequisites.md)
21 |
22 | ## Deploying to App Engine
23 |
24 | The following code changes have been made to enable the Snapshot Debugger:
25 |
26 | https://github.com/GoogleCloudPlatform/snapshot-debugger/blob/ae8358fb69ae8f9df10e4675d59671d950e0d6c1/samples/python/appengine-flexible/requirements.txt#L4
27 |
28 | https://github.com/GoogleCloudPlatform/snapshot-debugger/blob/ae8358fb69ae8f9df10e4675d59671d950e0d6c1/samples/python/appengine-flexible/main.py#L17-L21
29 |
30 | Then deploy to App Engine as usual:
31 |
32 | gcloud app deploy
33 |
34 | ## Determine the Debuggee ID
35 |
36 | Based on the service and version you'll be able to identify your debuggee ID
37 | based on the output from the `list_debuggees` command.
38 |
39 | ```
40 | snapshot-dbg-cli list_debuggees
41 | ```
42 |
43 | The output will resemble the following. The first column will contain an entry
44 | ` - `, which in this case is `default - 20221122t161333`.
45 |
46 | ```
47 | Name ID Description Last Active Status
48 | ------------------------- ---------- ----------------------------- -------------------- ------
49 | default - 20221122t161333 d-ad4829f7 my-project-id-20221122t161333 2022-11-22T16:15:00Z ACTIVE
50 | ```
51 |
52 | The debuggee ID in this case is `d-ad4829f7`. Using this ID you may now run
53 | through an [Example workflow](../../../README.md#example-workflow).
54 |
55 | E.g.
56 | * Use the `set_snapshot` CLI command to set a snapshot at `main.py:30`.
57 | Note the returned breakpoint ID.
58 | * Navigate to your application using the `target url` shown in the
59 | `gcloud app deploy` output. This will trigger the breakpoint and
60 | collect the snapshot.
61 | * Use the `get_snapshot` CLI command to retrieve the snapshot using the
62 | breakpoint ID created with the `set_snapshot` command.
63 |
64 | [sample-source]: https://github.com/GoogleCloudPlatform/python-docs-samples/tree/main/appengine/flexible/hello_world
65 |
--------------------------------------------------------------------------------
/samples/python/appengine-flexible/app.yaml:
--------------------------------------------------------------------------------
1 | # Copyright 2021, Google, Inc.
2 | # Licensed under the Apache License, Version 2.0 (the "License");
3 | # you may not use this file except in compliance with the License.
4 | # You may obtain a copy of the License at
5 | #
6 | # http://www.apache.org/licenses/LICENSE-2.0
7 | #
8 | # Unless required by applicable law or agreed to in writing, software
9 | # distributed under the License is distributed on an "AS IS" BASIS,
10 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 | # See the License for the specific language governing permissions and
12 | # limitations under the License.
13 |
14 | runtime: python
15 | env: flex
16 | entrypoint: gunicorn -b :$PORT main:app
17 |
18 | runtime_config:
19 | python_version: 3
20 |
21 | # This sample incurs costs to run on the App Engine flexible environment.
22 | # The settings below are to reduce costs during testing and are not appropriate
23 | # for production use. For more information, see:
24 | # https://cloud.google.com/appengine/docs/flexible/python/configuring-your-app-with-app-yaml
25 | manual_scaling:
26 | instances: 1
27 | resources:
28 | cpu: 1
29 | memory_gb: 0.5
30 | disk_size_gb: 10
31 |
32 |
--------------------------------------------------------------------------------
/samples/python/appengine-flexible/main.py:
--------------------------------------------------------------------------------
1 | # Copyright 2015 Google Inc. All Rights Reserved.
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | from flask import Flask
16 |
17 | try:
18 | import googleclouddebugger
19 | googleclouddebugger.enable(use_firebase=True)
20 | except ImportError:
21 | pass
22 |
23 |
24 | app = Flask(__name__)
25 |
26 |
27 | @app.route('/')
28 | def hello():
29 | """Return a friendly HTTP greeting."""
30 | return 'Hello World!'
31 |
32 |
33 | if __name__ == '__main__':
34 | # This is used when running locally only. When deploying to Google App
35 | # Engine, a webserver process such as Gunicorn will serve the app.
36 | app.run(host='127.0.0.1', port=8080, debug=True)
37 |
--------------------------------------------------------------------------------
/samples/python/appengine-flexible/requirements.txt:
--------------------------------------------------------------------------------
1 | Flask==2.2.5; python_version > '3.6'
2 | Flask==2.0.3; python_version < '3.7'
3 | gunicorn==20.1.0
4 | google-python-cloud-debugger
5 |
--------------------------------------------------------------------------------
/samples/python/appengine-standard/README.md:
--------------------------------------------------------------------------------
1 | # Snapshot Debugger Examples for Python in the App Engine standard environment
2 |
3 | NOTE: This sample application was copied from
4 | [python-docs-samples/appengine/hello-world/standard][sample-source]
5 | and modified for the Snapshot Debugger samples here.
6 |
7 |
8 | This is the hello-world application for the
9 | [Google App Engine Python standard environment](https://cloud.google.com/appengine/docs/standard/python3).
10 | More complete documentation about getting started in App Engine can be found in
11 | [this tutorial](https://cloud.google.com/appengine/docs/standard/python3/building-app).
12 |
13 | * [Setup](#setup)
14 | * [Deploying to App Engine](#deploying-to-app-engine)
15 |
16 | ## Setup
17 |
18 | Before you can run or deploy the sample, you need to do the following:
19 |
20 | 1. Perform all [Prerequisite Steps](../../app_engine_standard_prerequisites.md)
21 | 1. Refer to the [getting started documentation][create-cloud-project]
22 | for instructions on setting up your Cloud project.
23 |
24 | ## Deploying to App Engine
25 |
26 | The following code changes have been made to enable the Snapshot Debugger:
27 |
28 | https://github.com/GoogleCloudPlatform/snapshot-debugger/blob/33f8756c5adb7684650adbe0d81ebbc6f5051c4c/samples/python/appengine-standard/requirements.txt#L2
29 |
30 | https://github.com/GoogleCloudPlatform/snapshot-debugger/blob/33f8756c5adb7684650adbe0d81ebbc6f5051c4c/samples/python/appengine-standard/main.py#L17-L21
31 |
32 | Then deploy to App Engine as usual:
33 |
34 | gcloud app deploy
35 |
36 |
37 | ## Navigate To Your App
38 |
39 | This will ensure the app is run and is required as your app will not be
40 | debuggable until after the first request has been received. The URL should be
41 | provided in the `target url` output of the previous step.
42 |
43 | ## Determine the Debuggee ID
44 |
45 | Based on the service and version you'll be able to identify your debuggee ID
46 | based on the output from the `list_debuggees` command.
47 |
48 | ```
49 | snapshot-dbg-cli list_debuggees
50 | ```
51 |
52 | The output will resemble the following. The first column will contain an entry
53 | ` - `, which in this case is `default - 20221122t161333`.
54 |
55 | ```
56 | Name ID Description Last Active Status
57 | ------------------------- ---------- ----------------------------- -------------------- ------
58 | default - 20221122t161333 d-ad4829f7 my-project-id-20221122t161333 2022-11-22T16:15:00Z ACTIVE
59 | ```
60 |
61 | The debuggee ID in this case is `d-ad4829f7`. Using this ID you may now run
62 | through an [Example workflow](../../../README.md#example-workflow).
63 |
64 | E.g.
65 | * Use the `set_snapshot` CLI command to set a snapshot at `main.py:32`.
66 | Note the returned breakpoint ID.
67 | * Navigate to your application using the `target url` shown in the
68 | `gcloud app deploy` output. This will trigger the breakpoint and
69 | collect the snapshot.
70 | * Use the `get_snapshot` CLI command to retrieve the snapshot using the
71 | breakpoint ID created with the `set_snapshot` command.
72 |
73 | [sample-source]: https://github.com/GoogleCloudPlatform/python-docs-samples/tree/main/appengine/standard_python3/hello_world
74 | [create-cloud-project]: https://cloud.google.com/appengine/docs/standard/python3/building-app/creating-gcp-project
75 |
--------------------------------------------------------------------------------
/samples/python/appengine-standard/app.yaml:
--------------------------------------------------------------------------------
1 | # Copyright 2017, Google, Inc.
2 | # Licensed under the Apache License, Version 2.0 (the "License");
3 | # you may not use this file except in compliance with the License.
4 | # You may obtain a copy of the License at
5 | #
6 | # http://www.apache.org/licenses/LICENSE-2.0
7 | #
8 | # Unless required by applicable law or agreed to in writing, software
9 | # distributed under the License is distributed on an "AS IS" BASIS,
10 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 | # See the License for the specific language governing permissions and
12 | # limitations under the License.
13 |
14 | runtime: python39
15 |
--------------------------------------------------------------------------------
/samples/python/appengine-standard/main.py:
--------------------------------------------------------------------------------
1 | # Copyright 2018 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | from flask import Flask
16 |
17 | try:
18 | import googleclouddebugger
19 | googleclouddebugger.enable(use_firebase=True)
20 | except ImportError:
21 | pass
22 |
23 |
24 | # If `entrypoint` is not defined in app.yaml, App Engine will look for an app
25 | # called `app` in `main.py`.
26 | app = Flask(__name__)
27 |
28 |
29 | @app.route('/')
30 | def hello():
31 | """Return a friendly HTTP greeting."""
32 | return 'Hello World!'
33 |
34 |
35 | if __name__ == '__main__':
36 | # This is used when running locally only. When deploying to Google App
37 | # Engine, a webserver process such as Gunicorn will serve the app. You
38 | # can configure startup instructions by adding `entrypoint` to app.yaml.
39 | app.run(host='127.0.0.1', port=8080, debug=True)
40 |
--------------------------------------------------------------------------------
/samples/python/appengine-standard/requirements.txt:
--------------------------------------------------------------------------------
1 | Flask==2.2.5
2 | google-python-cloud-debugger
3 |
--------------------------------------------------------------------------------
/samples/python/gce/deploy.sh:
--------------------------------------------------------------------------------
1 | # Copyright 2019 Google LLC All Rights Reserved.
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | set -ex
16 |
17 | MY_INSTANCE_NAME="my-app-instance"
18 | ZONE=us-central1-a
19 |
20 | gcloud compute instances create $MY_INSTANCE_NAME \
21 | --image-family=debian-10 \
22 | --image-project=debian-cloud \
23 | --machine-type=g1-small \
24 | --scopes userinfo-email,cloud-platform,https://www.googleapis.com/auth/firebase.database \
25 | --metadata-from-file startup-script=startup-script.sh \
26 | --zone $ZONE \
27 | --tags http-server
28 |
29 | gcloud compute firewall-rules create default-allow-http-8080 \
30 | --allow tcp:8080 \
31 | --source-ranges 0.0.0.0/0 \
32 | --target-tags http-server \
33 | --description "Allow port 8080 access to http-server"
34 |
--------------------------------------------------------------------------------
/samples/python/gce/main.py:
--------------------------------------------------------------------------------
1 | # Copyright 2019 Google LLC All Rights Reserved.
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | from flask import Flask
16 | app = Flask(__name__)
17 |
18 | try:
19 | import googleclouddebugger
20 | googleclouddebugger.enable(
21 | use_firebase=True,
22 | module='gce-python-sample',
23 | version='v1')
24 | except ImportError:
25 | pass
26 |
27 |
28 | @app.route('/', methods=['GET'])
29 | def say_hello():
30 | return "Hello, world!"
31 |
32 |
33 | if __name__ == '__main__':
34 | app.run(host='127.0.0.1', port=8080, debug=True)
35 |
--------------------------------------------------------------------------------
/samples/python/gce/procfile:
--------------------------------------------------------------------------------
1 | hello: /opt/app/gce/env/bin/gunicorn -b 0.0.0.0:8080 main:app
2 |
--------------------------------------------------------------------------------
/samples/python/gce/python-app.conf:
--------------------------------------------------------------------------------
1 | [program:pythonapp]
2 | directory=/opt/app/gce
3 | command=/opt/app/gce/env/bin/honcho start -f ./procfile hello
4 | autostart=true
5 | autorestart=true
6 | user=pythonapp
7 | # Environment variables ensure that the application runs inside of the
8 | # configured virtualenv.
9 | environment=VIRTUAL_ENV="/opt/app/gce/env",PATH="/opt/app/gce/env/bin",HOME="/home/pythonapp",USER="pythonapp"
10 | stdout_logfile=syslog
11 | stderr_logfile=syslog
12 |
--------------------------------------------------------------------------------
/samples/python/gce/requirements.txt:
--------------------------------------------------------------------------------
1 | flask==2.2.5
2 | honcho==1.1.0
3 | gunicorn==20.1.0
4 | google-python-cloud-debugger
5 |
--------------------------------------------------------------------------------
/samples/python/gce/startup-script.sh:
--------------------------------------------------------------------------------
1 | # Copyright 2019 Google LLC All Rights Reserved.
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | # Echo commands and fail on error
16 | set -ev
17 |
18 | # Install or update needed software
19 | apt-get update
20 | apt-get install -yq git supervisor python python-pip python3-distutils
21 | pip install --upgrade pip virtualenv
22 |
23 | # Fetch source code
24 | export HOME=/root
25 | git clone https://github.com/GoogleCloudPlatform/snapshot-debugger.git /tmp/app
26 | mkdir /opt/app
27 | cp -r /tmp/app/samples/python/gce /opt/app
28 |
29 | # Install Cloud Ops Agent
30 | sudo bash /opt/app/gce/add-google-cloud-ops-agent-repo.sh --also-install
31 |
32 | # Account to own server process
33 | useradd -m -d /home/pythonapp pythonapp
34 |
35 | # Python environment setup
36 | virtualenv -p python3 /opt/app/gce/env
37 | /bin/bash -c "source /opt/app/gce/env/bin/activate"
38 | /opt/app/gce/env/bin/pip install -r /opt/app/gce/requirements.txt
39 |
40 | # Set ownership to newly created account
41 | chown -R pythonapp:pythonapp /opt/app
42 |
43 | # Put supervisor configuration in proper place
44 | cp /opt/app/gce/python-app.conf /etc/supervisor/conf.d/python-app.conf
45 |
46 | # Start service via supervisorctl
47 | supervisorctl reread
48 | supervisorctl update
49 |
--------------------------------------------------------------------------------
/samples/python/gce/teardown.sh:
--------------------------------------------------------------------------------
1 | #! /bin/bash
2 |
3 | # Copyright 2019 Google LLC All Rights Reserved.
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License");
6 | # you may not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 |
17 | set -x
18 |
19 | MY_INSTANCE_NAME="my-app-instance"
20 | ZONE=us-central1-a
21 |
22 | gcloud compute instances delete $MY_INSTANCE_NAME \
23 | --zone=$ZONE --delete-disks=all
24 |
25 | gcloud compute firewall-rules delete default-allow-http-8080
26 |
--------------------------------------------------------------------------------
/setup.cfg:
--------------------------------------------------------------------------------
1 | [metadata]
2 | name= snapshot-dbg-cli
3 | version = attr: snapshot_dbg_cli.__version__
4 | description='Snapshot Debugger CLI tool.'
5 | long_description = file: README.md
6 | long_description_content_type = text/markdown
7 | license = Apache License, Version 2.0
8 | author = Google Inc.
9 | keywords = cloud, snapshot, debugger
10 | url = https://github.com/GoogleCloudPlatform/snapshot-debugger
11 | python_requires= >=3.6
12 | project_urls =
13 | CLI Source = https://github.com/GoogleCloudPlatform/snapshot-debugger
14 | Java Agent Source = https://github.com/GoogleCloudPlatform/cloud-debug-java
15 | Node.js Agent Source = https://github.com/googleapis/cloud-debug-nodejs
16 | Python Agent Source = https://github.com/GoogleCloudPlatform/cloud-debug-python
17 |
18 | classifiers =
19 | Development Status :: 4 - Beta
20 | Intended Audience :: Developers
21 | Programming Language :: Python
22 | Programming Language :: Python :: 3
23 | Programming Language :: Python :: 3.6
24 | Programming Language :: Python :: 3.7
25 | Programming Language :: Python :: 3.8
26 | Programming Language :: Python :: 3.9
27 | Programming Language :: Python :: 3.10
28 | Topic :: Software Development :: Debuggers
29 |
30 | [options]
31 | packages = snapshot_dbg_cli
32 |
33 | [options.entry_points]
34 | console_scripts =
35 | snapshot-dbg-cli = snapshot_dbg_cli:run_main
36 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | # Copyright 2022 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 | """Minimal setup.py file for packaging snapshot-dbg-cli.
15 |
16 | All packaging configuriation can be found in setup.cfg
17 | """
18 |
19 | from setuptools import setup
20 |
21 | if __name__ == '__main__':
22 | setup()
23 |
--------------------------------------------------------------------------------
/snapshot_dbg_cli/__init__.py:
--------------------------------------------------------------------------------
1 | # Copyright 2022 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 | """ Snapshot Debugger CLI.
15 |
16 | This package provides the CLI of the Snapshot Debugger.
17 | """
18 |
19 | import sys
20 |
21 | import snapshot_dbg_cli.cli_run
22 | import snapshot_dbg_cli.cli_version
23 | from snapshot_dbg_cli.exceptions import SilentlyExitError
24 |
25 | __version__ = snapshot_dbg_cli.cli_version.running_version()
26 |
27 |
28 | def main():
29 | snapshot_dbg_cli.cli_version.check_for_newer_version()
30 | snapshot_dbg_cli.cli_run.run()
31 |
32 |
33 | def run_main():
34 | try:
35 | main()
36 | except SilentlyExitError:
37 | sys.exit(1)
38 |
39 | sys.exit(0)
40 |
41 |
42 | if __name__ == '__main__':
43 | run_main()
44 |
--------------------------------------------------------------------------------
/snapshot_dbg_cli/__main__.py:
--------------------------------------------------------------------------------
1 | # Copyright 2022 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 | """Main entry point for the cli package."""
15 | # pylint: disable=invalid-name
16 | import snapshot_dbg_cli
17 |
18 | snapshot_dbg_cli.run_main()
19 |
--------------------------------------------------------------------------------
/snapshot_dbg_cli/cli.py:
--------------------------------------------------------------------------------
1 | # Copyright 2022 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 | """Old CLI startup code, no longer in use.
15 |
16 | This file is no longer in use but is being kept to help any users attempting to
17 | run the cli via the old mechanism learn how to run it the new way.
18 | """
19 | import sys
20 |
21 | if __name__ == '__main__':
22 | print("Please run 'python3 -m snapshot_dbg_cli ...' from the root of the "
23 | 'repository.')
24 | sys.exit(1)
25 |
--------------------------------------------------------------------------------
/snapshot_dbg_cli/cli_run.py:
--------------------------------------------------------------------------------
1 | # Copyright 2022 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 | """ The main Snapshot Debugger CLI startup code.
15 |
16 | This module provides the run() function, which sets up the CLI commands, parses
17 | the command line arguments and runs the specified command.
18 | """
19 |
20 | import argparse
21 | import sys
22 |
23 | from snapshot_dbg_cli.cli_services import CliServices
24 | from snapshot_dbg_cli import cli_common_arguments
25 | from snapshot_dbg_cli.exceptions import SilentlyExitError
26 | from snapshot_dbg_cli.delete_debuggees_command import DeleteDebuggeesCommand
27 | from snapshot_dbg_cli.delete_logpoints_command import DeleteLogpointsCommand
28 | from snapshot_dbg_cli.delete_snapshots_command import DeleteSnapshotsCommand
29 | from snapshot_dbg_cli.get_logpoint_command import GetLogpointCommand
30 | from snapshot_dbg_cli.get_snapshot_command import GetSnapshotCommand
31 | from snapshot_dbg_cli.init_command import InitCommand
32 | from snapshot_dbg_cli.list_debuggees_command import ListDebuggeesCommand
33 | from snapshot_dbg_cli.list_logpoints_command import ListLogpointsCommand
34 | from snapshot_dbg_cli.list_snapshots_command import ListSnapshotsCommand
35 | from snapshot_dbg_cli.set_logpoint_command import SetLogpointCommand
36 | from snapshot_dbg_cli.set_snapshot_command import SetSnapshotCommand
37 |
38 |
39 | def run(cli_services=None):
40 | cli_commands = [
41 | DeleteDebuggeesCommand(),
42 | DeleteLogpointsCommand(),
43 | DeleteSnapshotsCommand(),
44 | GetLogpointCommand(),
45 | GetSnapshotCommand(),
46 | InitCommand(),
47 | ListLogpointsCommand(),
48 | ListSnapshotsCommand(),
49 | ListDebuggeesCommand(),
50 | SetLogpointCommand(),
51 | SetSnapshotCommand()
52 | ]
53 |
54 | args_parser = argparse.ArgumentParser()
55 | common_parsers = cli_common_arguments.CommonArgumentParsers()
56 | required_parsers = cli_common_arguments.RequiredArgumentParsers().parsers
57 |
58 | args_subparsers = args_parser.add_subparsers()
59 |
60 | for cmd in cli_commands:
61 | cmd.register(
62 | args_subparsers,
63 | required_parsers=required_parsers,
64 | common_parsers=common_parsers)
65 |
66 | args = args_parser.parse_args()
67 |
68 | if 'func' not in args:
69 | print(
70 | 'Missing required argument, please specify --help for more information',
71 | file=sys.stderr)
72 |
73 | raise SilentlyExitError
74 |
75 | if cli_services is None:
76 | cli_services = CliServices(args)
77 |
78 | # This will run the appropriate command.
79 | args.func(args=args, cli_services=cli_services)
80 |
--------------------------------------------------------------------------------
/snapshot_dbg_cli/cli_version.py:
--------------------------------------------------------------------------------
1 | # Copyright 2022 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 | """This module provides the current version of the package.
15 | """
16 |
17 | import re
18 | import sys
19 |
20 | from snapshot_dbg_cli.data_formatter import DataFormatter
21 | from snapshot_dbg_cli.exceptions import SilentlyExitError
22 | from snapshot_dbg_cli.http_service import HttpService
23 | from snapshot_dbg_cli.user_output import UserOutput
24 |
25 | VERSION = 'SNAPSHOT_DEBUGGER_CLI_VERSION_0_3_8'
26 |
27 | VERSION_PATTERN = 'SNAPSHOT_DEBUGGER_CLI_VERSION_[0-9]+_[0-9]+_[0-9]+'
28 |
29 | VERSION_URL = ('https://raw.githubusercontent.com/GoogleCloudPlatform'
30 | '/snapshot-debugger/main/snapshot_dbg_cli/cli_version.py')
31 |
32 | NEWER_VERSION_MESSAGE = """
33 | A newer version of the CLI is available ({latest} vs {running}). To install it please run:
34 | $ pip install --upgrade snapshot-dbg-cli
35 | """
36 |
37 |
38 | class SuppressedUserOutput(UserOutput):
39 | """A UserOutput subclass that prevents any user output from being emitted.
40 | """
41 |
42 | def __init__(self):
43 | # Just need to intialize it, the local_print override is the important bit
44 | # that will ensure no data is written to stdout/stderr.
45 | super().__init__(is_debug_enabled=False, data_formatter=DataFormatter())
46 |
47 | def local_print(self, *args, **kwargs):
48 | """Override the local_print of UserOutput to suppress all output.
49 | """
50 | pass
51 |
52 |
53 | def extract_version_number(version_string):
54 | # Expressly not using str.removeprefix as that was added in Python 3.9
55 | # and we want to support older versions.
56 | prefix = 'SNAPSHOT_DEBUGGER_CLI_VERSION_'
57 | if version_string.startswith(prefix):
58 | version_string = version_string[len(prefix):]
59 |
60 | return version_string.replace('_', '.')
61 |
62 |
63 | def running_version():
64 | return extract_version_number(VERSION)
65 |
66 |
67 | def latest_version():
68 | # Send in SuppressedUserOutput, we don't want any error messages emitted on a
69 | # failure to get the version file, as it's not critical for it to succeed.
70 | http_service = HttpService(
71 | project_id=None, access_token=None, user_output=SuppressedUserOutput())
72 | version = None
73 |
74 | try:
75 | # Make a quick call with a low timeout, and don't care if it fails. If we
76 | # can't get the latest version it's not critical, we simply can't warn the
77 | # user if they happen to be running an older version.
78 | content = http_service.send_request(
79 | 'GET', VERSION_URL, max_retries=0, timeout_sec=5)
80 | match = re.search(VERSION_PATTERN, content)
81 | if match is not None:
82 | version = extract_version_number(match.group(0))
83 | except SilentlyExitError:
84 | # Simply swallow the error, it is not critical to retrieve the latest, we'll
85 | # simply fall through and None will be returned in this case.
86 | pass
87 |
88 | return version
89 |
90 |
91 | def check_for_newer_version():
92 |
93 | def version(version_string):
94 | return tuple(map(int, version_string.split('.')))
95 |
96 | running_version_string = running_version()
97 | latest_version_string = latest_version()
98 |
99 | if latest_version_string is None:
100 | return
101 |
102 | if version(latest_version_string) > version(running_version_string):
103 | print(
104 | NEWER_VERSION_MESSAGE.format(
105 | latest=latest_version_string, running=running_version_string),
106 | file=sys.stderr)
107 |
--------------------------------------------------------------------------------
/snapshot_dbg_cli/data_formatter.py:
--------------------------------------------------------------------------------
1 | # Copyright 2022 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 | """This module provides utilities for formatting text.
15 |
16 | The DataFormatter class provides utility functions such as formatting tabular
17 | data and json data.
18 | """
19 |
20 | import json
21 |
22 |
23 | class DataFormatter:
24 | """Provides utilities for formatting text.
25 |
26 | The DataFormatter class provides utility functions such as formatting tabular
27 | data and json data.
28 | """
29 |
30 | def build_table(self, headers, values):
31 | """Builds a human friendly string of the data in table form.
32 |
33 | Args:
34 | headers: Array of strings to be used as the top row of the table,
35 | representing the column names.
36 | values: Array of tuples containing one row of output (1 value for each
37 | column). Each tuple must have the same number of elements, which much
38 | also match the length of the headers tuple.
39 | """
40 | widths = [
41 | max(len(headers[i]), max((len(v[i])
42 | for v in values), default=0))
43 | for i in range(len(headers))
44 | ]
45 |
46 | separator_row = list(map(lambda w: '-' * w, widths))
47 |
48 | rows = []
49 | rows.append(headers)
50 | rows.append(separator_row)
51 | rows += values
52 |
53 | field_buffer = ' '
54 | table = ''
55 |
56 | for i in range(len(rows)):
57 | fields = [f'{rows[i][j]:{widths[j]}}' for j in range(len(rows[i]))]
58 | table += field_buffer.join(fields)
59 | table += '\n'
60 |
61 | return table
62 |
63 | def to_json_string(self, data, pretty):
64 | """Transforms the data to a JSON string representation.
65 |
66 | Args:
67 | data: The data to convert to a JSON string. It is expected to be a Python
68 | object representation of the data.
69 | pretty: Boolean flag that when True will be cause the json string to be
70 | human readable, otherwise it will be in a compact representation.
71 | """
72 | if pretty:
73 | return json.dumps(data, indent=2)
74 | else:
75 | return json.dumps(data)
76 |
--------------------------------------------------------------------------------
/snapshot_dbg_cli/delete_logpoints_command.py:
--------------------------------------------------------------------------------
1 | # Copyright 2022 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 | """This module contains the support for the delete_logpoints command.
15 |
16 | The delete_logpoints command is used to delete logpoints from a debug target
17 | (debuggee).
18 | """
19 |
20 | from snapshot_dbg_cli import breakpoint_utils
21 | from snapshot_dbg_cli import delete_breakpoints
22 |
23 | DESCRIPTION = """
24 | Used to delete logpoints from a debug target (debuggee). You are prompted for
25 | confirmation before any logpoints are deleted. To suppress confirmation, use the
26 | --quiet option.
27 | """
28 |
29 | ID_HELP = """
30 | Zero or more logpoint IDs. The specified logpoints will be deleted. By default,
31 | if no logpoint IDs are specified, all active logpoints created by the user are
32 | selected for deletion.
33 | """
34 |
35 | ALL_USERS_HELP = """
36 | If set, logpoints from all users will be deleted, rather than only logpoints
37 | created by the current user. This flag is not required when specifying the exact
38 | ID of a logpoint.
39 | """
40 |
41 | INCLUDE_INACTIVE_HELP = """
42 | If set, also delete logpoints which have been completed. By default, only
43 | pending logpoints will be deleted. This flag is not required when specifying the
44 | exact ID of an inactive logpoint.
45 | """
46 |
47 | QUIET_HELP = 'If set, suppresses user confirmation of the command.'
48 |
49 | SUMMARY_HEADERS = [
50 | 'Location', 'Condition', 'Log Level', 'Log Message Format', 'ID'
51 | ]
52 |
53 |
54 | def transform_to_logpoint_summary(logpoint):
55 | # Match the fields from SUMMARY_HEADERS
56 | return [
57 | breakpoint_utils.transform_location_to_file_line(logpoint['location']),
58 | logpoint['condition'] if 'condition' in logpoint else '',
59 | logpoint['logLevel'],
60 | logpoint['logMessageFormatString'],
61 | logpoint['id'],
62 | ]
63 |
64 |
65 | class DeleteLogpointsCommand:
66 | """This class implements the delete_logpoints command.
67 |
68 | The register() method is called by the CLI startup code to install the
69 | delete_logpoints command information, and the cmd() function will be invoked
70 | if the delete_logpoints command was specified by the user.
71 | """
72 |
73 | def __init__(self):
74 | pass
75 |
76 | def register(self, args_subparsers, required_parsers, common_parsers):
77 | parent_parsers = [
78 | common_parsers.database_url, common_parsers.format,
79 | common_parsers.debuggee_id
80 | ]
81 | parent_parsers += required_parsers
82 | parser = args_subparsers.add_parser(
83 | 'delete_logpoints', description=DESCRIPTION, parents=parent_parsers)
84 | parser.add_argument('ID', help=ID_HELP, nargs='*')
85 | parser.add_argument('--all-users', help=ALL_USERS_HELP, action='store_true')
86 | parser.add_argument(
87 | '--include-inactive', help=INCLUDE_INACTIVE_HELP, action='store_true')
88 | parser.add_argument('--quiet', help=QUIET_HELP, action='store_true')
89 | parser.set_defaults(func=self.cmd)
90 |
91 | def cmd(self, args, cli_services):
92 | delete_breakpoints.run_cmd('LOG', args, cli_services, SUMMARY_HEADERS,
93 | transform_to_logpoint_summary)
94 |
--------------------------------------------------------------------------------
/snapshot_dbg_cli/delete_snapshots_command.py:
--------------------------------------------------------------------------------
1 | # Copyright 2022 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 | """This module contains the support for the delete_snapshots command.
15 |
16 | The delete_snapshots command is used to delete snapshots from a debug target
17 | (debuggee).
18 | """
19 |
20 | from snapshot_dbg_cli import breakpoint_utils
21 | from snapshot_dbg_cli import delete_breakpoints
22 |
23 | DESCRIPTION = """
24 | Used to delete snapshots from a debug target (debuggee). You are prompted for
25 | confirmation before any snapshots are deleted. To suppress confirmation, use the
26 | --quiet option.
27 | """
28 |
29 | ID_HELP = """
30 | Zero or more snapshot IDs. The specified snapshots will be deleted. By default,
31 | if no snapshot IDs are specified, all active snapshots created by the user are
32 | selected for deletion.
33 | """
34 |
35 | ALL_USERS_HELP = """
36 | If set, snapshots from all users will be deleted, rather than only snapshots
37 | created by the current user. This flag is not required when specifying the exact
38 | ID of a snapshot.
39 | """
40 |
41 | INCLUDE_INACTIVE_HELP = """
42 | If set, also delete snapshots which have been completed. By default, only
43 | pending snapshots will be deleted. This flag is not required when specifying the
44 | exact ID of an inactive snapshot.
45 | """
46 |
47 | QUIET_HELP = 'If set, suppresses user confirmation of the command.'
48 |
49 | SUMMARY_HEADERS = ['Status', 'Location', 'Condition', 'ID']
50 |
51 |
52 | def transform_to_snapshot_summary(snapshot):
53 | # Match the fields from SUMMARY_HEADERS
54 | return [
55 | 'COMPLETED' if snapshot['isFinalState'] else 'ACTIVE',
56 | breakpoint_utils.transform_location_to_file_line(snapshot['location']),
57 | snapshot['condition'] if 'condition' in snapshot else '', snapshot['id']
58 | ]
59 |
60 |
61 | class DeleteSnapshotsCommand:
62 | """This class implements the delete_snapshots command.
63 |
64 | The register() method is called by the CLI startup code to install the
65 | delete_snapshots command information, and the cmd() function will be invoked
66 | if the delete_snapshots command was specified by the user.
67 | """
68 |
69 | def __init__(self):
70 | pass
71 |
72 | def register(self, args_subparsers, required_parsers, common_parsers):
73 | parent_parsers = [
74 | common_parsers.database_url, common_parsers.format,
75 | common_parsers.debuggee_id
76 | ]
77 | parent_parsers += required_parsers
78 | parser = args_subparsers.add_parser(
79 | 'delete_snapshots', description=DESCRIPTION, parents=parent_parsers)
80 | parser.add_argument('ID', help=ID_HELP, nargs='*')
81 | parser.add_argument('--all-users', help=ALL_USERS_HELP, action='store_true')
82 | parser.add_argument(
83 | '--include-inactive', help=INCLUDE_INACTIVE_HELP, action='store_true')
84 | parser.add_argument('--quiet', help=QUIET_HELP, action='store_true')
85 | parser.set_defaults(func=self.cmd)
86 |
87 | def cmd(self, args, cli_services):
88 | delete_breakpoints.run_cmd('CAPTURE', args, cli_services, SUMMARY_HEADERS,
89 | transform_to_snapshot_summary)
90 |
--------------------------------------------------------------------------------
/snapshot_dbg_cli/exceptions.py:
--------------------------------------------------------------------------------
1 | # Copyright 2022 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 | """Module to hold custom exceptions for the CLI.
15 | """
16 |
17 |
18 | class SilentlyExitError(Exception):
19 | """Exception to cause the CLI to silently exit with an error.
20 |
21 | Should be thrown when an error occured and the code needs to exit. It's
22 | expected any user error messages have already been emitted. The top level
23 | CLI code will catch this and exit with an error without any further console
24 | output.
25 | """
26 | pass
27 |
--------------------------------------------------------------------------------
/snapshot_dbg_cli/firebase_rtdb_rest_service.py:
--------------------------------------------------------------------------------
1 | # Copyright 2022 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 | """Service for making Firebase RTDB REST requests.
15 |
16 | This service is for making read/write requests to a Firebase RTDB instance
17 | using the REST interface, which is documented at
18 | https://firebase.google.com/docs/reference/rest/database.
19 | """
20 |
21 | FULL_REQUEST_URL = "{database_url}/{db_path}.json"
22 |
23 |
24 | class FirebaseRtdbRestService:
25 | """This class implements a service for making Firebase RTDB REST requests.
26 |
27 | This service is for making read/write requests to a Firebase RTDB instance
28 | using the REST interface which is documented at
29 | https://firebase.google.com/docs/reference/rest/database.
30 | """
31 |
32 | def __init__(self, http_service, database_url, user_output):
33 | self._http_service = http_service
34 | self._database_url = database_url
35 | self._user_output = user_output
36 |
37 | def get(self, db_path, shallow=None, extra_retry_codes=None):
38 | """Gets the value at the specified path.
39 |
40 | Args:
41 | db_path: The database path to retrieve the value on.
42 | shallow: If specified will just get the values at the top level of the
43 | node, nothing further down.
44 | extra_retry_codes: A list of extra HTTP error codes that will be retried
45 | if the request fails in addition to the default error codes that are
46 | retried.
47 |
48 | Returns:
49 | The value at the specified path if it exists, None otherwise.
50 | """
51 | url = self.build_rtdb_url(db_path)
52 | parameters = ["shallow=true"] if shallow else []
53 | return self._http_service.send_request(
54 | "GET", url, extra_retry_codes=extra_retry_codes, parameters=parameters)
55 |
56 | def set(self, db_path, data):
57 | url = self.build_rtdb_url(db_path)
58 | return self._http_service.send_request("PUT", url, data=data, max_retries=0)
59 |
60 | def delete(self, db_path):
61 | url = self.build_rtdb_url(db_path)
62 |
63 | # From
64 | # https://firebase.google.com/docs/reference/rest/database#section-delete
65 | #
66 | # A successful DELETE request is indicated by a 200 OK HTTP status code with
67 | # a response containing JSON null.
68 | # Based on experimentation, even if the node didn't exist, this will be the
69 | # response. It seems the response is success if after the call the specified
70 | # path does not exist, as long as the database the call referenced did
71 | # exist.
72 | return self._http_service.send_request("DELETE", url, max_retries=5)
73 |
74 | def build_rtdb_url(self, db_path):
75 | return FULL_REQUEST_URL.format(
76 | database_url=self._database_url, db_path=db_path)
77 |
--------------------------------------------------------------------------------
/snapshot_dbg_cli/get_logpoint_command.py:
--------------------------------------------------------------------------------
1 | # Copyright 2022 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 | """This module contains the support for the get_logpoint command.
15 |
16 | The get_logpoint command is used to create a logpoint on a debug target
17 | (Debuggee).
18 | """
19 |
20 | from snapshot_dbg_cli.exceptions import SilentlyExitError
21 | from snapshot_dbg_cli import breakpoint_utils
22 |
23 | DESCRIPTION = """
24 | Used to retrieve a debug logpoint from a debug target (debuggee).
25 | """
26 |
27 | ID_HELP = 'Specify the logpoint ID to retrieve.'
28 |
29 |
30 | class GetLogpointCommand:
31 | """This class implements the get_logpoint command.
32 |
33 | The register() method is called by the CLI startup code to install the
34 | get_logpoint command information, and the cmd() function will be invoked if
35 | the get_logpoint command was specified by the user.
36 | """
37 |
38 | def __init__(self):
39 | pass
40 |
41 | def register(self, args_subparsers, required_parsers, common_parsers):
42 | parent_parsers = [
43 | common_parsers.database_url, common_parsers.format,
44 | common_parsers.debuggee_id
45 | ]
46 | parent_parsers += required_parsers
47 | parser = args_subparsers.add_parser(
48 | 'get_logpoint', description=DESCRIPTION, parents=parent_parsers)
49 | parser.add_argument('logpoint_id', metavar='ID', help=ID_HELP)
50 | parser.set_defaults(func=self.cmd)
51 |
52 | def display_summary(self, bp):
53 | # To note we're being defensive here with respect to the condition field the
54 | # that its absence is treated to the same way as it being present but empty.
55 | logpoint_id = bp['id']
56 | location = breakpoint_utils.transform_location_to_file_line(bp['location'])
57 | condition = bp.get('condition', '')
58 | condition = condition if condition != '' else 'No condition set'
59 | status = breakpoint_utils.get_logpoint_short_status(bp)
60 | log_message_format = bp['logMessageFormatString']
61 | create_time = bp['createTime']
62 | final_time = bp.get('finalTime', '')
63 | user_email = bp.get('userEmail', '')
64 |
65 | self.user_output.normal(f'Logpoint ID: {logpoint_id}')
66 | self.user_output.normal(f'Log Message Format: {log_message_format}')
67 | self.user_output.normal(f'Location: {location}')
68 | self.user_output.normal(f'Condition: {condition}')
69 | self.user_output.normal(f'Status: {status}')
70 | self.user_output.normal(f'Create Time: {create_time}')
71 | self.user_output.normal(f'Final Time: {final_time}')
72 | self.user_output.normal(f'User Email: {user_email}')
73 |
74 | def cmd(self, args, cli_services):
75 | self.data_formatter = cli_services.data_formatter
76 | self.user_output = cli_services.user_output
77 | debugger_rtdb_service = cli_services.get_snapshot_debugger_rtdb_service()
78 |
79 | debugger_rtdb_service.validate_debuggee_id(args.debuggee_id)
80 | logpoint = debugger_rtdb_service.get_breakpoint(args.debuggee_id,
81 | args.logpoint_id)
82 |
83 | if logpoint is None or logpoint['action'] != 'LOG':
84 | self.user_output.error(f'Logpoint ID not found: {args.logpoint_id}')
85 | raise SilentlyExitError
86 |
87 | if args.format.is_a_json_value():
88 | self.user_output.json_format(
89 | logpoint, pretty=args.format.is_pretty_json())
90 | return
91 |
92 | self.display_summary(logpoint)
93 |
--------------------------------------------------------------------------------
/snapshot_dbg_cli/list_debuggees_command.py:
--------------------------------------------------------------------------------
1 | # Copyright 2022 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 | """This module contains the support for the list_debuggees command.
15 |
16 | The list_debugees command is used to display a list of the debug targets
17 | (debuggees) registered with the Snapshot Debugger.
18 | """
19 |
20 | from snapshot_dbg_cli.debuggee_utils import get_debuggee_status
21 | from snapshot_dbg_cli.debuggee_utils import sort_debuggees
22 | from snapshot_dbg_cli.time_utils import get_current_time_unix_msec
23 |
24 | DESCRIPTION = """
25 | Used to display a list of the debug targets (debuggees) registered with the
26 | Snapshot Debugger. By default all active debuggees are returned. To also obtain
27 | inactive debuggees specify the --include-inactive option. A debuggee is
28 | considered to be active if it currently running or last ran in the past 5-6
29 | hours.
30 | """
31 |
32 | INCLUDE_INACTIVE_HELP = 'Include inactive debuggees.'
33 |
34 | SUMMARY_HEADERS = headers = [
35 | 'Name', 'ID', 'Description', 'Last Active', 'Status'
36 | ]
37 |
38 |
39 | def transform_to_debuggee_summary(debuggee):
40 | # Match the fields from SUMMARY_HEADERS
41 | return [
42 | debuggee['displayName'],
43 | debuggee['id'],
44 | debuggee['description'],
45 | debuggee['lastUpdateTime'],
46 | get_debuggee_status(debuggee),
47 | ]
48 |
49 |
50 | class ListDebuggeesCommand:
51 | """This class implements the list_debuggees command.
52 |
53 | The register() method is called by the CLI startup code to install the
54 | list_debuggees command information, and the cmd() function will be invoked if
55 | the list_debuggees command was specified by the user.
56 | """
57 |
58 | def __init__(self):
59 | pass
60 |
61 | def register(self, args_subparsers, required_parsers, common_parsers):
62 | parent_parsers = [common_parsers.database_url, common_parsers.format]
63 | parent_parsers += required_parsers
64 | parser = args_subparsers.add_parser(
65 | 'list_debuggees', description=DESCRIPTION, parents=parent_parsers)
66 | parser.add_argument(
67 | '--include-inactive', help=INCLUDE_INACTIVE_HELP, action='store_true')
68 | parser.set_defaults(func=self.cmd)
69 |
70 | def cmd(self, args, cli_services):
71 | user_output = cli_services.user_output
72 |
73 | current_time_unix_msec = get_current_time_unix_msec()
74 | debugger_rtdb_service = cli_services.get_snapshot_debugger_rtdb_service()
75 | debuggees = debugger_rtdb_service.get_debuggees(current_time_unix_msec)
76 |
77 | if not args.include_inactive:
78 | # If there are any debuggees that support the 'active debuggee' feature,
79 | # then we go ahead and apply isActive filter. Any debuggees that don't
80 | # support it will be filtered out, but given there are debuggees from
81 | # newer agents present odds then that debugees without this feature are
82 | # inactive.
83 | if any(d['activeDebuggeeEnabled'] for d in debuggees):
84 | debuggees = list(filter(lambda d: d['isActive'], debuggees))
85 |
86 | debuggees = sort_debuggees(debuggees)
87 |
88 | if args.format.is_a_json_value():
89 | user_output.json_format(debuggees, pretty=args.format.is_pretty_json())
90 | else:
91 | values = list(map(transform_to_debuggee_summary, debuggees))
92 | user_output.tabular(SUMMARY_HEADERS, values)
93 |
--------------------------------------------------------------------------------
/snapshot_dbg_cli/list_logpoints_command.py:
--------------------------------------------------------------------------------
1 | # Copyright 2022 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 | """This module contains the support for the list_logpoints command.
15 |
16 | The list_logpoints command is Used to display the debug logpoints for a debug
17 | target (debuggee).
18 | """
19 |
20 | from snapshot_dbg_cli import breakpoint_utils
21 |
22 | DESCRIPTION = """
23 | Used to display the debug logpoints for a debug target (debuggee). By default
24 | all active logpoints are returned. To obtain older, expired logpoints, specify
25 | the --include-inactive option.
26 | """
27 |
28 | INCLUDE_INACTIVE_HELP = 'Include all logpoints which have completed.'
29 |
30 | ALL_USERS_HELP = """
31 | If false, display only logpoints created by the current user. Enabled by
32 | default, use --no-all-users to disable.
33 | """
34 |
35 | NO_ALL_USERS_HELP = """
36 | Disables --all-users, which is enabled by default.
37 | """
38 |
39 | SUMMARY_HEADERS = [
40 | 'User Email', 'Location', 'Condition', 'Log Level', 'Log Message Format',
41 | 'ID', 'Status'
42 | ]
43 |
44 |
45 | def transform_to_logpoint_summary(logpoint):
46 | # Match the fields from SUMMARY_HEADERS
47 | return [
48 | logpoint['userEmail'],
49 | breakpoint_utils.transform_location_to_file_line(logpoint['location']),
50 | logpoint['condition'] if 'condition' in logpoint else '',
51 | logpoint['logLevel'],
52 | logpoint['logMessageFormatString'],
53 | logpoint['id'],
54 | breakpoint_utils.get_logpoint_short_status(logpoint),
55 | ]
56 |
57 |
58 | class ListLogpointsCommand:
59 | """This class implements the list_logpoints command.
60 |
61 | The register() method is called by the CLI startup code to install the
62 | list_logpoints command information, and the cmd() function will be invoked if
63 | the list_logpoints command was specified by the user.
64 | """
65 |
66 | def __init__(self):
67 | pass
68 |
69 | def register(self, args_subparsers, required_parsers, common_parsers):
70 | parent_parsers = [
71 | common_parsers.database_url, common_parsers.format,
72 | common_parsers.debuggee_id
73 | ]
74 | parent_parsers += required_parsers
75 | parser = args_subparsers.add_parser(
76 | 'list_logpoints', description=DESCRIPTION, parents=parent_parsers)
77 | parser.add_argument(
78 | '--include-inactive', help=INCLUDE_INACTIVE_HELP, action='store_true')
79 | parser.add_argument(
80 | '--all-users',
81 | help=ALL_USERS_HELP,
82 | default=True,
83 | action='store_true',
84 | dest='all_users')
85 | parser.add_argument(
86 | '--no-all-users',
87 | help=NO_ALL_USERS_HELP,
88 | action='store_false',
89 | dest='all_users')
90 | parser.set_defaults(func=self.cmd)
91 |
92 | def cmd(self, args, cli_services):
93 | user_output = cli_services.user_output
94 | debugger_rtdb_service = cli_services.get_snapshot_debugger_rtdb_service()
95 |
96 | debugger_rtdb_service.validate_debuggee_id(args.debuggee_id)
97 |
98 | user_email = None if args.all_users is True else cli_services.account
99 |
100 | logpoints = debugger_rtdb_service.get_logpoints(
101 | debuggee_id=args.debuggee_id,
102 | include_inactive=args.include_inactive,
103 | user_email=user_email)
104 |
105 | if args.format.is_a_json_value():
106 | user_output.json_format(logpoints, pretty=args.format.is_pretty_json())
107 | else:
108 | values = list(map(transform_to_logpoint_summary, logpoints))
109 | user_output.tabular(SUMMARY_HEADERS, values)
110 |
--------------------------------------------------------------------------------
/snapshot_dbg_cli/list_snapshots_command.py:
--------------------------------------------------------------------------------
1 | # Copyright 2022 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 | """This module contains the support for the list_snapshots command.
15 |
16 | The list_snapshots command is Used to display the debug snapshots for a debug
17 | target (debuggee).
18 | """
19 |
20 | from snapshot_dbg_cli import breakpoint_utils
21 | from snapshot_dbg_cli.status_message import StatusMessage
22 |
23 | DESCRIPTION = """
24 | Used to display the debug snapshots for a debug target (debuggee). By default
25 | all active snapshots are returned. To obtain completed snapshots specify the
26 | --include-inactive option.
27 | """
28 |
29 | INCLUDE_INACTIVE_HELP = 'Include all snapshots which have completed.'
30 |
31 | ALL_USERS_HELP = """
32 | If set, display snapshots from all users, rather than only the current user.
33 | """
34 |
35 | SUMMARY_HEADERS = ['Status', 'Location', 'Condition', 'CompletedTime', 'ID']
36 |
37 |
38 | def get_snapshot_state(snapshot):
39 | if not snapshot['isFinalState']:
40 | return 'ACTIVE'
41 |
42 | status_message = StatusMessage(snapshot)
43 |
44 | if not status_message.is_error:
45 | return 'COMPLETED'
46 |
47 | refers_to = status_message.refers_to
48 | if refers_to == 'BREAKPOINT_AGE':
49 | return 'EXPIRED'
50 |
51 | return 'FAILED'
52 |
53 |
54 | def transform_to_snapshot_summary(snapshot):
55 | # Match the fields from SUMMARY_HEADERS
56 | return [
57 | get_snapshot_state(snapshot),
58 | breakpoint_utils.transform_location_to_file_line(snapshot['location']),
59 | snapshot['condition'] if 'condition' in snapshot else '',
60 | snapshot['finalTime'] if 'finalTime' in snapshot else '', snapshot['id']
61 | ]
62 |
63 |
64 | class ListSnapshotsCommand:
65 | """This class implements the list_snapshots command.
66 |
67 | The register() method is called by the CLI startup code to install the
68 | list_snapshots command information, and the cmd() function will be invoked if
69 | the list_snapshots command was specified by the user.
70 | """
71 |
72 | def __init__(self):
73 | pass
74 |
75 | def register(self, args_subparsers, required_parsers, common_parsers):
76 | parent_parsers = [
77 | common_parsers.database_url, common_parsers.format,
78 | common_parsers.debuggee_id
79 | ]
80 | parent_parsers += required_parsers
81 | parser = args_subparsers.add_parser(
82 | 'list_snapshots', description=DESCRIPTION, parents=parent_parsers)
83 | parser.add_argument(
84 | '--include-inactive', help=INCLUDE_INACTIVE_HELP, action='store_true')
85 | parser.add_argument('--all-users', help=ALL_USERS_HELP, action='store_true')
86 | parser.set_defaults(func=self.cmd)
87 |
88 | def cmd(self, args, cli_services):
89 | user_output = cli_services.user_output
90 | debugger_rtdb_service = cli_services.get_snapshot_debugger_rtdb_service()
91 |
92 | debugger_rtdb_service.validate_debuggee_id(args.debuggee_id)
93 |
94 | user_email = None if args.all_users is True else cli_services.account
95 |
96 | snapshots = debugger_rtdb_service.get_snapshots(
97 | debuggee_id=args.debuggee_id,
98 | include_inactive=args.include_inactive,
99 | user_email=user_email)
100 |
101 | if args.format.is_a_json_value():
102 | user_output.json_format(snapshots, pretty=args.format.is_pretty_json())
103 | else:
104 | values = list(map(transform_to_snapshot_summary, snapshots))
105 | user_output.tabular(SUMMARY_HEADERS, values)
106 |
--------------------------------------------------------------------------------
/snapshot_dbg_cli/snapshot_debugger_schema.py:
--------------------------------------------------------------------------------
1 | # Copyright 2022 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 | """Provides a service to retrieve the paths entities in the database.
15 | """
16 |
17 | SNAPSHOT_DEBUGGER_ROOT_PATH = 'cdbg'
18 |
19 | DEBUGGEES_PATH = '{root_path}/debuggees'
20 | DEBUGGEES_PATH_FOR_ID = '{root_path}/debuggees/{debuggee_id}'
21 |
22 | SCHEMA_VERSION_PATH = '{root_path}/schema_version'
23 |
24 | BREAKPOINTS_PATH = '{root_path}/breakpoints/{debuggee_id}'
25 |
26 | BREAKPOINTS_ACTIVE_PATH = '{root_path}/breakpoints/{debuggee_id}/active'
27 | BREAKPOINTS_ACTIVE_PATH_FOR_ID = ('{root_path}/breakpoints/{debuggee_id}'
28 | '/active/{breakpoint_id}')
29 |
30 | BREAKPOINTS_FINAL_PATH = '{root_path}/breakpoints/{debuggee_id}/final'
31 | BREAKPOINTS_FINAL_PATH_FOR_ID = ('{root_path}/breakpoints/{debuggee_id}'
32 | '/final/{breakpoint_id}')
33 |
34 | BREAKPOINTS_SNAPSHOT_PATH = '{root_path}/breakpoints/{debuggee_id}/snapshot'
35 | BREAKPOINTS_SNAPSHOT_PATH_FOR_ID = ('{root_path}/breakpoints/{debuggee_id}'
36 | '/snapshot/{breakpoint_id}')
37 |
38 |
39 | class SnapshotDebuggerSchema:
40 | """This class provides methods for retrieving database paths.
41 |
42 | The purpose of this class is to keep all the information about the database
43 | paths in one location and provide utility methods for retrieving them.
44 | """
45 |
46 | def get_path_schema_version(self):
47 | return SCHEMA_VERSION_PATH.format(root_path=SNAPSHOT_DEBUGGER_ROOT_PATH)
48 |
49 | def get_path_debuggees(self):
50 | return DEBUGGEES_PATH.format(root_path=SNAPSHOT_DEBUGGER_ROOT_PATH)
51 |
52 | def get_path_debuggees_for_id(self, debuggee_id):
53 | return DEBUGGEES_PATH_FOR_ID.format(
54 | root_path=SNAPSHOT_DEBUGGER_ROOT_PATH, debuggee_id=debuggee_id)
55 |
56 | def get_path_breakpoints(self, debuggee_id):
57 | return self._get_path_breakpoints(BREAKPOINTS_PATH, debuggee_id)
58 |
59 | def get_path_breakpoints_active(self, debuggee_id):
60 | return self._get_path_breakpoints(BREAKPOINTS_ACTIVE_PATH, debuggee_id)
61 |
62 | def get_path_breakpoints_active_for_id(self, debuggee_id, breakpoint_id):
63 | return self._get_path_breakpoints_for_id(BREAKPOINTS_ACTIVE_PATH_FOR_ID,
64 | debuggee_id, breakpoint_id)
65 |
66 | def get_path_breakpoints_final(self, debuggee_id):
67 | return self._get_path_breakpoints(BREAKPOINTS_FINAL_PATH, debuggee_id)
68 |
69 | def get_path_breakpoints_final_for_id(self, debuggee_id, breakpoint_id):
70 | return self._get_path_breakpoints_for_id(BREAKPOINTS_FINAL_PATH_FOR_ID,
71 | debuggee_id, breakpoint_id)
72 |
73 | def get_path_breakpoints_snapshot(self, debuggee_id):
74 | return self._get_path_breakpoints(BREAKPOINTS_SNAPSHOT_PATH, debuggee_id)
75 |
76 | def get_path_breakpoints_snapshot_for_id(self, debuggee_id, breakpoint_id):
77 | return self._get_path_breakpoints_for_id(BREAKPOINTS_SNAPSHOT_PATH_FOR_ID,
78 | debuggee_id, breakpoint_id)
79 |
80 | def _get_path_breakpoints(self, base_breakpoints_format_string, debuggee_id):
81 | return base_breakpoints_format_string.format(
82 | root_path=SNAPSHOT_DEBUGGER_ROOT_PATH, debuggee_id=debuggee_id)
83 |
84 | def _get_path_breakpoints_for_id(self, base_breakpoints_format_string,
85 | debuggee_id, breakpoint_id):
86 | return base_breakpoints_format_string.format(
87 | root_path=SNAPSHOT_DEBUGGER_ROOT_PATH,
88 | debuggee_id=debuggee_id,
89 | breakpoint_id=breakpoint_id)
90 |
--------------------------------------------------------------------------------
/snapshot_dbg_cli/time_utils.py:
--------------------------------------------------------------------------------
1 | # Copyright 2022 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 | """This module provides utilities related to timestamps.
15 | """
16 |
17 | import datetime
18 | import time
19 |
20 |
21 | def get_current_time_unix_msec():
22 | return int(time.time() * 1000)
23 |
24 |
25 | def convert_unix_msec_to_rfc3339(unix_msec):
26 | """Converts a Unix timestamp represented in milliseconds since the epoch to an
27 |
28 | RFC3339 string representation.
29 |
30 | Args:
31 | unix_msec: The Unix timestamp, represented in milliseconds since the epoch.
32 |
33 | Returns:
34 | An RFC3339 encoded timestamp string in format: "%Y-%m-%dT%H:%M:%SZ".
35 | """
36 | try:
37 | seconds = unix_msec / 1000
38 | timestamp = seconds
39 | dt = datetime.datetime.fromtimestamp(timestamp, tz=datetime.timezone.utc)
40 | return dt.strftime('%Y-%m-%dT%H:%M:%S') + 'Z'
41 | except (OverflowError, OSError, TypeError, ValueError):
42 | # By using 0, we'll still get the expected formatted string, and the value
43 | # will be '1970-01-01...', which visually will be recognizable as beginning
44 | # of epoch and that the value was not known.
45 | return convert_unix_msec_to_rfc3339(0)
46 |
47 |
48 | def set_converted_timestamps(data, field_mappings):
49 | """Converts raw unix timestamp fields to human readable versions.
50 |
51 | If the destination field already exists it won't be overwritten.
52 |
53 | Example:
54 | data = {
55 | fooTimeUnixMsec: 1649962215000
56 | barTimeUnixMsec: 1649962216000
57 | }
58 |
59 | field_mappings = [
60 | ('fooTime', 'fooTimeUnixMsec'),
61 | ('barTIme, 'barTimeUnixMsec')
62 | ]
63 |
64 | set_converted_timestamps(data, field_mappings)
65 | data = {
66 | fooTimeUnixMsec: 1649962215000
67 | fooTime: '2022-04-14T18:50:15Z'
68 | barTimeUnixMsec = 1649962216000
69 | barTime: '2022-04-14T18:50:16Z'
70 | }
71 |
72 |
73 | Args:
74 | data: The container dict, expected to represent either a breakpoint a
75 | debuggee.
76 | field_mappings: [(str, str)] List of field mappings. For each entry the
77 | first member is the destination field to be set with a human readable
78 | timestamp. The second member is the source field and should map to a unix
79 | timestamp in data.
80 |
81 | Returns:
82 | The data dict that was passed in.
83 | """
84 |
85 | for m in field_mappings:
86 | if m[0] not in data and m[1] in data:
87 | data[m[0]] = convert_unix_msec_to_rfc3339(
88 | data[m[1]]) if data[m[1]] != 0 else 'not set'
89 |
90 | return data
91 |
--------------------------------------------------------------------------------
/snapshot_dbg_cli/user_input.py:
--------------------------------------------------------------------------------
1 | # Copyright 2022 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 | """This module provides a simple user input utility class.
15 |
16 | This class should be used by the CLI anytime it requires user input.
17 | """
18 |
19 |
20 | class UserInput:
21 | """This class handles prompting the user for input.
22 |
23 | This provides the CLI with the ability to prompt the user for input.
24 | """
25 |
26 | def prompt_user_to_continue(self):
27 | answer = input('Do you want to continue (Y/n)? ').lower()
28 |
29 | while answer and answer not in ['y', 'n']:
30 | answer = input("Please enter 'y' or 'n': ").lower()
31 |
32 | return answer != 'n'
33 |
--------------------------------------------------------------------------------
/snapshot_dbg_cli_tests/__init__.py:
--------------------------------------------------------------------------------
1 | # Copyright 2022 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
--------------------------------------------------------------------------------
/snapshot_dbg_cli_tests/test_cli_run.py:
--------------------------------------------------------------------------------
1 | # Copyright 2022 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 | """ Unit test file for the cli_run module.
15 | """
16 |
17 | import io
18 | import sys
19 | import unittest
20 |
21 | import snapshot_dbg_cli.cli_run
22 | import snapshot_dbg_cli.cli_services
23 | import snapshot_dbg_cli.exceptions
24 |
25 | from unittest.mock import MagicMock
26 | from unittest.mock import patch
27 |
28 |
29 | class CliRunTests(unittest.TestCase):
30 | """ Contains the unit tests for the cli_run module.
31 | """
32 |
33 | def setUp(self):
34 | self.cli_services_mock = MagicMock(
35 | spec=snapshot_dbg_cli.cli_services.CliServices)
36 |
37 | def test_expected_commands_are_registered(self):
38 | testcases = [
39 | ('delete_debuggees', 'Used to delete debuggees'),
40 | ('delete_logpoints', 'Used to delete logpoints'),
41 | ('delete_snapshots', 'Used to delete snapshots'),
42 | ('get_logpoint', 'Used to retrieve a debug logpoint'),
43 | ('get_snapshot', 'Used to retrieve a debug snapshot'),
44 | ('init', 'Initializes a GCP project with the required'),
45 | ('list_debuggees', 'Used to display a list of the debug targets'),
46 | ('list_logpoints', 'Used to display the debug logpoints'),
47 | ('list_snapshots', 'Used to display the debug snapshots'),
48 | ('set_logpoint', 'Adds a debug logpoint to a debug target'),
49 | ('set_snapshot', 'Creates a snapshot on a debug target'),
50 | ]
51 |
52 | for command, expected_description_substring in testcases:
53 | with self.subTest(command):
54 | argv = ['prog', command, '--help']
55 | with self.assertRaises(SystemExit), \
56 | patch.object(sys, 'argv', argv), \
57 | patch('sys.stdout', new_callable=io.StringIO) as out:
58 | snapshot_dbg_cli.cli_run.run(self.cli_services_mock)
59 |
60 | self.assertIn(expected_description_substring, out.getvalue())
61 |
62 | def test_command_missing_works_as_expected(self):
63 | argv = ['prog']
64 | with self.assertRaises(snapshot_dbg_cli.exceptions.SilentlyExitError), \
65 | patch.object(sys, 'argv', argv), \
66 | patch('sys.stderr', new_callable=io.StringIO) as err:
67 | snapshot_dbg_cli.cli_run.run(self.cli_services_mock)
68 |
69 | self.assertIn(
70 | 'Missing required argument, please specify --help for more '
71 | 'information', err.getvalue())
72 |
--------------------------------------------------------------------------------
/snapshot_dbg_cli_tests/test_firebase_types.py:
--------------------------------------------------------------------------------
1 | # Copyright 2023 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 | """ Unit test file for the firebase_types module.
15 | """
16 |
17 | import unittest
18 | from snapshot_dbg_cli.firebase_types import DatabaseInstance
19 |
20 |
21 | class DatabaseInstanceTests(unittest.TestCase):
22 | """ Contains the unit tests for the DatabaseInstance class.
23 | """
24 |
25 | def test_location_success(self):
26 | locations = ['us-central1', 'europe-west1']
27 | for location in locations:
28 | with self.subTest(location):
29 | db_instance = DatabaseInstance({
30 | 'name':
31 | f'projects/1111111111/locations/{location}/instances/foo-cdbg',
32 | 'project':
33 | 'projects/1111111111',
34 | 'databaseUrl':
35 | 'https://foo-cdbg.firebaseio.com',
36 | 'type':
37 | 'USER_DATABASE',
38 | 'state':
39 | 'ACTIVE'
40 | })
41 | self.assertEqual(location, db_instance.location)
42 |
43 | def test_location_could_not_be_found(self):
44 | invalid_names = [
45 | '',
46 | 'foo',
47 | # missing the /locations/
48 | 'projects/1111111111/us-central1/instances/foo-cdbg',
49 | 'projects/1111111111/locations',
50 | 'projects/1111111111/locations/'
51 | ]
52 |
53 | for full_db_name in invalid_names:
54 | with self.subTest(full_db_name):
55 | with self.assertRaises(ValueError) as ctxt:
56 | DatabaseInstance({
57 | 'name': full_db_name,
58 | 'project': 'projects/1111111111',
59 | 'databaseUrl': 'https://foo-cdbg.firebaseio.com',
60 | 'type': 'USER_DATABASE',
61 | 'state': 'ACTIVE'
62 | })
63 | self.assertEqual(
64 | f"Failed to extract location from project name '{full_db_name}'",
65 | str(ctxt.exception))
66 |
--------------------------------------------------------------------------------
/snapshot_dbg_cli_tests/test_init.py:
--------------------------------------------------------------------------------
1 | # Copyright 2022 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 | """ Unit test __init__.
15 | """
16 |
17 | import unittest
18 | import snapshot_dbg_cli
19 |
20 |
21 | class CliInitTests(unittest.TestCase):
22 | """ Contains the unit tests for __init__.py
23 | """
24 |
25 | def test_version_is_expected_value(self):
26 | # Yes, this will need to be updated for each new version.
27 | self.assertEqual('0.3.8', snapshot_dbg_cli.__version__)
28 |
--------------------------------------------------------------------------------
/snapshot_dbg_cli_tests/test_snapshot_debugger_schema.py:
--------------------------------------------------------------------------------
1 | # Copyright 2022 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 | """ Unit test file for the SnapshotDebuggerSchema class.
15 | """
16 |
17 | import unittest
18 | from snapshot_dbg_cli import snapshot_debugger_schema
19 |
20 |
21 | class SnapshotDebuggerSchemaTests(unittest.TestCase):
22 | """ Contains the unit tests for the SnapshotDebuggerSchema class.
23 | """
24 |
25 | def setUp(self):
26 | self.schema = snapshot_debugger_schema.SnapshotDebuggerSchema()
27 |
28 | def test_path_schema_version_is_correct(self):
29 | self.assertEqual(self.schema.get_path_schema_version(),
30 | 'cdbg/schema_version')
31 |
32 | def test_path_debuggees_is_correct(self):
33 | self.assertEqual(self.schema.get_path_debuggees(), 'cdbg/debuggees')
34 |
35 | def test_path_debuggees_for_id_is_correct(self):
36 | self.assertEqual(
37 | self.schema.get_path_debuggees_for_id(debuggee_id='123'),
38 | 'cdbg/debuggees/123')
39 |
40 | def test_get_path_breakpoints_is_correct(self):
41 | self.assertEqual(
42 | self.schema.get_path_breakpoints(debuggee_id='123'),
43 | 'cdbg/breakpoints/123')
44 |
45 | def test_path_breakpoints_active_is_correct(self):
46 | self.assertEqual(
47 | self.schema.get_path_breakpoints_active(debuggee_id='123'),
48 | 'cdbg/breakpoints/123/active')
49 |
50 | def test_path_breakpoints_active_for_id_is_correct(self):
51 | self.assertEqual(
52 | self.schema.get_path_breakpoints_active_for_id(
53 | debuggee_id='123', breakpoint_id='b-1653408119'),
54 | 'cdbg/breakpoints/123/active/b-1653408119')
55 |
56 | def test_path_breakpoints_final_is_correct(self):
57 | self.assertEqual(
58 | self.schema.get_path_breakpoints_final(debuggee_id='123'),
59 | 'cdbg/breakpoints/123/final')
60 |
61 | def test_path_breakpoints_final_for_id_is_correct(self):
62 | self.assertEqual(
63 | self.schema.get_path_breakpoints_final_for_id(
64 | debuggee_id='123', breakpoint_id='b-1653408119'),
65 | 'cdbg/breakpoints/123/final/b-1653408119')
66 |
67 | def test_path_breakpoints_snapshot_is_correct(self):
68 | self.assertEqual(
69 | self.schema.get_path_breakpoints_snapshot(debuggee_id='123'),
70 | 'cdbg/breakpoints/123/snapshot')
71 |
72 | def test_path_breakpoints_snapshot_for_id_is_correct(self):
73 | self.assertEqual(
74 | self.schema.get_path_breakpoints_snapshot_for_id(
75 | debuggee_id='123', breakpoint_id='b-1653408119'),
76 | 'cdbg/breakpoints/123/snapshot/b-1653408119')
77 |
78 |
79 | if __name__ == '__main__':
80 | unittest.main()
81 |
--------------------------------------------------------------------------------
/snapshot_dbg_cli_tests/test_user_input.py:
--------------------------------------------------------------------------------
1 | # Copyright 2022 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 | """ Unit test file for the user_input module.
15 | """
16 |
17 | import unittest
18 |
19 | from snapshot_dbg_cli.user_input import UserInput
20 | from unittest.mock import MagicMock
21 | from unittest.mock import call
22 | from unittest.mock import patch
23 |
24 |
25 | class SnapshotDebuggerSchemaTests(unittest.TestCase):
26 | """Contains the unit tests for the user_input module.
27 | """
28 |
29 | def test_prompt_user_to_continue(self):
30 | testcases = [
31 | ([''], True),
32 | (['o', ''], True),
33 | (['foo', ''], True),
34 | (['o', 'f', ''], True),
35 | (['y'], True),
36 | (['o', 'y'], True),
37 | (['Y'], True),
38 | (['foo', 'n'], False),
39 | (['n'], False),
40 | (['o', 'n'], False),
41 | (['N'], False),
42 | (['o', 'N'], False),
43 | ]
44 |
45 | for input_sequence, expected_response in testcases:
46 | with self.subTest(input_sequence):
47 | with patch('builtins.input',
48 | MagicMock(side_effect=input_sequence)) as input_mock:
49 | expected_calls = [call('Do you want to continue (Y/n)? ')]
50 | for _ in range(0, len(input_sequence) - 1):
51 | expected_calls.append(call("Please enter 'y' or 'n': "))
52 |
53 | self.assertEqual(expected_response,
54 | UserInput().prompt_user_to_continue())
55 | self.assertEqual(expected_calls, input_mock.mock_calls)
56 |
--------------------------------------------------------------------------------
/snapshot_dbg_extension/.gitignore:
--------------------------------------------------------------------------------
1 | .vscode
2 | .eslintrc.json
3 | node_modules/
4 | out/
5 | *.vsix
6 |
--------------------------------------------------------------------------------
/snapshot_dbg_extension/.vscodeignore:
--------------------------------------------------------------------------------
1 | .vscode/**
2 | .vscode-test/**
3 | out/test/**
4 | src/**
5 | .gitignore
6 | .yarnrc
7 | vsc-extension-quickstart.md
8 | **/tsconfig.json
9 | **/.eslintrc.json
10 | **/*.map
11 | **/*.ts
12 | node_modules/**
13 |
14 |
--------------------------------------------------------------------------------
/snapshot_dbg_extension/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # What's New?
2 |
3 | ## 0.1.0
4 |
5 | - Initial release
6 |
--------------------------------------------------------------------------------
/snapshot_dbg_extension/media/expr.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/snapshot_dbg_extension/media/icon-512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GoogleCloudPlatform/snapshot-debugger/50a5b3edcb20c4744d10116022277b2db50338dc/snapshot_dbg_extension/media/icon-512.png
--------------------------------------------------------------------------------
/snapshot_dbg_extension/src/debugUtil.ts:
--------------------------------------------------------------------------------
1 |
2 | let debugLogEnabled = true;
3 | export function setDebugLogEnabled(enabled: boolean): void {
4 | debugLogEnabled = enabled;
5 | }
6 |
7 | export function debugLog(message?: any, ...optionalParams: any[]): void {
8 | if (debugLogEnabled) {
9 | console.log(message, ...optionalParams);
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/snapshot_dbg_extension/src/debuggeePicker.ts:
--------------------------------------------------------------------------------
1 | import { Database } from 'firebase-admin/database';
2 | import * as vscode from 'vscode';
3 | import { debugLog } from './debugUtil';
4 |
5 | const NO_DEBUGGEES = 'No debuggees found';
6 | const NO_DEBUGGEES_DETAIL = 'Please check your configuration';
7 |
8 | class DebuggeeItem implements vscode.QuickPickItem {
9 | label: string;
10 | detail: string;
11 | kind?: vscode.QuickPickItemKind | undefined;
12 |
13 | constructor(public debuggeeId: string, description: string, public timestamp: Date) {
14 | if (this.debuggeeId === NO_DEBUGGEES) {
15 | this.label = NO_DEBUGGEES;
16 | this.detail = NO_DEBUGGEES_DETAIL;
17 | } else {
18 | this.label = description;
19 | this.detail = `${debuggeeId} | ${duractionToLastActiveString(new Date().getTime() - timestamp.getTime())}`;
20 | }
21 | }
22 | }
23 |
24 | function duractionToLastActiveString(d: number) {
25 | const seconds = d / 1000;
26 | const minutes = seconds / 60;
27 |
28 | if (minutes < 60) {
29 | return 'Recently active';
30 | }
31 |
32 | return `Last seen ${durationToFriendlyString(d)} ago`;
33 | }
34 |
35 | function durationToFriendlyString(d: number) {
36 | const seconds = d / 1000;
37 | const minutes = seconds / 60;
38 | const hours = minutes / 60;
39 | const days = hours / 24;
40 |
41 | if (seconds == 1) {
42 | return 'a second';
43 | } else if (seconds < 45) {
44 | return `${Math.round(seconds)} seconds`;
45 | } else if (seconds < 90) {
46 | return 'a minute';
47 | } else if (minutes < 45) {
48 | return `${Math.round(minutes)} minutes`;
49 | } else if (minutes < 90) {
50 | return 'an hour';
51 | } else if (hours < 24) {
52 | return `${Math.round(hours)} hours`;
53 | } else if (hours < 42) {
54 | return 'a day';
55 | } else if (days < 30) {
56 | return `${Math.round(days)} days`;
57 | } else if (days < 45) {
58 | return 'a month';
59 | } else {
60 | return `${Math.round(days / 30)} months`;
61 | }
62 | }
63 |
64 | async function fetchDebuggees(db: Database): Promise {
65 | const debuggees: DebuggeeItem[] = [];
66 |
67 | const snapshot = await db.ref('/cdbg/debuggees').get();
68 | const savedDebuggees = snapshot.val();
69 | if (savedDebuggees) {
70 | for (const debuggeeId in savedDebuggees) {
71 | const timestamp = new Date(savedDebuggees[debuggeeId].lastUpdateTimeUnixMsec);
72 | const labels = savedDebuggees[debuggeeId].labels;
73 | debuggees.push(
74 | new DebuggeeItem(
75 | debuggeeId,
76 | `${labels.module || 'default'} - ${labels.version}`,
77 | timestamp));
78 | }
79 | debuggees.sort((a: DebuggeeItem, b: DebuggeeItem) => b.timestamp.getTime() - a.timestamp.getTime());
80 | } else {
81 | const noDebuggees = new DebuggeeItem(NO_DEBUGGEES, "", new Date());
82 | noDebuggees.debuggeeId = "";
83 | debuggees.push();
84 | }
85 | return debuggees;
86 | }
87 |
88 | export async function pickDebuggeeId(db: Database): Promise {
89 | const selection = await vscode.window.showQuickPick(fetchDebuggees(db), {'title': 'Select Debuggee'});
90 | if (selection) {
91 | debugLog(`Selected Debuggee: ${selection.debuggeeId}`);
92 | return selection.debuggeeId;
93 | } else {
94 | return undefined;
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/snapshot_dbg_extension/src/expressionsPrompter.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from 'vscode';
2 |
3 | export async function promptUserForExpressions(): Promise {
4 | let expressions: string[] = [];
5 |
6 | while (true) {
7 | const title = (expressions.length == 0) ?
8 | "Add an Expression to the Breakpoint" : "Add Another Expression to the Breakpoint";
9 |
10 | const prompt = (expressions.length == 0) ?
11 | "Press 'Escape' or enter an empty line if you do not wish to add an expression." :
12 | "Press 'Escape' or enter an empty line if you do not wish to add any more expressions.";
13 |
14 | let expression = await vscode.window.showInputBox({title, prompt});
15 |
16 | expression = expression?.trim();
17 |
18 | if (!expression) {
19 | break;
20 | }
21 |
22 | expressions.push(expression);
23 | };
24 |
25 | return expressions.length ? expressions : undefined;
26 | }
27 |
--------------------------------------------------------------------------------
/snapshot_dbg_extension/src/extension.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from 'vscode';
2 | import { WorkspaceFolder, DebugConfiguration, DebugSession, ProviderResult, CancellationToken } from 'vscode';
3 | import { IsActiveWhenClauseContext } from './whenClauseContextUtil';
4 |
5 | import { CustomRequest, SnapshotDebuggerSession } from './adapter';
6 | import { UserPreferences } from './userPreferences';
7 | import { debugLog, setDebugLogEnabled } from './debugUtil';
8 |
9 | // This method is called when the extension is activated.
10 | // The extension is activated the very first time the command is executed
11 | export function activate(context: vscode.ExtensionContext) {
12 | IsActiveWhenClauseContext.create();
13 |
14 | const userPreferences: UserPreferences = {"isExpressionsPromptEnabled": true};
15 |
16 | context.subscriptions.push(vscode.commands.registerCommand('extension.snapshotdbg.viewHistoricalSnapshot', async args => {
17 | const activeDebugSession: DebugSession | undefined = vscode.debug.activeDebugSession;
18 | if (activeDebugSession) {
19 | activeDebugSession.customRequest(CustomRequest.RUN_HISTORICAL_SNAPSHOT_PICKER);
20 | } else {
21 | debugLog("Unexpected no active SnapshotDebugger session.")
22 | }
23 | }));
24 |
25 | context.subscriptions.push(vscode.commands.registerCommand('extension.snapshotdbg.toggleExpressions', async args => {
26 | const action = userPreferences.isExpressionsPromptEnabled ? "Disabled" : "Enabled";
27 | const message = `${action} expresssions prompt when creating a breakpoint.`;
28 | const reverseAction = userPreferences.isExpressionsPromptEnabled ? "re-enable" : "disable";
29 | const reverseMessage = `Click again to ${reverseAction} it.`;
30 |
31 | userPreferences.isExpressionsPromptEnabled = !userPreferences.isExpressionsPromptEnabled;
32 | await vscode.window.showInformationMessage(message, {"detail": reverseMessage, "modal": true});
33 | }));
34 |
35 | const provider = new SnapshotDebuggerConfigurationProvider();
36 | context.subscriptions.push(vscode.debug.registerDebugConfigurationProvider('snapshotdbg', provider));
37 |
38 | const factory = new DebugAdapterFactory(userPreferences);
39 | context.subscriptions.push(vscode.debug.registerDebugAdapterDescriptorFactory('snapshotdbg', factory));
40 | }
41 |
42 | // This method is called when the extension is deactivated
43 | export function deactivate() {
44 | }
45 |
46 |
47 | class SnapshotDebuggerConfigurationProvider implements vscode.DebugConfigurationProvider {
48 | resolveDebugConfiguration(folder: WorkspaceFolder | undefined, config: DebugConfiguration, token?: CancellationToken): ProviderResult {
49 | // Just accept all configuration.
50 | return config;
51 | }
52 | }
53 |
54 | class DebugAdapterFactory implements vscode.DebugAdapterDescriptorFactory {
55 | private userPreferences: UserPreferences;
56 |
57 | constructor(userPreferences: UserPreferences) {
58 | this.userPreferences = userPreferences;
59 | }
60 |
61 | createDebugAdapterDescriptor(session: vscode.DebugSession, executable: vscode.DebugAdapterExecutable | undefined): vscode.ProviderResult {
62 | return new vscode.DebugAdapterInlineImplementation(new SnapshotDebuggerSession(this.userPreferences));
63 | }
64 |
65 | }
66 |
--------------------------------------------------------------------------------
/snapshot_dbg_extension/src/gcloudCredential.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from 'vscode';
2 |
3 | import { exec } from 'child_process';
4 | import { Credential, GoogleOAuthAccessToken } from 'firebase-admin/app';
5 | import { debugLog } from './debugUtil';
6 |
7 | const NO_TOKEN_MESSAGE = 'Could not fetch access token from gcloud. Are you logged in?';
8 |
9 | export class GcloudCredential implements Credential {
10 | public initialized = false;
11 | private showingModal = false;
12 |
13 | async getAccessToken(): Promise {
14 | return new Promise((resolve, reject) => {
15 | exec('gcloud auth print-access-token', (err, stdout, stderr) => {
16 | if (stderr) {
17 | debugLog(stderr);
18 | }
19 | if (err) {
20 | reject(err);
21 | }
22 | if (!stdout.trim()) {
23 | // Only show the dialog if we've previously been connected and are not already showing the error.
24 | if (this.initialized && !this.showingModal) {
25 | this.showingModal = true;
26 | vscode.window.showErrorMessage(NO_TOKEN_MESSAGE, {"modal": true}).then(() => this.showingModal = false);
27 | }
28 | reject(new Error(NO_TOKEN_MESSAGE));
29 | }
30 | resolve({
31 | access_token: stdout.trim(),
32 | expires_in: 3600
33 | });
34 | });
35 | });
36 | }
37 |
38 | async getProjectId(): Promise {
39 | return new Promise((resolve, reject) => {
40 | exec('gcloud config get-value project', (err, stdout, stderr) => {
41 | if (stderr) {
42 | debugLog(stderr);
43 | }
44 | if (err) {
45 | reject(err);
46 | }
47 | if (!stdout.trim()) {
48 | reject(new Error('Unable to fetch project id'));
49 | }
50 | resolve(stdout.trim());
51 | });
52 | });
53 | }
54 |
55 | async getAccount(): Promise {
56 | return new Promise((resolve, reject) => {
57 | exec('gcloud config get-value account', (err, stdout, stderr) => {
58 | if (stderr) {
59 | debugLog(stderr);
60 | }
61 | if (err) {
62 | reject(err);
63 | }
64 | if (!stdout.trim()) {
65 | reject(new Error('Unable to fetch account'));
66 | }
67 | resolve(stdout.trim());
68 | });
69 | });
70 | }
71 | }
--------------------------------------------------------------------------------
/snapshot_dbg_extension/src/ideBreakpoints.ts:
--------------------------------------------------------------------------------
1 | import { DebugProtocol } from '@vscode/debugprotocol';
2 | import { sourceBreakpointToString, stringToSourceBreakpoint } from './util';
3 |
4 | export interface IdeBreakpointsDiff {
5 | added: DebugProtocol.SourceBreakpoint[];
6 | deleted: DebugProtocol.SourceBreakpoint[];
7 | }
8 |
9 | /**
10 | * Class for the adaptor to track the current breakpoints the IDE has.
11 | */
12 | export class IdeBreakpoints {
13 | private breakpoints: Map = new Map();
14 |
15 | public applyNewIdeSnapshot(path: string, newSnapshot: DebugProtocol.SourceBreakpoint[]): IdeBreakpointsDiff {
16 | const prevBPs: DebugProtocol.SourceBreakpoint[] = this.breakpoints.get(path) ?? [];
17 | const currBPs: DebugProtocol.SourceBreakpoint[] = newSnapshot;
18 | const prevBPSet = new Set(prevBPs.map(bp => sourceBreakpointToString(bp)));
19 | const currBPSet = new Set(currBPs.map(bp => sourceBreakpointToString(bp)));
20 |
21 | const newBPs = [...currBPSet].filter(bp => !prevBPSet.has(bp));
22 | const delBPs = [...prevBPSet].filter(bp => !currBPSet.has(bp));
23 |
24 | const bpDiff = {
25 | added: newBPs.map(bp => stringToSourceBreakpoint(bp)),
26 | deleted: delBPs.map(bp => stringToSourceBreakpoint(bp)),
27 | }
28 |
29 | this.breakpoints.set(path, newSnapshot);
30 | return bpDiff;
31 | }
32 |
33 | public add(path: string, breakpoint: DebugProtocol.SourceBreakpoint): void {
34 | const bps: DebugProtocol.SourceBreakpoint[] = this.breakpoints.get(path) ?? [];
35 | bps.push(breakpoint);
36 | this.breakpoints.set(path, bps);
37 | }
38 |
39 | public remove(path: string, breakpoint: DebugProtocol.SourceBreakpoint): void {
40 | const bps = this.breakpoints.get(path) ?? [];
41 | const bpString = sourceBreakpointToString(breakpoint);
42 | this.breakpoints.set(path, bps.filter(b => bpString !== sourceBreakpointToString(b)));
43 | }
44 |
45 | public updateLine(path: string, originalLine: number, newLine: number) : void {
46 | const bps: DebugProtocol.SourceBreakpoint[] = this.breakpoints.get(path) ?? [];
47 | const bp = bps?.find(b => b.line === originalLine);
48 | if (bp) { bp.line = newLine; }
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/snapshot_dbg_extension/src/lineMappings.ts:
--------------------------------------------------------------------------------
1 | import { DebugProtocol } from '@vscode/debugprotocol';
2 | import { sourceBreakpointToString, stringToSourceBreakpoint } from './util';
3 |
4 | export interface IdeBreakpointsDiff {
5 | added: DebugProtocol.SourceBreakpoint[];
6 | deleted: DebugProtocol.SourceBreakpoint[];
7 | }
8 |
9 | /**
10 | * Class to track line number that got remapped by the agent.
11 | *
12 | * When a BP gets set on a line the agent doesn't find code on, it may find the
13 | * closest line with code, set the BP and report back the updated line number.
14 | * This class tracks thes remappings per path.
15 | */
16 | export class LineMappings {
17 | // Key: File path.
18 | // Value: Map of requested line to actual line agent reported back with code.
19 | private paths: Map> = new Map();
20 |
21 | public add(path: string, requestedLine: number, actualLine: number): void {
22 | let lineMappings = this.paths.get(path);
23 | if (lineMappings === undefined) {
24 | lineMappings = new Map();
25 | this.paths.set(path, lineMappings);
26 | }
27 |
28 | lineMappings.set(requestedLine, actualLine);
29 | }
30 |
31 | public get(path: string, line: number): number|undefined {
32 | return this.paths.get(path)?.get(line);
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/snapshot_dbg_extension/src/logLevelPicker.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from 'vscode';
2 |
3 | import { sleep } from "./util";
4 |
5 | let gPickPending = false;
6 |
7 | async function pickLogLevel(title: string): Promise {
8 | /**
9 | * The lable field supports icons via the $()-syntax.
10 | * https://code.visualstudio.com/api/references/vscode-api#ThemeIcon
11 | *
12 | * A list of available icons can be found here:
13 | * https://code.visualstudio.com/api/references/icons-in-labels
14 | *
15 | * Here we're able to leverage the fact that icons with the names 'info',
16 | * 'warning' and 'error' exist and we can convert our log level to lowercase
17 | * to obtain a relevant icon.
18 | */
19 | const items: vscode.QuickPickItem[] =
20 | ['INFO', 'WARNING', 'ERROR'].map(level => (
21 | {'label': `${level} $(${level.toLowerCase()})`}
22 | ));
23 |
24 | // If we have one call to showQuickPick outstanding, any other calls to it
25 | // during this time will immediately return with a value of undefined.
26 | // During initialization time it's possible we'll be prompting the user
27 | // to select a log level for logpoints found in the IDE in multiple
28 | // different files. This can lead to this situation occurring, so here
29 | // we serialize the calls.
30 | while (gPickPending) {
31 | await (sleep(250));
32 | }
33 |
34 | gPickPending = true;
35 | const selection = await vscode.window.showQuickPick(items, {title});
36 | gPickPending = false;
37 |
38 | return selection?.label.split(' ')[0].trim();
39 | }
40 |
41 | export async function pickLogLevelNewlyCreated(): Promise {
42 | return pickLogLevel('Select Log Level');
43 | }
44 |
45 | export async function pickLogLevelSyncedFromIDE(path: string, line: number): Promise {
46 | return pickLogLevel(`Select Log Level For Logpoint: ${path}:${line}`);
47 | }
48 |
--------------------------------------------------------------------------------
/snapshot_dbg_extension/src/snapshotPicker.ts:
--------------------------------------------------------------------------------
1 | import { Database, DataSnapshot } from 'firebase-admin/database';
2 | import { CdbgBreakpoint } from './breakpoint';
3 | import * as vscode from 'vscode';
4 | import { debugLog } from './debugUtil';
5 |
6 | class SnapshotItem implements vscode.QuickPickItem {
7 | label: string;
8 | description: string | undefined;
9 | kind?: vscode.QuickPickItemKind | undefined;
10 |
11 | constructor(public cdbgBreakpoint: CdbgBreakpoint | undefined) {
12 | if (cdbgBreakpoint) {
13 | this.label = cdbgBreakpoint.id;
14 | this.description = ` ${cdbgBreakpoint.shortPath}:${cdbgBreakpoint.line} ${cdbgBreakpoint.finalTime}`
15 | } else {
16 | this.label = "No snapshots found";
17 | }
18 | }
19 | }
20 |
21 | async function fetchSnapshots(db: Database, debuggeeId: string): Promise {
22 | const snapshotItems: SnapshotItem[] = [];
23 |
24 | const snapshots: DataSnapshot= await db.ref(`cdbg/breakpoints/${debuggeeId}/final`).get();
25 | snapshots.forEach((breakpoint): void => {
26 | const cdbgBreakpoint: CdbgBreakpoint = CdbgBreakpoint.fromSnapshot(breakpoint);
27 | if (cdbgBreakpoint.isSnapshot()) {
28 | snapshotItems.push(new SnapshotItem(cdbgBreakpoint));
29 | }
30 | });
31 | return snapshotItems;
32 | }
33 |
34 | export async function pickSnapshot(db: Database, debuggeeId: string): Promise {
35 | const selection = await vscode.window.showQuickPick(fetchSnapshots(db, debuggeeId), {'title': 'Select Previously Captured Snapshot'});
36 | if (selection?.cdbgBreakpoint) {
37 | debugLog(`Selected Snapshot: ${selection.cdbgBreakpoint.id}`);
38 | return selection.cdbgBreakpoint;
39 | } else {
40 | return undefined;
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/snapshot_dbg_extension/src/statusMessage.ts:
--------------------------------------------------------------------------------
1 | import {ServerBreakpoint, Variable} from './breakpoint'
2 |
3 | /**
4 | * Convenience class that represents a StatusMessage. It provides convenience
5 | * functions for interpreting and using the message.
6 | */
7 | export class StatusMessage {
8 | /**
9 | * The parsed message or undefined if the status is not present or the message
10 | * could not be parsed.
11 | */
12 | message: string|undefined = undefined;
13 |
14 | constructor(private readonly parent: ServerBreakpoint|Variable) {
15 | this.message = this.parseMessage();
16 | }
17 |
18 | private parseMessage(): string|undefined {
19 | if (!this.parent.status?.description?.format) {
20 | return undefined;
21 | }
22 |
23 | // Get the formatting string such as "Failed to load '$0' which
24 | // helps debug $1"
25 | const formatMessage = this.parent.status.description;
26 | let formatString = formatMessage.format!;
27 |
28 | // Get the number of parameters to replace '$' prefixed vars.
29 | let totalParameters = 0;
30 | if (formatMessage.parameters) {
31 | totalParameters = formatMessage.parameters.length;
32 | }
33 | let dollarIndex;
34 | let outputString = '';
35 |
36 | // While we have remaining '$' place holders keep traversing the format
37 | // string.
38 | while ((dollarIndex = formatString.indexOf('$')) > -1) {
39 | // Add the first portion of the format string to the output.
40 | outputString += formatString.substr(0, dollarIndex);
41 | if (formatString.length > dollarIndex + 1) {
42 | // Get the parameters index value in the parameters list or a '$' if
43 | // the value is escaped.
44 | const nextChar = formatString.substr(dollarIndex + 1, 1);
45 |
46 | // Assumption is there are not more than 10 parameters.
47 | const intVal = Number(nextChar);
48 | if (nextChar === '$') {
49 | // The next character is a '$' this is an escaped character, be sure
50 | // to maintain the next '$'.
51 | outputString += '$';
52 | formatString = formatString.substr(dollarIndex + 2);
53 | } else if (
54 | !isNaN(intVal) && intVal < totalParameters &&
55 | formatMessage.parameters) {
56 | // Get the proper parameter for the index.
57 | outputString += formatMessage.parameters[intVal];
58 | formatString = formatString.substr(dollarIndex + 2);
59 | } else {
60 | // FormatMessage with too many arguments, unclear what to do from
61 | // the spec, will just keep it verbatim.
62 | outputString += '$';
63 | formatString = formatString.substr(dollarIndex + 1);
64 | }
65 |
66 | } else {
67 | // A '$' was the last value add it back.
68 | outputString += '$';
69 | formatString = '';
70 | }
71 | }
72 |
73 | // Add the remainder of the format string to the returned value.
74 | outputString += formatString;
75 | return outputString;
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/snapshot_dbg_extension/src/test/runTest.ts:
--------------------------------------------------------------------------------
1 | import * as path from 'path';
2 |
3 | import { runTests } from '@vscode/test-electron';
4 |
5 | async function main() {
6 | try {
7 | // The folder containing the Extension Manifest package.json
8 | // Passed to `--extensionDevelopmentPath`
9 | const extensionDevelopmentPath = path.resolve(__dirname, '../../');
10 |
11 | // The path to test runner
12 | // Passed to --extensionTestsPath
13 | const extensionTestsPath = path.resolve(__dirname, './suite/index');
14 |
15 | // Download VS Code, unzip it and run the integration test
16 | await runTests({ extensionDevelopmentPath, extensionTestsPath });
17 | } catch (err) {
18 | console.error('Failed to run tests', err);
19 | process.exit(1);
20 | }
21 | }
22 |
23 | main();
24 |
--------------------------------------------------------------------------------
/snapshot_dbg_extension/src/test/suite/extension.test.ts:
--------------------------------------------------------------------------------
1 | import * as assert from 'assert';
2 |
3 | // You can import and use all API from the 'vscode' module
4 | // as well as import your extension to test it
5 | import * as vscode from 'vscode';
6 | // import * as myExtension from '../../extension';
7 |
8 | suite('Extension Test Suite', () => {
9 | vscode.window.showInformationMessage('Start all tests.');
10 |
11 | test('Sample test', () => {
12 | assert.strictEqual(-1, [1, 2, 3].indexOf(5));
13 | assert.strictEqual(-1, [1, 2, 3].indexOf(0));
14 | });
15 | });
16 |
--------------------------------------------------------------------------------
/snapshot_dbg_extension/src/test/suite/index.ts:
--------------------------------------------------------------------------------
1 | import * as path from 'path';
2 | import * as Mocha from 'mocha';
3 | import * as glob from 'glob';
4 |
5 | export function run(): Promise {
6 | // Create the mocha test
7 | const mocha = new Mocha({
8 | ui: 'tdd',
9 | color: true
10 | });
11 |
12 | const testsRoot = path.resolve(__dirname, '..');
13 |
14 | return new Promise((c, e) => {
15 | glob('**/**.test.js', { cwd: testsRoot }, (err, files) => {
16 | if (err) {
17 | return e(err);
18 | }
19 |
20 | // Add files to the test suite
21 | files.forEach(f => mocha.addFile(path.resolve(testsRoot, f)));
22 |
23 | try {
24 | // Run the mocha test
25 | mocha.run(failures => {
26 | if (failures > 0) {
27 | e(new Error(`${failures} tests failed.`));
28 | } else {
29 | c();
30 | }
31 | });
32 | } catch (err) {
33 | console.error(err);
34 | e(err);
35 | }
36 | });
37 | });
38 | }
39 |
--------------------------------------------------------------------------------
/snapshot_dbg_extension/src/userPreferences.ts:
--------------------------------------------------------------------------------
1 | export interface UserPreferences {
2 | isExpressionsPromptEnabled: boolean;
3 | };
4 |
--------------------------------------------------------------------------------
/snapshot_dbg_extension/src/util.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from 'vscode';
2 | import { DebugProtocol } from '@vscode/debugprotocol';
3 |
4 | /**
5 | * Returns a promise that will resolve in ms milliseconds.
6 | * @param ms
7 | * @returns
8 | */
9 | export function sleep(ms: number) {
10 | return new Promise((resolve) => { setTimeout(resolve, ms) });
11 | }
12 |
13 | /**
14 | * Waits ms milliseconds for the promise to resolve, or rejects with a timeout.
15 | * @param ms
16 | * @param promise
17 | * @returns Promise wrapped in a timeout.
18 | */
19 | export function withTimeout(ms: number, promise: Promise) {
20 | const timeout = new Promise((_, reject) =>
21 | setTimeout(() => reject(`Timed out after ${ms} ms.`), ms)
22 | );
23 | return Promise.race([promise, timeout]);
24 | };
25 |
26 | // TODO: Plumb this through from outside this file if possible.
27 | // FIXME: Use a path library instead of this nonsense.
28 | function pwd() {
29 | if (vscode.workspace.workspaceFolders !== undefined) {
30 | return vscode.workspace.workspaceFolders[0].uri.fsPath + '/';
31 | } else {
32 | return '';
33 | }
34 | }
35 |
36 | export function stripPwd(path: string): string {
37 | const prefix = pwd();
38 | if (path.startsWith(prefix)) {
39 | return path.substring(prefix.length);
40 | }
41 | return path;
42 | }
43 |
44 | export function addPwd(path: string): string {
45 | const prefix = pwd();
46 | return `${prefix}${path}`;
47 | }
48 |
49 | export function sourceBreakpointToString(bp: DebugProtocol.SourceBreakpoint): string {
50 | return `${bp.line}\0${bp.condition}\0${bp.logMessage}`;
51 | }
52 |
53 | export function stringToSourceBreakpoint(bp: string): DebugProtocol.SourceBreakpoint {
54 | const parts = bp.split('\0');
55 | return {
56 | line: parseInt(parts[0]),
57 | ...(parts[1] !== 'undefined' && {condition: parts[1]}),
58 | ...(parts[2] !== 'undefined' && {logMessage: parts[2]})
59 | };
60 | }
61 |
62 |
--------------------------------------------------------------------------------
/snapshot_dbg_extension/src/whenClauseContextUtil.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from 'vscode';
2 |
3 | /**
4 | * We use a custom when clause context for tracking when the Snapshot Debugger
5 | * extension is actually active. The adapter will enable it when it receives an
6 | * attach request, and then disable it when it receives a disonnect request.
7 | * This is then available for use in when clauses in the package.json file. One
8 | * use is for controlling when the icons are displayed in the debug toolbar.
9 | * Without this the Snapshot Debugger icons would be visible in the debug
10 | * toolbar for any debugger in use. With this we can ensure it is only visible
11 | * when the Snapshot Debugger extension is actively being used.
12 | *
13 | * https://code.visualstudio.com/api/references/when-clause-contexts#add-a-custom-when-clause-context
14 | */
15 | export class IsActiveWhenClauseContext {
16 | public static CONTEXT_NAME: string = 'extension.snapshotdbg.isActive';
17 |
18 | private static setContext(value: boolean): void {
19 | vscode.commands.executeCommand('setContext', IsActiveWhenClauseContext.CONTEXT_NAME, value);
20 | }
21 |
22 | public static create(): void {
23 | IsActiveWhenClauseContext.setContext(false);
24 | }
25 |
26 | public static enable(): void {
27 | IsActiveWhenClauseContext.setContext(true);
28 | }
29 |
30 | public static disable(): void {
31 | IsActiveWhenClauseContext.setContext(false);
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/snapshot_dbg_extension/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "module": "commonjs",
4 | "target": "ES2020",
5 | "outDir": "out",
6 | "lib": [
7 | "ES2020"
8 | ],
9 | "sourceMap": true,
10 | "rootDir": "src",
11 | "strict": true /* enable all strict type-checking options */
12 | /* Additional Checks */
13 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
14 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
15 | // "noUnusedParameters": true, /* Report errors on unused parameters. */
16 | }
17 | }
18 |
--------------------------------------------------------------------------------