mirror of
https://github.com/woodpecker-ci/woodpecker.git
synced 2024-11-25 19:31:05 +00:00
Make sure plugins only mount the workspace base in a predefinde location (#3933)
This commit is contained in:
parent
d6e3ebf051
commit
764329ed1d
10 changed files with 75 additions and 24 deletions
|
@ -523,7 +523,9 @@ For more details check the [services docs](./60-services.md).
|
||||||
|
|
||||||
## `workspace`
|
## `workspace`
|
||||||
|
|
||||||
The workspace defines the shared volume and working directory shared by all workflow steps. The default workspace matches the pattern `/woodpecker/src/github.com/octocat/hello-world`, based on your repository URL.
|
The workspace defines the shared volume and working directory shared by all workflow steps.
|
||||||
|
The default workspace base is `/woodpecker` and the path is extended with the repository URL (`src/{url-without-schema}`).
|
||||||
|
So an example would be `/woodpecker/src/github.com/octocat/hello-world`.
|
||||||
|
|
||||||
The workspace can be customized using the workspace block in the YAML file:
|
The workspace can be customized using the workspace block in the YAML file:
|
||||||
|
|
||||||
|
@ -540,6 +542,10 @@ The workspace can be customized using the workspace block in the YAML file:
|
||||||
- go test
|
- go test
|
||||||
```
|
```
|
||||||
|
|
||||||
|
:::note
|
||||||
|
Plugins will always have the workspace base at `/woodpecker`
|
||||||
|
:::
|
||||||
|
|
||||||
The base attribute defines a shared base volume available to all steps. This ensures your source code, dependencies and compiled binaries are persisted and shared between steps.
|
The base attribute defines a shared base volume available to all steps. This ensures your source code, dependencies and compiled binaries are persisted and shared between steps.
|
||||||
|
|
||||||
```diff
|
```diff
|
||||||
|
|
|
@ -47,6 +47,11 @@ steps:
|
||||||
## Plugin Isolation
|
## Plugin Isolation
|
||||||
|
|
||||||
Plugins are just pipeline steps. They share the build workspace, mounted as a volume, and therefore have access to your source tree.
|
Plugins are just pipeline steps. They share the build workspace, mounted as a volume, and therefore have access to your source tree.
|
||||||
|
While normal steps are all about arbitrary code execution, plugins should only allow the functions intended by the plugin author.
|
||||||
|
|
||||||
|
So there are a few limitations, like the workspace base is always mounted at `/woodpecker`, but the working directory is dynamically adjusted acordingly. So as user of a plugin you should not have to care about this.
|
||||||
|
|
||||||
|
Also instead of using environment variables the plugin should only care about one prefixed with `PLUGIN_` witch are the internaml representation of the **settings** ([read more](./20-creating-plugins.md)).
|
||||||
|
|
||||||
## Finding Plugins
|
## Finding Plugins
|
||||||
|
|
||||||
|
|
|
@ -523,7 +523,9 @@ For more details check the [services docs](./60-services.md).
|
||||||
|
|
||||||
## `workspace`
|
## `workspace`
|
||||||
|
|
||||||
The workspace defines the shared volume and working directory shared by all workflow steps. The default workspace matches the pattern `/woodpecker/src/github.com/octocat/hello-world`, based on your repository URL.
|
The workspace defines the shared volume and working directory shared by all workflow steps.
|
||||||
|
The default workspace base is `/woodpecker` and the path is extended with the repository URL (`src/{url-without-schema}`).
|
||||||
|
So an example would be `/woodpecker/src/github.com/octocat/hello-world`.
|
||||||
|
|
||||||
The workspace can be customized using the workspace block in the YAML file:
|
The workspace can be customized using the workspace block in the YAML file:
|
||||||
|
|
||||||
|
@ -540,6 +542,10 @@ The workspace can be customized using the workspace block in the YAML file:
|
||||||
- go test
|
- go test
|
||||||
```
|
```
|
||||||
|
|
||||||
|
:::note
|
||||||
|
Plugins will always have the workspace base at `/woodpecker`
|
||||||
|
:::
|
||||||
|
|
||||||
The base attribute defines a shared base volume available to all steps. This ensures your source code, dependencies and compiled binaries are persisted and shared between steps.
|
The base attribute defines a shared base volume available to all steps. This ensures your source code, dependencies and compiled binaries are persisted and shared between steps.
|
||||||
|
|
||||||
```diff
|
```diff
|
||||||
|
|
|
@ -47,6 +47,11 @@ steps:
|
||||||
## Plugin Isolation
|
## Plugin Isolation
|
||||||
|
|
||||||
Plugins are just pipeline steps. They share the build workspace, mounted as a volume, and therefore have access to your source tree.
|
Plugins are just pipeline steps. They share the build workspace, mounted as a volume, and therefore have access to your source tree.
|
||||||
|
While normal steps are all about arbitrary code execution, plugins should only allow the functions intended by the plugin author.
|
||||||
|
|
||||||
|
So there are a few limitations, like the workspace base is always mounted at `/woodpecker`, but the working directory is dynamically adjusted acordingly. So as user of a plugin you should not have to care about this.
|
||||||
|
|
||||||
|
Also instead of using environment variables the plugin should only care about one prefixed with `PLUGIN_` witch are the internaml representation of the **settings** ([read more](./20-creating-plugins.md)).
|
||||||
|
|
||||||
## Finding Plugins
|
## Finding Plugins
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,7 @@ package compiler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"path"
|
||||||
|
|
||||||
backend_types "go.woodpecker-ci.org/woodpecker/v2/pipeline/backend/types"
|
backend_types "go.woodpecker-ci.org/woodpecker/v2/pipeline/backend/types"
|
||||||
"go.woodpecker-ci.org/woodpecker/v2/pipeline/frontend/metadata"
|
"go.woodpecker-ci.org/woodpecker/v2/pipeline/frontend/metadata"
|
||||||
|
@ -98,8 +99,8 @@ type Compiler struct {
|
||||||
networks []string
|
networks []string
|
||||||
env map[string]string
|
env map[string]string
|
||||||
cloneEnv map[string]string
|
cloneEnv map[string]string
|
||||||
base string
|
workspaceBase string
|
||||||
path string
|
workspacePath string
|
||||||
metadata metadata.Metadata
|
metadata metadata.Metadata
|
||||||
registries []Registry
|
registries []Registry
|
||||||
secrets map[string]Secret
|
secrets map[string]Secret
|
||||||
|
@ -156,10 +157,10 @@ func (c *Compiler) Compile(conf *yaml_types.Workflow) (*backend_types.Config, er
|
||||||
// overrides the default workspace paths when specified
|
// overrides the default workspace paths when specified
|
||||||
// in the YAML file.
|
// in the YAML file.
|
||||||
if len(conf.Workspace.Base) != 0 {
|
if len(conf.Workspace.Base) != 0 {
|
||||||
c.base = conf.Workspace.Base
|
c.workspaceBase = path.Clean(conf.Workspace.Base)
|
||||||
}
|
}
|
||||||
if len(conf.Workspace.Path) != 0 {
|
if len(conf.Workspace.Path) != 0 {
|
||||||
c.path = conf.Workspace.Path
|
c.workspacePath = path.Clean(conf.Workspace.Path)
|
||||||
}
|
}
|
||||||
|
|
||||||
cloneImage := constant.DefaultCloneImage
|
cloneImage := constant.DefaultCloneImage
|
||||||
|
|
|
@ -61,13 +61,14 @@ func TestSecretAvailable(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCompilerCompile(t *testing.T) {
|
func TestCompilerCompile(t *testing.T) {
|
||||||
|
repoURL := "https://github.com/octocat/hello-world"
|
||||||
compiler := New(
|
compiler := New(
|
||||||
WithMetadata(metadata.Metadata{
|
WithMetadata(metadata.Metadata{
|
||||||
Repo: metadata.Repo{
|
Repo: metadata.Repo{
|
||||||
Owner: "octacat",
|
Owner: "octacat",
|
||||||
Name: "hello-world",
|
Name: "hello-world",
|
||||||
Private: true,
|
Private: true,
|
||||||
ForgeURL: "https://github.com/octocat/hello-world",
|
ForgeURL: repoURL,
|
||||||
CloneURL: "https://github.com/octocat/hello-world.git",
|
CloneURL: "https://github.com/octocat/hello-world.git",
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
@ -76,6 +77,8 @@ func TestCompilerCompile(t *testing.T) {
|
||||||
"COLORED": "true",
|
"COLORED": "true",
|
||||||
}),
|
}),
|
||||||
WithPrefix("test"),
|
WithPrefix("test"),
|
||||||
|
// we use "/test" as custom workspace base to ensure the enforcement of the pluginWorkspaceBase is applied
|
||||||
|
WithWorkspaceFromURL("/test", repoURL),
|
||||||
)
|
)
|
||||||
|
|
||||||
defaultNetworks := []*backend_types.Network{{
|
defaultNetworks := []*backend_types.Network{{
|
||||||
|
@ -92,7 +95,8 @@ func TestCompilerCompile(t *testing.T) {
|
||||||
Image: constant.DefaultCloneImage,
|
Image: constant.DefaultCloneImage,
|
||||||
OnSuccess: true,
|
OnSuccess: true,
|
||||||
Failure: "fail",
|
Failure: "fail",
|
||||||
Volumes: []string{defaultVolumes[0].Name + ":"},
|
Volumes: []string{defaultVolumes[0].Name + ":/woodpecker"},
|
||||||
|
WorkingDir: "/woodpecker/src/github.com/octocat/hello-world",
|
||||||
Networks: []backend_types.Conn{{Name: "test_default", Aliases: []string{"clone"}}},
|
Networks: []backend_types.Conn{{Name: "test_default", Aliases: []string{"clone"}}},
|
||||||
ExtraHosts: []backend_types.HostAlias{},
|
ExtraHosts: []backend_types.HostAlias{},
|
||||||
}},
|
}},
|
||||||
|
@ -137,7 +141,8 @@ func TestCompilerCompile(t *testing.T) {
|
||||||
Image: "dummy_img",
|
Image: "dummy_img",
|
||||||
OnSuccess: true,
|
OnSuccess: true,
|
||||||
Failure: "fail",
|
Failure: "fail",
|
||||||
Volumes: []string{defaultVolumes[0].Name + ":"},
|
Volumes: []string{defaultVolumes[0].Name + ":/woodpecker"},
|
||||||
|
WorkingDir: "/woodpecker/src/github.com/octocat/hello-world",
|
||||||
Networks: []backend_types.Conn{{Name: "test_default", Aliases: []string{"dummy"}}},
|
Networks: []backend_types.Conn{{Name: "test_default", Aliases: []string{"dummy"}}},
|
||||||
ExtraHosts: []backend_types.HostAlias{},
|
ExtraHosts: []backend_types.HostAlias{},
|
||||||
}},
|
}},
|
||||||
|
@ -172,7 +177,8 @@ func TestCompilerCompile(t *testing.T) {
|
||||||
Commands: []string{"env"},
|
Commands: []string{"env"},
|
||||||
OnSuccess: true,
|
OnSuccess: true,
|
||||||
Failure: "fail",
|
Failure: "fail",
|
||||||
Volumes: []string{defaultVolumes[0].Name + ":"},
|
Volumes: []string{defaultVolumes[0].Name + ":/test"},
|
||||||
|
WorkingDir: "/test/src/github.com/octocat/hello-world",
|
||||||
Networks: []backend_types.Conn{{Name: "test_default", Aliases: []string{"echo env"}}},
|
Networks: []backend_types.Conn{{Name: "test_default", Aliases: []string{"echo env"}}},
|
||||||
ExtraHosts: []backend_types.HostAlias{},
|
ExtraHosts: []backend_types.HostAlias{},
|
||||||
}},
|
}},
|
||||||
|
@ -184,7 +190,8 @@ func TestCompilerCompile(t *testing.T) {
|
||||||
Commands: []string{"echo 1"},
|
Commands: []string{"echo 1"},
|
||||||
OnSuccess: true,
|
OnSuccess: true,
|
||||||
Failure: "fail",
|
Failure: "fail",
|
||||||
Volumes: []string{defaultVolumes[0].Name + ":"},
|
Volumes: []string{defaultVolumes[0].Name + ":/test"},
|
||||||
|
WorkingDir: "/test/src/github.com/octocat/hello-world",
|
||||||
Networks: []backend_types.Conn{{Name: "test_default", Aliases: []string{"parallel echo 1"}}},
|
Networks: []backend_types.Conn{{Name: "test_default", Aliases: []string{"parallel echo 1"}}},
|
||||||
ExtraHosts: []backend_types.HostAlias{},
|
ExtraHosts: []backend_types.HostAlias{},
|
||||||
}, {
|
}, {
|
||||||
|
@ -194,7 +201,8 @@ func TestCompilerCompile(t *testing.T) {
|
||||||
Commands: []string{"echo 2"},
|
Commands: []string{"echo 2"},
|
||||||
OnSuccess: true,
|
OnSuccess: true,
|
||||||
Failure: "fail",
|
Failure: "fail",
|
||||||
Volumes: []string{defaultVolumes[0].Name + ":"},
|
Volumes: []string{defaultVolumes[0].Name + ":/test"},
|
||||||
|
WorkingDir: "/test/src/github.com/octocat/hello-world",
|
||||||
Networks: []backend_types.Conn{{Name: "test_default", Aliases: []string{"parallel echo 2"}}},
|
Networks: []backend_types.Conn{{Name: "test_default", Aliases: []string{"parallel echo 2"}}},
|
||||||
ExtraHosts: []backend_types.HostAlias{},
|
ExtraHosts: []backend_types.HostAlias{},
|
||||||
}},
|
}},
|
||||||
|
@ -228,7 +236,8 @@ func TestCompilerCompile(t *testing.T) {
|
||||||
Commands: []string{"env"},
|
Commands: []string{"env"},
|
||||||
OnSuccess: true,
|
OnSuccess: true,
|
||||||
Failure: "fail",
|
Failure: "fail",
|
||||||
Volumes: []string{defaultVolumes[0].Name + ":"},
|
Volumes: []string{defaultVolumes[0].Name + ":/test"},
|
||||||
|
WorkingDir: "/test/src/github.com/octocat/hello-world",
|
||||||
Networks: []backend_types.Conn{{Name: "test_default", Aliases: []string{"echo env"}}},
|
Networks: []backend_types.Conn{{Name: "test_default", Aliases: []string{"echo env"}}},
|
||||||
ExtraHosts: []backend_types.HostAlias{},
|
ExtraHosts: []backend_types.HostAlias{},
|
||||||
}, {
|
}, {
|
||||||
|
@ -238,7 +247,8 @@ func TestCompilerCompile(t *testing.T) {
|
||||||
Commands: []string{"echo 2"},
|
Commands: []string{"echo 2"},
|
||||||
OnSuccess: true,
|
OnSuccess: true,
|
||||||
Failure: "fail",
|
Failure: "fail",
|
||||||
Volumes: []string{defaultVolumes[0].Name + ":"},
|
Volumes: []string{defaultVolumes[0].Name + ":/test"},
|
||||||
|
WorkingDir: "/test/src/github.com/octocat/hello-world",
|
||||||
Networks: []backend_types.Conn{{Name: "test_default", Aliases: []string{"echo 2"}}},
|
Networks: []backend_types.Conn{{Name: "test_default", Aliases: []string{"echo 2"}}},
|
||||||
ExtraHosts: []backend_types.HostAlias{},
|
ExtraHosts: []backend_types.HostAlias{},
|
||||||
}},
|
}},
|
||||||
|
@ -250,7 +260,8 @@ func TestCompilerCompile(t *testing.T) {
|
||||||
Commands: []string{"echo 1"},
|
Commands: []string{"echo 1"},
|
||||||
OnSuccess: true,
|
OnSuccess: true,
|
||||||
Failure: "fail",
|
Failure: "fail",
|
||||||
Volumes: []string{defaultVolumes[0].Name + ":"},
|
Volumes: []string{defaultVolumes[0].Name + ":/test"},
|
||||||
|
WorkingDir: "/test/src/github.com/octocat/hello-world",
|
||||||
Networks: []backend_types.Conn{{Name: "test_default", Aliases: []string{"echo 1"}}},
|
Networks: []backend_types.Conn{{Name: "test_default", Aliases: []string{"echo 1"}}},
|
||||||
ExtraHosts: []backend_types.HostAlias{},
|
ExtraHosts: []backend_types.HostAlias{},
|
||||||
}},
|
}},
|
||||||
|
|
|
@ -30,6 +30,13 @@ import (
|
||||||
"go.woodpecker-ci.org/woodpecker/v2/pipeline/frontend/yaml/utils"
|
"go.woodpecker-ci.org/woodpecker/v2/pipeline/frontend/yaml/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// The pluginWorkspaceBase should not be changed, only if you are sure what you do.
|
||||||
|
pluginWorkspaceBase = "/woodpecker"
|
||||||
|
// DefaultWorkspaceBase is set if not altered by the user.
|
||||||
|
DefaultWorkspaceBase = pluginWorkspaceBase
|
||||||
|
)
|
||||||
|
|
||||||
func (c *Compiler) createProcess(container *yaml_types.Container, stepType backend_types.StepType) (*backend_types.Step, error) {
|
func (c *Compiler) createProcess(container *yaml_types.Container, stepType backend_types.StepType) (*backend_types.Step, error) {
|
||||||
var (
|
var (
|
||||||
uuid = ulid.Make()
|
uuid = ulid.Make()
|
||||||
|
@ -37,11 +44,17 @@ func (c *Compiler) createProcess(container *yaml_types.Container, stepType backe
|
||||||
detached bool
|
detached bool
|
||||||
workingDir string
|
workingDir string
|
||||||
|
|
||||||
workspace = fmt.Sprintf("%s_default:%s", c.prefix, c.base)
|
|
||||||
privileged = container.Privileged
|
privileged = container.Privileged
|
||||||
networkMode = container.NetworkMode
|
networkMode = container.NetworkMode
|
||||||
)
|
)
|
||||||
|
|
||||||
|
workspaceBase := c.workspaceBase
|
||||||
|
if container.IsPlugin() {
|
||||||
|
// plugins have a predefined workspace base to not tamper with entrypoint executables
|
||||||
|
workspaceBase = pluginWorkspaceBase
|
||||||
|
}
|
||||||
|
workspaceVolume := fmt.Sprintf("%s_default:%s", c.prefix, workspaceBase)
|
||||||
|
|
||||||
networks := []backend_types.Conn{
|
networks := []backend_types.Conn{
|
||||||
{
|
{
|
||||||
Name: fmt.Sprintf("%s_default", c.prefix),
|
Name: fmt.Sprintf("%s_default", c.prefix),
|
||||||
|
@ -66,7 +79,7 @@ func (c *Compiler) createProcess(container *yaml_types.Container, stepType backe
|
||||||
|
|
||||||
var volumes []string
|
var volumes []string
|
||||||
if !c.local {
|
if !c.local {
|
||||||
volumes = append(volumes, workspace)
|
volumes = append(volumes, workspaceVolume)
|
||||||
}
|
}
|
||||||
volumes = append(volumes, c.volumes...)
|
volumes = append(volumes, c.volumes...)
|
||||||
for _, volume := range container.Volumes.Volumes {
|
for _, volume := range container.Volumes.Volumes {
|
||||||
|
@ -77,7 +90,7 @@ func (c *Compiler) createProcess(container *yaml_types.Container, stepType backe
|
||||||
environment := map[string]string{}
|
environment := map[string]string{}
|
||||||
maps.Copy(environment, c.env)
|
maps.Copy(environment, c.env)
|
||||||
|
|
||||||
environment["CI_WORKSPACE"] = path.Join(c.base, c.path)
|
environment["CI_WORKSPACE"] = path.Join(workspaceBase, c.workspacePath)
|
||||||
|
|
||||||
if stepType == backend_types.StepTypeService || container.Detached {
|
if stepType == backend_types.StepTypeService || container.Detached {
|
||||||
detached = true
|
detached = true
|
||||||
|
@ -219,7 +232,11 @@ func (c *Compiler) stepWorkingDir(container *yaml_types.Container) string {
|
||||||
if path.IsAbs(container.Directory) {
|
if path.IsAbs(container.Directory) {
|
||||||
return container.Directory
|
return container.Directory
|
||||||
}
|
}
|
||||||
return path.Join(c.base, c.path, container.Directory)
|
base := c.workspaceBase
|
||||||
|
if container.IsPlugin() {
|
||||||
|
base = pluginWorkspaceBase
|
||||||
|
}
|
||||||
|
return path.Join(base, c.workspacePath, container.Directory)
|
||||||
}
|
}
|
||||||
|
|
||||||
func convertPort(portDef string) (backend_types.Port, error) {
|
func convertPort(portDef string) (backend_types.Port, error) {
|
||||||
|
|
|
@ -97,8 +97,8 @@ func WithNetrc(username, password, machine string) Option {
|
||||||
// plugin steps in the pipeline.
|
// plugin steps in the pipeline.
|
||||||
func WithWorkspace(base, path string) Option {
|
func WithWorkspace(base, path string) Option {
|
||||||
return func(compiler *Compiler) {
|
return func(compiler *Compiler) {
|
||||||
compiler.base = base
|
compiler.workspaceBase = base
|
||||||
compiler.path = path
|
compiler.workspacePath = path
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -29,8 +29,8 @@ func TestWithWorkspace(t *testing.T) {
|
||||||
"src/github.com/octocat/hello-world",
|
"src/github.com/octocat/hello-world",
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
assert.Equal(t, "/pipeline", compiler.base)
|
assert.Equal(t, "/pipeline", compiler.workspaceBase)
|
||||||
assert.Equal(t, "src/github.com/octocat/hello-world", compiler.path)
|
assert.Equal(t, "src/github.com/octocat/hello-world", compiler.workspacePath)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestWithEscalated(t *testing.T) {
|
func TestWithEscalated(t *testing.T) {
|
||||||
|
|
|
@ -291,7 +291,7 @@ func (b *StepBuilder) toInternalRepresentation(parsed *yaml_types.Workflow, envi
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
compiler.WithProxy(b.ProxyOpts),
|
compiler.WithProxy(b.ProxyOpts),
|
||||||
compiler.WithWorkspaceFromURL("/woodpecker", b.Repo.ForgeURL),
|
compiler.WithWorkspaceFromURL(compiler.DefaultWorkspaceBase, b.Repo.ForgeURL),
|
||||||
compiler.WithMetadata(metadata),
|
compiler.WithMetadata(metadata),
|
||||||
compiler.WithTrusted(b.Repo.IsTrusted),
|
compiler.WithTrusted(b.Repo.IsTrusted),
|
||||||
compiler.WithNetrcOnlyTrusted(b.Repo.NetrcOnlyTrusted),
|
compiler.WithNetrcOnlyTrusted(b.Repo.NetrcOnlyTrusted),
|
||||||
|
|
Loading…
Reference in a new issue