Skip to content

Commit

Permalink
Merge pull request #1393 from newrelic/alec/o11y-pack-fetch
Browse files Browse the repository at this point in the history
Observability Pack fetch workflow
  • Loading branch information
aswanson-nr authored Jun 17, 2021
2 parents 4032c08 + f7a8800 commit 7780b79
Show file tree
Hide file tree
Showing 9 changed files with 921 additions and 551 deletions.
100 changes: 100 additions & 0 deletions .github/workflows/fetch-observability-packs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
name: Fetch Observability Packs

on:
workflow_dispatch:
schedule:
- cron: '0 0 * * *' # midnight

env:
BOT_NAME: nr-opensource-bot
BOT_EMAIL: [email protected]
NODE_OPTIONS: '--max-old-space-size=4096'

jobs:
fetch-observability-packs:
runs-on: ubuntu-latest
steps:
- name: Checkout repo
uses: actions/checkout@v2
with:
ref: main

- name: Setup node.js
uses: actions/setup-node@v1
with:
node-version: 12

- name: Cache dependencies
id: yarn-cache
uses: actions/cache@v2
with:
path: '**/node_modules'
key: ${{ runner.os }}-node-modules-${{ hashFiles('**/yarn.lock') }}

- name: Install dependencies
if: steps.yarn-cache.outputs.cache-hit != 'true'
run: yarn install --frozen-lockfile

- name: Fetch observability packs
run: yarn run fetch-observability-packs
env:
NR_API_URL: 'https://api.newrelic.com/graphql'
NR_API_TOKEN: ${{ secrets.NR_API_TOKEN }}

- name: Temporarily disable branch protection
id: disable-branch-protection
uses: actions/github-script@v1
with:
github-token: ${{ secrets.OPENSOURCE_BOT_TOKEN }}
previews: luke-cage-preview
script: |
const result = await github.repos.updateBranchProtection({
owner: context.repo.owner,
repo: context.repo.repo,
branch: 'main',
required_status_checks: null,
restrictions: null,
enforce_admins: null,
required_pull_request_reviews: null
})
console.log("Result:", result)
- name: Commit changes
id: commit-changes
run: |
git config --local user.email "${{ env.BOT_EMAIL }}"
git config --local user.name "${{ env.BOT_NAME }}"
git add ./src/data/observability-packs.json
git diff-index --quiet HEAD ./src/data/observability-packs.json || git commit -m 'chore(observability-packs): updated observability packs'
echo "::set-output name=commit::true"
- name: Push Commit
if: steps.commit-changes.outputs.commit == 'true'
uses: ad-m/[email protected]
with:
github_token: ${{ secrets.OPENSOURCE_BOT_TOKEN }}
branch: main

- name: Re-enable branch protection
id: enable-branch-protection
if: always()
uses: actions/github-script@v1
with:
github-token: ${{ secrets.OPENSOURCE_BOT_TOKEN }}
previews: luke-cage-preview
script: |
const result = await github.repos.updateBranchProtection({
owner: context.repo.owner,
repo: context.repo.repo,
branch: 'main',
required_status_checks: {
strict: false,
contexts: [
'Gatsby Build'
]
},
restrictions: null,
enforce_admins: true,
required_pull_request_reviews: null
})
console.log("Result:", result)
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,9 @@ Please submit any issues or enhancement requests using one of our
[Issue Templates](https://github.com/newrelic/developer-website/issues/new/choose).
Please search for and review the existing open issues before submitting a new
issue to prevent the submission of duplicate issues.

## CI/CD
### fetch-observability-packs
* Purpose: This workflow pulls down Observability Packs from the GraphQL API, writes them to src/data/observability-packs.json (overwriting any previous content), and commits that file to the `main` branch.
* Trigger: Schedule, 12am everyday

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
"gatsby-transformer-remark": "^4.0.0",
"gatsby-transformer-sharp": "^3.3.0",
"js-cookie": "^2.2.1",
"lodash.get": "^4.4.2",
"node-fetch": "^2.6.1",
"node-sass": "^4.14.1",
"prism-react-renderer": "^1.1.1",
Expand Down Expand Up @@ -92,6 +93,7 @@
"build:production": "GATSBY_NEWRELIC_ENV=production gatsby build",
"build:staging": "GATSBY_NEWRELIC_ENV=staging gatsby build",
"build:related-content": "BUILD_RELATED_CONTENT=true yarn run build:production",
"fetch-observability-packs": "CI=true node ./scripts/actions/fetch-observability-packs.js",
"develop": "gatsby develop",
"format": "prettier --write \"**/*.{js,jsx,json,md}\"",
"start": "yarn run develop",
Expand Down
117 changes: 117 additions & 0 deletions scripts/actions/__tests__/fetch-observability-packs.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
'use strict';

const fs = require('fs');
const fetch = require('node-fetch');
const fetchObservabilityPacks = require('../fetch-observability-packs');

jest.mock('node-fetch');
jest.mock('fs');

describe('Action: Fetch Observability Packs', () => {
const fakeAPIURL = 'fakeapi.com/graphql';
const fakeToken = 'fake_token';
const fakeGqlQuery = 'fake_gql_query';

afterEach(() => {
jest.resetAllMocks();
});

test('writes observability packs to file', async () => {
const apiReturnValue = {
data: {
docs: {
openInstallation: {
observabilityPackSearch: {
results: {
observabilityPacks: [
{
test: 'test'
}
]
}
}
}
}
}
};
fetch.mockResolvedValueOnce({
ok: true,
json: jest.fn(() => Promise.resolve(apiReturnValue))
});

await fetchObservabilityPacks(fakeGqlQuery, fakeAPIURL, fakeToken);
expect(fs.writeFileSync.mock.calls.length).toBe(1);
expect(fs.writeFileSync.mock.calls[0][0]).toStrictEqual('./src/data/observability-packs.json');
expect(fs.writeFileSync.mock.calls[0][1]).toStrictEqual(JSON.stringify([{ test: 'test' }], null, 2));
});

test('does not write file when graphql errors are returned', async () => {
const apiReturnValue = {
errors: {
testError: 'error'
},
data: {
docs: {
openInstallation: {
observabilityPackSearch: {
results: {
observabilityPacks: [
{
test: 'test'
}
]
}
}
}
}
}
};
fetch.mockResolvedValueOnce({
json: jest.fn(() => Promise.resolve(apiReturnValue)),
ok: true
});

await fetchObservabilityPacks(fakeGqlQuery, fakeAPIURL, fakeToken);
expect(fs.writeFileSync).not.toHaveBeenCalled();
});

test('does not write file when graphql response is malformed', async () => {
const apiReturnValue = {
data: {
docs: {
observabilityPackSearch: {
results: {
observabilityPacks: [
{
test: 'test'
}
]
}
}
}
}
};
fetch.mockResolvedValueOnce({
json: jest.fn(() => Promise.resolve(apiReturnValue)),
ok: true
});

await fetchObservabilityPacks(fakeGqlQuery, fakeAPIURL, fakeToken);
expect(fs.writeFileSync).not.toHaveBeenCalled();
});

test('does not write file when a network error occurs', async () => {
fetch.mockImplementation(() => Promise.reject());
await fetchObservabilityPacks(fakeGqlQuery, fakeAPIURL, fakeToken);
expect(fs.writeFileSync).not.toHaveBeenCalled();
});

test('does not write file when a non-200 status is returned from the API', async () => {
fetch.mockResolvedValueOnce({
status: 500,
ok: false
});
await fetchObservabilityPacks(fakeGqlQuery, fakeAPIURL, fakeToken);
expect(fs.writeFileSync).not.toHaveBeenCalled();
});
});
128 changes: 128 additions & 0 deletions scripts/actions/fetch-observability-packs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
'use strict';

/**
* This script is used to query the New Relic GraphQL API for Observability Packs.
* It then writes the array of Observability Packs to src/data/observability-packs.json
* It requires the following environment variables to be set:
* NR_GQL_URL - The New Relic GraphQL URL
* NR_API_TOKEN - A New Relic personal API token
**/

/* eslint-disable no-console */
const fs = require('fs');
const fetch = require('node-fetch');
const get = require('lodash.get');

const PACKS_FILE_PATH = './src/data/observability-packs.json';
const NR_API_URL = process.env.NR_API_URL;
const NR_API_TOKEN = process.env.NR_API_TOKEN;

const packQuery = `# gql
{
docs {
openInstallation {
observabilityPackSearch {
count
results {
observabilityPacks {
authors
dashboards {
description
name
screenshots
url
}
description
iconUrl
id
level
logoUrl
name
websiteUrl
}
}
}
}
}
}
`;

/**
* Queries graphql for the provided query
* @param {String} queryString the graphql query to send
* @param {String} url NR graphql endpoint
* @param {String} token NR api token
* @returns {Promise<Object[]|undefined>} returns the resulting array
* or `undefined` if there was an error
**/
const fetchPacks = async (queryString, url, token) => {
try {
const res = await fetch(url, {
method: 'post',
body: JSON.stringify({ query: queryString }),
headers: {
'Content-Type': 'application/json',
'Api-Key': token,
},
});

if (!res.ok) {
throw new Error(`Received status code ${res.status} from the API`);
}

const results = await res.json();

if (results.errors) {
throw new Error(JSON.stringify(results.errors, null, 2));
}

return get(
results,
'data.docs.openInstallation.observabilityPackSearch.results'
);
} catch (error) {
console.error('Encountered a problem querying the graphql api', error);
}
};

const validateEnvVars = () => {
if (typeof NR_API_URL !== 'string') {
throw new Error('NR_GQL_URL environment variable not set, exiting...');
}

if (typeof NR_API_TOKEN !== 'string') {
throw new Error('NR_API_TOKEN environment variable not set, exiting...');
}
};

/*
* @param {String} query a graphql query for observability packs
* @param {String} url the New Relic API endpoint
* @param {String} token a New Relic API token
**/
const main = async (query, url, token) => {
const results = await fetchPacks(query, url, token);

if (results) {
const packs = results.observabilityPacks;
console.log(`Found ${packs.length} packs.`);
console.log(`Writing ${PACKS_FILE_PATH}`);
fs.writeFileSync(PACKS_FILE_PATH, JSON.stringify(packs, null, 2));
} else {
console.log(
'No packs were returned from the api, check the logs for errors.'
);
if (process.env.CI) {
process.exit(1);
}
}
};

if (process.env.CI) {
validateEnvVars();
main(packQuery, NR_API_URL, NR_API_TOKEN);
}

module.exports = main;

/* eslint-enable no-console */
Loading

0 comments on commit 7780b79

Please sign in to comment.