2021-06-15 21:11:35 +00:00
package stacks
import (
"errors"
"net/http"
"github.com/asaskevich/govalidator"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
"github.com/portainer/libhttp/response"
portainer "github.com/portainer/portainer/api"
bolterrors "github.com/portainer/portainer/api/bolt/errors"
2021-08-17 01:12:07 +00:00
gittypes "github.com/portainer/portainer/api/git/types"
2021-06-15 21:11:35 +00:00
httperrors "github.com/portainer/portainer/api/http/errors"
"github.com/portainer/portainer/api/http/security"
"github.com/portainer/portainer/api/internal/stackutils"
)
2021-08-17 01:12:07 +00:00
type stackGitUpdatePayload struct {
AutoUpdate * portainer . StackAutoUpdate
Env [ ] portainer . Pair
2021-06-15 21:11:35 +00:00
RepositoryReferenceName string
RepositoryAuthentication bool
RepositoryUsername string
RepositoryPassword string
}
2021-08-17 01:12:07 +00:00
func ( payload * stackGitUpdatePayload ) Validate ( r * http . Request ) error {
if govalidator . IsNull ( payload . RepositoryReferenceName ) {
payload . RepositoryReferenceName = defaultGitReferenceName
}
if err := validateStackAutoUpdate ( payload . AutoUpdate ) ; err != nil {
return err
2021-06-15 21:11:35 +00:00
}
return nil
}
2021-07-21 23:40:53 +00:00
// @id StackUpdateGit
// @summary Redeploy a stack
// @description Pull and redeploy a stack via Git
// @description **Access policy**: restricted
// @tags stacks
// @security jwt
// @accept json
// @produce json
// @param id path int true "Stack identifier"
// @param endpointId query int false "Stacks created before version 1.18.0 might not have an associated endpoint identifier. Use this optional parameter to set the endpoint identifier used by the stack."
// @param body body updateStackGitPayload true "Git configs for pull and redeploy a stack"
// @success 200 {object} portainer.Stack "Success"
// @failure 400 "Invalid request"
// @failure 403 "Permission denied"
// @failure 404 "Not found"
// @failure 500 "Server error"
// @router /stacks/{id}/git [put]
2021-06-15 21:11:35 +00:00
func ( handler * Handler ) stackUpdateGit ( w http . ResponseWriter , r * http . Request ) * httperror . HandlerError {
stackID , err := request . RetrieveNumericRouteVariableValue ( r , "id" )
if err != nil {
2021-08-17 01:12:07 +00:00
return & httperror . HandlerError { StatusCode : http . StatusBadRequest , Message : "Invalid stack identifier route variable" , Err : err }
}
var payload stackGitUpdatePayload
err = request . DecodeAndValidateJSONPayload ( r , & payload )
if err != nil {
return & httperror . HandlerError { StatusCode : http . StatusBadRequest , Message : "Invalid request payload" , Err : err }
2021-06-15 21:11:35 +00:00
}
stack , err := handler . DataStore . Stack ( ) . Stack ( portainer . StackID ( stackID ) )
if err == bolterrors . ErrObjectNotFound {
2021-08-17 01:12:07 +00:00
return & httperror . HandlerError { StatusCode : http . StatusNotFound , Message : "Unable to find a stack with the specified identifier inside the database" , Err : err }
2021-06-15 21:11:35 +00:00
} else if err != nil {
2021-08-17 01:12:07 +00:00
return & httperror . HandlerError { StatusCode : http . StatusInternalServerError , Message : "Unable to find a stack with the specified identifier inside the database" , Err : err }
} else if stack . GitConfig == nil {
msg := "No Git config in the found stack"
return & httperror . HandlerError { StatusCode : http . StatusInternalServerError , Message : msg , Err : errors . New ( msg ) }
2021-06-15 21:11:35 +00:00
}
// TODO: this is a work-around for stacks created with Portainer version >= 1.17.1
// The EndpointID property is not available for these stacks, this API endpoint
// can use the optional EndpointID query parameter to associate a valid endpoint identifier to the stack.
endpointID , err := request . RetrieveNumericQueryParameter ( r , "endpointId" , true )
if err != nil {
2021-08-17 01:12:07 +00:00
return & httperror . HandlerError { StatusCode : http . StatusBadRequest , Message : "Invalid query parameter: endpointId" , Err : err }
2021-06-15 21:11:35 +00:00
}
if endpointID != int ( stack . EndpointID ) {
stack . EndpointID = portainer . EndpointID ( endpointID )
}
endpoint , err := handler . DataStore . Endpoint ( ) . Endpoint ( stack . EndpointID )
if err == bolterrors . ErrObjectNotFound {
2021-08-17 01:12:07 +00:00
return & httperror . HandlerError { StatusCode : http . StatusNotFound , Message : "Unable to find the endpoint associated to the stack inside the database" , Err : err }
2021-06-15 21:11:35 +00:00
} else if err != nil {
2021-08-17 01:12:07 +00:00
return & httperror . HandlerError { StatusCode : http . StatusInternalServerError , Message : "Unable to find the endpoint associated to the stack inside the database" , Err : err }
2021-06-15 21:11:35 +00:00
}
err = handler . requestBouncer . AuthorizedEndpointOperation ( r , endpoint )
if err != nil {
2021-08-17 01:12:07 +00:00
return & httperror . HandlerError { StatusCode : http . StatusForbidden , Message : "Permission denied to access endpoint" , Err : err }
2021-06-15 21:11:35 +00:00
}
resourceControl , err := handler . DataStore . ResourceControl ( ) . ResourceControlByResourceIDAndType ( stackutils . ResourceControlID ( stack . EndpointID , stack . Name ) , portainer . StackResourceControl )
if err != nil {
2021-08-17 01:12:07 +00:00
return & httperror . HandlerError { StatusCode : http . StatusInternalServerError , Message : "Unable to retrieve a resource control associated to the stack" , Err : err }
2021-06-15 21:11:35 +00:00
}
securityContext , err := security . RetrieveRestrictedRequestContext ( r )
if err != nil {
2021-08-17 01:12:07 +00:00
return & httperror . HandlerError { StatusCode : http . StatusInternalServerError , Message : "Unable to retrieve info from request context" , Err : err }
2021-06-15 21:11:35 +00:00
}
access , err := handler . userCanAccessStack ( securityContext , endpoint . ID , resourceControl )
if err != nil {
2021-08-17 01:12:07 +00:00
return & httperror . HandlerError { StatusCode : http . StatusInternalServerError , Message : "Unable to verify user authorizations to validate stack access" , Err : err }
2021-06-15 21:11:35 +00:00
}
if ! access {
2021-08-17 01:12:07 +00:00
return & httperror . HandlerError { StatusCode : http . StatusForbidden , Message : "Access denied to resource" , Err : httperrors . ErrResourceAccessDenied }
2021-06-15 21:11:35 +00:00
}
2021-08-17 01:12:07 +00:00
//stop the autoupdate job if there is any
if stack . AutoUpdate != nil {
stopAutoupdate ( stack . ID , stack . AutoUpdate . JobID , * handler . Scheduler )
2021-06-15 21:11:35 +00:00
}
2021-08-17 01:12:07 +00:00
//update retrieved stack data based on the payload
2021-06-15 21:11:35 +00:00
stack . GitConfig . ReferenceName = payload . RepositoryReferenceName
2021-08-17 01:12:07 +00:00
stack . AutoUpdate = payload . AutoUpdate
stack . Env = payload . Env
stack . GitConfig . Authentication = nil
if payload . RepositoryAuthentication {
password := payload . RepositoryPassword
if password == "" && stack . GitConfig != nil && stack . GitConfig . Authentication != nil {
password = stack . GitConfig . Authentication . Password
}
stack . GitConfig . Authentication = & gittypes . GitAuthentication {
Username : payload . RepositoryUsername ,
Password : password ,
2021-06-15 21:11:35 +00:00
}
}
2021-08-17 01:12:07 +00:00
if payload . AutoUpdate != nil && payload . AutoUpdate . Interval != "" {
jobID , e := startAutoupdate ( stack . ID , stack . AutoUpdate . Interval , handler . Scheduler , handler . StackDeployer , handler . DataStore , handler . GitService )
if e != nil {
return e
2021-06-22 00:58:54 +00:00
}
2021-08-17 01:12:07 +00:00
stack . AutoUpdate . JobID = jobID
2021-06-15 21:11:35 +00:00
}
2021-08-17 01:12:07 +00:00
//save the updated stack to DB
2021-06-15 21:11:35 +00:00
err = handler . DataStore . Stack ( ) . UpdateStack ( stack . ID , stack )
if err != nil {
2021-08-17 01:12:07 +00:00
return & httperror . HandlerError { StatusCode : http . StatusInternalServerError , Message : "Unable to persist the stack changes inside the database" , Err : err }
2021-06-15 21:11:35 +00:00
}
2021-08-17 01:12:07 +00:00
if stack . GitConfig != nil && stack . GitConfig . Authentication != nil && stack . GitConfig . Authentication . Password != "" {
// sanitize password in the http response to minimise possible security leaks
stack . GitConfig . Authentication . Password = ""
2021-06-15 21:11:35 +00:00
}
2021-08-17 01:12:07 +00:00
return response . JSON ( w , stack )
2021-06-15 21:11:35 +00:00
}