Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-pages.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--acceptance_test.go5
-rw-r--r--internal/domain/domain.go25
-rw-r--r--internal/domain/group.go38
-rw-r--r--internal/domain/group_test.go97
-rw-r--r--internal/domain/map.go59
-rw-r--r--internal/domain/map_test.go8
-rw-r--r--shared/pages/group/subgroup/project/public/index.html1
-rw-r--r--shared/pages/group/subgroup/project/public/subdir/index.html1
8 files changed, 204 insertions, 30 deletions
diff --git a/acceptance_test.go b/acceptance_test.go
index 4100fde1..cf3c7d09 100644
--- a/acceptance_test.go
+++ b/acceptance_test.go
@@ -128,6 +128,11 @@ func TestKnownHostReturns200(t *testing.T) {
host: "CapitalGroup.gitlab-example.com",
path: "CapitalProject/",
},
+ {
+ name: "subgroup",
+ host: "group.gitlab-example.com",
+ path: "subgroup/project/",
+ },
}
for _, test := range tests {
diff --git a/internal/domain/domain.go b/internal/domain/domain.go
index 5555ab91..3638c372 100644
--- a/internal/domain/domain.go
+++ b/internal/domain/domain.go
@@ -21,6 +21,13 @@ import (
"gitlab.com/gitlab-org/gitlab-pages/internal/httputil"
)
+const (
+ // MaxProjectDepth is set to the maximum nested project depth in gitlab (21) plus 3.
+ // One for the project, one for the first empty element of the split (URL.Path starts with /),
+ // and one for the real file path
+ MaxProjectDepth int = 24
+)
+
type locationDirectoryError struct {
FullPath string
RelativePath string
@@ -33,16 +40,6 @@ type project struct {
ID uint64
}
-type projects map[string]*project
-type subgroups map[string]*group
-
-type group struct {
- name string
-
- // group domains:
- projects projects
-}
-
// D is a domain that gitlab-pages can serve.
type D struct {
group
@@ -123,11 +120,11 @@ func getHost(r *http.Request) string {
func (d *D) getProjectWithSubpath(r *http.Request) (*project, string, string) {
// Check for a project specified in the URL: http://group.gitlab.io/projectA
// If present, these projects shadow the group domain.
- split := strings.SplitN(r.URL.Path, "/", 3)
+ split := strings.SplitN(r.URL.Path, "/", MaxProjectDepth)
if len(split) >= 2 {
- projectName := strings.ToLower(split[1])
- if project := d.projects[projectName]; project != nil {
- return project, split[1], strings.Join(split[2:], "/")
+ project, projectPath, urlPath := d.digProjectWithSubpath("", split[1:])
+ if project != nil {
+ return project, projectPath, urlPath
}
}
diff --git a/internal/domain/group.go b/internal/domain/group.go
new file mode 100644
index 00000000..83b8d255
--- /dev/null
+++ b/internal/domain/group.go
@@ -0,0 +1,38 @@
+package domain
+
+import (
+ "path"
+ "strings"
+)
+
+type projects map[string]*project
+type subgroups map[string]*group
+
+type group struct {
+ name string
+
+ // nested groups
+ subgroups subgroups
+
+ // group domains:
+ projects projects
+}
+
+func (g *group) digProjectWithSubpath(parentPath string, keys []string) (*project, string, string) {
+ if len(keys) >= 1 {
+ head := keys[0]
+ tail := keys[1:]
+ currentPath := path.Join(parentPath, head)
+ search := strings.ToLower(head)
+
+ if project := g.projects[search]; project != nil {
+ return project, currentPath, path.Join(tail...)
+ }
+
+ if subgroup := g.subgroups[search]; subgroup != nil {
+ return subgroup.digProjectWithSubpath(currentPath, tail)
+ }
+ }
+
+ return nil, "", ""
+}
diff --git a/internal/domain/group_test.go b/internal/domain/group_test.go
new file mode 100644
index 00000000..2e41ef53
--- /dev/null
+++ b/internal/domain/group_test.go
@@ -0,0 +1,97 @@
+package domain
+
+import (
+ "strings"
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestGroupDig(t *testing.T) {
+ matchingProject := &project{ID: 1}
+
+ tests := []struct {
+ name string
+ g group
+ path string
+ expectedProject *project
+ expectedProjectPath string
+ expectedPath string
+ }{
+ {
+ name: "empty group",
+ path: "projectb/demo/features.html",
+ g: group{},
+ },
+ {
+ name: "group with project",
+ path: "projectb/demo/features.html",
+ g: group{
+ projects: projects{"projectb": matchingProject},
+ },
+ expectedProject: matchingProject,
+ expectedProjectPath: "projectb",
+ expectedPath: "demo/features.html",
+ },
+ {
+ name: "group with project and no path in URL",
+ path: "projectb",
+ g: group{
+ projects: projects{"projectb": matchingProject},
+ },
+ expectedProject: matchingProject,
+ expectedProjectPath: "projectb",
+ },
+ {
+ name: "group with subgroup and project",
+ path: "projectb/demo/features.html",
+ g: group{
+ projects: projects{"projectb": matchingProject},
+ subgroups: subgroups{
+ "sub1": &group{
+ projects: projects{"another": &project{}},
+ },
+ },
+ },
+ expectedProject: matchingProject,
+ expectedProjectPath: "projectb",
+ expectedPath: "demo/features.html",
+ },
+ {
+ name: "group with project inside a subgroup",
+ path: "sub1/projectb/demo/features.html",
+ g: group{
+ subgroups: subgroups{
+ "sub1": &group{
+ projects: projects{"projectb": matchingProject},
+ },
+ },
+ projects: projects{"another": &project{}},
+ },
+ expectedProject: matchingProject,
+ expectedProjectPath: "sub1/projectb",
+ expectedPath: "demo/features.html",
+ },
+ {
+ name: "group with matching subgroup but no project",
+ path: "sub1/projectb/demo/features.html",
+ g: group{
+ subgroups: subgroups{
+ "sub1": &group{
+ projects: projects{"another": &project{}},
+ },
+ },
+ },
+ },
+ }
+
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ project, projectPath, urlPath := test.g.digProjectWithSubpath("", strings.Split(test.path, "/"))
+
+ assert.Equal(t, test.expectedProject, project)
+ assert.Equal(t, test.expectedProjectPath, projectPath)
+ assert.Equal(t, test.expectedPath, urlPath)
+ })
+ }
+}
diff --git a/internal/domain/map.go b/internal/domain/map.go
index 3d9a4073..26657a1c 100644
--- a/internal/domain/map.go
+++ b/internal/domain/map.go
@@ -36,7 +36,7 @@ func (dm Map) updateDomainMap(domainName string, domain *D) {
func (dm Map) addDomain(rootDomain, groupName, projectName string, config *domainConfig) {
newDomain := &D{
- group: group { name: groupName },
+ group: group{name: groupName},
projectName: projectName,
config: config,
}
@@ -46,21 +46,41 @@ func (dm Map) addDomain(rootDomain, groupName, projectName string, config *domai
dm.updateDomainMap(domainName, newDomain)
}
-func (dm Map) updateGroupDomain(rootDomain, groupName, projectName string, httpsOnly bool, accessControl bool, id uint64) {
+func (dm Map) updateGroupDomain(rootDomain, groupName, projectPath string, httpsOnly bool, accessControl bool, id uint64) {
domainName := strings.ToLower(groupName + "." + rootDomain)
groupDomain := dm[domainName]
if groupDomain == nil {
groupDomain = &D{
- group: group{
- name: groupName,
- projects: make(projects),
+ group: group{
+ name: groupName,
+ projects: make(projects),
+ subgroups: make(subgroups),
},
}
}
- groupDomain.projects[strings.ToLower(projectName)] = &project{
- NamespaceProject: domainName == strings.ToLower(projectName),
+ split := strings.SplitN(strings.ToLower(projectPath), "/", MaxProjectDepth)
+ projectName := split[len(split)-1]
+ g := &groupDomain.group
+
+ for i := 0; i < len(split)-1; i++ {
+ subgroupName := split[i]
+ subgroup := g.subgroups[subgroupName]
+ if subgroup == nil {
+ subgroup = &group{
+ name: subgroupName,
+ projects: make(projects),
+ subgroups: make(subgroups),
+ }
+ g.subgroups[subgroupName] = subgroup
+ }
+
+ g = subgroup
+ }
+
+ g.projects[projectName] = &project{
+ NamespaceProject: domainName == projectName,
HTTPSOnly: httpsOnly,
AccessControl: accessControl,
ID: id,
@@ -88,7 +108,7 @@ func (dm Map) readProjectConfig(rootDomain string, group, projectName string, co
}
}
-func readProject(group, projectName string, fanIn chan<- jobResult) {
+func readProject(group, parent, projectName string, fanIn chan<- jobResult) {
if strings.HasPrefix(projectName, ".") {
return
}
@@ -98,25 +118,32 @@ func readProject(group, projectName string, fanIn chan<- jobResult) {
return
}
- if _, err := os.Lstat(filepath.Join(group, projectName, "public")); err != nil {
+ projectPath := filepath.Join(parent, projectName)
+ if _, err := os.Lstat(filepath.Join(group, projectPath, "public")); err != nil {
+ // maybe it's a subgroup
+ buf := make([]byte, 2*os.Getpagesize())
+ readProjects(group, projectPath, buf, fanIn)
+
return
}
// We read the config.json file _before_ fanning in, because it does disk
// IO and it does not need access to the domains map.
config := &domainsConfig{}
- if err := config.Read(group, projectName); err != nil {
+ if err := config.Read(group, projectPath); err != nil {
config = nil
}
- fanIn <- jobResult{group: group, project: projectName, config: config}
+ fanIn <- jobResult{group: group, project: projectPath, config: config}
}
-func readProjects(group string, buf []byte, fanIn chan<- jobResult) {
- fis, err := godirwalk.ReadDirents(group, buf)
+func readProjects(group, parent string, buf []byte, fanIn chan<- jobResult) {
+ subgroup := filepath.Join(group, parent)
+ fis, err := godirwalk.ReadDirents(subgroup, buf)
if err != nil {
log.WithError(err).WithFields(log.Fields{
- "group": group,
+ "group": group,
+ "parent": parent,
}).Print("readdir failed")
return
}
@@ -127,7 +154,7 @@ func readProjects(group string, buf []byte, fanIn chan<- jobResult) {
continue
}
- readProject(group, project.Name(), fanIn)
+ readProject(group, parent, project.Name(), fanIn)
}
}
@@ -151,7 +178,7 @@ func (dm Map) ReadGroups(rootDomain string, fis godirwalk.Dirents) {
for group := range fanOutGroups {
started := time.Now()
- readProjects(group, buf, fanIn)
+ readProjects(group, "", buf, fanIn)
log.WithFields(log.Fields{
"group": group,
diff --git a/internal/domain/map_test.go b/internal/domain/map_test.go
index a2b2b575..2306edef 100644
--- a/internal/domain/map_test.go
+++ b/internal/domain/map_test.go
@@ -70,6 +70,14 @@ func TestReadProjects(t *testing.T) {
exp2 := &domainConfig{Domain: "other.domain.com", Certificate: "test", Key: "key"}
assert.Equal(t, exp2, dm["other.domain.com"].config)
+
+ // check subgroups
+ domain, ok := dm["group.test.io"]
+ require.True(t, ok, "missing group.test.io domain")
+ subgroup, ok := domain.subgroups["subgroup"]
+ require.True(t, ok, "missing group.test.io subgroup")
+ _, ok = subgroup.projects["project"]
+ require.True(t, ok, "missing project for subgrup in group.test.io domain")
}
// This write must be atomic, otherwise we cannot predict the state of the
diff --git a/shared/pages/group/subgroup/project/public/index.html b/shared/pages/group/subgroup/project/public/index.html
new file mode 100644
index 00000000..91ec223d
--- /dev/null
+++ b/shared/pages/group/subgroup/project/public/index.html
@@ -0,0 +1 @@
+A subgroup project
diff --git a/shared/pages/group/subgroup/project/public/subdir/index.html b/shared/pages/group/subgroup/project/public/subdir/index.html
new file mode 100644
index 00000000..59bf0492
--- /dev/null
+++ b/shared/pages/group/subgroup/project/public/subdir/index.html
@@ -0,0 +1 @@
+A subgroup project-subsubdir