Skip to content
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
26 changes: 24 additions & 2 deletions pkg/app/api/grpcapi/piped_api.go
Original file line number Diff line number Diff line change
Expand Up @@ -957,8 +957,30 @@ func (a *PipedAPI) GetDesiredVersion(ctx context.Context, _ *pipedservice.GetDes
}

func (a *PipedAPI) UpdateApplicationConfigurations(ctx context.Context, req *pipedservice.UpdateApplicationConfigurationsRequest) (*pipedservice.UpdateApplicationConfigurationsResponse, error) {
// TODO: Update the given application configurations
return nil, status.Errorf(codes.Unimplemented, "UpdateApplicationConfigurations is not implemented yet")
_, pipedID, _, err := rpcauth.ExtractPipedToken(ctx)
if err != nil {
return nil, err
}
Copy link
Member

Choose a reason for hiding this comment

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

since we have appInfo.Id, better to validate if the updating application belongs to the requested piped as other RPCs.

Copy link
Member Author

Choose a reason for hiding this comment

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

For sure

Copy link
Member Author

Choose a reason for hiding this comment

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

Applied at d22b018

// Scan all of them to guarantee in advance that there is no invalid request.
for _, appInfo := range req.Applications {
if err := a.validateAppBelongsToPiped(ctx, appInfo.Id, pipedID); err != nil {
return nil, err
}
}
// TODO: Consider bulk-updating multiple apps
for _, appInfo := range req.Applications {
updater := func(app *model.Application) error {
app.Name = appInfo.Name
app.Kind = appInfo.Kind
app.Labels = appInfo.Labels
return nil
}
if err := a.applicationStore.UpdateApplication(ctx, appInfo.Id, updater); err != nil {
a.logger.Error("failed to update application", zap.Error(err))
return nil, status.Error(codes.Internal, "failed to update application")
}
}
return &pipedservice.UpdateApplicationConfigurationsResponse{}, nil
}

func (a *PipedAPI) ReportUnregisteredApplicationConfigurations(ctx context.Context, req *pipedservice.ReportUnregisteredApplicationConfigurationsRequest) (*pipedservice.ReportUnregisteredApplicationConfigurationsResponse, error) {
Expand Down
106 changes: 64 additions & 42 deletions pkg/app/piped/appconfigreporter/appconfigreporter.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,12 +142,12 @@ func (r *Reporter) scanAppConfigs(ctx context.Context) error {
}
}

// Create a map to determine from GitPath if the application is registered.
apps := r.applicationLister.List()
registeredAppPaths := make(map[string]struct{}, len(apps))
// Create a map from Git path key to app id, to determine if the application is registered.
registeredAppPaths := make(map[string]string, len(apps))
for _, app := range apps {
key := makeGitPathKey(app.GitPath.Repo.Id, filepath.Join(app.GitPath.Path, app.GitPath.ConfigFilename))
registeredAppPaths[key] = struct{}{}
registeredAppPaths[key] = app.Id
}

if err := r.updateRegisteredApps(ctx, registeredAppPaths); err != nil {
Expand All @@ -161,7 +161,7 @@ func (r *Reporter) scanAppConfigs(ctx context.Context) error {
}

// updateRegisteredApps sends application configurations that have changed since the last time to the control-plane.
func (r *Reporter) updateRegisteredApps(ctx context.Context, registeredAppPaths map[string]struct{}) (err error) {
func (r *Reporter) updateRegisteredApps(ctx context.Context, registeredAppPaths map[string]string) (err error) {
apps := make([]*model.ApplicationInfo, 0)
headCommits := make(map[string]string, len(r.gitRepos))
for repoID, repo := range r.gitRepos {
Expand Down Expand Up @@ -207,15 +207,45 @@ func (r *Reporter) updateRegisteredApps(ctx context.Context, registeredAppPaths
}

// findRegisteredApps finds out registered application info in the given git repository.
func (r *Reporter) findRegisteredApps(ctx context.Context, repoID string, repo gitRepo, lastScannedCommit, headCommitHash string, registeredAppPaths map[string]struct{}) ([]*model.ApplicationInfo, error) {
func (r *Reporter) findRegisteredApps(ctx context.Context, repoID string, repo gitRepo, lastScannedCommit, headCommitHash string, registeredAppPaths map[string]string) ([]*model.ApplicationInfo, error) {
if lastScannedCommit == "" {
return r.scanAllFiles(repo.GetPath(), repoID, func(fileRelPath string) bool {
gitPathKey := makeGitPathKey(repoID, fileRelPath)
if _, registered := registeredAppPaths[gitPathKey]; !registered {
return true
// Scan all files because it seems to be the first commit since Piped starts.
apps := make([]*model.ApplicationInfo, 0)
err := fs.WalkDir(r.fileSystem, repo.GetPath(), func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
Comment on lines +212 to +216
Copy link
Member

Choose a reason for hiding this comment

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

Just want to ask, do we have any reason to not use that scanAllFiles function?

Copy link
Member Author

Choose a reason for hiding this comment

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

@nghialv I feel you. Actually we can use the original scanAllFiles() removed just now. But we have to retrieve appID from registeredAppPaths only if called by findRegisteredApps. It is not a shared helper function anymore. It was not too huge function and originally it has been received shouldSkip which makes us confused.
So I settled on writing a bit overlapping logic directly into each function.

}
return false
if d.IsDir() {
return nil
}
cfgRelPath, err := filepath.Rel(repo.GetPath(), path)
if err != nil {
return err
}
gitPathKey := makeGitPathKey(repoID, cfgRelPath)
appID, registered := registeredAppPaths[gitPathKey]
if !registered {
return nil
}

appInfo, err := r.readApplicationInfo(repo.GetPath(), repoID, filepath.Dir(cfgRelPath), filepath.Base(cfgRelPath))
if err != nil {
r.logger.Error("failed to read application info",
zap.String("repo-id", repoID),
zap.String("config-file-path", cfgRelPath),
zap.Error(err),
)
return nil
}
appInfo.Id = appID
apps = append(apps, appInfo)
// Continue reading so that it can return apps as much as possible.
return nil
})
if err != nil {
return nil, fmt.Errorf("failed to inspect files under %s: %w", repo.GetPath(), err)
}
return apps, nil
}

filePaths, err := repo.ChangedFiles(ctx, lastScannedCommit, headCommitHash)
Expand All @@ -229,10 +259,11 @@ func (r *Reporter) findRegisteredApps(ctx context.Context, repoID string, repo g
apps := make([]*model.ApplicationInfo, 0)
for _, path := range filePaths {
gitPathKey := makeGitPathKey(repoID, path)
if _, registered := registeredAppPaths[gitPathKey]; !registered {
appID, registered := registeredAppPaths[gitPathKey]
if !registered {
continue
}
appInfo, err := r.readApplicationInfo(repo.GetPath(), filepath.Dir(path), filepath.Base(path))
appInfo, err := r.readApplicationInfo(repo.GetPath(), repoID, filepath.Dir(path), filepath.Base(path))
if err != nil {
r.logger.Error("failed to read application info",
zap.String("repo-id", repoID),
Expand All @@ -241,13 +272,14 @@ func (r *Reporter) findRegisteredApps(ctx context.Context, repoID string, repo g
)
continue
}
appInfo.Id = appID
apps = append(apps, appInfo)
}
return apps, nil
}

// updateUnregisteredApps sends all unregistered application configurations to the control-plane.
func (r *Reporter) updateUnregisteredApps(ctx context.Context, registeredAppPaths map[string]struct{}) error {
func (r *Reporter) updateUnregisteredApps(ctx context.Context, registeredAppPaths map[string]string) error {
apps := make([]*model.ApplicationInfo, 0)
for repoID, repo := range r.gitRepos {
as, err := r.findUnregisteredApps(repo.GetPath(), repoID, registeredAppPaths)
Expand Down Expand Up @@ -280,40 +312,29 @@ func (r *Reporter) updateUnregisteredApps(ctx context.Context, registeredAppPath

// findUnregisteredApps finds out unregistered application info in the given git repository.
// The file name must be default name in order to be recognized as an Application config.
func (r *Reporter) findUnregisteredApps(repoPath, repoID string, registeredAppPaths map[string]struct{}) ([]*model.ApplicationInfo, error) {
return r.scanAllFiles(repoPath, repoID, func(fileRelPath string) bool {
if !model.IsApplicationConfigFile(filepath.Base(fileRelPath)) {
return true
}

gitPathKey := makeGitPathKey(repoID, fileRelPath)
if _, registered := registeredAppPaths[gitPathKey]; registered {
return true
}
return false
})
}

// scanAllFiles inspects all files under the root or the given repository.
// And gives back all application info as much as possible.
func (r *Reporter) scanAllFiles(repoRoot, repoID string, shouldSkip func(string) bool) ([]*model.ApplicationInfo, error) {
func (r *Reporter) findUnregisteredApps(repoPath, repoID string, registeredAppPaths map[string]string) ([]*model.ApplicationInfo, error) {
apps := make([]*model.ApplicationInfo, 0)
err := fs.WalkDir(r.fileSystem, repoRoot, func(path string, d fs.DirEntry, err error) error {
// Scan all files under the repository.
err := fs.WalkDir(r.fileSystem, repoPath, func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
if d.IsDir() {
return nil
}
cfgRelPath, err := filepath.Rel(repoRoot, path)
cfgRelPath, err := filepath.Rel(repoPath, path)
if err != nil {
return err
}
if shouldSkip(cfgRelPath) {
if !model.IsApplicationConfigFile(filepath.Base(cfgRelPath)) {
return nil
}
gitPathKey := makeGitPathKey(repoID, cfgRelPath)
if _, registered := registeredAppPaths[gitPathKey]; registered {
return nil
}

appInfo, err := r.readApplicationInfo(repoRoot, filepath.Dir(cfgRelPath), filepath.Base(cfgRelPath))
appInfo, err := r.readApplicationInfo(repoPath, repoID, filepath.Dir(cfgRelPath), filepath.Base(cfgRelPath))
if err != nil {
r.logger.Error("failed to read application info",
zap.String("repo-id", repoID),
Expand All @@ -327,18 +348,12 @@ func (r *Reporter) scanAllFiles(repoRoot, repoID string, shouldSkip func(string)
return nil
})
if err != nil {
return nil, fmt.Errorf("failed to inspect files under %s: %w", repoRoot, err)
return nil, fmt.Errorf("failed to inspect files under %s: %w", repoPath, err)
}
return apps, nil
}

// makeGitPathKey builds a unique path between repositories.
// cfgFilePath is a relative path from the repo root.
func makeGitPathKey(repoID, cfgFilePath string) string {
return fmt.Sprintf("%s:%s", repoID, cfgFilePath)
}

func (r *Reporter) readApplicationInfo(repoDir, appDirRelPath, cfgFilename string) (*model.ApplicationInfo, error) {
func (r *Reporter) readApplicationInfo(repoDir, repoID, appDirRelPath, cfgFilename string) (*model.ApplicationInfo, error) {
b, err := fs.ReadFile(r.fileSystem, filepath.Join(repoDir, appDirRelPath, cfgFilename))
if err != nil {
return nil, fmt.Errorf("failed to open the configuration file: %w", err)
Expand All @@ -361,7 +376,14 @@ func (r *Reporter) readApplicationInfo(repoDir, appDirRelPath, cfgFilename strin
// TODO: Convert Kind string into dedicated type
//Kind: cfg.Kind,
Labels: spec.Labels,
RepoId: repoID,
Path: appDirRelPath,
ConfigFilename: cfgFilename,
}, nil
}

// makeGitPathKey builds a unique path between repositories.
// cfgFilePath is a relative path from the repo root.
func makeGitPathKey(repoID, cfgFilePath string) string {
return fmt.Sprintf("%s:%s", repoID, cfgFilePath)
}
34 changes: 20 additions & 14 deletions pkg/app/piped/appconfigreporter/appconfigreporter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import (

func TestReporter_findUnregisteredApps(t *testing.T) {
type args struct {
registeredAppPaths map[string]struct{}
registeredAppPaths map[string]string
repoPath, repoID string
}
testcases := []struct {
Expand All @@ -48,7 +48,7 @@ func TestReporter_findUnregisteredApps(t *testing.T) {
args: args{
repoPath: "invalid",
repoID: "repo-1",
registeredAppPaths: map[string]struct{}{},
registeredAppPaths: map[string]string{},
},
want: nil,
wantErr: true,
Expand All @@ -64,8 +64,8 @@ func TestReporter_findUnregisteredApps(t *testing.T) {
args: args{
repoPath: "path/to/repo-1",
repoID: "repo-1",
registeredAppPaths: map[string]struct{}{
"repo-1:app-1/.pipe.yaml": {},
registeredAppPaths: map[string]string{
"repo-1:app-1/.pipe.yaml": "id-1",
},
},
want: []*model.ApplicationInfo{},
Expand All @@ -82,7 +82,7 @@ func TestReporter_findUnregisteredApps(t *testing.T) {
args: args{
repoPath: "path/to/repo-1",
repoID: "repo-1",
registeredAppPaths: map[string]struct{}{},
registeredAppPaths: map[string]string{},
},
want: []*model.ApplicationInfo{},
wantErr: false,
Expand All @@ -104,12 +104,13 @@ spec:
args: args{
repoPath: "path/to/repo-1",
repoID: "repo-1",
registeredAppPaths: map[string]struct{}{},
registeredAppPaths: map[string]string{},
},
want: []*model.ApplicationInfo{
{
Name: "app-1",
Labels: map[string]string{"key-1": "value-1"},
RepoId: "repo-1",
Path: "app-1",
ConfigFilename: ".pipe.yaml",
},
Expand All @@ -133,12 +134,13 @@ spec:
args: args{
repoPath: "path/to/repo-1",
repoID: "repo-1",
registeredAppPaths: map[string]struct{}{},
registeredAppPaths: map[string]string{},
},
want: []*model.ApplicationInfo{
{
Name: "app-1",
Labels: map[string]string{"key-1": "value-1"},
RepoId: "repo-1",
Path: "app-1",
ConfigFilename: "dev.pipecd.yaml",
},
Expand Down Expand Up @@ -174,7 +176,7 @@ func TestReporter_findRegisteredApps(t *testing.T) {
repoID string
repo gitRepo
lastScannedCommit string
registeredAppPaths map[string]struct{}
registeredAppPaths map[string]string
}
testcases := []struct {
name string
Expand Down Expand Up @@ -227,8 +229,8 @@ func TestReporter_findRegisteredApps(t *testing.T) {
repoID: "repo-1",
repo: &fakeGitRepo{path: "path/to/repo-1", changedFiles: []string{"app-1/.pipe.yaml"}},
lastScannedCommit: "xxx",
registeredAppPaths: map[string]struct{}{
"repo-1:app-1/.pipe.yaml": {},
registeredAppPaths: map[string]string{
"repo-1:app-1/.pipe.yaml": "id-1",
},
},
want: []*model.ApplicationInfo{},
Expand All @@ -252,14 +254,16 @@ spec:
repoID: "repo-1",
repo: &fakeGitRepo{path: "path/to/repo-1", changedFiles: []string{"app-1/.pipe.yaml"}},
lastScannedCommit: "xxx",
registeredAppPaths: map[string]struct{}{
"repo-1:app-1/.pipe.yaml": {},
registeredAppPaths: map[string]string{
"repo-1:app-1/.pipe.yaml": "id-1",
},
},
want: []*model.ApplicationInfo{
{
Id: "id-1",
Name: "app-1",
Labels: map[string]string{"key-1": "value-1"},
RepoId: "repo-1",
Path: "app-1",
ConfigFilename: ".pipe.yaml",
},
Expand All @@ -284,14 +288,16 @@ spec:
repoID: "repo-1",
repo: &fakeGitRepo{path: "path/to/repo-1"},
lastScannedCommit: "",
registeredAppPaths: map[string]struct{}{
"repo-1:app-1/.pipe.yaml": {},
registeredAppPaths: map[string]string{
"repo-1:app-1/.pipe.yaml": "id-1",
},
},
want: []*model.ApplicationInfo{
{
Id: "id-1",
Name: "app-1",
Labels: map[string]string{"key-1": "value-1"},
RepoId: "repo-1",
Path: "app-1",
ConfigFilename: ".pipe.yaml",
},
Expand Down
12 changes: 7 additions & 5 deletions pkg/model/common.proto
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,11 @@ enum SyncStrategy {
}

message ApplicationInfo {
string name = 1 [(validate.rules).string.min_len = 1];
ApplicationKind kind = 2 [(validate.rules).enum.defined_only = true];
map<string, string> labels = 3;
string path = 4 [(validate.rules).string.pattern = "^[^/].+$"];
string config_filename = 5 [(validate.rules).string.min_len = 1];
string id = 1;
string name = 2 [(validate.rules).string.min_len = 1];
ApplicationKind kind = 3 [(validate.rules).enum.defined_only = true];
map<string, string> labels = 4;
string repo_id = 5 [(validate.rules).string.min_len = 1];
string path = 6 [(validate.rules).string.pattern = "^[^/].+$"];
string config_filename = 7;
}