-
Notifications
You must be signed in to change notification settings - Fork 2k
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
Conversation
One note: I definitely want to make the tests not print a zillion |
2faa90d
to
3d61611
Compare
69b390f
to
526443a
Compare
Added a small number of explicit |
Naming thought: ApolloConfigInput becomes ApolloConfig, ApolloConfig becomes ApolloContext? |
Updated this PR's description to be broader than the first step (the existing commit's description maintains) and added a checklist. |
f0b08ea
to
0d94ec6
Compare
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!, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I know this is WIP, just noting FIXMEs for later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks. It's in the PR description checklist too.
// 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', () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I had the same thought, but there's a beforeEach/afterEach above.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@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.)
process.env.ENGINE_API_KEY = 'just:fake:stuff'; | ||
process.env.APOLLO_KEY = 'also:fake:stuff'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same concern here, maybe.
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.', | ||
); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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:
- they're identical (safe)
- we just decide that newer config option takes more precedence (arguably less safe)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Executable schema generally means an actual GraphQLSchema
, so the naming here is a bit misleading. printedSchema
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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!, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems like this shouldn't be necessary, we assert apollo.key
's existence above.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It 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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// This class is a helper for ApoloServerPluginUsageReporting and | |
// This class is a helper for ApolloServerPluginUsageReporting and |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks
3d61611
to
67dec32
Compare
9692ee5
to
9c352bb
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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, likeapollo-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'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@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.)
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.', | ||
); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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 { cache: apqCache = requestOptions.cache!, ...apqOtherOptions } = | ||
requestOptions.persistedQueries || Object.create(null); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks a lot like Prettier ran on this file and made a bunch of changes. This one in particular certainly is questionable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
PR is now de-prettiered (or well, the part outside of core/src/plugin).
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."); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure how you feel about backing these out, but it would be nice.
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'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just my same concern about Node.js APIs being now in the direct line of imports / module resolution.
packages/apollo-server-core/src/plugin/schemaReporting/schemaReporter.ts
Show resolved
Hide resolved
packages/apollo-server-core/src/plugin/schemaReporting/schemaReporter.ts
Show resolved
Hide resolved
67dec32
to
dc5b8f5
Compare
dd2e81f
to
73700cb
Compare
@abernix This is moderately close to "code complete" (I've finished and fixed up the commit that gets rid of One thing I just learned is how the Lambda/Cloud Functions/Azure ApolloServer constructors default
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? |
73700cb
to
4953b86
Compare
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.
(By avery and glasser!)
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.)
8a17501
to
a897b59
Compare
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.
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.
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.
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.
Relates to #4453. Co-authored-by: Jesse Rosenberger <git@jro.cc>
Relates to #4453. Co-authored-by: Jesse Rosenberger <git@jro.cc>
Currently, the EngineReportingAgent is deeply embedded in ApolloServer, configured via a special
engine
option tonew 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 anapollo
argument that's used only to configure the API key and graph ID/variant, not for detailed configuration of reporting semantics. Other than thisapollo
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.apollo
constructor argument and use it in existing plugins/gatewayengine
optionapollo
, etc)engine
in the repocurrent
into apollo-server more than it already is@apollo/federation
and@apollo/gateway
from theapollo-server
repository. federation#134 merges?)