Skip to content

Commit

Permalink
Conditional at Launch
Browse files Browse the repository at this point in the history
Previously, a binding was required at build time in order to contribute the
layers for the Application Insights 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 additional, this includes a fix where the provided build plan wasn't
exhaustive for combinations that a user need when using the buildpack.

Signed-off-by: Ben Hale <[email protected]>
  • Loading branch information
nebhale committed Nov 18, 2020
1 parent 17ba919 commit e807a20
Show file tree
Hide file tree
Showing 35 changed files with 1,008 additions and 533 deletions.
2 changes: 1 addition & 1 deletion .github/pipeline-descriptor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ codeowners:
owner: "@paketo-buildpacks/java-buildpacks"

package:
repository: gcr.io/paketo-buildpacks/azure-application-insights
repository: gcr.io/paketo-buildpacks/microsoft-azure
register: true
registry_token: ${{ secrets.JAVA_GITHUB_TOKEN }}

Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/create-package.yml
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ jobs:
--config "${HOME}"/package.toml
fi
env:
PACKAGE: gcr.io/paketo-buildpacks/azure-application-insights
PACKAGE: gcr.io/paketo-buildpacks/microsoft-azure
PUBLISH: "true"
VERSION: ${{ steps.version.outputs.version }}
- name: Update release with digest
Expand All @@ -164,7 +164,7 @@ jobs:
GITHUB_TOKEN: ${{ secrets.JAVA_GITHUB_TOKEN }}
- uses: docker://ghcr.io/buildpacks/actions/registry:main
with:
address: gcr.io/paketo-buildpacks/azure-application-insights@${{ steps.package.outputs.digest }}
id: paketo-buildpacks/azure-application-insights
address: gcr.io/paketo-buildpacks/microsoft-azure@${{ steps.package.outputs.digest }}
id: paketo-buildpacks/microsoft-azure
token: ${{ secrets.JAVA_GITHUB_TOKEN }}
version: ${{ steps.version.outputs.version }}
2 changes: 1 addition & 1 deletion NOTICE
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
azure-application-insights
microsoft-azure

Copyright (c) 2020-Present CloudFoundry.org Foundation, Inc. All Rights Reserved.

Expand Down
38 changes: 23 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,31 +1,39 @@
# `gcr.io/paketo-buildpacks/azure-application-insights`
The Paketo Azure Application Insights Buildpack is a Cloud Native Buildpack that contributes the Application Insights Agent and configures it to connect to the service.
# `paketo-buildpacks/microsoft-azure`
The Paketo Microsoft Azure Buildpack is a Cloud Native Buildpack that contributes Microsoft Azure agents and configures them to connec to their services.

## Behavior
This buildpack will participate if all the following conditions are met

* A binding exists with `type` of `ApplicationInsights`
* If `$BP_APPLICATION_INSIGHTS_ENABLED` is set to `true` and the application is Java
* At build time, contributes an agent to a layer
* At launch time, if credentials are available, configures the application to use the agent
* If `$BP_APPLICATION_INSIGHTS_ENABLED` is set to `true` and the application is NodeJS
* At build time, contributes an agent to a layer
* At launch time, if credentials are available, configures `$NODE_MODULES` with the agent path. If the main module does not already require `applicationinsights`, prepends the main module with `require('applicationinsights').start();`.

The buildpack will do the following for Java applications:
### Credential Availability
If the applications runs within Microsoft Azure and the [Azure Metadata Service][m] is accessible, those credentials will be used. If the application runs within any other environment, credentials must be provided with a service binding as described below.

* Contributes a Java agent to a layer and configures `JAVA_TOOL_OPTIONS` to use it
* Transforms the contents of the binding secret to environment variables with the pattern `APPINSIGHTS_<KEY>=<VALUE>`
[m]: https://docs.microsoft.com/en-us/azure/virtual-machines/windows/instance-metadata-service

The buildpack will do the following NodeJS applications:

* Contributes a NodeJS agent to a layer and configures `$NODE_MODULES` to use it
* If main module does not already require `appinsights` module, prepends the main module with `require('applicationinsights').start();`
* Transforms the contents of the binding secret to environment variables with the pattern `APPINSIGHTS_<KEY>=<VALUE>`

## License
This buildpack is released under version 2.0 of the [Apache License][a].
## Configuration
| Environment Variable | Description
| -------------------- | -----------
| `$BP_AZURE_APPLICATION_INSIGHTS_ENABLED` | Whether to add Microsoft Azure Application Insights during build

## Bindings
The buildpack optionally accepts the following bindings:

### Type: `MicrosoftAzure`
|Key | Value | Description
|---------------------|---------|------------
|`InstrumentationKey` | `<key>` | Azure Application Insights instrumentation key

### Type: `dependency-mapping`
|Key | Value | Description
|----------------------|---------|------------
|`<dependency-digest>` | `<uri>` | If needed, the buildpack will fetch the dependency with digest `<dependency-digest>` from `<uri>`

## License
This buildpack is released under version 2.0 of the [Apache License][a].

[a]: http://www.apache.org/licenses/LICENSE-2.0
31 changes: 31 additions & 0 deletions appinsights/init_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* Copyright 2018-2020 the original author or authors.
*
* 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.
*/

package appinsights_test

import (
"testing"

"github.com/sclevine/spec"
"github.com/sclevine/spec/report"
)

func TestUnit(t *testing.T) {
suite := spec.New("appinsights", spec.Report(report.Terminal{}))
suite("Java", testJava)
suite("NodeJS", testNodeJS)
suite.Run(t)
}
45 changes: 37 additions & 8 deletions insights/java_agent.go → appinsights/java.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,35 +14,39 @@
* limitations under the License.
*/

package insights
package appinsights

import (
"fmt"
"os"
"path/filepath"

"github.com/buildpacks/libcnb"

"github.com/paketo-buildpacks/libpak"
"github.com/paketo-buildpacks/libpak/bard"
"github.com/paketo-buildpacks/libpak/sherpa"
"github.com/paketo-buildpacks/microsoft-azure/internal/common"
)

type JavaAgent struct {
const AgentPath = "BPI_AZURE_APPLICATION_INSIGHTS_AGENT_PATH"

type JavaBuild struct {
BuildpackPath string
LayerContributor libpak.DependencyLayerContributor
Logger bard.Logger
}

func NewJavaAgent(buildpackPath string, dependency libpak.BuildpackDependency, cache libpak.DependencyCache,
plan *libcnb.BuildpackPlan) JavaAgent {
func NewJavaBuild(buildpackPath string, dependency libpak.BuildpackDependency, cache libpak.DependencyCache,
plan *libcnb.BuildpackPlan) JavaBuild {

return JavaAgent{
return JavaBuild{
BuildpackPath: buildpackPath,
LayerContributor: libpak.NewDependencyLayerContributor(dependency, cache, plan),
}
}

func (j JavaAgent) Contribute(layer libcnb.Layer) (libcnb.Layer, error) {
func (j JavaBuild) Contribute(layer libcnb.Layer) (libcnb.Layer, error) {
j.LayerContributor.Logger = j.Logger

return j.LayerContributor.Contribute(layer, func(artifact *os.File) (libcnb.Layer, error) {
Expand All @@ -52,7 +56,8 @@ func (j JavaAgent) Contribute(layer libcnb.Layer) (libcnb.Layer, error) {
if err := sherpa.CopyFile(artifact, file); err != nil {
return libcnb.Layer{}, fmt.Errorf("unable to copy %s to %s\n%w", artifact.Name(), file, err)
}
layer.LaunchEnvironment.Appendf("JAVA_TOOL_OPTIONS", " ", "-javaagent:%s", file)

layer.LaunchEnvironment.Default(AgentPath, file)

file = filepath.Join(j.BuildpackPath, "resources", "AI-Agent.xml")
in, err := os.Open(file)
Expand All @@ -70,6 +75,30 @@ func (j JavaAgent) Contribute(layer libcnb.Layer) (libcnb.Layer, error) {
}, libpak.LaunchLayer)
}

func (j JavaAgent) Name() string {
func (j JavaBuild) Name() string {
return j.LayerContributor.LayerName()
}

type JavaLaunch struct {
CredentialSource common.CredentialSource
Logger bard.Logger
}

// https://docs.microsoft.com/en-us/azure/azure-monitor/app/java-agent
func (j JavaLaunch) Execute() (map[string]string, error) {
if j.CredentialSource == common.None {
j.Logger.Info("Azure Application Insights disabled")
return nil, nil
}

p, err := sherpa.GetEnvRequired(AgentPath)
if err != nil {
return nil, err
}

j.Logger.Info("Azure Application Insights enabled")

return map[string]string{
"JAVA_TOOL_OPTIONS": sherpa.AppendToEnvVar("JAVA_TOOL_OPTIONS", " ", fmt.Sprintf("-javaagent:%s", p)),
}, nil
}
135 changes: 135 additions & 0 deletions appinsights/java_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
/*
* Copyright 2018-2020 the original author or authors.
*
* 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.
*/

package appinsights_test

import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"testing"

"github.com/buildpacks/libcnb"
. "github.com/onsi/gomega"
"github.com/sclevine/spec"

"github.com/paketo-buildpacks/libpak"
"github.com/paketo-buildpacks/microsoft-azure/appinsights"
"github.com/paketo-buildpacks/microsoft-azure/internal/common"
)

func testJava(t *testing.T, context spec.G, it spec.S) {
var (
Expect = NewWithT(t).Expect
)

context("Build", func() {
var (
ctx libcnb.BuildContext
)

it.Before(func() {
var err error

ctx.Buildpack.Path, err = ioutil.TempDir("", "appinsights-java-build-buildpack")
Expect(err).NotTo(HaveOccurred())

Expect(os.MkdirAll(filepath.Join(ctx.Buildpack.Path, "resources"), 0755)).To(Succeed())
Expect(ioutil.WriteFile(filepath.Join(ctx.Buildpack.Path, "resources", "AI-Agent.xml"), []byte{}, 0644)).
To(Succeed())

ctx.Layers.Path, err = ioutil.TempDir("", "appinsights-java-build-layers")
Expect(err).NotTo(HaveOccurred())
})

it.After(func() {
Expect(os.RemoveAll(ctx.Buildpack.Path)).To(Succeed())
Expect(os.RemoveAll(ctx.Layers.Path)).To(Succeed())
})

it("contributes Java agent", func() {
dep := libpak.BuildpackDependency{
URI: "https://localhost/stub-azure-application-insights-agent.jar",
SHA256: "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
}
dc := libpak.DependencyCache{CachePath: "testdata"}

layer, err := ctx.Layers.Layer("test-layer")
Expect(err).NotTo(HaveOccurred())

layer, err = appinsights.NewJavaBuild(ctx.Buildpack.Path, dep, dc, &libcnb.BuildpackPlan{}).Contribute(layer)
Expect(err).NotTo(HaveOccurred())

Expect(layer.Launch).To(BeTrue())
Expect(filepath.Join(layer.Path, "stub-azure-application-insights-agent.jar")).To(BeARegularFile())
Expect(filepath.Join(layer.Path, "AI-Agent.xml")).To(BeARegularFile())
Expect(layer.LaunchEnvironment[fmt.Sprintf("%s.default", appinsights.AgentPath)]).
To(Equal(filepath.Join(layer.Path, "stub-azure-application-insights-agent.jar")))
})

})

context("Launch", func() {
var (
l = appinsights.JavaLaunch{
CredentialSource: common.MetadataServer,
}
)

it.Before(func() {
Expect(os.Setenv(appinsights.AgentPath, "test-path")).To(Succeed())
})

it.After(func() {
Expect(os.Unsetenv(appinsights.AgentPath)).To(Succeed())
})

it("does not contribute if source is None", func() {
l.CredentialSource = common.None

Expect(l.Execute()).To(BeNil())
})

it("returns error if BPI_AZURE_APPLICATION_INSIGHTS_AGENT_PATH is not set", func() {
Expect(os.Unsetenv(appinsights.AgentPath)).To(Succeed())

_, err := l.Execute()
Expect(err).To(MatchError("$BPI_AZURE_APPLICATION_INSIGHTS_AGENT_PATH must be set"))
})

it("contributes JAVA_TOOL_OPTIONS", func() {
Expect(l.Execute()).To(Equal(map[string]string{"JAVA_TOOL_OPTIONS": "-javaagent:test-path"}))
})

context("existing $JAVA_TOOL_OPTIONS", func() {

it.Before(func() {
Expect(os.Setenv("JAVA_TOOL_OPTIONS", "test-java-tool-options")).To(Succeed())
})

it.After(func() {
Expect(os.Unsetenv("JAVA_TOOL_OPTIONS")).To(Succeed())
})

it("contributes JAVA_TOOL_OPTIONS", func() {
Expect(l.Execute()).To(Equal(map[string]string{
"JAVA_TOOL_OPTIONS": "test-java-tool-options -javaagent:test-path",
}))
})
})
})
}
Loading

0 comments on commit e807a20

Please sign in to comment.