diff --git a/api/git/git.go b/api/git/git.go index 5e58363c7..add688b01 100644 --- a/api/git/git.go +++ b/api/git/git.go @@ -5,6 +5,7 @@ import ( "strings" "gopkg.in/src-d/go-git.v4" + "gopkg.in/src-d/go-git.v4/plumbing" ) // Service represents a service for managing Git. @@ -19,21 +20,27 @@ func NewService(dataStorePath string) (*Service, error) { // ClonePublicRepository clones a public git repository using the specified URL in the specified // destination folder. -func (service *Service) ClonePublicRepository(repositoryURL, destination string) error { - return cloneRepository(repositoryURL, destination) +func (service *Service) ClonePublicRepository(repositoryURL, referenceName string, destination string) error { + return cloneRepository(repositoryURL, referenceName, destination) } // ClonePrivateRepositoryWithBasicAuth clones a private git repository using the specified URL in the specified // destination folder. It will use the specified username and password for basic HTTP authentication. -func (service *Service) ClonePrivateRepositoryWithBasicAuth(repositoryURL, destination, username, password string) error { +func (service *Service) ClonePrivateRepositoryWithBasicAuth(repositoryURL, referenceName string, destination, username, password string) error { credentials := username + ":" + url.PathEscape(password) repositoryURL = strings.Replace(repositoryURL, "://", "://"+credentials+"@", 1) - return cloneRepository(repositoryURL, destination) + return cloneRepository(repositoryURL, referenceName, destination) } -func cloneRepository(repositoryURL, destination string) error { - _, err := git.PlainClone(destination, false, &git.CloneOptions{ +func cloneRepository(repositoryURL, referenceName string, destination string) error { + options := &git.CloneOptions{ URL: repositoryURL, - }) + } + + if referenceName != "" { + options.ReferenceName = plumbing.ReferenceName(referenceName) + } + + _, err := git.PlainClone(destination, false, options) return err } diff --git a/api/http/handler/stacks/create_compose_stack.go b/api/http/handler/stacks/create_compose_stack.go index ccbf48e57..a343c96fa 100644 --- a/api/http/handler/stacks/create_compose_stack.go +++ b/api/http/handler/stacks/create_compose_stack.go @@ -90,6 +90,7 @@ func (handler *Handler) createComposeStackFromFileContent(w http.ResponseWriter, type composeStackFromGitRepositoryPayload struct { Name string RepositoryURL string + RepositoryReferenceName string RepositoryAuthentication bool RepositoryUsername string RepositoryPassword string @@ -146,19 +147,21 @@ func (handler *Handler) createComposeStackFromGitRepository(w http.ResponseWrite gitCloneParams := &cloneRepositoryParameters{ url: payload.RepositoryURL, + referenceName: payload.RepositoryReferenceName, path: projectPath, authentication: payload.RepositoryAuthentication, username: payload.RepositoryUsername, password: payload.RepositoryPassword, } + + doCleanUp := true + defer handler.cleanUp(stack, &doCleanUp) + err = handler.cloneGitRepository(gitCloneParams) if err != nil { return &httperror.HandlerError{http.StatusInternalServerError, "Unable to clone git repository", err} } - doCleanUp := true - defer handler.cleanUp(stack, &doCleanUp) - config, configErr := handler.createComposeDeployConfig(r, stack, endpoint) if configErr != nil { return configErr diff --git a/api/http/handler/stacks/create_swarm_stack.go b/api/http/handler/stacks/create_swarm_stack.go index 05084a99d..87c94e6ef 100644 --- a/api/http/handler/stacks/create_swarm_stack.go +++ b/api/http/handler/stacks/create_swarm_stack.go @@ -97,6 +97,7 @@ type swarmStackFromGitRepositoryPayload struct { SwarmID string Env []portainer.Pair RepositoryURL string + RepositoryReferenceName string RepositoryAuthentication bool RepositoryUsername string RepositoryPassword string @@ -156,19 +157,21 @@ func (handler *Handler) createSwarmStackFromGitRepository(w http.ResponseWriter, gitCloneParams := &cloneRepositoryParameters{ url: payload.RepositoryURL, + referenceName: payload.RepositoryReferenceName, path: projectPath, authentication: payload.RepositoryAuthentication, username: payload.RepositoryUsername, password: payload.RepositoryPassword, } + + doCleanUp := true + defer handler.cleanUp(stack, &doCleanUp) + err = handler.cloneGitRepository(gitCloneParams) if err != nil { return &httperror.HandlerError{http.StatusInternalServerError, "Unable to clone git repository", err} } - doCleanUp := true - defer handler.cleanUp(stack, &doCleanUp) - config, configErr := handler.createSwarmDeployConfig(r, stack, endpoint, false) if configErr != nil { return configErr diff --git a/api/http/handler/stacks/git.go b/api/http/handler/stacks/git.go index 1ac62a443..b3e2448f7 100644 --- a/api/http/handler/stacks/git.go +++ b/api/http/handler/stacks/git.go @@ -2,6 +2,7 @@ package stacks type cloneRepositoryParameters struct { url string + referenceName string path string authentication bool username string @@ -10,7 +11,7 @@ type cloneRepositoryParameters struct { func (handler *Handler) cloneGitRepository(parameters *cloneRepositoryParameters) error { if parameters.authentication { - return handler.GitService.ClonePrivateRepositoryWithBasicAuth(parameters.url, parameters.path, parameters.username, parameters.password) + return handler.GitService.ClonePrivateRepositoryWithBasicAuth(parameters.url, parameters.referenceName, parameters.path, parameters.username, parameters.password) } - return handler.GitService.ClonePublicRepository(parameters.url, parameters.path) + return handler.GitService.ClonePublicRepository(parameters.url, parameters.referenceName, parameters.path) } diff --git a/api/portainer.go b/api/portainer.go index 9c6e0f21e..6ce1df511 100644 --- a/api/portainer.go +++ b/api/portainer.go @@ -570,8 +570,8 @@ type ( // GitService represents a service for managing Git. GitService interface { - ClonePublicRepository(repositoryURL, destination string) error - ClonePrivateRepositoryWithBasicAuth(repositoryURL, destination, username, password string) error + ClonePublicRepository(repositoryURL, referenceName string, destination string) error + ClonePrivateRepositoryWithBasicAuth(repositoryURL, referenceName string, destination, username, password string) error } // JobScheduler represents a service to run jobs on a periodic basis. diff --git a/api/swagger.yaml b/api/swagger.yaml index 7b556b0eb..07c5a9f45 100644 --- a/api/swagger.yaml +++ b/api/swagger.yaml @@ -4184,6 +4184,10 @@ definitions: type: "string" example: "https://github.com/openfaas/faas" description: "URL of a Git repository hosting the Stack file. Required when using the 'repository' deployment method." + RepositoryReferenceName: + type: "string" + example: "refs/heads/master" + description: "Reference name of a Git repository hosting the Stack file. Used in 'repository' deployment method." ComposeFilePathInRepository: type: "string" example: "docker-compose.yml" diff --git a/app/portainer/services/api/stackService.js b/app/portainer/services/api/stackService.js index c0add5cd7..e21da318c 100644 --- a/app/portainer/services/api/stackService.js +++ b/app/portainer/services/api/stackService.js @@ -282,6 +282,7 @@ function StackServiceFactory($q, Stack, ResourceControlService, FileUploadServic var payload = { Name: name, RepositoryURL: repositoryOptions.RepositoryURL, + RepositoryReferenceName: repositoryOptions.RepositoryReferenceName, ComposeFilePathInRepository: repositoryOptions.ComposeFilePathInRepository, RepositoryAuthentication: repositoryOptions.RepositoryAuthentication, RepositoryUsername: repositoryOptions.RepositoryUsername, @@ -301,6 +302,7 @@ function StackServiceFactory($q, Stack, ResourceControlService, FileUploadServic Name: name, SwarmID: swarm.Id, RepositoryURL: repositoryOptions.RepositoryURL, + RepositoryReferenceName: repositoryOptions.RepositoryReferenceName, ComposeFilePathInRepository: repositoryOptions.ComposeFilePathInRepository, RepositoryAuthentication: repositoryOptions.RepositoryAuthentication, RepositoryUsername: repositoryOptions.RepositoryUsername, diff --git a/app/portainer/views/stacks/create/createStackController.js b/app/portainer/views/stacks/create/createStackController.js index 9c17bfe49..17c2f39f3 100644 --- a/app/portainer/views/stacks/create/createStackController.js +++ b/app/portainer/views/stacks/create/createStackController.js @@ -7,6 +7,7 @@ function ($scope, $state, StackService, Authentication, Notifications, FormValid StackFileContent: '', StackFile: null, RepositoryURL: '', + RepositoryReferenceName: '', RepositoryAuthentication: false, RepositoryUsername: '', RepositoryPassword: '', @@ -55,6 +56,7 @@ function ($scope, $state, StackService, Authentication, Notifications, FormValid } else if (method === 'repository') { var repositoryOptions = { RepositoryURL: $scope.formValues.RepositoryURL, + RepositoryReferenceName: $scope.formValues.RepositoryReferenceName, ComposeFilePathInRepository: $scope.formValues.ComposeFilePathInRepository, RepositoryAuthentication: $scope.formValues.RepositoryAuthentication, RepositoryUsername: $scope.formValues.RepositoryUsername, @@ -77,6 +79,7 @@ function ($scope, $state, StackService, Authentication, Notifications, FormValid } else if (method === 'repository') { var repositoryOptions = { RepositoryURL: $scope.formValues.RepositoryURL, + RepositoryReferenceName: $scope.formValues.RepositoryReferenceName, ComposeFilePathInRepository: $scope.formValues.ComposeFilePathInRepository, RepositoryAuthentication: $scope.formValues.RepositoryAuthentication, RepositoryUsername: $scope.formValues.RepositoryUsername, diff --git a/app/portainer/views/stacks/create/createstack.html b/app/portainer/views/stacks/create/createstack.html index 84bee5d86..c5c98b28c 100644 --- a/app/portainer/views/stacks/create/createstack.html +++ b/app/portainer/views/stacks/create/createstack.html @@ -125,6 +125,17 @@ +
refs/heads/branch_name
or tags with refs/tags/tag_name
. If not specified, will use the default HEAD
reference normally the master
branch.
+
+