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

MM-21722 - Repository synchronization tool #86

Merged
merged 49 commits into from
Sep 13, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
908c42f
Init sync tool.
tasdomas Jan 24, 2020
f048aed
Clean git repository check.
tasdomas Jan 24, 2020
1d5a03d
Runner tests.
tasdomas Jan 27, 2020
eea2093
File hash history.
tasdomas Jan 31, 2020
fb7e4d6
Unmarshaling of plan structure.
tasdomas Feb 2, 2020
a9919f3
Move clean repo checker to plan package.
tasdomas Feb 2, 2020
3351816
Added sync plan skeleton.
tasdomas Feb 2, 2020
d05b4f1
Copy directory.
tasdomas Feb 3, 2020
459fd97
Main entry point.
tasdomas Feb 5, 2020
f332bb8
Overwrite directory action.
tasdomas Feb 5, 2020
b2166c9
Overwrite file action.
tasdomas Feb 7, 2020
83b5b29
PathExists checker.
tasdomas Feb 7, 2020
cfe6ccb
Check whether a file has been altered.
tasdomas Feb 7, 2020
16f7d97
Plan unmarshalling.
tasdomas Feb 8, 2020
256d3e6
Plan execution.
tasdomas Feb 9, 2020
dc81b8e
Amend sync plan.
tasdomas Feb 10, 2020
447bf9f
Additional logging in checks and actions.
tasdomas Feb 10, 2020
21b7e77
Cleanups.
tasdomas Feb 10, 2020
4916c85
Added README.
tasdomas Feb 10, 2020
92e2835
Lint.
tasdomas Feb 10, 2020
a540756
Fixup and lint.
tasdomas Feb 10, 2020
a696455
Fix lint.
tasdomas Feb 10, 2020
ab9eab2
Extend sync plan with more paths.
tasdomas Feb 12, 2020
cb2ba0f
Handle not found errors more gracefully.
tasdomas Feb 12, 2020
c7bc1b8
Simplified logging.
tasdomas Feb 13, 2020
1c01dd2
Print out sync report.
tasdomas Feb 15, 2020
9313120
Add usage message.
tasdomas Feb 18, 2020
24e9ede
Address review comments.
tasdomas Jun 24, 2020
72f9c60
Group paths in plan structure.
tasdomas Jun 24, 2020
89515bd
Fix lint errors.
tasdomas Jun 24, 2020
d318011
Ignore sha1 lint warnings.
tasdomas Jun 24, 2020
a60dedb
Clean up go.sum
tasdomas Jun 24, 2020
2fb5087
Add comment for hardcoded relative path.
tasdomas Jun 30, 2020
1351d8e
Remove reference to removed file webapp/.babelrc from build/sync/plan…
tasdomas Jun 30, 2020
7376f08
Document plan.yml file format.
tasdomas Jun 30, 2020
4ba7798
Merge remote-tracking branch 'origin/master' into sync
tasdomas Jul 28, 2020
32a11d4
Merge remote-tracking branch 'origin/master' into sync
tasdomas Aug 4, 2020
660a4e0
Remove static/hello.html from sync plan.
tasdomas Aug 17, 2020
e57272d
Fix formatting in README.md.:
tasdomas Aug 17, 2020
1657842
Unconditionally overwrite Makefile when syncing.
tasdomas Aug 17, 2020
ff8d9b0
Sort files in sync plan.
tasdomas Aug 17, 2020
8318c16
Do no sync go.mod and webapp/i18n/en.json.
tasdomas Aug 17, 2020
f0a24d9
Add babel config to sync plan.
tasdomas Aug 17, 2020
1892385
Use 'source' and 'target' instead of 'template' and 'plugin'.
tasdomas Aug 17, 2020
7a35a37
Default values for repo parameters.
tasdomas Aug 17, 2020
0b067fe
Use separate go.mod for sync tool.
tasdomas Aug 17, 2020
99fbef0
Fix typo.
tasdomas Aug 17, 2020
161a5fe
Remove sync tool go.mod and go.sum.
tasdomas Sep 7, 2020
df5be34
Clean up go.mod.
tasdomas Sep 7, 2020
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 Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,7 @@ endif
ifneq ($(HAS_WEBAPP),)
cd webapp && $(NPM) run test;
endif
cd ./build/sync && $(GO) test -v $(GO_TEST_FLAGS) ./...

## Creates a coverage report for the server code.
.PHONY: coverage
Expand Down
113 changes: 113 additions & 0 deletions build/sync/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
sync
====

The sync tool is a proof-of-concept implementation of a tool for synchronizing mattermost plugin
repositories with the mattermost-plugin-starter-template repo.

Overview
--------

At its core the tool is just a collection of checks and actions that are executed according to a
synchronization plan (see [./build/sync/plan.yml](https://github.com/mattermost/mattermost-plugin-starter-template/blob/sync/build/sync/plan.yml)
for an example). The plan defines a set of files
and/or directories that need to be kept in sync between the plugin repository and the template (this
repo).

For each set of paths, a set of actions to be performed is outlined. No more than one action of that set
will be executed - the first one whose checks pass. Other actions are meant to act as fallbacks.
The idea is to be able to e.g. overwrite a file if it has no local changes or apply a format-specific
merge algorithm otherwise.

Before running each action, the tool will check if any checks are defined for that action. If there
are any, they will be executed and their results examined. If all checks pass, the action will be executed.
If there is a check failure, the tool will locate the next applicable action according to the plan and
start over with it.

The synchronization plan can also run checks before running any actions, e.g. to check if the source and
target worktrees are clean.

Running
-------

The tool can be executed from the root of this repository with a command:
```
$ go run ./build/sync/main.go ./build/sync/plan.yml ../mattermost-plugin-github
```

(assuming `mattermost-plugin-github` is the target repository we want to synchronize with the source).

plan.yml
---------

The `plan.yml` file (located in `build/sync/plan.yml`) consists of two parts:
- checks
- actions

The `checks` section defines tests to run before executing the plan itself. Currently the only available such check is `repo_is_clean` defined as:
```
type: repo_is_clean
params:
repo: source
```
The `repo` parameter takes one of two values:
- `source` - the `mattermost-plugin-starter-template` repository
- `target` - the repository of the plugin being updated.

The `actions` section defines actions to be run as part of the synchronization.
Each entry in this section has the form:
```
paths:
- path1
- path2
actions:
- type: action_type
params:
action_parameter: value
conditions:
- type: check_type
params:
check_parameter: value
```

`paths` is a list of file or directory paths (relative to the root of the repository)
synchronization should be performed on.

Each action in the `actions` section is defined by its type. Currently supported action types are:
- `overwrite_file` - overwrite the specified file in the `target` repository with the file in the `source` repository.
- `overwrite_directory` - overwrite a directory.

Both actions accept a parameter called `create` which determines if the file or directory should be created if it does not exist in the target repository.

The `conditions` part of an action definition defines tests that need to pass for the
action to be run. Available checks are:
- `exists`
- `file_unaltered`

The `exists` check takes a single parameter - `repo` (referencing either the source or target repository) and it passes only if the file or directory the action is about to be run on exists. If the repo parameter is not specified, it will default to `target`.

The `file_unaltered` check is only applicable to file paths. It passes if the file
has not been altered - i.e. it is identical to some version of that same file in the reference repository (usually `source`). This check takes two parameters:
- `in` - repository to check the file in, default `target`
- `compared-to` - repository to check the file against, default `source`.

When multiple actions are specified for a set of paths, the `sync` tool will only
execute a single action for each path. The first action in the list, whose conditions
are all satisfied will be executed.

If an acton fails due to an error, the synchronization run will be aborted.

Caveat emptor
-------------

This is a very basic proof-of-concept and there are many things that should be improved/implemented:
(in no specific order)

1. Format-specific merge actions for `go.mod`, `go.sum`, `webapp/package.json` and other files should
be implemented.
2. Better logging should be implemented.
3. Handling action dependencies should be investigated.
e.g. if the `build` directory is overwritten, that will in some cases mean that the go.mod file also needs
to be updated.
4. Storing the tree-hash of the template repository that the plugin was synchronized with would allow
improving the performance of the tool by restricting the search space when examining if a file
has been altered in the plugin repository.
85 changes: 85 additions & 0 deletions build/sync/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package main

import (
"flag"
"fmt"
"io/ioutil"
"os"
"path/filepath"

"sigs.k8s.io/yaml"

"github.com/mattermost/mattermost-plugin-starter-template/build/sync/plan"
)

func main() {
verbose := flag.Bool("verbose", false, "enable verbose output")
flag.Usage = func() {
fmt.Fprintf(flag.CommandLine.Output(), "Update a pluging directory with /mattermost-plugin-starter-template/.\n")
fmt.Fprintf(flag.CommandLine.Output(), "Usage of %s:\n", os.Args[0])
fmt.Fprintf(flag.CommandLine.Output(), "%s <plan.yml> <plugin_directory>\n", os.Args[0])
flag.PrintDefaults()
}

flag.Parse()
// TODO: implement proper command line parameter parsing.
if len(os.Args) != 3 {
fmt.Fprintf(os.Stderr, "running: \n $ sync [plan.yaml] [plugin path]\n")
os.Exit(1)
}

syncPlan, err := readPlan(os.Args[1])
if err != nil {
fmt.Fprintf(os.Stderr, "coud not load plan: %s\n", err)
os.Exit(1)
}

srcDir, err := os.Getwd()
if err != nil {
fmt.Fprintf(os.Stderr, "failed to get current directory: %s\n", err)
os.Exit(1)
}

trgDir, err := filepath.Abs(os.Args[2])
if err != nil {
fmt.Fprintf(os.Stderr, "could not determine target directory: %s\n", err)
os.Exit(1)
}

srcRepo, err := plan.GetRepoSetup(srcDir)
if err != nil {
fmt.Fprintf(os.Stderr, "%s\n", err)
os.Exit(1)
}
trgRepo, err := plan.GetRepoSetup(trgDir)
if err != nil {
fmt.Fprintf(os.Stderr, "%s\n", err)
os.Exit(1)
}

planSetup := plan.Setup{
Source: srcRepo,
Target: trgRepo,
VerboseLogging: *verbose,
}
err = syncPlan.Execute(planSetup)
if err != nil {
fmt.Fprintf(os.Stderr, "%s\n", err)
os.Exit(1)
}
}

func readPlan(path string) (*plan.Plan, error) {
raw, err := ioutil.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("failed to read plan file %q: %v", path, err)
}

var p plan.Plan
err = yaml.Unmarshal(raw, &p)
if err != nil {
return nil, fmt.Errorf("failed to unmarshal plan yaml: %v", err)
}

return &p, err
}
44 changes: 44 additions & 0 deletions build/sync/plan.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
checks:
- type: repo_is_clean
params:
repo: source
- type: repo_is_clean
params:
repo: target
actions:
- paths:
- build
actions:
- type: overwrite_directory
params:
create: true
- paths:
- .editorconfig
actions:
- type: overwrite_file
params:
create: true
conditions:
- type: file_unaltered
- paths:
- Makefile
actions:
- type: overwrite_file
params:
create: true
- paths:
- .circleci/config.yml
- server/configuration.go
- server/plugin.go
- server/plugin_test.go
- webapp/.eslintrc.json
- webapp/babel.config.js
- webapp/package.json
- webapp/src/index.js
- webapp/tsconfig.json
- webapp/webpack.config.js
actions:
- type: overwrite_file
conditions:
- type: exists
- type: file_unaltered
Loading