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

feat(fs/git): add support for a directory scope #2939

Merged
merged 2 commits into from
Apr 3, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
1 change: 1 addition & 0 deletions go.work.sum
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,7 @@ github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b/go.mod h1:obH5gd0Bsq
github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE=
github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw=
github.com/cenkalti/backoff/v4 v4.1.2/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw=
github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
github.com/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw=
github.com/certifi/gocertifi v0.0.0-20191021191039-0944d244cd40/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA=
github.com/certifi/gocertifi v0.0.0-20200922220541-2c3bb06c6054/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA=
Expand Down
17 changes: 17 additions & 0 deletions internal/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -726,6 +726,23 @@ func TestLoad(t *testing.T) {
return cfg
},
},
{
name: "git config provided with directory",
path: "./testdata/storage/git_provided_with_directory.yml",
expected: func() *Config {
cfg := Default()
cfg.Storage = StorageConfig{
Type: GitStorageType,
Git: &Git{
Ref: "main",
Repository: "git@github.com:foo/bar.git",
Directory: "baz",
PollInterval: 30 * time.Second,
},
}
return cfg
},
},
{
name: "git repository not provided",
path: "./testdata/storage/invalid_git_repo_not_specified.yml",
Expand Down
1 change: 1 addition & 0 deletions internal/config/storage.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ type Local struct {
type Git struct {
Repository string `json:"repository,omitempty" mapstructure:"repository" yaml:"repository,omitempty"`
Ref string `json:"ref,omitempty" mapstructure:"ref" yaml:"ref,omitempty"`
Directory string `json:"directory,omitempty" mapstructure:"directory" yaml:"directory,omitempty"`
CaCertBytes string `json:"-" mapstructure:"ca_cert_bytes" yaml:"-" `
CaCertPath string `json:"-" mapstructure:"ca_cert_path" yaml:"-" `
InsecureSkipTLS bool `json:"-" mapstructure:"insecure_skip_tls" yaml:"-"`
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
storage:
type: git
git:
repository: "git@github.com:foo/bar.git"
directory: "baz"
22 changes: 20 additions & 2 deletions internal/storage/fs/git/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package git
import (
"context"
"errors"
"io/fs"
"sync"

"github.com/go-git/go-git/v5"
Expand Down Expand Up @@ -34,6 +35,7 @@ type SnapshotStore struct {
logger *zap.Logger
url string
baseRef string
directory string
auth transport.AuthMethod
insecureSkipTLS bool
caBundle []byte
Expand Down Expand Up @@ -88,6 +90,14 @@ func WithCABundle(caCertBytes []byte) containers.Option[SnapshotStore] {
}
}

// WithDirectory sets a root directory which the store will walk from
// to discover feature flag state files.
func WithDirectory(directory string) containers.Option[SnapshotStore] {
return func(ss *SnapshotStore) {
ss.directory = directory
}
}

// NewSnapshotStore constructs and configures a Store.
// The store uses the connection and credential details provided to build
// fs.FS implementations around a target git repository.
Expand Down Expand Up @@ -256,10 +266,18 @@ func (s *SnapshotStore) resolve(ref string) (plumbing.Hash, error) {

// buildSnapshot builds a new store snapshot based on the provided hash.
func (s *SnapshotStore) buildSnapshot(ctx context.Context, hash plumbing.Hash) (*storagefs.Snapshot, error) {
fs, err := gitfs.NewFromRepoHash(s.logger, s.repo, hash)
var gfs fs.FS
gfs, err := gitfs.NewFromRepoHash(s.logger, s.repo, hash)
if err != nil {
return nil, err
}

return storagefs.SnapshotFromFS(s.logger, fs)
if s.directory != "" {
gfs, err = fs.Sub(gfs, s.directory)
if err != nil {
return nil, err
}
}

return storagefs.SnapshotFromFS(s.logger, gfs)
}
47 changes: 45 additions & 2 deletions internal/storage/fs/git/store_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ func Test_Store_Subscribe_Hash(t *testing.T) {
testStore(t, gitRepoURL, WithRef(head))
}

func Test_Store_Subscribe(t *testing.T) {
func Test_Store_View(t *testing.T) {
ch := make(chan struct{})
store, skip := testStore(t, gitRepoURL, WithPollOptions(
fs.WithInterval(time.Second),
Expand Down Expand Up @@ -121,7 +121,7 @@ flags:
}))
}

func Test_Store_Subscribe_WithRevision(t *testing.T) {
func Test_Store_View_WithRevision(t *testing.T) {
ch := make(chan struct{})
store, skip := testStore(t, gitRepoURL, WithPollOptions(
fs.WithInterval(time.Second),
Expand Down Expand Up @@ -216,6 +216,49 @@ flags:
}))
}

func Test_Store_View_WithDirectory(t *testing.T) {
ch := make(chan struct{})
store, skip := testStore(t, gitRepoURL, WithPollOptions(
fs.WithInterval(time.Second),
fs.WithNotify(t, func(modified bool) {
if modified {
close(ch)
}
}),
),
// scope flag state discovery to sub-directory
WithDirectory("subdir"),
)
if skip {
return
}

ctx, cancel := context.WithCancel(context.Background())
t.Cleanup(cancel)

// pull repo
workdir := memfs.New()
repo, err := git.Clone(memory.NewStorage(), workdir, &git.CloneOptions{
Auth: &http.BasicAuth{Username: "root", Password: "password"},
URL: gitRepoURL,
RemoteName: "origin",
ReferenceName: plumbing.NewBranchReferenceName("main"),
})
require.NoError(t, err)

tree, err := repo.Worktree()
require.NoError(t, err)

require.NoError(t, tree.Checkout(&git.CheckoutOptions{
Branch: "refs/heads/main",
}))

require.NoError(t, store.View(ctx, "", func(s storage.ReadOnlyStore) error {
_, err = s.GetFlag(ctx, storage.NewResource("alternative", "otherflag"))
return err
}))
}

func Test_Store_SelfSignedSkipTLS(t *testing.T) {
ts := httptest.NewTLSServer(nil)
defer ts.Close()
Expand Down
3 changes: 3 additions & 0 deletions internal/storage/fs/git/testdata/subdir/.flipt.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
version: "1.0"
include:
- "alternative.yml"
5 changes: 5 additions & 0 deletions internal/storage/fs/git/testdata/subdir/alternative.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
namespace: "alternative"
flags:
- key: otherflag
name: Other Flag

1 change: 1 addition & 0 deletions internal/storage/fs/store/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ func NewStore(ctx context.Context, logger *zap.Logger, cfg *config.Config) (_ st
storagefs.WithInterval(cfg.Storage.Git.PollInterval),
),
git.WithInsecureTLS(cfg.Storage.Git.InsecureSkipTLS),
git.WithDirectory(cfg.Storage.Git.Directory),
}

if cfg.Storage.Git.CaCertBytes != "" {
Expand Down
Loading