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

Implementation of HTTPD + FPM use case #4

Merged
merged 6 commits into from
Mar 22, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions NOTICE
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
Copyright (c) 2018-Present CloudFoundry.org Foundation, Inc. All Rights Reserved.

This project is licensed to you under the Apache License, Version 2.0 (the "License").
You may not use this project except in compliance with the License.

This project may include a number of subcomponents with separate copyright notices
and license terms. Your use of these subcomponents is subject to the terms and
conditions of the subcomponent's license, as noted in the LICENSE file.
74 changes: 72 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,72 @@
# php-start
PHP Start Command CNB
# PHP Start Cloud Native Buildpack
## `gcr.io/paketo-buildpacks/php-start`

A Cloud Native Buildpack for running HTTPD, Nginx, and/or FPM start commands
for PHP apps.

## Behavior

This buildpack will always participate if it's `requirements` are met. In the
HTTPD server case `requires` `php`, `php-fpm` optionally, `httpd`, and
`httpd-config`. In the Nginx case, it will require `nginx` and `nginx-config`
instead of `httpd` and `httpd-config`. These requirements will be met when used
in conjunction with the other buildpacks in the Paketo PHP language family.

| Requirement | Build | Launch |
|--------------------|-------|--------|
| `php` | x | |
| `php-fpm` | x | x |
| `httpd` or `nginx` | x | |
| `httpd-config` or `nginx-config` | x | x |


It will set the default start command to something that looks like:
```
procmgr-binary /layers/paketo-buildpacks_php-start/php-start/procs.yml
```

The `procmgr-binary` is a process manager that will run multiple start commands
on the container. This is done to allow for FPM to run on the container
alongside the web server. The `procs.yml` file it runs with contains the
commands and arguments for both `php-fpm` and the web-server.

When the buildpack runs, you will see in the logs what processes are addded to
procs.yml.


## Integration

This CNB writes a start command, so there's currently no scenario we can
imagine that you would need to require it as dependency.

## Usage

To package this buildpack for consumption:

```
$ ./scripts/package.sh --version <version-number>
```

This will create a `buildpackage.cnb` file under the `build` directory which you
can use to build your app as follows:
```
pack build <app-name> -p <path-to-app> -b build/buildpackage.cnb
```

## Run Tests

To run all unit tests, run:
```
./scripts/unit.sh
```

To run all integration tests, run:
```
/scripts/integration.sh
```

## Debug Logs
For extra debug logs from the image build process, set the `$BP_LOG_LEVEL`
environment variable to `DEBUG` at build-time (ex. `pack build my-app --env
BP_LOG_LEVEL=DEBUG` or through a [`project.toml`
file](https://github.com/buildpacks/spec/blob/main/extensions/project-descriptor.md).
110 changes: 110 additions & 0 deletions build.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package phpstart

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

"github.com/paketo-buildpacks/packit/v2"
"github.com/paketo-buildpacks/packit/v2/fs"
"github.com/paketo-buildpacks/packit/v2/scribe"
)

//go:generate faux --interface ProcMgr --output fakes/procmgr.go

// ProcMgr manages all processes that the build phase may need to run by
// adding them to a procs.yml file for execution at launch time.
type ProcMgr interface {
Add(name string, proc Proc)
WriteFile(path string) error
}

// Build will return a packit.BuildFunc that will be invoked during the build
// phase of the buildpack lifecycle.
//
// It will create a layer dedicated to storing a process manager and YAML file
// of processes to run, since there are multiple process that could be run. The
// layer is available at and launch-time, and its contents are used in the
// image launch process.
func Build(procs ProcMgr, logger scribe.Emitter) packit.BuildFunc {
return func(context packit.BuildContext) (packit.BuildResult, error) {
logger.Title("%s %s", context.BuildpackInfo.Name, context.BuildpackInfo.Version)

logger.Debug.Process("Getting the layer associated with the server start command")
layer, err := context.Layers.Get("php-start")
if err != nil {
return packit.BuildResult{}, err
}
logger.Debug.Subprocess(layer.Path)
logger.Break()

layer, err = layer.Reset()
if err != nil {
return packit.BuildResult{}, err
}
layer.Launch = true

logger.Process("Determining start commands to include in procs.yml:")
// HTTPD Case
httpdConfPath := os.Getenv("PHP_HTTPD_PATH")
if httpdConfPath != "" {
serverProc := NewProc("httpd", []string{"-f", httpdConfPath, "-k", "start", "-DFOREGROUND"})
procs.Add("httpd", serverProc)
logger.Subprocess("HTTPD: %s %v", serverProc.Command, strings.Join(serverProc.Args, " "))
}

// FPM Case
fpmConfPath := os.Getenv("PHP_FPM_PATH")
if fpmConfPath != "" {
phprcPath, ok := os.LookupEnv("PHPRC")
if !ok {
return packit.BuildResult{}, errors.New("failed to lookup $PHPRC path for FPM")
}
fpmProc := NewProc("php-fpm", []string{"-y", fpmConfPath, "-c", phprcPath})
procs.Add("fpm", fpmProc)
logger.Subprocess("FPM: %s %v", fpmProc.Command, strings.Join(fpmProc.Args, " "))
}

// Write the process file
logger.Debug.Subprocess("Writing process file to %s", filepath.Join(layer.Path, "procs.yml"))
logger.Break()
err = procs.WriteFile(filepath.Join(layer.Path, "procs.yml"))
if err != nil {
return packit.BuildResult{}, err
}

// Make the process manager binary available in layer
logger.Debug.Process("Copying procmgr-binary into %s", filepath.Join(layer.Path, "bin", "procmgr-binary"))
logger.Debug.Break()
err = os.Mkdir(filepath.Join(layer.Path, "bin"), os.ModePerm)
if err != nil {
//untested
return packit.BuildResult{}, err
}

err = fs.Copy(filepath.Join(context.CNBPath, "bin", "procmgr-binary"), filepath.Join(layer.Path, "bin", "procmgr-binary"))
if err != nil {
return packit.BuildResult{}, fmt.Errorf("failed to copy procmgr-binary into layer: %w", err)
}

processes := []packit.Process{
{
Type: "web",
Command: "procmgr-binary",
Args: []string{filepath.Join(layer.Path, "procs.yml")},
Default: true,
Direct: true,
},
}
logger.LaunchProcesses(processes)

return packit.BuildResult{
Layers: []packit.Layer{layer},
Launch: packit.LaunchMetadata{
Processes: processes,
},
}, nil
}
}
Loading