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

Add composer.json resolver #151

Merged
merged 4 commits into from
Nov 23, 2023
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
15 changes: 15 additions & 0 deletions build/docker/alpine.Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -51,5 +51,20 @@ RUN apk --no-cache --update add \

RUN dotnet --version

RUN apk add --no-cache \
git \
php82 \
php82-curl \
php82-mbstring \
php82-openssl \
php82-phar \
&& ln -s /usr/bin/php82 /usr/bin/php

RUN apk add --no-cache --virtual build-dependencies curl && \
curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/bin --filename=composer \
&& apk del build-dependencies

RUN php -v && composer --version

# Put copy at the end to speedup Docker build by caching previous RUNs and run those concurrently
COPY --from=dev /cli/debricked /usr/bin/debricked
17 changes: 17 additions & 0 deletions build/docker/debian.Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -81,5 +81,22 @@ RUN apt -y update && apt -y upgrade && apt -y install openjdk-11-jre \

RUN dotnet --version

RUN apt update -y && \
apt install lsb-release apt-transport-https ca-certificates software-properties-common -y && \
curl -o /etc/apt/trusted.gpg.d/php.gpg https://packages.sury.org/php/apt.gpg && \
sh -c 'echo "deb https://packages.sury.org/php/ $(lsb_release -sc) main" > /etc/apt/sources.list.d/php.list' && \
apt -y clean && rm -rf /var/lib/apt/lists/*

RUN apt -y update && apt -y install \
php8.2 \
php8.2-curl \
php8.2-mbstring \
php8.2-phar && \
apt -y clean && rm -rf /var/lib/apt/lists/*

RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/bin --filename=composer

RUN php -v && composer --version

# Put copy at the end to speedup Docker build by caching previous RUNs and run those concurrently
COPY --from=dev /cli/debricked /usr/bin/debricked
47 changes: 47 additions & 0 deletions internal/resolution/pm/composer/cmd_factory.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package composer

import (
"os"
"os/exec"
"path/filepath"
)

type ICmdFactory interface {
MakeInstallCmd(command string, file string) (*exec.Cmd, error)
}

type IExecPath interface {
LookPath(file string) (string, error)
}

type ExecPath struct {
}

func (ExecPath) LookPath(file string) (string, error) {
return exec.LookPath(file)
}

type CmdFactory struct {
execPath IExecPath
}

func (cmdf CmdFactory) MakeInstallCmd(command string, file string) (*exec.Cmd, error) {
path, err := cmdf.execPath.LookPath(command)

fileDir := filepath.Dir(file)

return &exec.Cmd{
Path: path,
Args: []string{command, "update",
"--no-interaction", // We can't answer any prompts...
"--no-scripts", // Avoid risky scripts
"--ignore-platform-reqs", // We won't run the code, so we don't care about the platform
"--no-autoloader", // We won't execute any code, no need for autoloader
"--no-install", // No need to install packages
"--no-plugins", // We won't run the code, so no plugins needed
"--no-audit", // We don't want to run an audit
sweoggy marked this conversation as resolved.
Show resolved Hide resolved
},
Dir: fileDir,
sweoggy marked this conversation as resolved.
Show resolved Hide resolved
Env: os.Environ(),
}, err
}
19 changes: 19 additions & 0 deletions internal/resolution/pm/composer/cmd_factory_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package composer

import (
"testing"

"github.com/stretchr/testify/assert"
)

func TestMakeInstallCmd(t *testing.T) {
composerCommand := "composer"
cmd, err := CmdFactory{
execPath: ExecPath{},
}.MakeInstallCmd(composerCommand, "file")
assert.NoError(t, err)
assert.NotNil(t, cmd)
args := cmd.Args
assert.Contains(t, args, "composer")
assert.Contains(t, args, "update")
}
62 changes: 62 additions & 0 deletions internal/resolution/pm/composer/job.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package composer

import (
"github.com/debricked/cli/internal/resolution/job"
)

const (
composer = "composer"
)

type Job struct {
job.BaseJob
install bool
composerCommand string
cmdFactory ICmdFactory
}

func NewJob(
file string,
install bool,
cmdFactory ICmdFactory,
) *Job {
return &Job{
BaseJob: job.NewBaseJob(file),
install: install,
cmdFactory: cmdFactory,
}
}

func (j *Job) Install() bool {
return j.install
}

func (j *Job) Run() {
if j.install {

j.SendStatus("installing dependencies")
_, err := j.runInstallCmd()
if err != nil {
j.Errors().Critical(err)

return
}
}

}

func (j *Job) runInstallCmd() ([]byte, error) {

j.composerCommand = composer
installCmd, err := j.cmdFactory.MakeInstallCmd(j.composerCommand, j.GetFile())
if err != nil {
return nil, err
}

installCmdOutput, err := installCmd.Output()
if err != nil {
return nil, j.GetExitError(err)
}

return installCmdOutput, nil
}
64 changes: 64 additions & 0 deletions internal/resolution/pm/composer/job_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package composer

import (
"errors"
"testing"

jobTestdata "github.com/debricked/cli/internal/resolution/job/testdata"
"github.com/debricked/cli/internal/resolution/pm/composer/testdata"
"github.com/stretchr/testify/assert"
)

const (
badName = "bad-name"
)

func TestNewJob(t *testing.T) {
j := NewJob("file", false, CmdFactory{
execPath: ExecPath{},
})
assert.Equal(t, "file", j.GetFile())
assert.False(t, j.Errors().HasError())
}

func TestRunInstall(t *testing.T) {
cmdFactoryMock := testdata.NewEchoCmdFactory()
j := NewJob("file", false, cmdFactoryMock)

_, err := j.runInstallCmd()
assert.NoError(t, err)

assert.False(t, j.Errors().HasError())
}

func TestInstall(t *testing.T) {
j := Job{install: true}
assert.Equal(t, true, j.Install())

j = Job{install: false}
assert.Equal(t, false, j.Install())
}

func TestRunInstallCmdErr(t *testing.T) {
cmdErr := errors.New("cmd-error")
cmdFactoryMock := testdata.NewEchoCmdFactory()
cmdFactoryMock.MakeInstallErr = cmdErr
j := NewJob("file", true, cmdFactoryMock)

go jobTestdata.WaitStatus(j)
j.Run()

assert.Len(t, j.Errors().GetAll(), 1)
assert.Contains(t, j.Errors().GetAll(), cmdErr)
}

func TestRunInstallCmdOutputErr(t *testing.T) {
cmdMock := testdata.NewEchoCmdFactory()
cmdMock.InstallCmdName = badName
j := NewJob("file", true, cmdMock)

go jobTestdata.WaitStatus(j)
j.Run()

jobTestdata.AssertPathErr(t, j.Errors())
}
23 changes: 23 additions & 0 deletions internal/resolution/pm/composer/pm.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package composer

const Name = "composer"

type Pm struct {
name string
}

func NewPm() Pm {
return Pm{
name: Name,
}
}

func (pm Pm) Name() string {
return pm.name
}

func (Pm) Manifests() []string {
return []string{
`composer\.json$`,
}
}
40 changes: 40 additions & 0 deletions internal/resolution/pm/composer/pm_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package composer

import (
"regexp"
"testing"

"github.com/stretchr/testify/assert"
)

func TestNewPm(t *testing.T) {
pm := NewPm()
assert.Equal(t, Name, pm.name)
}

func TestName(t *testing.T) {
pm := NewPm()
assert.Equal(t, Name, pm.Name())
}

func TestManifests(t *testing.T) {
pm := Pm{}
manifests := pm.Manifests()
assert.Len(t, manifests, 1)
manifest := manifests[0]
assert.Equal(t, `composer\.json$`, manifest)
_, err := regexp.Compile(manifest)
assert.NoError(t, err)

cases := map[string]bool{
"composer.json": true,
"composer.lock": false,
"package-lock.json": false,
}
for file, isMatch := range cases {
t.Run(file, func(t *testing.T) {
matched, _ := regexp.MatchString(manifest, file)
assert.Equal(t, isMatch, matched)
})
}
}
29 changes: 29 additions & 0 deletions internal/resolution/pm/composer/strategy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package composer

import (
"github.com/debricked/cli/internal/resolution/job"
)

type Strategy struct {
files []string
}

func (s Strategy) Invoke() ([]job.IJob, error) {
var jobs []job.IJob
for _, file := range s.files {
jobs = append(jobs, NewJob(
file,
true,
CmdFactory{
execPath: ExecPath{},
},
),
)
}

return jobs, nil
}

func NewStrategy(files []string) Strategy {
return Strategy{files}
}
43 changes: 43 additions & 0 deletions internal/resolution/pm/composer/strategy_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package composer

import (
"testing"

"github.com/stretchr/testify/assert"
)

func TestNewStrategy(t *testing.T) {
s := NewStrategy(nil)
assert.NotNil(t, s)
assert.Len(t, s.files, 0)

s = NewStrategy([]string{})
assert.NotNil(t, s)
assert.Len(t, s.files, 0)

s = NewStrategy([]string{"file"})
assert.NotNil(t, s)
assert.Len(t, s.files, 1)

s = NewStrategy([]string{"file-1", "file-2"})
assert.NotNil(t, s)
assert.Len(t, s.files, 2)
}

func TestInvokeNoFiles(t *testing.T) {
s := NewStrategy([]string{})
jobs, _ := s.Invoke()
assert.Empty(t, jobs)
}

func TestInvokeOneFile(t *testing.T) {
s := NewStrategy([]string{"file"})
jobs, _ := s.Invoke()
assert.Len(t, jobs, 1)
}

func TestInvokeManyFiles(t *testing.T) {
s := NewStrategy([]string{"file-1", "file-2"})
jobs, _ := s.Invoke()
assert.Len(t, jobs, 2)
}
20 changes: 20 additions & 0 deletions internal/resolution/pm/composer/testdata/cmd_factory_mock.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package testdata

import (
"os/exec"
)

type CmdFactoryMock struct {
InstallCmdName string
MakeInstallErr error
}

func NewEchoCmdFactory() CmdFactoryMock {
return CmdFactoryMock{
InstallCmdName: "echo",
}
}

func (f CmdFactoryMock) MakeInstallCmd(command string, file string) (*exec.Cmd, error) {
return exec.Command(f.InstallCmdName), f.MakeInstallErr
}
Loading