diff --git a/container-analysis/snippets/.eslintrc.yml b/container-analysis/snippets/.eslintrc.yml new file mode 100644 index 0000000000..282535f55f --- /dev/null +++ b/container-analysis/snippets/.eslintrc.yml @@ -0,0 +1,3 @@ +--- +rules: + no-console: off diff --git a/container-analysis/snippets/createNote.js b/container-analysis/snippets/createNote.js new file mode 100644 index 0000000000..6abf822a2a --- /dev/null +++ b/container-analysis/snippets/createNote.js @@ -0,0 +1,68 @@ +// Copyright 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +'use strict'; + +// sample-metadata: +// title: Create Note +// description: Creates a Note with specified ID +// usage: node createNote.js "project-id" "note-id" +async function main( + projectId = 'your-project-id', // Your GCP Project ID + noteId = 'my-note-id' // Id of the note +) { + // [START containeranalysis_create_note] + /** + * TODO(developer): Uncomment these variables before running the sample + */ + // const projectId = 'your-project-id', // Your GCP Project ID + // const noteId = 'my-note-id' // Id of the note + + // Import the library and create a client + const {ContainerAnalysisClient} = require('@google-cloud/containeranalysis'); + const client = new ContainerAnalysisClient(); + + // Construct request + // Associate the Note with a metadata type + // https://cloud.google.com/container-registry/docs/container-analysis#supported_metadata_types + // Here, we use the type "vulnerabiltity" + const formattedParent = client.getGrafeasClient().projectPath(projectId); + + // Creates and returns a new Note + const [note] = await client.getGrafeasClient().createNote({ + parent: formattedParent, + noteId: noteId, + note: { + vulnerability: { + details: [ + { + affectedCpeUri: 'foo.uri', + affectedPackage: 'foo', + affectedVersionStart: { + kind: 'MINIMUM', + }, + affectedVersionEnd: { + kind: 'MAXIMUM', + }, + }, + ], + }, + }, + }); + + console.log(`Note ${note.name} created.`); + // [END containeranalysis_create_note] +} + +main(...process.argv.slice(2)); diff --git a/container-analysis/snippets/createOccurrence.js b/container-analysis/snippets/createOccurrence.js new file mode 100644 index 0000000000..b2af600eeb --- /dev/null +++ b/container-analysis/snippets/createOccurrence.js @@ -0,0 +1,81 @@ +// Copyright 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +'use strict'; + +// sample-metadata: +// title: Create Occurrence +// description: Creates an Occurrence of a Note and attaches it as a metadata to an image +// usage: node createOccurrence.js "note-project-id" "note-id" "occurrence-project-id" "image url" +async function main( + noteProjectId = 'your-project-id', // Your GCP Project Id + noteId = 'my-note-id', // Id of the note + occurrenceProjectId = 'your-project-id', // GCP Project Id of Occurrence + // If you are using Google Container Registry + imageUrl = 'https://gcr.io/my-project/my-repo/my-image:123' // Image to attach metadata to + // If you are using Google Artifact Registry + // imageUrl = 'https://LOCATION-docker.pkg.dev/my-project/my-repo/my-image:123' // Image to attach metadata to +) { + // [START containeranalysis_create_occurrence] + /** + * TODO(developer): Uncomment these variables before running the sample + */ + // const noteProjectId = 'your-project-id', // Your GCP Project Id + // const noteId = 'my-note-id', // Id of the note + // const occurrenceProjectId = 'your-project-id', // GCP Project Id of Occurrence + // If you are using Google Container Registry + // const imageUrl = 'https://gcr.io/my-project/my-repo/my-image:123' // Image to attach metadata to + // If you are using Google Artifact Registry + // const imageUrl = 'https://LOCATION-docker.pkg.dev/my-project/my-repo/my-image:123' // Image to attach metadata to + + // Import the library and create a client + const {ContainerAnalysisClient} = require('@google-cloud/containeranalysis'); + const client = new ContainerAnalysisClient(); + + // Construct request + const formattedParent = client + .getGrafeasClient() + .projectPath(occurrenceProjectId); + const formattedNote = client + .getGrafeasClient() + .notePath(noteProjectId, noteId); + + // Creates and returns a new Occurrence associated with an existing Note + const [occurrence] = await client.getGrafeasClient().createOccurrence({ + parent: formattedParent, + occurrence: { + noteName: formattedNote, + resourceUri: imageUrl, + vulnerability: { + packageIssue: [ + { + affectedCpeUri: 'foo.uri', + affectedPackage: 'foo', + affectedVersion: { + kind: 'MINIMUM', + }, + fixedVersion: { + kind: 'MAXIMUM', + }, + }, + ], + }, + }, + }); + console.log(`Occurrence created ${occurrence.name}.`); + return occurrence; + // [END containeranalysis_create_occurrence] +} + +main(...process.argv.slice(2)); diff --git a/container-analysis/snippets/deleteNote.js b/container-analysis/snippets/deleteNote.js new file mode 100644 index 0000000000..a0c8a10782 --- /dev/null +++ b/container-analysis/snippets/deleteNote.js @@ -0,0 +1,45 @@ +// Copyright 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +'use strict'; + +// sample-metadata: +// title: Delete Note +// description: Deletes a specified Note +// usage: node deleteNote.js "project-id" "note-id" +async function main( + projectId = 'your-project-id', // Your GCP Project Id + noteId = 'my-note-id' // Id of the note +) { + // [START containeranalysis_delete_note] + /** + * TODO(developer): Uncomment these variables before running the sample + */ + // const projectId = 'your-project-id', // Your GCP Project Id + // const noteId = 'my-note-id' // Id of the note + + // Import the library and create a client + const {ContainerAnalysisClient} = require('@google-cloud/containeranalysis'); + const client = new ContainerAnalysisClient(); + + // Get the full path to the note + const formattedName = client.notePath(projectId, noteId); + + // Delete the note + await client.getGrafeasClient().deleteNote({name: formattedName}); + console.log(`Note ${formattedName} deleted.`); + // [END containeranalysis_delete_note] +} + +main(...process.argv.slice(2)); diff --git a/container-analysis/snippets/deleteOccurrence.js b/container-analysis/snippets/deleteOccurrence.js new file mode 100644 index 0000000000..400b2659ff --- /dev/null +++ b/container-analysis/snippets/deleteOccurrence.js @@ -0,0 +1,50 @@ +// Copyright 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +'use strict'; + +// sample-metadata: +// title: Delete Occurrence +// description: Deletes a specified Occurrence +// usage: node deleteOccurrence.js "project-id" "occurrence-id" +async function main( + projectId = 'your-project-id', // Your GCP Project ID + occurrenceId = 'my-occurrence' // The API-generated identifier associated with the occurrence +) { + // [START containeranalysis_delete_occurrence] + /** + * TODO(developer): Uncomment these variables before running the sample + */ + // const projectId = 'your-project-id', // Your GCP Project ID + // const occurrenceId = 'my-occurrence' // The API-generated identifier associated with the occurrence + + // Import the library and create a client + const {ContainerAnalysisClient} = require('@google-cloud/containeranalysis'); + const client = new ContainerAnalysisClient(); + + // Get full path to occurrence + const formattedName = client + .getGrafeasClient() + .occurrencePath(projectId, occurrenceId); + + // Deletes an existing Occurrence from the server + await client.getGrafeasClient().deleteOccurrence({ + name: formattedName, + }); + + console.log(`Occurrence deleted: ${formattedName}`); + // [END containeranalysis_delete_occurrence] +} + +main(...process.argv.slice(2)); diff --git a/container-analysis/snippets/getDiscoveryInfo.js b/container-analysis/snippets/getDiscoveryInfo.js new file mode 100644 index 0000000000..81960ed198 --- /dev/null +++ b/container-analysis/snippets/getDiscoveryInfo.js @@ -0,0 +1,61 @@ +// Copyright 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +'use strict'; + +// sample-metadata: +// title: Get Discovery Info +// description: Gets all Discovery Occurrences attached to specified image +// usage: node getDiscoveryInfo.js "project-id" "image-url" +async function main( + projectId = 'your-project-id', // Your GCP Project ID + // If you are using Google Container Registry + imageUrl = 'https://gcr.io/my-project/my-repo/my-image:123' // Image to attach metadata to + // If you are using Google Artifact Registry + // imageUrl = 'https://LOCATION-docker.pkg.dev/my-project/my-repo/my-image:123' // Image to attach metadata to +) { + // [START containeranalysis_discovery_info] + /** + * TODO(developer): Uncomment these variables before running the sample + */ + // const projectId = 'your-project-id', // Your GCP Project ID + // If you are using Google Container Registry + // const imageUrl = 'https://gcr.io/my-project/my-repo/my-image:123' // Image to attach metadata to + // If you are using Google Artifact Registry + // const imageUrl = 'https://LOCATION-docker.pkg.dev/my-project/my-repo/my-image:123' // Image to attach metadata to + + // Import the library and create a client + const {ContainerAnalysisClient} = require('@google-cloud/containeranalysis'); + const client = new ContainerAnalysisClient(); + + const formattedParent = client.getGrafeasClient().projectPath(projectId); + // Retrieves and prints the Discovery Occurrence created for a specified image + // The Discovery Occurrence contains information about the initial scan on the image + const [occurrences] = await client.getGrafeasClient().listOccurrences({ + parent: formattedParent, + filter: `kind = "DISCOVERY" AND resourceUrl = "${imageUrl}"`, + }); + + if (occurrences.length > 0) { + console.log(`Discovery Occurrences for ${imageUrl}`); + occurrences.forEach(occurrence => { + console.log(`${occurrence.name}:`); + }); + } else { + console.log('No occurrences found.'); + } + // [END containeranalysis_discovery_info] +} + +main(...process.argv.slice(2)); diff --git a/container-analysis/snippets/getNote.js b/container-analysis/snippets/getNote.js new file mode 100644 index 0000000000..a1d3630545 --- /dev/null +++ b/container-analysis/snippets/getNote.js @@ -0,0 +1,45 @@ +// Copyright 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +'use strict'; + +// sample-metadata: +// title: Get Note +// description: Retrieves and prints a specified note +// usage: node getNote.js "project-id" "note-id" +async function main( + projectId = 'your-project-id', // Your GCP Project ID + noteId = 'my-note-id' // Id of the note +) { + // [START containeranalysis_get_note] + /** + * TODO(developer): Uncomment these variables before running the sample + */ + // const projectId = 'your-project-id', // Your GCP Project ID + // const noteId = 'my-note-id' // Id of the note + + // Import the library and create a client + const {ContainerAnalysisClient} = require('@google-cloud/containeranalysis'); + const client = new ContainerAnalysisClient(); + + // Get the full path to the note + const formattedName = client.notePath(projectId, noteId); + // Retrieve the specified note + const [note] = await client.getGrafeasClient().getNote({name: formattedName}); + + console.log(`Note name: ${note.name}`); + // [END containeranalysis_get_note] +} + +main(...process.argv.slice(2)); diff --git a/container-analysis/snippets/getOccurrence.js b/container-analysis/snippets/getOccurrence.js new file mode 100644 index 0000000000..a3dc8292a4 --- /dev/null +++ b/container-analysis/snippets/getOccurrence.js @@ -0,0 +1,50 @@ +// Copyright 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +'use strict'; + +// sample-metadata: +// title: Get Occurrence +// description: Retrieves and prints a specified Occurrence +// usage: node getOccurrence.js "project-id" "occurrence-id" +async function main( + projectId = 'your-project-id', // Your GCP Project ID + occurrenceId = 'my-occurrence' // The API-generated identifier associated with the occurrence +) { + // [START containeranalysis_get_occurrence] + /** + * TODO(developer): Uncomment these variables before running the sample + */ + // const projectId = 'your-project-id', // Your GCP Project ID + // const occurrenceId = 'my-occurrence' // The API-generated identifier associated with the occurrence + + // Import the library and create a client + const {ContainerAnalysisClient} = require('@google-cloud/containeranalysis'); + const client = new ContainerAnalysisClient(); + + // Get full path to occurrence + const formattedName = client + .getGrafeasClient() + .occurrencePath(projectId, occurrenceId); + + // Retrieves the specified occurrence + const [occurrence] = await client.getGrafeasClient().getOccurrence({ + name: formattedName, + }); + + console.log(`Occurrence name: ${occurrence.name}`); + // [END containeranalysis_get_occurrence] +} + +main(...process.argv.slice(2)); diff --git a/container-analysis/snippets/highVulnerabilitiesForImage.js b/container-analysis/snippets/highVulnerabilitiesForImage.js new file mode 100644 index 0000000000..6f696e8a63 --- /dev/null +++ b/container-analysis/snippets/highVulnerabilitiesForImage.js @@ -0,0 +1,65 @@ +// Copyright 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +'use strict'; + +// sample-metadata: +// title: Get High Vulnerabilities for Image +// description: Retrieves all Vulnerability Occurrences of High Severity from Specified Image +// usage: node highVulnerabilitiesForImage.js "project-id" "image-url" +async function main( + projectId = 'your-project-id', // Your GCP Project ID + imageUrl = 'https://gcr.io/my-project/my-image:123' // Image to attach metadata to + // Use imageURL = 'https://LOCATION-docker.pkg.dev/my-project/my-image:123' when + // using Artifact Registry +) { + // [START containeranalysis_filter_vulnerability_occurrences] + /** + * TODO(developer): Uncomment these variables before running the sample + */ + // const projectId = 'your-project-id', // Your GCP Project ID + // const occurrenceProjectId = 'your-project-id', // GCP Project Id of Occurrence + // If you are using Google Container Registry + // const imageUrl = 'https://gcr.io/my-project/my-repo/my-image:123' // Image to attach metadata to + // If you are using Google Artifact Registry + + // Import the library and create a client + const {ContainerAnalysisClient} = require('@google-cloud/containeranalysis'); + const client = new ContainerAnalysisClient(); + + const formattedParent = client.getGrafeasClient().projectPath(projectId); + + // Retrieve a list of vulnerability occurrences with a severity level of 'HIGH' or greater + const [occurrences] = await client.getGrafeasClient().listOccurrences({ + parent: formattedParent, + filter: `kind = "VULNERABILITY" AND resourceUrl = "${imageUrl}"`, + }); + + if (occurrences.length) { + console.log(`High Severity Vulnerabilities for ${imageUrl}`); + occurrences.forEach(occurrence => { + if ( + occurrence.vulnerability.effective_severity === 'HIGH' || + occurrence.vulnerability.effective_severity === 'CRITICAL' + ) { + console.log(`${occurrence.name}:`); + } + }); + } else { + console.log('No occurrences found.'); + } + // [END containeranalysis_filter_vulnerability_occurrences] +} + +main(...process.argv.slice(2)); diff --git a/container-analysis/snippets/occurrencePubSub.js b/container-analysis/snippets/occurrencePubSub.js new file mode 100644 index 0000000000..ac5ddc774c --- /dev/null +++ b/container-analysis/snippets/occurrencePubSub.js @@ -0,0 +1,56 @@ +// Copyright 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +'use strict'; + +// sample-metadata: +// title: Occurrence PubSub +// description: Polls a specified PubSub subscription for Occurrences. Requires a subscription to a topic named 'container-analysis-occurrences-v1' +// usage: node occurrencePubSub.js "project-id" "subscription-id" "timeout-in-seconds" +async function main( + projectId = 'your-project-id', // Your GCP Project ID + subscriptionId = 'my-sub-id', // A user-specified subscription to the 'container-analysis-occurrences-v1' topic + timeoutSeconds = 30 // The number of seconds to listen for the new Pub/Sub messages +) { + // [START containeranalysis_pubsub] + /** + * TODO(developer): Uncomment these variables before running the sample + */ + // const projectId = 'your-project-id', // Your GCP Project ID + // const subscriptionId = 'my-sub-id', // A user-specified subscription to the 'container-analysis-occurrences-v1' topic + // const timeoutSeconds = 30 // The number of seconds to listen for the new Pub/Sub Messages + + // Import the pubsub library and create a client, topic and subscription + const {PubSub} = require('@google-cloud/pubsub'); + const pubsub = new PubSub({projectId}); + const subscription = pubsub.subscription(subscriptionId); + + // Handle incoming Occurrences using a Cloud Pub/Sub subscription + let count = 0; + const messageHandler = message => { + count++; + message.ack(); + }; + + // Listen for new messages until timeout is hit + subscription.on('message', messageHandler); + + setTimeout(() => { + subscription.removeListener('message', messageHandler); + console.log(`Polled ${count} occurrences`); + }, timeoutSeconds * 1000); + // [END containeranalysis_pubsub] +} + +main(...process.argv.slice(2)); diff --git a/container-analysis/snippets/occurrencesForImage.js b/container-analysis/snippets/occurrencesForImage.js new file mode 100644 index 0000000000..e077d2dfe5 --- /dev/null +++ b/container-analysis/snippets/occurrencesForImage.js @@ -0,0 +1,60 @@ +// Copyright 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +'use strict'; + +// sample-metadata: +// title: Occurrences for Image +// description: Retrieves all Occurrences attached to the metadata of a specified image +// usage: node occurrencesForImage.js "project-id" "image-url" +async function main( + projectId = 'your-project-id', // Your GCP Project ID + imageUrl = 'https://gcr.io/my-project/my-image:123' // Image to attach metadata to + // If you are using Google Artifact Registry + // imageUrl = 'https://LOCATION-docker.pkg.dev/my-project/my-repo/my-image:123' // Image to attach metadata to +) { + // [START containeranalysis_occurrences_for_image] + /** + * TODO(developer): Uncomment these variables before running the sample + */ + // const projectId = 'your-project-id', // Your GCP Project ID + // If you are using Google Container Registry + // const imageUrl = 'https://gcr.io/my-project/my-repo/my-image:123' // Image to attach metadata to + // If you are using Google Artifact Registry + // const imageUrl = 'https://LOCATION-docker.pkg.dev/my-project/my-repo/my-image:123' // Image to attach metadata to + + // Import the library and create a client + const {ContainerAnalysisClient} = require('@google-cloud/containeranalysis'); + const client = new ContainerAnalysisClient(); + + const formattedParent = client.getGrafeasClient().projectPath(projectId); + + // Retrieves all the Occurrences associated with a specified image + const [occurrences] = await client.getGrafeasClient().listOccurrences({ + parent: formattedParent, + filter: `resourceUrl = "${imageUrl}"`, + }); + + if (occurrences.length) { + console.log(`Occurrences for ${imageUrl}`); + occurrences.forEach(occurrence => { + console.log(`${occurrence.name}:`); + }); + } else { + console.log('No occurrences found.'); + } + // [END containeranalysis_occurrences_for_image] +} + +main(...process.argv.slice(2)); diff --git a/container-analysis/snippets/occurrencesForNote.js b/container-analysis/snippets/occurrencesForNote.js new file mode 100644 index 0000000000..8960a36b20 --- /dev/null +++ b/container-analysis/snippets/occurrencesForNote.js @@ -0,0 +1,55 @@ +// Copyright 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +'use strict'; + +// sample-metadata: +// title: Occurrences for Note +// description: Retrieves all Occurrences of a specified Note +// usage: node occurrencesForNote.js "project-id" "note-id" +async function main( + projectId = 'your-project-id', // Your GCP Project ID + noteId = 'my-note-id' // Id of the note +) { + // [START containeranalysis_occurrences_for_note] + /** + * TODO(developer): Uncomment these variables before running the sample + */ + // const projectId = 'your-project-id', // Your GCP Project ID + // const noteId = 'my-note-id' // Id of the note + + // Import the library and create a client + const {ContainerAnalysisClient} = require('@google-cloud/containeranalysis'); + const client = new ContainerAnalysisClient(); + + // Get path to Note + const formattedNote = client.notePath(projectId, noteId); + + // Retrieves all the Occurrences associated with a specified Note + const [occurrences] = await client.getGrafeasClient().listNoteOccurrences({ + name: formattedNote, + }); + + if (occurrences.length) { + console.log('Occurrences:'); + occurrences.forEach(occurrence => { + console.log(`${occurrence.name}:`); + }); + } else { + console.log('No occurrences found.'); + } + // [END containeranalysis_occurrences_for_note] +} + +main(...process.argv.slice(2)); diff --git a/container-analysis/snippets/package.json b/container-analysis/snippets/package.json new file mode 100644 index 0000000000..89bb3bcdfd --- /dev/null +++ b/container-analysis/snippets/package.json @@ -0,0 +1,26 @@ +{ + "name": "nodejs-containeranalysis-samples", + "private": true, + "license": "Apache-2.0", + "author": "Google Inc.", + "repository": "googleapis/nodejs-containeranalysis", + "files": [ + "*.js" + ], + "engines": { + "node": ">=12.0.0" + }, + "scripts": { + "test": "mocha --timeout 100000 test/**.test.js" + }, + "dependencies": { + "@google-cloud/containeranalysis": "^4.4.1", + "@google-cloud/pubsub": "^3.0.0", + "p-retry": "^4.1.0" + }, + "devDependencies": { + "chai": "^4.2.0", + "mocha": "^8.0.0", + "uuid": "^9.0.0" + } +} diff --git a/container-analysis/snippets/pollDiscoveryOccurrenceFinished.js b/container-analysis/snippets/pollDiscoveryOccurrenceFinished.js new file mode 100644 index 0000000000..fea680e5f5 --- /dev/null +++ b/container-analysis/snippets/pollDiscoveryOccurrenceFinished.js @@ -0,0 +1,98 @@ +// Copyright 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +'use strict'; + +// sample-metadata: +// title: Poll Discovery Occurrence Finished +// description: Waits for a Discovery Occurrence to reach a terminal state +// usage: node pollDiscoveryOccurrenceFinished.js "project-id" "image-url" "retries" +async function main( + projectId = 'your-project-id', // Your GCP Project ID + imageUrl = 'https://gcr.io/my-project/my-image:123', // Image to attach metadata to + // If you are using Google Artifact Registry + // imageUrl = 'https://LOCATION-docker.pkg.dev/my-project/my-repo/my-image:123', // Image to attach metadata to + retries = 5 // The number of retries to listen for the new Pub/Sub messages +) { + // [START containeranalysis_poll_discovery_occurrence_finished] + /** + * TODO(developer): Uncomment these variables before running the sample + */ + // const projectId = 'your-project-id', // Your GCP Project ID + // If you are using Google Container Registry + // const imageUrl = 'https://gcr.io/my-project/my-repo/my-image:123' // Image to attach metadata to + // If you are using Google Artifact Registry + // const imageUrl = 'https://LOCATION-docker.pkg.dev/my-project/my-repo/my-image:123' // Image to attach metadata to + // const retries = 5 // The number of retries to listen for the new Pub/Sub messages + + // Import the library and create a client + const {ContainerAnalysisClient} = require('@google-cloud/containeranalysis'); + const client = new ContainerAnalysisClient(); + + const formattedParent = client.getGrafeasClient().projectPath(projectId); + + let filter = `resourceUrl="${imageUrl}" AND noteProjectId="goog-analysis" AND noteId="PACKAGE_VULNERABILITY"`; + // [END containeranalysis_poll_discovery_occurrence_finished] + // The above filter isn't testable, since it looks for occurrences in a locked down project + // Fall back to a more permissive filter for testing + filter = `kind = "DISCOVERY" AND resourceUrl = "${imageUrl}"`; + // [START containeranalysis_poll_discovery_occurrence_finished] + + // Repeatedly query the Container Analysis API for the latest discovery occurrence until it is + // either in a terminal state, or the timeout value has been exceeded + const pRetry = require('p-retry'); + const discoveryOccurrence = await pRetry( + async () => { + const [occurrences] = await client.getGrafeasClient().listOccurrences({ + parent: formattedParent, + filter: filter, + }); + if (occurrences.length < 0) { + throw new Error('No occurrences found for ' + imageUrl); + } + return occurrences[0]; + }, + { + retries: retries, + } + ); + + // Wait for discovery occurrence to enter a terminal state or the timeout value has been exceeded + const finishedOccurrence = await pRetry( + async () => { + let status = 'PENDING'; + const [updated] = await client.getGrafeasClient().getOccurrence({ + name: discoveryOccurrence.name, + }); + status = updated.discovery.analysisStatus; + if ( + status !== 'FINISHED_SUCCESS' && + status !== 'FINISHED_FAILED' && + status !== 'FINISHED_UNSUPPORTED' + ) { + throw new Error('Timeout while retrieving discovery occurrence'); + } + return updated; + }, + { + retries: retries, + } + ); + console.log( + `Found discovery occurrence ${finishedOccurrence.name}. Status: ${finishedOccurrence.discovery.analysisStatus}` + ); + // [END containeranalysis_poll_discovery_occurrence_finished] +} + +main(...process.argv.slice(2)); diff --git a/container-analysis/snippets/quickstart.js b/container-analysis/snippets/quickstart.js new file mode 100644 index 0000000000..f5490a3931 --- /dev/null +++ b/container-analysis/snippets/quickstart.js @@ -0,0 +1,71 @@ +// Copyright 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +'use strict'; + +// sample-metadata: +// title: Quickstart +// description: fetching an instance of Grafeas and creating a note. +// usage: node quickstart.js "project-id" "note-id" +async function main( + projectId = 'your-project-id', // Your GCP Project ID + noteId = 'my-note-id' // Id of the note +) { + // [START containeranalysis_quickstart] + /** + * TODO(developer): Uncomment these variables before running the sample + */ + // const projectId = 'your-project-id', // Your GCP Project ID + // const noteId = 'my-note-id' // Id of the note + + // Import the library and create a client + const {ContainerAnalysisClient} = require('@google-cloud/containeranalysis'); + const client = new ContainerAnalysisClient(); + // Fetch an instance of a Grafeas client: + // see: https://googleapis.dev/nodejs/grafeas/latest + const grafeasClient = client.getGrafeasClient(); + + // Construct request + // Associate the Note with a metadata type + // https://cloud.google.com/container-registry/docs/container-analysis#supported_metadata_types + // Here, we use the type "vulnerabiltity" + const formattedParent = grafeasClient.projectPath(projectId); + + // Creates and returns a new Note + const [note] = await grafeasClient.createNote({ + parent: formattedParent, + noteId: noteId, + note: { + vulnerability: { + details: [ + { + affectedCpeUri: 'foo.uri', + affectedPackage: 'foo', + minAffectedVersion: { + kind: 'MINIMUM', + }, + fixedVersion: { + kind: 'MAXIMUM', + }, + }, + ], + }, + }, + }); + + console.log(`Note ${note.name} created.`); + // [END containeranalysis_quickstart] +} + +main(...process.argv.slice(2)); diff --git a/container-analysis/snippets/test/containerAnalysis.test.js b/container-analysis/snippets/test/containerAnalysis.test.js new file mode 100644 index 0000000000..fc5c04e86c --- /dev/null +++ b/container-analysis/snippets/test/containerAnalysis.test.js @@ -0,0 +1,421 @@ +// Copyright 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +'use strict'; + +const {assert} = require('chai'); +const {describe, it, before, after} = require('mocha'); +const cp = require('child_process'); +const {delay} = require('./util'); +const uuid = require('uuid'); + +const {ContainerAnalysisClient} = require('@google-cloud/containeranalysis'); +const client = new ContainerAnalysisClient(); + +const execSync = cmd => cp.execSync(cmd, {encoding: 'utf-8'}); + +const uuidVal = uuid.v4(); +const noteId = `test-note-${uuidVal}`; +const resourceUrl = `gcr.io/test-project/test-image-${uuidVal}`; +const subscriptionId = `occurrence-subscription-${uuidVal}`; +const timeoutSeconds = 5; +const retries = 10; + +const {PubSub} = require('@google-cloud/pubsub'); +const pubsub = new PubSub(); +const topicName = 'container-analysis-occurrences-v1'; +let topic; + +let projectId; +let formattedParent; +let formattedNoteName; + +describe.skip('Note tests', () => { + before(async () => { + // define projectId and related vars + projectId = await client.getProjectId(); + formattedParent = `projects/${projectId}`; + formattedNoteName = `projects/${projectId}/notes/${noteId}`; + }); + after(async () => { + const [allOccurrences] = await client.getGrafeasClient().listOccurrences({ + parent: formattedParent, + filter: `resourceUrl = "${resourceUrl}"`, + }); + + allOccurrences.forEach(async occurrence => { + await client.getGrafeasClient().deleteOccurrence({name: occurrence.name}); + console.log(`deleted occurrence ${occurrence.name}`); + }); + + const [allNotes] = await client.getGrafeasClient().listNotes({ + parent: formattedParent, + }); + + allNotes.forEach(async note => { + await client.getGrafeasClient().deleteNote({name: note.name}); + console.log(`deleted note ${note.name}`); + }); + }); + it('should create a note', () => { + const output = execSync(`node createNote.js "${projectId}" "${noteId}"`); + assert.include(output, `Note ${formattedNoteName} created.`); + }); + + it('should get note', () => { + const output = execSync(`node getNote.js "${projectId}" "${noteId}"`); + assert.include(output, `Note name: ${formattedNoteName}`); + }); + + it('should create occurrence', () => { + const output = execSync( + `node createOccurrence.js "${projectId}" "${noteId}" "${projectId}" "${resourceUrl}"` + ); + assert.include(output, 'Occurrence created'); + }); + + it('should get occurrence', async () => { + const [occurrences] = await client.getGrafeasClient().listOccurrences({ + parent: formattedParent, + filter: `resourceUrl = "${resourceUrl}"`, + }); + assert(occurrences.length > 0); + + const occurrence = occurrences[0]; + const occurrenceId = occurrence.name.split('/')[3]; + let output; + for (let i = 0; i < retries; i++) { + output = execSync( + `node getOccurrence.js "${projectId}" "${occurrenceId}"` + ); + if (output.includes('Occurrence name:')) { + break; + } + } + assert.include(output, `Occurrence name: ${occurrence.name}`); + }); + + it('should get occurrences for note', () => { + let output; + for (let i = 0; i < retries; i++) { + output = execSync( + `node occurrencesForNote.js "${projectId}" "${noteId}"` + ); + if (!output.includes('No occurrences found.')) { + break; + } + } + assert.include(output, 'Occurrences:'); + }); + + it('should get occurrences for image', () => { + const output = execSync( + `node occurrencesForImage.js "${projectId}" "${resourceUrl}"` + ); + assert.include(output, `Occurrences for ${resourceUrl}`); + }); + + it('should get discovery info for image', async () => { + const discoveryNoteRequest = { + parent: formattedParent, + noteId: `${noteId}-discovery`, + note: { + discovery: { + analysisKind: 'DISCOVERY', + }, + }, + }; + + await client.getGrafeasClient().createNote(discoveryNoteRequest); + + const occurrenceRequest = { + parent: formattedParent, + occurrence: { + noteName: `${formattedNoteName}-discovery`, + resourceUri: resourceUrl, + discovery: { + analysisStatus: 'FINISHED_SUCCESS', + }, + }, + }; + + await client.getGrafeasClient().createOccurrence(occurrenceRequest); + + const output = execSync( + `node getDiscoveryInfo "${projectId}" "${resourceUrl}"` + ); + assert.include(output, `Discovery Occurrences for ${resourceUrl}`); + }); + + it('should get high severity vulnerabilities for image', async () => { + const criticalNoteReq = { + parent: formattedParent, + noteId: `${noteId}-critical`, + note: { + vulnerability: { + severity: 'CRITICAL', + details: [ + { + affectedCpeUri: 'foo.uri', + affectedPackage: 'foo', + affectedVersionStart: { + kind: 'MINIMUM', + }, + affectedVersionEnd: { + kind: 'MAXIMUM', + }, + }, + ], + }, + }, + }; + + await client.getGrafeasClient().createNote(criticalNoteReq); + + const criticalOccurrenceReq = { + parent: formattedParent, + occurrence: { + noteName: `${formattedNoteName}-critical`, + resourceUri: resourceUrl, + vulnerability: { + effective_severity: 'CRITICAL', + packageIssue: [ + { + affectedCpeUri: 'foo.uri', + affectedPackage: 'foo', + affectedVersion: { + kind: 'MINIMUM', + }, + fixedVersion: { + kind: 'MAXIMUM', + }, + }, + ], + }, + }, + }; + + await client.getGrafeasClient().createOccurrence(criticalOccurrenceReq); + + const output = execSync( + `node highVulnerabilitiesForImage "${projectId}" "${resourceUrl}"` + ); + + assert.include(output, `High Severity Vulnerabilities for ${resourceUrl}`); + }); + + it('should get all vulnerabilites for image', () => { + const output = execSync( + `node vulnerabilityOccurrencesForImage "${projectId}" "${resourceUrl}"` + ); + assert.include(output, `All Vulnerabilities for ${resourceUrl}`); + }); + + it('should delete occurrence', async () => { + const [occurrences] = await client.getGrafeasClient().listOccurrences({ + parent: formattedParent, + filter: `resourceUrl = "${resourceUrl}"`, + }); + assert(occurrences.length > 0); + const occurrence = occurrences[0]; + const occurrenceId = occurrence.name.split('/')[3]; + + const output = execSync( + `node deleteOccurrence.js "${projectId}" "${occurrenceId}"` + ); + assert.include(output, 'Occurrence deleted:'); + }); + + it('should delete note', () => { + const output = execSync(`node deleteNote.js "${projectId}" "${noteId}" `); + assert.include(output, `Note ${formattedNoteName} deleted.`); + // Sometimes the delete note test is failing with the error: + // Error: 5 NOT_FOUND: note with ID "test-note-${uuid}" for project + // ${projectId} does not exist. + }); +}); + +describe.skip('polling', () => { + before(async () => { + // define project id and related vars + projectId = await client.getProjectId(); + formattedParent = `projects/${projectId}`; + formattedNoteName = `projects/${projectId}/notes/${noteId}`; + + const discoveryNoteRequest = { + parent: formattedParent, + noteId: `${noteId}-discovery-polling`, + note: { + discovery: { + analysisKind: 'DISCOVERY', + }, + }, + }; + + await client.getGrafeasClient().createNote(discoveryNoteRequest); + + const occurrenceRequest = { + parent: formattedParent, + occurrence: { + noteName: `${formattedNoteName}-discovery-polling`, + resourceUri: resourceUrl, + discovery: { + analysisStatus: 'FINISHED_SUCCESS', + }, + }, + }; + + await client.getGrafeasClient().createOccurrence(occurrenceRequest); + }); + + after(async () => { + const [discoveryOccurrences] = await client + .getGrafeasClient() + .listNoteOccurrences({ + name: `${formattedNoteName}-discovery-polling`, + }); + discoveryOccurrences.forEach(async occurrence => { + await client.getGrafeasClient().deleteOccurrence({name: occurrence.name}); + }); + await client + .getGrafeasClient() + .deleteNote({name: `${formattedNoteName}-discovery-polling`}); + }); + + it('should successfully poll latest discovery occurrence', () => { + const output = execSync( + `node pollDiscoveryOccurrenceFinished.js "${projectId}" "${resourceUrl}" "${timeoutSeconds}"` + ); + assert.include(output, 'Found discovery occurrence'); + }); +}); + +describe.skip('pubsub', () => { + before(async () => { + // define project id and related vars + projectId = await client.getProjectId(); + formattedParent = `projects/${projectId}`; + formattedNoteName = `projects/${projectId}/notes/${noteId}`; + topic = pubsub.topic(topicName); + }); + + describe('occurrences from pubsub subscription', () => { + it('should get count of occurrences from pubsub topic', async function () { + this.retries(3); + await delay(this.test); + try { + // attempt to create topic if missing + await pubsub.createTopic(topicName); + } catch (err) { + console.log(`topic creation failed: ${topicName} ${err.message}`); + if (!err.details.includes('Resource already exists')) { + throw err; + } + } + try { + await topic.createSubscription(subscriptionId); + } catch (err) { + console.log( + `subscription creation failed: ${subscriptionId} ${err.message}` + ); + if (!err.details.includes('Resource already exists')) { + throw err; + } + } + const pubSubNoteReq = { + parent: formattedParent, + noteId: `${noteId}-pubsub`, + note: { + vulnerability: { + details: [ + { + affectedCpeUri: 'foo.uri', + affectedPackage: 'foo', + affectedVersionStart: { + kind: 'MINIMUM', + }, + affectedVersionEnd: { + kind: 'MAXIMUM', + }, + }, + ], + }, + }, + }; + await client.getGrafeasClient().createNote(pubSubNoteReq); + const occurrenceCount = 3; + const pubSubOccurrenceReq = { + parent: formattedParent, + occurrence: { + noteName: `${formattedNoteName}-pubsub`, + resourceUri: resourceUrl, + vulnerability: { + packageIssue: [ + { + affectedCpeUri: 'foo.uri', + affectedPackage: 'foo', + affectedVersion: { + kind: 'MINIMUM', + }, + fixedVersion: { + kind: 'MAXIMUM', + }, + }, + ], + }, + }, + }; + + // empty subscription + execSync( + `node occurrencePubSub.js "${projectId}" "${subscriptionId}" "${timeoutSeconds}"` + ); + + // create test occurrences + for (let i = 0; i < occurrenceCount; i++) { + const [pubSubOccurrence] = await client + .getGrafeasClient() + .createOccurrence(pubSubOccurrenceReq); + await client + .getGrafeasClient() + .deleteOccurrence({name: pubSubOccurrence.name}); + } + const output = execSync( + `node occurrencePubSub.js "${projectId}" "${subscriptionId}" "${timeoutSeconds}"` + ); + + // ensure that our occcurences were enqueued: + assert.match(output, /Polled [1-9]+ occurrences/); + }); + + it('should delete the pubsub subscription', async function () { + this.retries(3); + await delay(this.test); + + try { + await client + .getGrafeasClient() + .deleteNote({name: `${formattedNoteName}-pubsub`}); + } catch (err) { + assert.fail(err); + } + + try { + await pubsub.subscription(subscriptionId).delete(); + } catch (err) { + assert.fail(err); + } + }); + }); +}); diff --git a/container-analysis/snippets/test/util.js b/container-analysis/snippets/test/util.js new file mode 100644 index 0000000000..97b804a43c --- /dev/null +++ b/container-analysis/snippets/test/util.js @@ -0,0 +1,28 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// ML tests frequently run into concurrency and quota issues, for which +// retrying with a backoff is a good strategy: +module.exports = { + async delay(test) { + const retries = test.currentRetry(); + if (retries === 0) return; // no retry on the first failure. + // see: https://cloud.google.com/storage/docs/exponential-backoff: + const ms = Math.pow(2, retries) * 1000 + Math.random() * 2000; + return new Promise(done => { + console.info(`retrying "${test.title}" in ${ms}ms`); + setTimeout(done, ms); + }); + }, +}; diff --git a/container-analysis/snippets/vulnerabilityOccurrencesForImage.js b/container-analysis/snippets/vulnerabilityOccurrencesForImage.js new file mode 100644 index 0000000000..611a161d01 --- /dev/null +++ b/container-analysis/snippets/vulnerabilityOccurrencesForImage.js @@ -0,0 +1,60 @@ +// Copyright 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +'use strict'; + +// sample-metadata: +// title: Vulnerability Occurrences for Image +// description: Retrieves all Vulnerability Occurrences attached to a specified image +// usage: node vulnerabilityOccurrencesForImage.js "project-id" "image-url" +async function main( + projectId = 'your-project-id', // Your GCP Project ID + imageUrl = 'https://gcr.io/my-project/my-image:123' // Image to attach metadata to + // If you are using Google Artifact Registry + // imageUrl = 'https://LOCATION-docker.pkg.dev/my-project/my-repo/my-image:123' // Image to attach metadata to +) { + // [START containeranalysis_vulnerability_occurrences_for_image] + /** + * TODO(developer): Uncomment these variables before running the sample + */ + // const projectId = 'your-project-id', // Your GCP Project ID + // If you are using Google Container Registry + // const imageUrl = 'https://gcr.io/my-project/my-repo/my-image:123' // Image to attach metadata to + // If you are using Google Artifact Registry + // const imageUrl = 'https://LOCATION-docker.pkg.dev/my-project/my-repo/my-image:123' // Image to attach metadata to + + // Import the library and create a client + const {ContainerAnalysisClient} = require('@google-cloud/containeranalysis'); + const client = new ContainerAnalysisClient(); + + const formattedParent = client.getGrafeasClient().projectPath(projectId); + + // Retrieve a list of vulnerability occurrences assoviated with a resource + const [occurrences] = await client.getGrafeasClient().listOccurrences({ + parent: formattedParent, + filter: `kind = "VULNERABILITY" AND resourceUrl = "${imageUrl}"`, + }); + + if (occurrences.length) { + console.log(`All Vulnerabilities for ${imageUrl}`); + occurrences.forEach(occurrence => { + console.log(`${occurrence.name}:`); + }); + } else { + console.log('No occurrences found.'); + } + // [END containeranalysis_vulnerability_occurrences_for_image] +} + +main(...process.argv.slice(2));