mirror of
https://github.com/Graylog2/graylog-project-cli.git
synced 2026-03-13 08:02:57 +08:00
530 lines
15 KiB
Go
530 lines
15 KiB
Go
package project
|
|
|
|
import (
|
|
"fmt"
|
|
"github.com/samber/lo"
|
|
"path/filepath"
|
|
"regexp"
|
|
"strings"
|
|
|
|
"github.com/Graylog2/graylog-project-cli/config"
|
|
"github.com/Graylog2/graylog-project-cli/logger"
|
|
"github.com/Graylog2/graylog-project-cli/manifest"
|
|
"github.com/Graylog2/graylog-project-cli/pomparse"
|
|
"github.com/Graylog2/graylog-project-cli/utils"
|
|
"github.com/imdario/mergo"
|
|
)
|
|
|
|
type Project struct {
|
|
config config.Config
|
|
Server Module
|
|
Modules []Module
|
|
AssemblyPlatforms []string
|
|
JVMVersion int
|
|
}
|
|
|
|
type Apply struct {
|
|
FromRevision string
|
|
NewBranch string
|
|
NewVersion string
|
|
}
|
|
|
|
type Module struct {
|
|
Name string
|
|
Path string
|
|
Repository string
|
|
Revision string
|
|
BaseRevision string
|
|
FetchRevision string
|
|
Assemblies []string
|
|
AssemblyAttachment string
|
|
Server bool
|
|
Submodules []Module
|
|
apply Apply
|
|
ApplyExecute bool
|
|
SkipRelease bool
|
|
}
|
|
|
|
func (module *Module) IsMavenModule() bool {
|
|
return utils.FileExists(filepath.Join(module.Path, "pom.xml"))
|
|
}
|
|
|
|
func (module *Module) IsNpmModule() bool {
|
|
return utils.FileExists(filepath.Join(module.Path, "package.json"))
|
|
}
|
|
|
|
func (module *Module) HasSubmodules() bool {
|
|
return len(module.Submodules) > 0
|
|
}
|
|
|
|
// Returns a list of pom.xml files for this modules. If the relative parameter
|
|
// is set to "true", the path to the pom.xml files will be relative to the
|
|
// module root.
|
|
func (module *Module) PomFiles(relative bool) []string {
|
|
var list []string
|
|
if relative {
|
|
utils.InDirectory(module.Path, func() {
|
|
list = pomparse.FindPomFiles("")
|
|
})
|
|
} else {
|
|
list = pomparse.FindPomFiles(module.Path)
|
|
}
|
|
return list
|
|
}
|
|
|
|
func (module *Module) RelativePath() string {
|
|
// The path in the "<module>" tag needs to be relative to make maven happy!
|
|
// Using the GetRelativePathEvalSymlinks function here to make sure the relative path is as short as possible.
|
|
// Otherwise we might get a very long relative path if the current working dir is inside a symlink.
|
|
//
|
|
// Example:
|
|
// cwd = /home/bernd/workspace/graylog-project (where workspace is a symlink to /tmp/workspace)
|
|
// module.Path = /home/bernd/workspace/graylog-project-repos/graylog2-server
|
|
//
|
|
// Calling utils.GetRelativePath(module.Path) (which does not evaluate the symlink in the given path, but uses an
|
|
// evaluated current working dir) would result in:
|
|
// ../../../home/bernd/workspace/graylog-project-repos/graylog2-server
|
|
//
|
|
// Calling utils.GetRelativePathEvalSymlinks(module.Path) returns the following instead:
|
|
// ../graylog-project-repos/graylog2-server
|
|
//
|
|
// TODO: This behavior is an implementation detail of utils.GetCwd which calls filepath.EvalSymlinks on the result
|
|
// TODO: of os.Getwd to help filepath.Walk... This is a bit of a mess and should be fixed eventually.
|
|
//
|
|
// IntelliJ cannot handle the first variant so we have to make sure we get a short relative path when putting it
|
|
// in the "<module/>" attribute in pom.xml.
|
|
return utils.GetRelativePathEvalSymlinks(module.Path)
|
|
}
|
|
|
|
func (module *Module) GroupId() string {
|
|
return getMavenCoordinates(module.Path).GroupId
|
|
}
|
|
|
|
func (module *Module) ArtifactId() string {
|
|
return getMavenCoordinates(module.Path).ArtifactId
|
|
}
|
|
|
|
func (module *Module) Version() string {
|
|
return getMavenCoordinates(module.Path).Version
|
|
}
|
|
|
|
func (module *Module) ParentGroupId() string {
|
|
return getMavenCoordinates(module.Path).ParentGroupId
|
|
}
|
|
|
|
func (module *Module) ParentArtifactId() string {
|
|
return getMavenCoordinates(module.Path).ParentArtifactId
|
|
}
|
|
|
|
func (module *Module) ParentVersion() string {
|
|
return getMavenCoordinates(module.Path).ParentVersion
|
|
}
|
|
|
|
func (module *Module) ParentRelativePath() string {
|
|
return getMavenCoordinates(module.Path).ParentRelativePath
|
|
}
|
|
|
|
func (module *Module) HasParent() bool {
|
|
coordinates := getMavenCoordinates(module.Path)
|
|
return coordinates.ParentGroupId != "" && coordinates.ParentArtifactId != ""
|
|
}
|
|
|
|
func (module *Module) ApplyFromRevision() string {
|
|
return module.apply.FromRevision
|
|
}
|
|
|
|
func (module *Module) ApplyNewBranch() string {
|
|
return module.apply.NewBranch
|
|
}
|
|
|
|
func (module *Module) ApplyNewVersion() string {
|
|
return module.apply.NewVersion
|
|
}
|
|
|
|
func getMavenCoordinates(path string) pomparse.MavenCoordinates {
|
|
return pomparse.GetMavenCoordinates(filepath.Join(path, "pom.xml"))
|
|
}
|
|
|
|
type projectOptions struct {
|
|
moduleOverride bool
|
|
pullRequests bool
|
|
}
|
|
|
|
type projectOption func(*projectOptions)
|
|
|
|
func WithModuleOverride() projectOption {
|
|
return func(o *projectOptions) {
|
|
o.moduleOverride = true
|
|
}
|
|
}
|
|
|
|
func WithPullRequests() projectOption {
|
|
return func(o *projectOptions) {
|
|
o.pullRequests = true
|
|
}
|
|
}
|
|
|
|
func New(config config.Config, manifestFiles []string, options ...projectOption) Project {
|
|
// Create a new project options object and process all given options
|
|
opt := projectOptions{}
|
|
for _, o := range options {
|
|
o(&opt)
|
|
}
|
|
|
|
readManifest := manifest.ReadManifest(manifestFiles)
|
|
|
|
var server Module
|
|
|
|
// Make sure we use an absolute path!
|
|
repositoryRoot := utils.GetAbsolutePath(config.RepositoryRoot)
|
|
projectModules := make([]Module, 0)
|
|
|
|
defaultApply := Apply{
|
|
FromRevision: readManifest.DefaultApply.FromRevision,
|
|
NewBranch: readManifest.DefaultApply.NewBranch,
|
|
NewVersion: readManifest.DefaultApply.NewVersion,
|
|
}
|
|
|
|
for _, module := range readManifest.Modules {
|
|
moduleName, _ := utils.FirstNonEmpty(module.Name, utils.NameFromRepository(module.Repository))
|
|
moduleRepository := module.Repository
|
|
submodules := make([]Module, 0)
|
|
|
|
// Create apply data for this module and fill the blanks from the default apply data
|
|
moduleApply := newApplyFromTemplate(module, defaultApply)
|
|
|
|
if config.ForceHttpsRepos {
|
|
moduleRepository = utils.ConvertGithubGitToHTTPS(module.Repository)
|
|
}
|
|
|
|
if module.HasSubmodules() {
|
|
for _, submodule := range module.SubModules {
|
|
path := getModulePath(repositoryRoot, moduleName, submodule)
|
|
name := getMavenCoordinates(path).ArtifactId
|
|
|
|
// Create apply data for this submodule and fill the blanks from the parent module apply data
|
|
submoduleApply := newApplyFromTemplate(submodule, moduleApply)
|
|
|
|
if name == "" {
|
|
name = moduleName
|
|
}
|
|
|
|
submodules = append(submodules, Module{
|
|
Name: name,
|
|
Path: path,
|
|
Repository: moduleRepository,
|
|
Revision: module.Revision,
|
|
Assemblies: submodule.Assemblies,
|
|
AssemblyAttachment: submodule.AssemblyAttachment,
|
|
SkipRelease: submodule.SkipRelease,
|
|
apply: submoduleApply,
|
|
})
|
|
}
|
|
}
|
|
|
|
path := getModulePath(repositoryRoot, moduleName, module)
|
|
name := getMavenCoordinates(path).ArtifactId
|
|
|
|
if name == "" {
|
|
name = moduleName
|
|
}
|
|
|
|
newModule := Module{
|
|
Name: name,
|
|
Path: path,
|
|
Repository: moduleRepository,
|
|
Revision: module.Revision,
|
|
BaseRevision: module.Revision,
|
|
Assemblies: module.Assemblies,
|
|
AssemblyAttachment: module.AssemblyAttachment,
|
|
SkipRelease: module.SkipRelease,
|
|
Server: module.Server,
|
|
Submodules: submodules,
|
|
apply: moduleApply,
|
|
}
|
|
|
|
// Set execute flag if the manifest should be applied if it contains apply config
|
|
newModule.ApplyExecute = config.ApplyManifest.Execute
|
|
|
|
projectModules = append(projectModules, newModule)
|
|
|
|
// Decide if this module is the server module based on the config option
|
|
if newModule.Server {
|
|
// Only set if server is not already set
|
|
if !server.Server {
|
|
server = newModule
|
|
} else {
|
|
logger.Error("Server module already set to %v, not setting it to %v", server.Name, newModule.Name)
|
|
logger.Error("Check your manifests %v, only one module should have 'server: true'", manifestFiles)
|
|
}
|
|
}
|
|
}
|
|
|
|
if server.Name == "" {
|
|
logger.Fatal("No server module in manifests: %v", manifestFiles)
|
|
}
|
|
|
|
if len(config.Checkout.ModuleOverride) > 0 && opt.moduleOverride {
|
|
projectModules = applyModuleOverride(config, projectModules)
|
|
}
|
|
|
|
if len(config.Checkout.PullRequests) > 0 && opt.pullRequests {
|
|
projectModules = applyPullRequestsOverride(config, projectModules)
|
|
}
|
|
|
|
project := Project{
|
|
config: config,
|
|
Server: server,
|
|
Modules: projectModules,
|
|
AssemblyPlatforms: readManifest.AssemblyPlatforms,
|
|
JVMVersion: readManifest.JVMVersion,
|
|
}
|
|
|
|
return project
|
|
}
|
|
|
|
func newApplyFromTemplate(module manifest.ManifestModule, template Apply) Apply {
|
|
newApply := Apply{
|
|
FromRevision: module.Apply.FromRevision,
|
|
NewBranch: module.Apply.NewBranch,
|
|
NewVersion: module.Apply.NewVersion,
|
|
}
|
|
|
|
// Fill in missing Apply attributes from template
|
|
if err := mergo.Merge(&newApply, template); err != nil {
|
|
logger.Fatal("Couldn't merge apply state: src=%#v dst=%#v", template, newApply)
|
|
}
|
|
|
|
return newApply
|
|
}
|
|
|
|
func applyModuleOverride(c config.Config, modules []Module) []Module {
|
|
newModules := make([]Module, 0)
|
|
|
|
// We check if there is an override for any module in our manifests
|
|
for _, module := range modules {
|
|
for _, override := range c.Checkout.ModuleOverride {
|
|
// The override option is "<repo-match-substring>=<repo-replacement-name>@<revision>
|
|
parts := strings.SplitN(override, "=", 2)
|
|
if len(parts) != 2 {
|
|
logger.Error("invalid override <%s> - skipping", override)
|
|
continue
|
|
}
|
|
matchString, replacement := parts[0], parts[1]
|
|
|
|
// Now split the replacement
|
|
replacementParts := strings.SplitN(replacement, "@", 2)
|
|
if len(parts) != 2 {
|
|
logger.Error("invalid override <%s> - skipping", override)
|
|
continue
|
|
}
|
|
replacementRepo, replacementRev := replacementParts[0], replacementParts[1]
|
|
|
|
// The repo-match-substring of the override is used to select if the current module has
|
|
// an override
|
|
if strings.Contains(module.Repository, matchString) {
|
|
// Build a new repo URL depending on the original type. (SSH, HTTPS, ...)
|
|
newRepo, err := utils.ReplaceGitHubURL(module.Repository, replacementRepo)
|
|
if err != nil {
|
|
logger.Error("couldn't replace repository for module <%S>: %v", module.Name, err)
|
|
continue
|
|
}
|
|
|
|
logger.Info("Overriding <%s@%s> with <%s@%s> for module <%s>",
|
|
module.Repository, module.Revision, newRepo, replacementRev, module.Name)
|
|
|
|
module.Repository = newRepo
|
|
module.Revision = replacementRev
|
|
}
|
|
}
|
|
|
|
newModules = append(newModules, module)
|
|
}
|
|
|
|
return newModules
|
|
}
|
|
|
|
func applyPullRequestsOverride(c config.Config, modules []Module) []Module {
|
|
newModules := make([]Module, 0)
|
|
regexp.MustCompile("^\\d+$")
|
|
|
|
// We check if there is an override for any module in our manifests
|
|
for _, module := range modules {
|
|
for _, pullRequest := range c.Checkout.PullRequests {
|
|
prRepo, prNumber, err := utils.ParseGitHubPRString(pullRequest)
|
|
if err != nil {
|
|
logger.Fatal("Error parsing pull request: %v", err)
|
|
}
|
|
|
|
repoUrl, err := utils.ParseGitHubURL(module.Repository)
|
|
if err != nil {
|
|
logger.Error("couldn't parse repository URL: %v", err)
|
|
}
|
|
|
|
if repoUrl.Matches(prRepo) {
|
|
logger.Debug("Checking out pull-request %s for module %s", pullRequest, module.Name)
|
|
|
|
module.Revision = fmt.Sprintf("pull-request/%d", prNumber)
|
|
// PRs can be fetched from GitHub. See "Checking out pull requests locally":
|
|
// https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/checking-out-pull-requests-locally
|
|
module.FetchRevision = fmt.Sprintf("+refs/pull/%d/head:refs/remotes/origin/%s", prNumber, module.Revision)
|
|
}
|
|
}
|
|
|
|
newModules = append(newModules, module)
|
|
}
|
|
|
|
return newModules
|
|
}
|
|
|
|
func getModulePath(repositoryPath string, name string, module manifest.ManifestModule) string {
|
|
if module.Path == "" {
|
|
return filepath.Join(repositoryPath, name)
|
|
} else {
|
|
return filepath.Join(repositoryPath, name, module.Path)
|
|
}
|
|
}
|
|
|
|
func MavenDependencies(project Project) []Module {
|
|
dependencies := make([]Module, 0)
|
|
|
|
ForEachModuleOrSubmodules(project, func(module Module) {
|
|
if module.IsMavenModule() {
|
|
dependencies = append(dependencies, module)
|
|
}
|
|
})
|
|
|
|
return dependencies
|
|
}
|
|
|
|
func forEachModule(modules []Module, callback func(Module)) {
|
|
for _, module := range modules {
|
|
callback(module)
|
|
}
|
|
}
|
|
|
|
func forEachModuleE(modules []Module, callback func(Module) error) error {
|
|
for _, module := range modules {
|
|
if err := callback(module); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func forEachModuleOrSubmodules(modules []Module, callback func(Module)) {
|
|
for _, module := range modules {
|
|
if module.HasSubmodules() {
|
|
forEachModule(module.Submodules, callback)
|
|
} else {
|
|
callback(module)
|
|
}
|
|
}
|
|
}
|
|
|
|
func forEachModuleAndSubmodules(modules []Module, callback func(Module)) {
|
|
for _, module := range modules {
|
|
callback(module)
|
|
if module.HasSubmodules() {
|
|
forEachModule(module.Submodules, callback)
|
|
}
|
|
}
|
|
}
|
|
|
|
func ForEachModule(project Project, callback func(Module)) {
|
|
forEachModule(project.Modules, callback)
|
|
}
|
|
|
|
func ForEachSelectedModule(project Project, callback func(Module)) {
|
|
forEachModule(SelectedModules(project), callback)
|
|
}
|
|
|
|
func ForEachSelectedModuleE(project Project, callback func(Module) error) error {
|
|
return forEachModuleE(SelectedModules(project), callback)
|
|
}
|
|
|
|
func ForEachModuleOrSubmodules(project Project, callback func(Module)) {
|
|
forEachModuleOrSubmodules(project.Modules, callback)
|
|
}
|
|
|
|
func ForEachSelectedModuleOrSubmodules(project Project, callback func(Module)) {
|
|
forEachModuleOrSubmodules(SelectedModules(project), callback)
|
|
}
|
|
|
|
func ForEachModuleAndSubmodules(project Project, callback func(Module)) {
|
|
forEachModuleAndSubmodules(project.Modules, callback)
|
|
}
|
|
|
|
func ForEachSelectedModuleAndSubmodules(project Project, callback func(Module)) {
|
|
forEachModuleAndSubmodules(SelectedModules(project), callback)
|
|
}
|
|
|
|
func SelectedModules(project Project) []Module {
|
|
var selectedModules []Module
|
|
|
|
if project.config.SelectedModules == "" && project.config.SelectedAssemblies == "" {
|
|
return project.Modules
|
|
}
|
|
|
|
if project.config.SelectedModules == "" && project.config.SelectedAssemblies != "" {
|
|
return lo.Filter(project.Modules, func(module Module, _ int) bool {
|
|
moduleAssemblies := lo.Union(module.Assemblies, lo.FlatMap(module.Submodules, func(submodule Module, _ int) []string {
|
|
return submodule.Assemblies
|
|
}))
|
|
|
|
selected := false
|
|
|
|
for _, assembly := range strings.Split(project.config.SelectedAssemblies, ",") {
|
|
if strings.HasPrefix(assembly, "-") {
|
|
selected = !lo.Contains(moduleAssemblies, strings.TrimPrefix(assembly, "-"))
|
|
} else {
|
|
selected = lo.Contains(moduleAssemblies, assembly)
|
|
}
|
|
}
|
|
|
|
return selected
|
|
})
|
|
}
|
|
|
|
substrings := strings.Split(project.config.SelectedModules, ",")
|
|
|
|
for _, module := range project.Modules {
|
|
for _, substring := range substrings {
|
|
if strings.Contains(module.Name, substring) {
|
|
selectedModules = append(selectedModules, module)
|
|
}
|
|
}
|
|
}
|
|
|
|
return selectedModules
|
|
}
|
|
|
|
func MaxModuleNameLength(project Project) int {
|
|
maxNameLength := 0
|
|
|
|
ForEachSelectedModule(project, func(module Module) {
|
|
if len(module.Name) > maxNameLength {
|
|
maxNameLength = len(module.Name)
|
|
}
|
|
})
|
|
|
|
return maxNameLength
|
|
}
|
|
|
|
func HasModule(project Project, groupId string, artifactId string) (bool, Module) {
|
|
var matchingModule Module
|
|
var matched bool
|
|
|
|
forEachModuleOrSubmodules(project.Modules, func(module Module) {
|
|
c := getMavenCoordinates(module.Path)
|
|
|
|
if (c.GroupId == groupId || c.ParentGroupId == groupId) && (c.ArtifactId == artifactId || c.ParentArtifactId == artifactId) {
|
|
matchingModule = module
|
|
matched = true
|
|
}
|
|
})
|
|
|
|
return matched, matchingModule
|
|
}
|