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

Modules support relative path 2 #1726

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ Main (unreleased)

- Add the function `path_join` to the stdlib. (@wildum)

- Add support for relative paths to `import.file`. This new functionality allows users to use `import.file` blocks in modules
imported via `import.git` and other `import.file`. (@wildum)

### Bugfixes

- Update yet-another-cloudwatch-exporter from v0.60.0 vo v0.61.0: (@morremeyer)
Expand Down
126 changes: 124 additions & 2 deletions docs/sources/reference/config-blocks/import.file.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ Imported directories are treated as single modules to support composability.
That means that you can define a custom component in one file and use it in another custom component in another file
in the same directory.

You can use the keyword `module_path` in combination with the `stdlib` function [file.path_join][] to import a module relative to the current module's path.
The `module_path` keyword works for modules that are imported via `import.file`, `import.git` and `import.string`.

## Usage

```alloy
Expand All @@ -41,6 +44,21 @@ The following arguments are supported:

This example imports a module from a file and instantiates a custom component from the import that adds two numbers:

{{< collapse title="main.alloy" >}}
Copy link
Contributor

@clayton-cornell clayton-cornell Oct 2, 2024

Choose a reason for hiding this comment

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

I'm not sure that using collapse is really the best way to present the examples. What's the reason for choosing collapse over something simpler like a heading?

image

vs.

image


```alloy
import.file "math" {
filename = "module.alloy"
}

math.add "default" {
a = 15
b = 45
}
```

{{< /collapse >}}

{{< collapse title="module.alloy" >}}

```alloy
Expand All @@ -56,11 +74,71 @@ declare "add" {

{{< /collapse >}}

{{< collapse title="importer.alloy" >}}

This example imports a module from a file inside of a module that is imported via [import.git][]:

{{< collapse title="main.alloy" >}}

```alloy
import.git "math" {
repository = "https://github.com/wildum/module.git"
path = "relative_math.alloy"
}

math.add "default" {
a = 15
b = 45
}
```

{{< /collapse >}}

{{< collapse title="relative_math.alloy" >}}

```alloy
import.file "lib" {
filename = file.path_join(module_path, "lib.alloy")
thampiotr marked this conversation as resolved.
Show resolved Hide resolved
}
wildum marked this conversation as resolved.
Show resolved Hide resolved

declare "add" {
argument "a" {}
argument "b" {}

lib.plus "default" {
a = argument.a.value
b = argument.b.value
}

export "output" {
value = lib.plus.default.sum
}
}
```

{{< /collapse >}}

{{< collapse title="lib.alloy" >}}

```alloy
declare "plus" {
argument "a" {}
argument "b" {}

export "sum" {
value = argument.a.value + argument.b.value
}
}
```

{{< /collapse >}}

This example imports a module from a file inside of a module that is imported via another `import.file`:

{{< collapse title="main.alloy" >}}

```alloy
import.file "math" {
filename = "module.alloy"
filename = "path/to/module/relative_math.alloy"
}

math.add "default" {
Expand All @@ -70,3 +148,47 @@ math.add "default" {
```

{{< /collapse >}}

{{< collapse title="relative_math.alloy" >}}

```alloy
import.file "lib" {
filename = file.path_join(module_path, "lib.alloy")
}

declare "add" {
argument "a" {}
argument "b" {}

lib.plus "default" {
a = argument.a.value
b = argument.b.value
}

export "output" {
value = lib.plus.default.sum
}
}
```

{{< /collapse >}}

{{< collapse title="lib.alloy" >}}

```alloy
declare "plus" {
argument "a" {}
argument "b" {}

export "sum" {
value = argument.a.value + argument.b.value
}
}
```

{{< /collapse >}}



[file.path_join]: ../../stdlib/file/
[import.git]: ../import.git/
4 changes: 4 additions & 0 deletions docs/sources/reference/config-blocks/import.git.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ title: import.git
The `import.git` block imports custom components from a Git repository and exposes them to the importer.
`import.git` blocks must be given a label that determines the namespace where custom components are exposed.

The entire repository is cloned, and the module path is accessible via the `module_path` keyword.
This enables your module to import other modules within the repository by setting relative paths in the [import.file][] blocks.

## Usage

```alloy
Expand Down Expand Up @@ -101,5 +104,6 @@ math.add "default" {
}
```

[import.file]: ../import.file/
[basic_auth]: #basic_auth-block
[ssh_key]: #ssh_key-block
3 changes: 2 additions & 1 deletion internal/alloycli/cmd_run.go
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,7 @@ func (fr *alloyRun) Run(configPath string) error {

remoteCfgService, err := remotecfgservice.New(remotecfgservice.Options{
Logger: log.With(l, "service", "remotecfg"),
ConfigPath: configPath,
StoragePath: fr.storagePath,
Metrics: reg,
})
Expand Down Expand Up @@ -326,7 +327,7 @@ func (fr *alloyRun) Run(configPath string) error {
if err != nil {
return nil, fmt.Errorf("reading config path %q: %w", configPath, err)
}
if err := f.LoadSource(alloySource, nil); err != nil {
if err := f.LoadSource(alloySource, nil, configPath); err != nil {
return alloySource, fmt.Errorf("error during the initial load: %w", err)
}

Expand Down
2 changes: 1 addition & 1 deletion internal/converter/internal/test_common/testing.go
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,7 @@ func attemptLoadingAlloyConfig(t *testing.T, bb []byte) {
},
EnableCommunityComps: true,
})
err = f.LoadSource(cfg, nil)
err = f.LoadSource(cfg, nil, "")

// Many components will fail to build as e.g. the cert files are missing, so we ignore these errors.
// This is not ideal, but we still validate for other potential issues.
Expand Down
35 changes: 28 additions & 7 deletions internal/runtime/alloy.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,14 @@ import (

"github.com/grafana/alloy/internal/featuregate"
"github.com/grafana/alloy/internal/runtime/internal/controller"
"github.com/grafana/alloy/internal/runtime/internal/importsource"
"github.com/grafana/alloy/internal/runtime/internal/worker"
"github.com/grafana/alloy/internal/runtime/logging"
"github.com/grafana/alloy/internal/runtime/logging/level"
"github.com/grafana/alloy/internal/runtime/tracing"
"github.com/grafana/alloy/internal/service"
"github.com/grafana/alloy/internal/util"
"github.com/grafana/alloy/syntax/vm"
)

// Options holds static options for an Alloy controller.
Expand Down Expand Up @@ -296,22 +299,40 @@ func (f *Runtime) Run(ctx context.Context) {
// The controller will only start running components after Load is called once
// without any configuration errors.
// LoadSource uses default loader configuration.
func (f *Runtime) LoadSource(source *Source, args map[string]any) error {
return f.loadSource(source, args, nil)
func (f *Runtime) LoadSource(source *Source, args map[string]any, configPath string) error {
modulePath, err := util.ExtractDirPath(configPath)
if err != nil {
level.Warn(f.log).Log("msg", "failed to extract directory path from configPath", "configPath", configPath, "err", err)
}
return f.applyLoaderConfig(controller.ApplyOptions{
Args: args,
ComponentBlocks: source.components,
ConfigBlocks: source.configBlocks,
DeclareBlocks: source.declareBlocks,
ArgScope: &vm.Scope{
Parent: nil,
Variables: map[string]interface{}{
importsource.ModulePath: modulePath,
},
},
})
}

// Same as above but with a customComponentRegistry that provides custom component definitions.
func (f *Runtime) loadSource(source *Source, args map[string]any, customComponentRegistry *controller.CustomComponentRegistry) error {
f.loadMut.Lock()
defer f.loadMut.Unlock()

applyOptions := controller.ApplyOptions{
return f.applyLoaderConfig(controller.ApplyOptions{
Args: args,
ComponentBlocks: source.components,
ConfigBlocks: source.configBlocks,
DeclareBlocks: source.declareBlocks,
CustomComponentRegistry: customComponentRegistry,
}
ArgScope: customComponentRegistry.Scope(),
})
}

func (f *Runtime) applyLoaderConfig(applyOptions controller.ApplyOptions) error {
f.loadMut.Lock()
defer f.loadMut.Unlock()

diags := f.loader.Apply(applyOptions)
if !f.loadedOnce.Load() && diags.HasErrors() {
Expand Down
4 changes: 2 additions & 2 deletions internal/runtime/alloy_services.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,12 +93,12 @@ type ServiceController struct {
}

func (sc ServiceController) Run(ctx context.Context) { sc.f.Run(ctx) }
func (sc ServiceController) LoadSource(b []byte, args map[string]any) error {
func (sc ServiceController) LoadSource(b []byte, args map[string]any, configPath string) error {
source, err := ParseSource("", b)
if err != nil {
return err
}
return sc.f.LoadSource(source, args)
return sc.f.LoadSource(source, args, configPath)
}
func (sc ServiceController) Ready() bool { return sc.f.Ready() }

Expand Down
14 changes: 7 additions & 7 deletions internal/runtime/alloy_services_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ func TestServices(t *testing.T) {
opts.Services = append(opts.Services, svc)

ctrl := New(opts)
require.NoError(t, ctrl.LoadSource(makeEmptyFile(t), nil))
require.NoError(t, ctrl.LoadSource(makeEmptyFile(t), nil, ""))

// Start the controller. This should cause our service to run.
go ctrl.Run(ctx)
Expand Down Expand Up @@ -90,7 +90,7 @@ func TestServices_Configurable(t *testing.T) {

ctrl := New(opts)

require.NoError(t, ctrl.LoadSource(f, nil))
require.NoError(t, ctrl.LoadSource(f, nil, ""))

// Start the controller. This should cause our service to run.
go ctrl.Run(ctx)
Expand Down Expand Up @@ -137,7 +137,7 @@ func TestServices_Configurable_Optional(t *testing.T) {

ctrl := New(opts)

require.NoError(t, ctrl.LoadSource(makeEmptyFile(t), nil))
require.NoError(t, ctrl.LoadSource(makeEmptyFile(t), nil, ""))

// Start the controller. This should cause our service to run.
go ctrl.Run(ctx)
Expand Down Expand Up @@ -171,7 +171,7 @@ func TestAlloy_GetServiceConsumers(t *testing.T) {

ctrl := New(opts)
defer cleanUpController(ctrl)
require.NoError(t, ctrl.LoadSource(makeEmptyFile(t), nil))
require.NoError(t, ctrl.LoadSource(makeEmptyFile(t), nil, ""))

expectConsumers := []service.Consumer{{
Type: service.ConsumerTypeService,
Expand Down Expand Up @@ -253,7 +253,7 @@ func TestComponents_Using_Services(t *testing.T) {
ComponentRegistry: registry,
ModuleRegistry: newModuleRegistry(),
})
require.NoError(t, ctrl.LoadSource(f, nil))
require.NoError(t, ctrl.LoadSource(f, nil, ""))
go ctrl.Run(ctx)

require.NoError(t, componentBuilt.Wait(5*time.Second), "Component should have been built")
Expand Down Expand Up @@ -332,7 +332,7 @@ func TestComponents_Using_Services_In_Modules(t *testing.T) {
ComponentRegistry: registry,
ModuleRegistry: newModuleRegistry(),
})
require.NoError(t, ctrl.LoadSource(f, nil))
require.NoError(t, ctrl.LoadSource(f, nil, ""))
go ctrl.Run(ctx)

require.NoError(t, componentBuilt.Wait(5*time.Second), "Component should have been built")
Expand Down Expand Up @@ -360,7 +360,7 @@ func TestNewControllerNoLeak(t *testing.T) {
opts.Services = append(opts.Services, svc)

ctrl := New(opts)
require.NoError(t, ctrl.LoadSource(makeEmptyFile(t), nil))
require.NoError(t, ctrl.LoadSource(makeEmptyFile(t), nil, ""))

// Start the controller. This should cause our service to run.
go ctrl.Run(ctx)
Expand Down
Loading