Skip to content

Commit

Permalink
Merge pull request #338 from runatlantis/projectname
Browse files Browse the repository at this point in the history
Allow slashes in project names
  • Loading branch information
lkysow authored Oct 30, 2018
2 parents a91af8b + 2d214b2 commit 6af4d79
Show file tree
Hide file tree
Showing 4 changed files with 92 additions and 2 deletions.
13 changes: 11 additions & 2 deletions server/events/runtime/runtime.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ package runtime

import (
"fmt"
"regexp"

"github.com/hashicorp/go-version"
"github.com/runatlantis/atlantis/server/events/yaml/valid"
Expand All @@ -23,11 +24,19 @@ func MustConstraint(constraint string) version.Constraints {
return c
}

// invalidFilenameChars matches chars that are invalid for linux and windows
// filenames.
// From https://www.oreilly.com/library/view/regular-expressions-cookbook/9781449327453/ch08s25.html
var invalidFilenameChars = regexp.MustCompile(`[\\/:"*?<>|]`)

// GetPlanFilename returns the filename (not the path) of the generated tf plan
// given a workspace and maybe a project's config.
func GetPlanFilename(workspace string, maybeCfg *valid.Project) string {
var unescapedFilename string
if maybeCfg == nil || maybeCfg.Name == nil {
return fmt.Sprintf("%s.tfplan", workspace)
unescapedFilename = fmt.Sprintf("%s.tfplan", workspace)
} else {
unescapedFilename = fmt.Sprintf("%s-%s.tfplan", *maybeCfg.Name, workspace)
}
return fmt.Sprintf("%s-%s.tfplan", *maybeCfg.Name, workspace)
return invalidFilenameChars.ReplaceAllLiteralString(unescapedFilename, "-")
}
28 changes: 28 additions & 0 deletions server/events/runtime/runtime_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,34 @@ func TestGetPlanFilename(t *testing.T) {
},
"project-workspace.tfplan",
},
{
"workspace",
&valid.Project{
Name: String("project/with/slash"),
},
"project-with-slash-workspace.tfplan",
},
{
"workspace",
&valid.Project{
Name: String("project with space"),
},
"project with space-workspace.tfplan",
},
{
"workspace😀",
&valid.Project{
Name: String("project😀"),
},
"project😀-workspace😀.tfplan",
},
{
"default",
&valid.Project{
Name: String(`all.invalid.chars \/"*?<>`),
},
"all.invalid.chars --------default.tfplan",
},
}

for i, c := range cases {
Expand Down
13 changes: 13 additions & 0 deletions server/events/yaml/raw/project.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package raw

import (
"fmt"
"net/url"
"path/filepath"
"strings"

Expand Down Expand Up @@ -58,6 +59,9 @@ func (p Project) Validate() error {
if *strPtr == "" {
return errors.New("if set cannot be empty")
}
if !validProjectName(*strPtr) {
return fmt.Errorf("%q is not allowed: must contain only URL safe characters", *strPtr)
}
return nil
}
return validation.ValidateStruct(&p,
Expand Down Expand Up @@ -99,3 +103,12 @@ func (p Project) ToValid() valid.Project {

return v
}

// validProjectName returns true if the project name is valid.
// Since the name might be used in URLs and definitely in files we don't
// support any characters that must be url escaped *except* for '/' because
// users like to name their projects to match the directory it's in.
func validProjectName(name string) bool {
nameWithoutSlashes := strings.Replace(name, "/", "-", -1)
return nameWithoutSlashes == url.QueryEscape(nameWithoutSlashes)
}
40 changes: 40 additions & 0 deletions server/events/yaml/raw/project_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,46 @@ func TestProject_Validate(t *testing.T) {
},
expErr: "name: if set cannot be empty.",
},
{
description: "project name with slashes",
input: raw.Project{
Dir: String("."),
Name: String("my/name"),
},
expErr: "",
},
{
description: "project name with emoji",
input: raw.Project{
Dir: String("."),
Name: String("😀"),
},
expErr: "name: \"😀\" is not allowed: must contain only URL safe characters.",
},
{
description: "project name with spaces",
input: raw.Project{
Dir: String("."),
Name: String("name with spaces"),
},
expErr: "name: \"name with spaces\" is not allowed: must contain only URL safe characters.",
},
{
description: "project name with +",
input: raw.Project{
Dir: String("."),
Name: String("namewith+"),
},
expErr: "name: \"namewith+\" is not allowed: must contain only URL safe characters.",
},
{
description: `project name with \`,
input: raw.Project{
Dir: String("."),
Name: String(`namewith\`),
},
expErr: `name: "namewith\\" is not allowed: must contain only URL safe characters.`,
},
}
validation.ErrorTag = "yaml"
for _, c := range cases {
Expand Down

0 comments on commit 6af4d79

Please sign in to comment.