Skip to content
Open
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
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,22 @@ func loadConfig(ctx context.Context, client *github.Client, owner, repo, ref str
}
```

To load a specific remote file without discovery or remote-reference parsing, use
`LoadRemoteRef`. Missing files are errors; existing empty files retain their
source metadata.

```go
c, err := loader.LoadRemoteRef(ctx, client, clientOwner, appconfig.RemoteRef{
Remote: "example/shared-config",
Path: "config/app.yml",
Ref: "main",
})
```

`clientOwner` must be the supplied client's original installation owner, even if
an earlier load returned a file owned by another organization. Using another
owner's installation requires `appconfig.WithPrivateRemotes`.

## OAuth2

The `oauth2` package provides an `http.Handler` implementation that simplifies
Expand Down
10 changes: 10 additions & 0 deletions appconfig/appconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,16 @@ func (ld *Loader) LoadConfig(ctx context.Context, client *github.Client, owner,
return Config{}, nil
}

// LoadRemoteRef loads the file identified by remote. Unlike LoadConfig, it
// does not follow remote references, try other paths, or fall back to owner
// defaults. A missing file is an error.
//
// clientOwner is the owner of the installation for that client.
// It determines whether WithPrivateRemotes selects a different installation.
func (ld *Loader) LoadRemoteRef(ctx context.Context, client *github.Client, clientOwner string, remote RemoteRef) (Config, error) {
return ld.loadRemoteConfig(ctx, client, clientOwner, remote, Config{})
}

func (ld *Loader) loadRemoteConfig(ctx context.Context, client *github.Client, sourceOwner string, remote RemoteRef, c Config) (Config, error) {
logger := zerolog.Ctx(ctx)
notFoundErr := fmt.Errorf("invalid remote reference: file does not exist")
Expand Down
66 changes: 66 additions & 0 deletions appconfig/appconfig_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,72 @@ func TestLoadConfig(t *testing.T) {
}
}

func TestLoadRemoteRef(t *testing.T) {
tests := map[string]struct {
Remote RemoteRef
Expected Config
Error string
}{
"literalContent": {
Remote: RemoteRef{Remote: "test/remote-ref", Path: ".github/test-app.yml", Ref: TestRef},
Expected: Config{
Content: []byte("remote: remote/config\npath: config/test-app.yml\nref: develop\n"),
Source: "test/remote-ref@develop",
Path: ".github/test-app.yml",
IsRemote: true,
},
},
"missingFile": {
Remote: RemoteRef{Remote: "test/local-file", Path: ".github/test-app.v2.yml", Ref: TestRef},
Error: "invalid remote reference: file does not exist",
},
"defaultBranch": {
Remote: RemoteRef{Remote: "remote/config", Path: "config/test-app.yml"},
Expected: Config{
Content: []byte("message: hello\n"),
Source: "remote/config@main",
Path: "config/test-app.yml",
IsRemote: true,
},
},
}

ctx := context.Background()
client := makeTestClient()

for name, test := range tests {
t.Run(name, func(t *testing.T) {
ld := NewLoader([]string{".github/test-app.v2.yml", ".github/test-app.yml"})
cfg, err := ld.LoadRemoteRef(ctx, client, TestOwner, test.Remote)
if test.Error != "" {
if err == nil {
t.Fatal("expected error loading config, but got nil")
}
if err.Error() != test.Error {
t.Errorf("incorrect error: expected: %q, actual: %q", test.Error, err)
}
return
}
if err != nil {
t.Fatalf("unexpected error loading config: %v", err)
}

if test.Expected.Source != cfg.Source {
t.Errorf("incorrect source: expected: %q, actual: %q", test.Expected.Source, cfg.Source)
}
if test.Expected.Path != cfg.Path {
t.Errorf("incorrect path: expected: %q, actual: %q", test.Expected.Path, cfg.Path)
}
if test.Expected.IsRemote != cfg.IsRemote {
t.Errorf("incorrect remote flag: expected: %t, actual: %t", test.Expected.IsRemote, cfg.IsRemote)
}
if !bytes.Equal(test.Expected.Content, cfg.Content) {
t.Errorf("incorrect content\nexpected: %s\n actual: %s", test.Expected.Content, cfg.Content)
}
})
}
}

func TestLoadConfigWithPrivateRemotes(t *testing.T) {
newClient := func(rp *ResponsePlayer) *github.Client {
client, _ := github.NewClient(github.WithHTTPClient(&http.Client{Transport: rp}))
Expand Down
3 changes: 2 additions & 1 deletion appconfig/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ import (
type Option func(*Loader)

// WithRemoteRefParser sets the parser for encoded RemoteRefs. The default
// parser uses YAML. Set a nil parser to disable remote references.
// parser uses YAML. Set a nil parser to disable automatic remote references in
// LoadConfig.
func WithRemoteRefParser(parser RemoteRefParser) Option {
return func(ld *Loader) {
ld.parser = parser
Expand Down
Loading