2017-09-26 12:50:09 +00:00
package docker
import (
"context"
"github.com/cuigh/auxo/errors"
2021-12-06 12:24:22 +00:00
"github.com/cuigh/swirl/docker/compose"
composetypes "github.com/cuigh/swirl/docker/compose/types"
2017-09-26 12:50:09 +00:00
"github.com/docker/docker/api/types"
2017-09-27 06:30:00 +00:00
"github.com/docker/docker/api/types/container"
2017-09-26 12:50:09 +00:00
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/api/types/swarm"
"github.com/docker/docker/client"
)
const stackLabel = "com.docker.stack.namespace"
// StackList return all stacks.
2021-12-06 12:24:22 +00:00
func ( d * Docker ) StackList ( ctx context . Context ) ( stacks map [ string ] [ ] string , err error ) {
err = d . call ( func ( c * client . Client ) ( err error ) {
2017-09-26 12:50:09 +00:00
var services [ ] swarm . Service
opts := types . ServiceListOptions {
Filters : filters . NewArgs ( ) ,
}
opts . Filters . Add ( "label" , stackLabel )
2021-12-06 12:24:22 +00:00
services , err = c . ServiceList ( ctx , opts )
2017-09-26 12:50:09 +00:00
if err != nil {
return
}
2021-12-06 12:24:22 +00:00
stacks = make ( map [ string ] [ ] string )
2017-09-26 12:50:09 +00:00
for _ , service := range services {
2021-12-06 12:24:22 +00:00
name := service . Spec . Labels [ stackLabel ]
stacks [ name ] = append ( stacks [ name ] , service . Spec . Name )
2017-09-26 12:50:09 +00:00
}
return
} )
return
}
// StackRemove remove a stack.
2021-12-06 12:24:22 +00:00
func ( d * Docker ) StackRemove ( ctx context . Context , name string ) error {
return d . call ( func ( c * client . Client ) ( err error ) {
2017-09-26 12:50:09 +00:00
var (
services [ ] swarm . Service
networks [ ] types . NetworkResource
secrets [ ] swarm . Secret
configs [ ] swarm . Config
errs [ ] error
)
args := filters . NewArgs ( )
args . Add ( "label" , stackLabel + "=" + name )
2021-12-06 12:24:22 +00:00
services , err = c . ServiceList ( ctx , types . ServiceListOptions { Filters : args } )
2017-09-26 12:50:09 +00:00
if err != nil {
return
}
2021-12-06 12:24:22 +00:00
networks , err = c . NetworkList ( ctx , types . NetworkListOptions { Filters : args } )
2017-09-26 12:50:09 +00:00
if err != nil {
return
}
// API version >= 1.25
2021-12-06 12:24:22 +00:00
secrets , err = c . SecretList ( ctx , types . SecretListOptions { Filters : args } )
2017-09-26 12:50:09 +00:00
if err != nil {
return
}
// API version >= 1.30
2021-12-06 12:24:22 +00:00
configs , err = c . ConfigList ( ctx , types . ConfigListOptions { Filters : args } )
2017-09-26 12:50:09 +00:00
if err != nil {
return
}
if len ( services ) + len ( networks ) + len ( secrets ) + len ( configs ) == 0 {
2018-04-16 09:21:20 +00:00
//return fmt.Errorf("nothing found in stack: %s", name)
return nil
2017-09-26 12:50:09 +00:00
}
// Remove services
for _ , service := range services {
2021-12-06 12:24:22 +00:00
if err = c . ServiceRemove ( ctx , service . ID ) ; err != nil {
2017-09-26 12:50:09 +00:00
e := errors . Format ( "Failed to remove service %s: %s" , service . Spec . Name , err )
errs = append ( errs , e )
2021-12-06 12:24:22 +00:00
d . logger . Warn ( e )
2017-09-26 12:50:09 +00:00
}
}
// Remove secrets
for _ , secret := range secrets {
2021-12-06 12:24:22 +00:00
if err = c . SecretRemove ( ctx , secret . ID ) ; err != nil {
2017-09-26 12:50:09 +00:00
e := errors . Format ( "Failed to remove secret %s: %s" , secret . Spec . Name , err )
errs = append ( errs , e )
2021-12-06 12:24:22 +00:00
d . logger . Warn ( e )
2017-09-26 12:50:09 +00:00
}
}
// Remove configs
for _ , config := range configs {
2021-12-06 12:24:22 +00:00
if err = c . ConfigRemove ( ctx , config . ID ) ; err != nil {
2017-09-26 12:50:09 +00:00
e := errors . Format ( "Failed to remove config %s: %s" , config . Spec . Name , err )
errs = append ( errs , e )
2021-12-06 12:24:22 +00:00
d . logger . Warn ( e )
2017-09-26 12:50:09 +00:00
}
}
// Remove networks
for _ , network := range networks {
2021-12-06 12:24:22 +00:00
if err = c . NetworkRemove ( ctx , network . ID ) ; err != nil {
2017-09-26 12:50:09 +00:00
e := errors . Format ( "Failed to remove network %s: %s" , network . Name , err )
errs = append ( errs , e )
2021-12-06 12:24:22 +00:00
d . logger . Warn ( e )
2017-09-26 12:50:09 +00:00
}
}
if len ( errs ) > 0 {
2017-11-08 10:36:13 +00:00
return errors . List ( errs ... )
2017-09-26 12:50:09 +00:00
}
return nil
} )
}
// StackDeploy deploy a stack.
2021-12-06 12:24:22 +00:00
func ( d * Docker ) StackDeploy ( ctx context . Context , cfg * composetypes . Config , authes map [ string ] string ) error {
c , err := d . client ( )
2017-09-26 12:50:09 +00:00
if err != nil {
return err
}
2021-12-06 12:24:22 +00:00
namespace := compose . NewNamespace ( cfg . Filename )
2017-09-26 12:50:09 +00:00
serviceNetworks := compose . GetServicesDeclaredNetworks ( cfg . Services )
networks , externalNetworks := compose . Networks ( namespace , cfg . Networks , serviceNetworks )
2021-12-06 12:24:22 +00:00
if err = validateExternalNetworks ( ctx , c , externalNetworks ) ; err != nil {
2017-09-26 12:50:09 +00:00
return err
}
2021-12-06 12:24:22 +00:00
if err = d . createNetworks ( ctx , c , namespace , networks ) ; err != nil {
2017-09-26 12:50:09 +00:00
return err
}
secrets , err := compose . Secrets ( namespace , cfg . Secrets )
if err != nil {
return err
}
2021-12-06 12:24:22 +00:00
if err = createSecrets ( ctx , c , secrets ) ; err != nil {
2017-09-26 12:50:09 +00:00
return err
}
configs , err := compose . Configs ( namespace , cfg . Configs )
if err != nil {
return err
}
2021-12-06 12:24:22 +00:00
if err = createConfigs ( ctx , c , configs ) ; err != nil {
2017-09-26 12:50:09 +00:00
return err
}
2021-12-06 12:24:22 +00:00
services , err := compose . Services ( namespace , cfg , c )
2017-09-26 12:50:09 +00:00
if err != nil {
return err
}
2021-12-06 12:24:22 +00:00
return d . deployServices ( ctx , c , services , namespace , authes )
}
// StackCount return number of stacks.
func ( d * Docker ) StackCount ( ctx context . Context ) ( count int , err error ) {
err = d . call ( func ( c * client . Client ) ( err error ) {
var services [ ] swarm . Service
opts := types . ServiceListOptions { Filters : filters . NewArgs ( ) }
opts . Filters . Add ( "label" , stackLabel )
services , err = c . ServiceList ( ctx , opts )
if err != nil {
return
}
m := make ( map [ string ] struct { } )
for _ , service := range services {
labels := service . Spec . Labels
m [ labels [ stackLabel ] ] = struct { } { }
}
count = len ( m )
return
} )
return
2017-09-26 12:50:09 +00:00
}
2021-12-06 12:24:22 +00:00
func validateExternalNetworks ( ctx context . Context , c * client . Client , externalNetworks [ ] string ) error {
2017-09-26 12:50:09 +00:00
for _ , networkName := range externalNetworks {
2017-09-27 06:30:00 +00:00
if ! container . NetworkMode ( networkName ) . IsUserDefined ( ) {
// Networks that are not user defined always exist on all nodes as
// local-scoped networks, so there's no need to inspect them.
continue
}
2021-12-06 12:24:22 +00:00
network , err := c . NetworkInspect ( ctx , networkName , types . NetworkInspectOptions { } )
2017-09-26 12:50:09 +00:00
switch {
case client . IsErrNotFound ( err ) :
return errors . Format ( "network %q is declared as external, but could not be found. You need to create a swarm-scoped network before the stack is deployed" , networkName )
case err != nil :
return err
case network . Scope != "swarm" :
return errors . Format ( "network %q is declared as external, but it is not in the right scope: %q instead of \"swarm\"" , networkName , network . Scope )
}
}
return nil
}
2021-12-06 12:24:22 +00:00
func ( d * Docker ) createNetworks ( ctx context . Context , c * client . Client , namespace compose . Namespace , networks map [ string ] types . NetworkCreate ) error {
2017-09-26 12:50:09 +00:00
opts := types . NetworkListOptions {
Filters : filters . NewArgs ( ) ,
}
opts . Filters . Add ( "label" , stackLabel + "=" + namespace . Name ( ) )
2021-12-06 12:24:22 +00:00
existingNetworks , err := c . NetworkList ( ctx , opts )
2017-09-26 12:50:09 +00:00
if err != nil {
return err
}
existingNetworkMap := make ( map [ string ] types . NetworkResource )
for _ , network := range existingNetworks {
existingNetworkMap [ network . Name ] = network
}
2019-04-30 06:07:11 +00:00
for name , createOpts := range networks {
2017-09-26 12:50:09 +00:00
if _ , exists := existingNetworkMap [ name ] ; exists {
continue
}
if createOpts . Driver == "" {
createOpts . Driver = "overlay"
}
2021-12-06 12:24:22 +00:00
d . logger . Infof ( "Creating network %s" , name )
if _ , err = c . NetworkCreate ( ctx , name , createOpts ) ; err != nil {
2019-04-30 06:07:11 +00:00
return errors . Wrap ( err , "failed to create network " + name )
2017-09-26 12:50:09 +00:00
}
}
return nil
}
2021-12-06 12:24:22 +00:00
func createSecrets ( ctx context . Context , c * client . Client , secrets [ ] swarm . SecretSpec ) error {
2017-09-26 12:50:09 +00:00
for _ , secretSpec := range secrets {
2021-12-06 12:24:22 +00:00
secret , _ , err := c . SecretInspectWithRaw ( ctx , secretSpec . Name )
2017-09-26 12:50:09 +00:00
switch {
case err == nil :
// secret already exists, then we update that
2021-12-06 12:24:22 +00:00
if err = c . SecretUpdate ( ctx , secret . ID , secret . Meta . Version , secretSpec ) ; err != nil {
2017-11-08 10:36:13 +00:00
return errors . Wrap ( err , "failed to update secret " + secretSpec . Name )
2017-09-26 12:50:09 +00:00
}
2017-11-15 06:31:37 +00:00
case client . IsErrNotFound ( err ) :
2017-09-26 12:50:09 +00:00
// secret does not exist, then we create a new one.
2021-12-06 12:24:22 +00:00
if _ , err = c . SecretCreate ( ctx , secretSpec ) ; err != nil {
2017-11-08 10:36:13 +00:00
return errors . Wrap ( err , "failed to create secret " + secretSpec . Name )
2017-09-26 12:50:09 +00:00
}
default :
return err
}
}
return nil
}
2021-12-06 12:24:22 +00:00
func createConfigs ( ctx context . Context , c * client . Client , configs [ ] swarm . ConfigSpec ) error {
2017-09-26 12:50:09 +00:00
for _ , configSpec := range configs {
2021-12-06 12:24:22 +00:00
config , _ , err := c . ConfigInspectWithRaw ( ctx , configSpec . Name )
2017-09-26 12:50:09 +00:00
switch {
case err == nil :
// config already exists, then we update that
2021-12-06 12:24:22 +00:00
if err = c . ConfigUpdate ( ctx , config . ID , config . Meta . Version , configSpec ) ; err != nil {
2018-05-26 06:57:32 +00:00
return errors . Wrap ( err , "failed to update config " + configSpec . Name )
2017-09-26 12:50:09 +00:00
}
2017-11-15 06:31:37 +00:00
case client . IsErrNotFound ( err ) :
2017-09-26 12:50:09 +00:00
// config does not exist, then we create a new one.
2021-12-06 12:24:22 +00:00
if _ , err = c . ConfigCreate ( ctx , configSpec ) ; err != nil {
2018-05-26 06:57:32 +00:00
return errors . Wrap ( err , "failed to create config " + configSpec . Name )
2017-09-26 12:50:09 +00:00
}
default :
return err
}
}
return nil
}
2021-12-06 12:24:22 +00:00
func getServices ( ctx context . Context , c * client . Client , namespace string ) ( [ ] swarm . Service , error ) {
2017-09-26 12:50:09 +00:00
opts := types . ServiceListOptions {
Filters : filters . NewArgs ( ) ,
}
opts . Filters . Add ( "label" , stackLabel + "=" + namespace )
2021-12-06 12:24:22 +00:00
return c . ServiceList ( ctx , opts )
2017-09-26 12:50:09 +00:00
}
2021-12-06 12:24:22 +00:00
func ( d * Docker ) deployServices (
2017-09-26 12:50:09 +00:00
ctx context . Context ,
2021-12-06 12:24:22 +00:00
c * client . Client ,
2017-09-26 12:50:09 +00:00
services map [ string ] swarm . ServiceSpec ,
namespace compose . Namespace ,
authes map [ string ] string ,
//sendAuth bool,
//resolveImage string,
) error {
2021-12-06 12:24:22 +00:00
existingServices , err := getServices ( ctx , c , namespace . Name ( ) )
2017-09-26 12:50:09 +00:00
if err != nil {
return err
}
existingServiceMap := make ( map [ string ] swarm . Service )
for _ , service := range existingServices {
existingServiceMap [ service . Spec . Name ] = service
}
for internalName , serviceSpec := range services {
name := namespace . Scope ( internalName )
// TODO: Add auth
encodedAuth := authes [ serviceSpec . TaskTemplate . ContainerSpec . Image ]
//image := serviceSpec.TaskTemplate.ContainerSpec.Image
//if sendAuth {
// // Retrieve encoded auth token from the image reference
// encodedAuth, err = command.RetrieveAuthTokenFromImage(ctx, dockerCli, image)
// if err != nil {
// return err
// }
//}
if service , exists := existingServiceMap [ name ] ; exists {
2021-12-06 12:24:22 +00:00
d . logger . Infof ( "Updating service %s (id: %s)" , name , service . ID )
2017-09-26 12:50:09 +00:00
updateOpts := types . ServiceUpdateOptions {
RegistryAuthFrom : types . RegistryAuthFromSpec ,
EncodedRegistryAuth : encodedAuth ,
}
//if resolveImage == resolveImageAlways || (resolveImage == resolveImageChanged && image != service.Spec.Labels[compose.LabelImage]) {
// updateOpts.QueryRegistry = true
//}
2021-12-06 12:24:22 +00:00
response , err := c . ServiceUpdate (
2017-09-26 12:50:09 +00:00
ctx ,
service . ID ,
service . Version ,
serviceSpec ,
updateOpts ,
)
if err != nil {
2017-11-08 10:36:13 +00:00
return errors . Wrap ( err , "failed to update service " + name )
2017-09-26 12:50:09 +00:00
}
for _ , warning := range response . Warnings {
2021-12-06 12:24:22 +00:00
d . logger . Warn ( warning )
2017-09-26 12:50:09 +00:00
}
} else {
2021-12-06 12:24:22 +00:00
d . logger . Infof ( "Creating service %s" , name )
2017-09-26 12:50:09 +00:00
createOpts := types . ServiceCreateOptions { EncodedRegistryAuth : encodedAuth }
// query registry if flag disabling it was not set
//if resolveImage == resolveImageAlways || resolveImage == resolveImageChanged {
// createOpts.QueryRegistry = true
//}
2021-12-06 12:24:22 +00:00
if _ , err = c . ServiceCreate ( ctx , serviceSpec , createOpts ) ; err != nil {
2017-11-08 10:36:13 +00:00
return errors . Wrap ( err , "failed to create service " + name )
2017-09-26 12:50:09 +00:00
}
}
}
return nil
}