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

Conditional at Launch #38

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft

Conditional at Launch #38

wants to merge 1 commit into from

Conversation

nebhale
Copy link
Member

@nebhale nebhale commented Nov 13, 2020

Previously, a binding was required at build time in order to contribute the layers for the Stackdriver components. We've started to think that requiring a binding during build is too onerous a restriction and instead environment variables should be used to indicate that a dependency should be contributed. The knock-on effect of this change is that we have to move a lot more of the conditional logic out to launch time including ensuring that if the binding doesn't exist nothing is contributed. This change makes those updates to the buildpack's behavior.

In addition, this change also includes an update that enables the buildpack to determine if the application is running in GCP by detecting the metadata server. In this case, no binding is required at launch time either as the credentials typically provided by the binding are instead provided by the metadata server.

Finally, this includes a fix where the provided build plan wasn't anywhere near exhaustive for combinations that a user need when using the buildpack.

[#2]

@nebhale nebhale added semver:major A change requiring a major version bump type:enhancement A general enhancement labels Nov 13, 2020
@nebhale nebhale requested a review from ekcasey November 13, 2020 19:17
helper/in_gcp.go Outdated Show resolved Hide resolved
helper/java_debugger.go Outdated Show resolved Hide resolved
helper/java_debugger.go Outdated Show resolved Hide resolved
stackdriver/build_test.go Outdated Show resolved Hide resolved
stackdriver/detect.go Outdated Show resolved Hide resolved
README.md Outdated Show resolved Hide resolved
helper/nodejs_profiler.go Outdated Show resolved Hide resolved
helper/java_debugger.go Outdated Show resolved Hide resolved
helper/credentials.go Outdated Show resolved Hide resolved
helper/credentials.go Outdated Show resolved Hide resolved
@ekcasey
Copy link
Member

ekcasey commented Nov 16, 2020

I think we may need to explicitly exclude add the metadata server host to no_proxy if we want our InGCP function to play nice with a user-configured proxy at runtime. paketo-buildpacks/azure-application-insights#32 (review)

@nebhale nebhale requested a review from ekcasey November 17, 2020 21:53
@nebhale nebhale force-pushed the conditional-at-launch branch 6 times, most recently from d1e647b to 1c37f23 Compare November 18, 2020 00:52
Previously, a binding was required at build time in order to contribute the
layers for the Stackdriver components.  We've started to think that requiring
a binding during build is too onerous a restriction and instead environment
variables should be used to indicate that a dependency should be contributed.
The knock-on effect of this change is that we have to move a lot more of the
conditional logic out to launch time including ensuring that if the binding
doesn't exist _nothing_ is contributed.  This change makes those updates to
the buildpack's behavior.

In addition, this change also includes an update that enables the buildpack to
determine if the application is running in GCP by detecting the metadata
server.  In this case, no binding is required at launch time either as the
credentials typically provided by the binding are instead provided by the
metadata server.

Finally, this includes a fix where the provided build plan wasn't anywhere
near exhaustive for combinations that a user need when using the buildpack.

Signed-off-by: Ben Hale <bhale@vmware.com>
Copy link
Member

@ekcasey ekcasey left a comment

Choose a reason for hiding this comment

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

Love the direction this is going! But I still have a couple questions.

| `$BP_GOOGLE_CLOUD_DEBUGGER_ENABLED` | Whether to add Google Cloud Debugger during build
| `$BP_GOOGLE_CLOUD_PROFILER_ENABLED` | Whether to add Google Cloud Profiler during build
| `$BPL_GOOGLE_CLOUD_MODULE` | Configure the name of the application (required)
| `$BPL_GOOGLE_CLOUD_PROJECT_ID` | Configure the project id for the application (required if running outside of Google Cloud)
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 I made the opposite comment earlier but you mentioned that the project ID is now included in the service account key and therefore is not actually required in all situations. If we wanted to be clever (maybe too clever) we could pull the project ID out of the key name or project_id fields https://cloud.google.com/iam/docs/creating-managing-service-account-keys.

### Type: `GoogleCloud`
|Key | Value | Description
|-------------------------|------------------|------------
|`ApplicationCredentials` | `<JSON Payload>` | Google Cloud Application Credentials in JSON form
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
|`ApplicationCredentials` | `<JSON Payload>` | Google Cloud Application Credentials in JSON form
|`ApplicationCredentials` | `<JSON Payload>` | Google Cloud [Service Account Key][s] (JSON)
[s]: https://cloud.google.com/iam/docs/creating-managing-service-account-keys

I think using the more specific terminology and a link will help folks understand the workflow more quickly.

Comment on lines +26 to +30
Credentials = "google-cloud-credentials"
DebuggerJava = "google-cloud-debugger-java"
DebuggerNodeJS = "google-cloud-debugger-nodejs"
ProfilerJava = "google-cloud-profiler-java"
ProfilerNodeJS = "google-cloud-profiler-nodejs"
Copy link
Member

Choose a reason for hiding this comment

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

All of the plan entry constants are only used in the google package. Why not move them there?


const (
Credentials = "google-cloud-credentials"
DebuggerJava = "google-cloud-debugger-java"
Copy link
Member

Choose a reason for hiding this comment

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

I like provide a common prefix for a set of related constants. What do you think about prefixing all plan entry constants with PlanEntry (e.g. PlanEntryJavaDebugger) so it is clear what the constant is for.

Other prefix suggestions:

  • Env* (e.g. EnvModule)
  • Source* (e.g. SourceBinding)

)

if hasMetadataServer() {
c = common.MetadataServer
Copy link
Member

Choose a reason for hiding this comment

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

Wouldn't we want the binding to take precedence over the metadata server?

Comment on lines +49 to +64
Provides: []libcnb.BuildPlanProvide{
{Name: common.Credentials},
{Name: common.DebuggerJava},
{Name: common.ProfilerJava},
{Name: common.DebuggerNodeJS},
{Name: common.ProfilerNodeJS},
},
Requires: []libcnb.BuildPlanRequire{
{Name: common.Credentials},
{Name: common.DebuggerJava},
{Name: common.ProfilerJava},
{Name: "jvm-application"},
{Name: common.DebuggerNodeJS},
{Name: common.ProfilerNodeJS},
{Name: "node", Metadata: map[string]interface{}{"build": true}},
},
Copy link
Member

Choose a reason for hiding this comment

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

Looking at this more carefully, won't we always select this plan if both jvm-application and node are provided by other buildpacks, rendering the subsequent two plans in the debugger && profiler conditional moot?

{Name: "jvm-application"},
{Name: common.DebuggerNodeJS},
{Name: common.ProfilerNodeJS},
{Name: "node", Metadata: map[string]interface{}{"build": true}},
Copy link
Member

Choose a reason for hiding this comment

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

This is interesting.... if node is provided by another buildpack but only at build time (i.e node is necessary at build time but the resulting app is not a NodeJS application we probably don't want to install the NodeJS profiler and debugger. However, the https://github.com/paketo-buildpacks/npm-start and https://github.com/paketo-buildpacks/yarn-start buildpacks don't seem to provide anything we can key off of that would be analogous to jvm-application.

)

func IsModuleRequired(module string, content []byte) (bool, error) {
p := fmt.Sprintf(`require\(['"]%s['"]\)`, module)
Copy link
Member

Choose a reason for hiding this comment

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

I am not sure what all the allowed characters are in a module name; is there any danger that special characters in the name could prevent the regex from compiling? Some alternatives would be:

  1. add a capture group and then compare the matched module names to module.
  2. use two naive strings.Contains, one with "s and one with 's and return if either is true

Comment on lines +38 to +48
t, err := template.New("require-module").
Parse(`require('{{.Module}}').start({
serviceContext: {
service: '{{.Service}}',
version: '{{.Version}}',
},
});
`)
if err != nil {
return nil, fmt.Errorf("unable to parse template\n%w", err)
}
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
t, err := template.New("require-module").
Parse(`require('{{.Module}}').start({
serviceContext: {
service: '{{.Service}}',
version: '{{.Version}}',
},
});
`)
if err != nil {
return nil, fmt.Errorf("unable to parse template\n%w", err)
}
t := template.Must(template.New("require-module").
Parse(`require('{{.Module}}').start({
serviceContext: {
service: '{{.Service}}',
version: '{{.Version}}',
},
});
`))

Given the template is constant it is safe to use a Must here.

Comment on lines +59 to +70
t, err := template.New("require-module-external").
Parse(`require('{{.Module}}').start({
projectId: '{{.ProjectId}}',
serviceContext: {
service: '{{.Service}}',
version: '{{.Version}}',
},
});
`)
if err != nil {
return nil, fmt.Errorf("unable to parse template\n%w", err)
}
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
t, err := template.New("require-module-external").
Parse(`require('{{.Module}}').start({
projectId: '{{.ProjectId}}',
serviceContext: {
service: '{{.Service}}',
version: '{{.Version}}',
},
});
`)
if err != nil {
return nil, fmt.Errorf("unable to parse template\n%w", err)
}
t := template.Must(template.New("require-module-external").
Parse(`require('{{.Module}}').start({
projectId: '{{.ProjectId}}',
serviceContext: {
service: '{{.Service}}',
version: '{{.Version}}',
},
});
`))

@dmikusa dmikusa self-requested a review July 13, 2021 12:35
@dmikusa dmikusa marked this pull request as ready for review December 17, 2021 14:13
@dmikusa dmikusa requested a review from a team December 17, 2021 14:13
@dmikusa dmikusa marked this pull request as draft December 17, 2021 14:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
semver:major A change requiring a major version bump type:enhancement A general enhancement
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Ability to contribute the layer and configure the startup even w/o the credentials binding
3 participants