Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make communicating with Apollo's commercial products into ordinary plugins #4453

Merged
merged 35 commits into from
Sep 18, 2020

Conversation

glasser
Copy link
Member

@glasser glasser commented Aug 4, 2020

Currently, the EngineReportingAgent is deeply embedded in ApolloServer, configured via a special engine option to new ApolloServer. Additionally, Engine is no longer the name of the product.

This PR will remove this special-casing and replace EngineReportingAgent with a few standard ApolloServerPlugins. It will replace the engine constructor argument with an apollo argument that's used only to configure the API key and graph ID/variant, not for detailed configuration of reporting semantics. Other than this apollo argument (which will also be passed to the plugin's serverWillStart hook) and backwards-compatibility handling, the only special treatment of these Apollo platform plugins will be that if you do actively configure an Apollo API key and don't set up a usage reporting plugin yourself or actively disable it, a plugin will be set up with the default configuration.

  • Implement serverWillStop hook (formerly a separate PR, Add serverWillStop lifecycle hook; call stop() on signals by default #4450)
  • Implement new apollo constructor argument and use it in existing plugins/gateway
  • Factor out ApolloServerPluginSchemaReporting
  • Move protobuf into apollo-server-types or rename to apollo-reporting-protobuf
  • Factor out ApolloServerPluginInlineTracing (ie, federated tracing)
  • Factor out ApolloServerPluginUsageReporting
  • Delete apollo-engine-reporting entirely
  • Ensure full backwards compatibility of engine option
  • Solve the "azure functions defaults to sendReportsImmediately" issue
  • Fix defaultEngineReportingSignature (in another repo)
  • Docs:
    • Write migration docs
    • Write new reference docs for ASPUsageReporting (make sure overrideReportedSchema is clear that you do it twice)
    • Write new reference docs for ASPSchemaReporting (make sure overrideReportedSchema is clear that you do it twice)
    • Write new reference docs for ASPInlineTrace
    • Update api/apollo-server docs (delete EngineReportingOptions, allude to default plugins, add apollo, etc)
    • Update rewriteError docs in data/errors.md
    • Update federation/metrics
    • Update monitoring/metrics
    • Update op reg README
    • make sure rewriteError references are good
    • generally look for any references to engine in docs
    • Update studio docs
  • Examples
    • Update acephei gateway
    • Update acephei services
  • Write changelog (include TS 3.8 requirement, extra logging)
  • Ensure all FIXME comments are resolved
  • Look for any other uses of engine in the repo
  • Revert any accidental prettier changes (have done this once but will double-check before merging)
  • Understand if it's OK to hardcode current into apollo-server more than it already is
  • Split out gateway change to the federation repo (once Import JavaScript @apollo/federation and @apollo/gateway from the apollo-server repository. federation#134 merges?)
  • Make sure importing apollo-server-core doesn't import too much or too node-specific code
  • Update reports Ingress URL (including in some tests) — @zionts is setting up new URLs now and documenting them
  • Improve startup logging to include Studio URL
  • Dogfood

@glasser
Copy link
Member Author

glasser commented Aug 4, 2020

One note: I definitely want to make the tests not print a zillion No graph variant provided. Defaulting to current. warnings! Will get to that next.

@glasser glasser force-pushed the glasser/server-will-stop branch 2 times, most recently from 2faa90d to 3d61611 Compare August 5, 2020 06:02
@glasser glasser force-pushed the glasser/apollo-config branch 2 times, most recently from 69b390f to 526443a Compare August 5, 2020 06:19
@glasser
Copy link
Member Author

glasser commented Aug 5, 2020

Added a small number of explicit graphVariants to silence those warnings.

@glasser
Copy link
Member Author

glasser commented Aug 5, 2020

Naming thought: ApolloConfigInput becomes ApolloConfig, ApolloConfig becomes ApolloContext?

@glasser glasser changed the title Move basic configuration from engine to apollo Make communicating with Apollo's commercial products into ordinary plugins Aug 5, 2020
@glasser
Copy link
Member Author

glasser commented Aug 5, 2020

Updated this PR's description to be broader than the first step (the existing commit's description maintains) and added a checklist.

@glasser
Copy link
Member Author

glasser commented Aug 5, 2020

I'm kinda unsure about naming for the plugins. Our whole-package plugins export them as default exports, so they don't really have names.

@@ -702,7 +629,7 @@ export class EngineReportingAgent<TContext = any> {
method: 'POST',
headers: {
'user-agent': 'apollo-engine-reporting',
'x-api-key': this.apiKey,
'x-api-key': this.apolloConfig.key!,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Non-blocking / nit: this.apolloConfig is actually more of a WithRequired<ApolloConfig, 'key'> due to the assertion in the constructor. Refining that type would mean we don't have to assert its existence here or anywhere else it's being used.

// mode and warn about deprecation
if (apollo) {
throw new Error("You cannot provide both `engine` and `apollo` to `new ApolloServer()`. " +
"For details on how to migrate all of your options out of `engine`, see FIXME(no-engine) URL MISSING");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know this is WIP, just noting FIXMEs for later.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. It's in the PR description checklist too.

Comment on lines 49 to 52
// FIXME(no-engine): This should be changed to check for a deprecation
// warning for any use of `engine` (which we can't really do until splitting
// out the plugins).
it.skip('spits out a deprecation warning when passed a schemaTag in construction', () => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Noting FIXME

@@ -112,32 +116,47 @@ describe('environment variables', () => {
it('constructs a reporting agent with the ENGINE_API_KEY (deprecated) environment variable and warns', async () => {
// set the variables
process.env.ENGINE_API_KEY = 'just:fake:stuff';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nothing you've done here, but is this polluting the global test env? Do we need to delete it as cleanup? I'm actually not sure if jest persists process.env between tests.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had the same thought, but there's a beforeEach/afterEach above.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@trevor-scheer fwiw, I think process.env within the same Jest test-runner worker would be persisted (i.e., would not be reset to its state at startup), yes. (But the beforeEach / afterEach is the key here.)

Comment on lines 141 to 140
process.env.ENGINE_API_KEY = 'just:fake:stuff';
process.env.APOLLO_KEY = 'also:fake:stuff';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same concern here, maybe.

Comment on lines 79 to 95
throw new Error(
'Cannot set more than one of apollo.graphVariant, ' +
'engine.graphVariant, and engine.schemaTag. Please use apollo.graphVariant.',
);
}
apolloConfig.graphVariant = engine.graphVariant;
} else if (typeof engine === 'object' && engine.schemaTag) {
// No need to warn here, because ApolloServer's constructor should warn about
// the existence of `engine` at all.
apolloConfig.graphVariant = engine.schemaTag;
} else if (APOLLO_GRAPH_VARIANT) {
if (ENGINE_SCHEMA_TAG) {
throw new Error(
'`APOLLO_GRAPH_VARIANT` and `ENGINE_SCHEMA_TAG` (deprecated) environment variables must not both be set.',
);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want to warn and carry on gracefully in these throw cases? Or are we throwing because if the values are different we don't know which is the right one to pick?

I know this complicates the logic a bit but we could carry on if:

  1. they're identical (safe)
  2. we just decide that newer config option takes more precedence (arguably less safe)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This throw is preserving existing behavior. Has anyone complained about it yet?

In general I think "make sure untouched configuration still works" is a good goal but "let people write confusing config that is half old and half new" isn't positive.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the half-old/half-new requirement was because of the apollo CLI (or other tooling) respecting one set of environment variables and Server respecting something different. We can probably leave this now that I think the CLI is sorted out.

);
}

const executableSchema = overrideReportedSchema ?? printSchema(schema);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Executable schema generally means an actual GraphQLSchema, so the naming here is a bit misleading. printedSchema?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For some reason, executableSchema is the name of the parameter in our GraphQL API. So I think matching is good?

const schemaReporter = new SchemaReporter({
serverInfo,
schemaSdl: executableSchema,
apiKey: apollo.key!,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like this shouldn't be necessary, we assert apollo.key's existence above.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is necessary (presumably TypeScript isn't positive that apollo.key isn't from some sort of getter?) but I can fix it in another way by saving to a const.

@@ -0,0 +1,271 @@
// This class is a helper for ApoloServerPluginUsageReporting and
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// This class is a helper for ApoloServerPluginUsageReporting and
// This class is a helper for ApolloServerPluginUsageReporting and

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks

Copy link
Member

@abernix abernix left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you so much for putting in the effort to organize and de-Engine-ize this code!

I've left a number of comments throughout, but I think my notable points are around the:

  • Un-conditional importing of modules which were previously avoided when Apollo Cloud services were disabled. This includes Node.js built-in APIs (like crypto, zlib, os) but also heavier dependencies, like apollo-reporting-protobuf. Not only had we switched to conditionally importing these some time ago (to support non-Node.js runtimes, like JavaScriptCore and V8), but also to improve the cold-boot time in Worker/Edge/Lambda-type environments.
  • The __internal_plugin_id__ (and if there is another way)

@@ -112,32 +116,47 @@ describe('environment variables', () => {
it('constructs a reporting agent with the ENGINE_API_KEY (deprecated) environment variable and warns', async () => {
// set the variables
process.env.ENGINE_API_KEY = 'just:fake:stuff';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@trevor-scheer fwiw, I think process.env within the same Jest test-runner worker would be persisted (i.e., would not be reset to its state at startup), yes. (But the beforeEach / afterEach is the key here.)

Comment on lines 79 to 95
throw new Error(
'Cannot set more than one of apollo.graphVariant, ' +
'engine.graphVariant, and engine.schemaTag. Please use apollo.graphVariant.',
);
}
apolloConfig.graphVariant = engine.graphVariant;
} else if (typeof engine === 'object' && engine.schemaTag) {
// No need to warn here, because ApolloServer's constructor should warn about
// the existence of `engine` at all.
apolloConfig.graphVariant = engine.schemaTag;
} else if (APOLLO_GRAPH_VARIANT) {
if (ENGINE_SCHEMA_TAG) {
throw new Error(
'`APOLLO_GRAPH_VARIANT` and `ENGINE_SCHEMA_TAG` (deprecated) environment variables must not both be set.',
);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the half-old/half-new requirement was because of the apollo CLI (or other tooling) respecting one set of environment variables and Server respecting something different. We can probably leave this now that I think the CLI is sorted out.

Comment on lines 259 to 260
const { cache: apqCache = requestOptions.cache!, ...apqOtherOptions } =
requestOptions.persistedQueries || Object.create(null);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks a lot like Prettier ran on this file and made a bunch of changes. This one in particular certainly is questionable.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah sorry, yeah, I am planning to de-prettier files that are not new (however, I am using prettier on all new files because as a multi-lingual developer I no longer possess the ability to use my brain to make choices about formatting in N different languages).

Copy link
Member Author

@glasser glasser Aug 25, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR is now de-prettiered (or well, the part outside of core/src/plugin).

Comment on lines 471 to 445
const message = "This data graph is missing a valid configuration.";
this.logger.error(message + " " + (err && err.message || err));
const message = 'This data graph is missing a valid configuration.';
this.logger.error(message + ' ' + ((err && err.message) || err));
throw new Error(
message + " More details may be available in the server logs.");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure how you feel about backing these out, but it would be nice.

Comment on lines 1 to 10
import os from 'os';
import { gzip } from 'zlib';
// FIXME(no-engine): make sure these deps are declared
import retry from 'async-retry';
import { defaultEngineReportingSignature } from 'apollo-graphql';
import {
Report,
ReportHeader,
Trace,
TracesAndStats,
} from 'apollo-reporting-protobuf';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just my same concern about Node.js APIs being now in the direct line of imports / module resolution.

@glasser glasser force-pushed the glasser/apollo-config branch 4 times, most recently from dd2e81f to 73700cb Compare August 25, 2020 23:58
@glasser
Copy link
Member Author

glasser commented Aug 26, 2020

@abernix This is moderately close to "code complete" (I've finished and fixed up the commit that gets rid of apollo-engine-reporting), though there are still a number of things to track down, including of course docs/changelog/migration notes.

One thing I just learned is how the Lambda/Cloud Functions/Azure ApolloServer constructors default engine.sendReportsImmediately to true. I think a better way to implement this would be:

  • ApolloServer defines an overloadable boolean method with some name like serverLifetimeIsShort() (this is not a very good name). The base class version returns false.
  • The Lambda etc ApolloServer objects override these to return true.
  • We add a boolean of the same name to GraphQLServiceContext (ie, the argument to serverWillStart)
  • The usage reporting plugin defaults sendReportsImmediately to true if it is not explicitly specified and if serverLifetimeIsShort

Does that sound like a reasonable thing to have in the API? I think it may be helpful to other plugins as well. Do you have a better name in mind?

glasser and others added 10 commits September 18, 2020 14:38
Primarily docs, comments, and strings changes. Fix an URL added incorrectly in
this PR. Remove a comment mentioning GraphQLServerOptions.reporting which was
removed in v2.15.0.
Delete maskErrorDetails integration test and replace it with a unit test.
it's helpful to have an actionable message when something goes wrong
aside from a simple "contact support" if there is a way to achieve
the action that caused the error. in this case, the user is likely
trying to set up Apollo Studio with their service, which they can do
if they follow along with the documentation. this change adds a link
to said documentation to immediately unblock users who run into this
error.
This doesn't change the run-time mechanism but does remove it from the public
ApolloServerPlugin interface while still allowing for type safety within this
package.
There are very few uses of `logger.info` in Apollo Server (though custom plugins
could use it). This mostly means that startup logs in the plugins will be
visible by default.

Users can override this by providing their own `logger`.
The Apollo API has recently changed such that `reportServerInfo` will now return
error for schemas that don't parse or validate.

This PR changes `apollo-engine-reporting` to check in the `EngineReportingAgent`
constructor whether override schemas successfully parse and validate, so that we
may return early before talking to the Apollo API.

(Originally #4492 by @sachindshinde, merged directly into release-2.18.0;
because the main change being released with v2.18 is #4453 which entirely
replaced the file which that PR changed, this is being reconstructed as part of
PR #4453.)
@glasser glasser changed the base branch from main to release-2.18.0 September 18, 2020 21:39
@glasser glasser merged commit 9501238 into release-2.18.0 Sep 18, 2020
@glasser glasser deleted the glasser/apollo-config branch September 18, 2020 21:48
glasser added a commit to apollographql/federation that referenced this pull request Sep 22, 2020
apollographql/apollo-server#4453 removes the old product
name "Engine" from the Apollo Server API. Most relevantly for the JS Gateway:

- The package `apollo-engine-reporting-protobuf` (used in the gateway to build
  federated traces) is renamed to `apollo-reporting-protobuf`.

- The `GraphQLService` interface implemented by `ApolloGateway` now passes
  configuration related to Apollo's hosted services in the `apollo` key instead of
  the `engine` key, and the values in that object change slightly
  too (`apiKeyHash` changes to `keyHash`, and `graphVariant` is always
  provided). Apollo Server continues to pass the old `engine` value as well for
  compatibility purposes. After this change, `ApolloGateway` can read its
  configuration from either key too.

There is no constraint on the order in which you upgrade Apollo Server and
Apollo Gateway, though it is still a good idea to upgrade them at the same
time (eg, so you only are using one protobuf package).

Also update a comment in federation-js, and upgrade the top-level
`@types/node-fetch` dependency so that (after running `npm dedup`) there's no
need for a `gateway-js/node_modules` directory.
glasser added a commit to apollographql/federation that referenced this pull request Sep 22, 2020
apollographql/apollo-server#4453 removes the old product
name "Engine" from the Apollo Server API. Most relevantly for the JS Gateway:

- The package `apollo-engine-reporting-protobuf` (used in the gateway to build
  federated traces) is renamed to `apollo-reporting-protobuf`.

- The `GraphQLService` interface implemented by `ApolloGateway` now passes
  configuration related to Apollo's hosted services in the `apollo` key instead of
  the `engine` key, and the values in that object change slightly
  too (`apiKeyHash` changes to `keyHash`, and `graphVariant` is always
  provided). Apollo Server continues to pass the old `engine` value as well for
  compatibility purposes. After this change, `ApolloGateway` can read its
  configuration from either key too.

There is no constraint on the order in which you upgrade Apollo Server and
Apollo Gateway, though it is still a good idea to upgrade them at the same
time (eg, so you only are using one protobuf package).

Also update a comment in federation-js, and upgrade the top-level
`@types/node-fetch` dependency so that (after running `npm dedup`) there's no
need for a `gateway-js/node_modules` directory.
glasser added a commit to apollographql/federation that referenced this pull request Sep 22, 2020
apollographql/apollo-server#4453 removes the old product
name "Engine" from the Apollo Server API. Most relevantly for the JS Gateway:

- The package `apollo-engine-reporting-protobuf` (used in the gateway to build
  federated traces) is renamed to `apollo-reporting-protobuf`.

- The `GraphQLService` interface implemented by `ApolloGateway` now passes
  configuration related to Apollo's hosted services in the `apollo` key instead of
  the `engine` key, and the values in that object change slightly
  too (`apiKeyHash` changes to `keyHash`, and `graphVariant` is always
  provided). Apollo Server continues to pass the old `engine` value as well for
  compatibility purposes. After this change, `ApolloGateway` can read its
  configuration from either key too.

There is no constraint on the order in which you upgrade Apollo Server and
Apollo Gateway, though it is still a good idea to upgrade them at the same
time (eg, so you only are using one protobuf package).

Also update a comment in federation-js, and upgrade the top-level
`@types/node-fetch` dependency so that (after running `npm dedup`) there's no
need for a `gateway-js/node_modules` directory.
glasser added a commit that referenced this pull request Sep 22, 2020
In #4561 we removed `@apollo/federation` and `@apollo/gateway` from the
apollo-server repo. This means that the acephei example doesn't know where to
import them from. Adding them to examples/acephei/package.json led to very
strange errors so let's just add them as top-level dev dependencies.

Additionally, removing `@apollo/federation` seemed to make it so that TypeScript
can't find `apollo-env` any more, but it also seems like maybe the references to
it in the tsconfig.json lines aren't necessary? Not sure what's going on here
but leaving them in led to build errors and removing them didn't.

Finally, update the uses of `engine:` to use the new post-#4453 APIs, and spell
the version "set up" as two words.
glasser added a commit to apollographql/federation that referenced this pull request Sep 22, 2020
apollographql/apollo-server#4453 removes the old product
name "Engine" from the Apollo Server API. Most relevantly for the JS Gateway:

- The package `apollo-engine-reporting-protobuf` (used in the gateway to build
  federated traces) is renamed to `apollo-reporting-protobuf`.

- The `GraphQLService` interface implemented by `ApolloGateway` now passes
  configuration related to Apollo's hosted services in the `apollo` key instead of
  the `engine` key, and the values in that object change slightly
  too (`apiKeyHash` changes to `keyHash`, and `graphVariant` is always
  provided). Apollo Server continues to pass the old `engine` value as well for
  compatibility purposes. After this change, `ApolloGateway` can read its
  configuration from either key too.

There is no constraint on the order in which you upgrade Apollo Server and
Apollo Gateway, though it is still a good idea to upgrade them at the same
time (eg, so you only are using one protobuf package).

Also update a comment in federation-js, and upgrade the top-level
`@types/node-fetch` dependency so that (after running `npm dedup`) there's no
need for a `gateway-js/node_modules` directory.
glasser added a commit that referenced this pull request Sep 22, 2020
In #4561 we removed `@apollo/federation` and `@apollo/gateway` from the
apollo-server repo. This means that the acephei example doesn't know where to
import them from. Adding them to examples/acephei/package.json led to very
strange errors so let's just add them as top-level dev dependencies.

Additionally, removing `@apollo/federation` seemed to make it so that TypeScript
can't find `apollo-env` any more, but it also seems like maybe the references to
it in the tsconfig.json lines aren't necessary? Not sure what's going on here
but leaving them in led to build errors and removing them didn't.

Finally, update the uses of `engine:` to use the new post-#4453 APIs, and spell
the version "set up" as two words.
glasser added a commit to apollographql/federation that referenced this pull request Sep 24, 2020
apollographql/apollo-server#4453 removes the old product
name "Engine" from the Apollo Server API. Most relevantly for the JS Gateway:

- The package `apollo-engine-reporting-protobuf` (used in the gateway to build
  federated traces) is renamed to `apollo-reporting-protobuf`.

- The `GraphQLService` interface implemented by `ApolloGateway` now passes
  configuration related to Apollo's hosted services in the `apollo` key instead of
  the `engine` key, and the values in that object change slightly
  too (`apiKeyHash` changes to `keyHash`, and `graphVariant` is always
  provided). Apollo Server continues to pass the old `engine` value as well for
  compatibility purposes. After this change, `ApolloGateway` can read its
  configuration from either key too.

There is no constraint on the order in which you upgrade Apollo Server and
Apollo Gateway, though it is still a good idea to upgrade them at the same
time (eg, so you only are using one protobuf package).

Also update a comment in federation-js, and upgrade the top-level
`@types/node-fetch` dependency so that (after running `npm dedup`) there's no
need for a `gateway-js/node_modules` directory.
abernix added a commit that referenced this pull request Oct 5, 2020
Relates to #4453.

Co-authored-by: Jesse Rosenberger <git@jro.cc>
glasser pushed a commit that referenced this pull request Oct 5, 2020
Relates to #4453.

Co-authored-by: Jesse Rosenberger <git@jro.cc>
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Apr 21, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants