2020-06-18 16:13:24 +02:00
/ *
Copyright 2020 Docker , Inc .
Licensed under the Apache License , Version 2.0 ( the "License" ) ;
you may not use this file except in compliance with the License .
You may obtain a copy of the License at
http : //www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing , software
distributed under the License is distributed on an "AS IS" BASIS ,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND , either express or implied .
See the License for the specific language governing permissions and
limitations under the License .
* /
2020-04-29 19:57:53 +02:00
package azure
import (
"context"
2020-05-03 13:41:45 +02:00
"fmt"
2020-05-03 13:35:25 +02:00
"io"
2020-05-10 22:37:28 +02:00
"net/http"
2020-05-04 16:38:02 +02:00
"strconv"
"strings"
2020-04-29 19:57:53 +02:00
"github.com/Azure/azure-sdk-for-go/services/containerinstance/mgmt/2018-10-01/containerinstance"
2020-06-30 12:23:22 +02:00
"github.com/Azure/go-autorest/autorest/to"
2020-07-02 16:05:45 +02:00
"github.com/compose-spec/compose-go/cli"
2020-05-01 15:28:44 +02:00
"github.com/compose-spec/compose-go/types"
2020-04-29 19:57:53 +02:00
"github.com/pkg/errors"
2020-05-01 15:28:44 +02:00
"github.com/sirupsen/logrus"
2020-05-01 15:48:20 +02:00
"github.com/docker/api/azure/convert"
2020-05-12 13:37:28 +02:00
"github.com/docker/api/azure/login"
2020-04-29 19:57:53 +02:00
"github.com/docker/api/backend"
2020-05-01 15:28:44 +02:00
"github.com/docker/api/compose"
2020-04-29 19:57:53 +02:00
"github.com/docker/api/containers"
apicontext "github.com/docker/api/context"
2020-06-30 12:23:22 +02:00
"github.com/docker/api/context/cloud"
2020-05-01 15:48:20 +02:00
"github.com/docker/api/context/store"
2020-06-30 12:23:22 +02:00
"github.com/docker/api/errdefs"
2020-04-29 19:57:53 +02:00
)
2020-07-03 10:53:50 +02:00
const (
singleContainerName = "single--container--aci"
composeContainerSeparator = "_"
)
2020-05-06 17:14:53 +02:00
2020-05-10 22:37:28 +02:00
// ErrNoSuchContainer is returned when the mentioned container does not exist
var ErrNoSuchContainer = errors . New ( "no such container" )
2020-04-29 19:57:53 +02:00
func init ( ) {
2020-05-28 17:37:59 +02:00
backend . Register ( "aci" , "aci" , service , getCloudService )
2020-04-29 19:57:53 +02:00
}
2020-05-28 17:37:59 +02:00
func service ( ctx context . Context ) ( backend . Service , error ) {
2020-05-20 17:15:56 +02:00
contextStore := store . ContextStore ( ctx )
2020-05-28 17:37:59 +02:00
currentContext := apicontext . CurrentContext ( ctx )
Change the way a context is stored
Initially we stored the context data in the `Metadata` of the context
but in hindsight this data would be better of in the `Endpoints` because
that's what it is used for.
Before:
```json
{
"Name": "aci",
"Metadata": {
"Type": "aci",
"Data": {
"key": "value"
}
},
"Endpoints": {
"docker": {}
}
}
```
After:
```json
{
"Name": "aci",
"Type": "aci",
"Metadata": {},
"Endpoints": {
"aci": {
"key": "value"
},
"docker": {}
}
}
```
With this change the contexts that we create are more in line with the contexts the docker cli creates.
It also makes the code less complicated since we don't need to marsal twice any more. The API is nicer too:
```go
// Get a context:
c, err := store.Get(contextName)
// Get the stored endpoint:
var aciContext store.AciContext
if err := contextStore.GetEndpoint(currentContext, &aciContext); err != nil {
return nil, err
}
```
2020-05-22 11:16:01 +02:00
var aciContext store . AciContext
2020-05-28 17:37:59 +02:00
Change the way a context is stored
Initially we stored the context data in the `Metadata` of the context
but in hindsight this data would be better of in the `Endpoints` because
that's what it is used for.
Before:
```json
{
"Name": "aci",
"Metadata": {
"Type": "aci",
"Data": {
"key": "value"
}
},
"Endpoints": {
"docker": {}
}
}
```
After:
```json
{
"Name": "aci",
"Type": "aci",
"Metadata": {},
"Endpoints": {
"aci": {
"key": "value"
},
"docker": {}
}
}
```
With this change the contexts that we create are more in line with the contexts the docker cli creates.
It also makes the code less complicated since we don't need to marsal twice any more. The API is nicer too:
```go
// Get a context:
c, err := store.Get(contextName)
// Get the stored endpoint:
var aciContext store.AciContext
if err := contextStore.GetEndpoint(currentContext, &aciContext); err != nil {
return nil, err
}
```
2020-05-22 11:16:01 +02:00
if err := contextStore . GetEndpoint ( currentContext , & aciContext ) ; err != nil {
return nil , err
2020-04-29 19:57:53 +02:00
}
2020-05-28 17:37:59 +02:00
return getAciAPIService ( aciContext ) , nil
2020-05-05 15:37:12 +02:00
}
2020-05-28 17:37:59 +02:00
func getCloudService ( ) ( cloud . Service , error ) {
2020-05-13 23:33:16 +02:00
service , err := login . NewAzureLoginService ( )
if err != nil {
return nil , err
}
2020-05-28 17:37:59 +02:00
return & aciCloudService {
loginService : service ,
} , nil
}
func getAciAPIService ( aciCtx store . AciContext ) * aciAPIService {
2020-05-05 16:27:22 +02:00
return & aciAPIService {
2020-05-28 17:37:59 +02:00
aciContainerService : & aciContainerService {
2020-05-28 10:16:42 +02:00
ctx : aciCtx ,
2020-05-05 15:37:12 +02:00
} ,
2020-05-28 17:37:59 +02:00
aciComposeService : & aciComposeService {
2020-05-12 23:00:58 +02:00
ctx : aciCtx ,
2020-05-05 15:37:12 +02:00
} ,
2020-05-28 17:37:59 +02:00
}
2020-05-05 15:37:12 +02:00
}
2020-05-05 16:27:22 +02:00
type aciAPIService struct {
2020-05-28 17:37:59 +02:00
* aciContainerService
* aciComposeService
2020-04-29 19:57:53 +02:00
}
2020-05-05 16:27:22 +02:00
func ( a * aciAPIService ) ContainerService ( ) containers . Service {
2020-05-28 17:37:59 +02:00
return a . aciContainerService
2020-05-05 15:37:12 +02:00
}
2020-05-05 16:27:22 +02:00
func ( a * aciAPIService ) ComposeService ( ) compose . Service {
2020-05-28 17:37:59 +02:00
return a . aciComposeService
2020-05-12 13:37:28 +02:00
}
2020-05-05 15:37:12 +02:00
type aciContainerService struct {
2020-05-28 10:16:42 +02:00
ctx store . AciContext
2020-05-05 15:37:12 +02:00
}
2020-05-16 12:13:51 +02:00
func ( cs * aciContainerService ) List ( ctx context . Context , _ bool ) ( [ ] containers . Container , error ) {
2020-05-28 10:16:42 +02:00
groupsClient , err := getContainerGroupsClient ( cs . ctx . SubscriptionID )
if err != nil {
return nil , err
}
2020-05-04 09:57:52 +02:00
var containerGroups [ ] containerinstance . ContainerGroup
2020-05-28 10:16:42 +02:00
result , err := groupsClient . ListByResourceGroup ( ctx , cs . ctx . ResourceGroup )
2020-04-29 19:57:53 +02:00
if err != nil {
return [ ] containers . Container { } , err
}
for result . NotDone ( ) {
2020-05-04 09:57:52 +02:00
containerGroups = append ( containerGroups , result . Values ( ) ... )
2020-04-29 19:57:53 +02:00
if err := result . NextWithContext ( ctx ) ; err != nil {
return [ ] containers . Container { } , err
}
}
2020-05-05 10:58:24 +02:00
var res [ ] containers . Container
2020-05-04 09:57:52 +02:00
for _ , containerGroup := range containerGroups {
2020-05-28 10:16:42 +02:00
group , err := groupsClient . Get ( ctx , cs . ctx . ResourceGroup , * containerGroup . Name )
2020-05-03 14:51:03 +02:00
if err != nil {
return [ ] containers . Container { } , err
}
2020-05-04 09:57:52 +02:00
for _ , container := range * group . Containers {
2020-05-06 17:14:53 +02:00
var containerID string
2020-06-18 15:51:13 +02:00
// don't list sidecar container
if * container . Name == convert . ComposeDNSSidecarName {
continue
}
2020-05-06 17:14:53 +02:00
if * container . Name == singleContainerName {
containerID = * containerGroup . Name
} else {
2020-07-03 10:53:50 +02:00
containerID = * containerGroup . Name + composeContainerSeparator + * container . Name
2020-05-06 17:14:53 +02:00
}
2020-05-03 14:51:03 +02:00
status := "Unknown"
2020-05-04 09:57:52 +02:00
if container . InstanceView != nil && container . InstanceView . CurrentState != nil {
status = * container . InstanceView . CurrentState . State
2020-05-03 14:51:03 +02:00
}
2020-05-18 11:00:09 +02:00
2020-04-29 19:57:53 +02:00
res = append ( res , containers . Container {
2020-05-06 17:14:53 +02:00
ID : containerID ,
2020-05-04 09:57:52 +02:00
Image : * container . Image ,
2020-05-03 14:51:03 +02:00
Status : status ,
2020-05-18 11:00:09 +02:00
Ports : convert . ToPorts ( group . IPAddress , * container . Ports ) ,
2020-04-29 19:57:53 +02:00
} )
}
}
return res , nil
}
2020-05-01 15:28:44 +02:00
2020-05-05 15:37:12 +02:00
func ( cs * aciContainerService ) Run ( ctx context . Context , r containers . ContainerConfig ) error {
2020-07-03 10:53:50 +02:00
if strings . Contains ( r . ID , composeContainerSeparator ) {
return errors . New ( fmt . Sprintf ( ` invalid container name. ACI container name cannot include "%s" ` , composeContainerSeparator ) )
}
2020-05-01 16:03:33 +02:00
var ports [ ] types . ServicePortConfig
for _ , p := range r . Ports {
ports = append ( ports , types . ServicePortConfig {
2020-05-14 12:17:06 +02:00
Target : p . ContainerPort ,
Published : p . HostPort ,
2020-05-01 16:03:33 +02:00
} )
}
2020-05-07 04:58:04 +02:00
projectVolumes , serviceConfigVolumes , err := convert . GetRunVolumes ( r . Volumes )
if err != nil {
return err
}
2020-07-02 16:05:45 +02:00
project := types . Project {
2020-05-04 11:51:40 +02:00
Name : r . ID ,
2020-07-02 16:05:45 +02:00
Services : [ ] types . ServiceConfig {
{
Name : singleContainerName ,
Image : r . Image ,
Ports : ports ,
Labels : r . Labels ,
Volumes : serviceConfigVolumes ,
Deploy : & types . DeployConfig {
Resources : types . Resources {
Limits : & types . Resource {
NanoCPUs : fmt . Sprintf ( "%f" , r . CPULimit ) ,
MemoryBytes : types . UnitBytes ( r . MemLimit . Value ( ) ) ,
2020-06-24 09:44:47 +02:00
} ,
} ,
2020-05-04 11:51:40 +02:00
} ,
} ,
2020-05-01 15:28:44 +02:00
} ,
2020-07-02 16:05:45 +02:00
Volumes : projectVolumes ,
2020-05-01 15:28:44 +02:00
}
logrus . Debugf ( "Running container %q with name %q\n" , r . Image , r . ID )
2020-05-01 15:48:20 +02:00
groupDefinition , err := convert . ToContainerGroup ( cs . ctx , project )
if err != nil {
return err
}
2020-05-05 16:27:22 +02:00
return createACIContainers ( ctx , cs . ctx , groupDefinition )
2020-05-01 15:28:44 +02:00
}
2020-05-03 13:35:25 +02:00
2020-05-18 12:02:04 +02:00
func ( cs * aciContainerService ) Stop ( ctx context . Context , containerName string , timeout * uint32 ) error {
return errdefs . ErrNotImplemented
2020-05-16 12:13:51 +02:00
}
2020-05-18 14:56:32 +02:00
func getGroupAndContainerName ( containerID string ) ( groupName string , containerName string ) {
2020-07-03 10:53:50 +02:00
tokens := strings . Split ( containerID , composeContainerSeparator )
2020-05-06 17:14:53 +02:00
groupName = tokens [ 0 ]
if len ( tokens ) > 1 {
containerName = tokens [ len ( tokens ) - 1 ]
groupName = containerID [ : len ( containerID ) - ( len ( containerName ) + 1 ) ]
} else {
containerName = singleContainerName
}
return groupName , containerName
}
2020-05-05 15:37:12 +02:00
func ( cs * aciContainerService ) Exec ( ctx context . Context , name string , command string , reader io . Reader , writer io . Writer ) error {
2020-05-18 14:56:32 +02:00
groupName , containerAciName := getGroupAndContainerName ( name )
2020-05-06 17:14:53 +02:00
containerExecResponse , err := execACIContainer ( ctx , cs . ctx , command , groupName , containerAciName )
2020-05-03 13:35:25 +02:00
if err != nil {
return err
}
return exec (
context . Background ( ) ,
* containerExecResponse . WebSocketURI ,
* containerExecResponse . Password ,
reader ,
writer ,
)
}
2020-05-03 13:41:45 +02:00
2020-05-05 15:37:12 +02:00
func ( cs * aciContainerService ) Logs ( ctx context . Context , containerName string , req containers . LogsRequest ) error {
2020-05-18 14:56:32 +02:00
groupName , containerAciName := getGroupAndContainerName ( containerName )
2020-06-29 15:40:58 +02:00
var tail * int32
2020-06-30 12:23:22 +02:00
if req . Follow {
return streamLogs ( ctx , cs . ctx , groupName , containerAciName , req . Writer )
}
2020-05-04 16:38:02 +02:00
if req . Tail != "all" {
2020-06-29 15:40:58 +02:00
reqTail , err := strconv . Atoi ( req . Tail )
2020-05-04 16:38:02 +02:00
if err != nil {
return err
}
2020-06-29 15:40:58 +02:00
i32 := int32 ( reqTail )
tail = & i32
}
2020-05-04 16:38:02 +02:00
2020-06-29 15:40:58 +02:00
logs , err := getACIContainerLogs ( ctx , cs . ctx , groupName , containerAciName , tail )
if err != nil {
return err
2020-05-04 16:38:02 +02:00
}
_ , err = fmt . Fprint ( req . Writer , logs )
2020-05-03 13:41:45 +02:00
return err
}
2020-05-05 10:58:24 +02:00
2020-05-10 22:37:28 +02:00
func ( cs * aciContainerService ) Delete ( ctx context . Context , containerID string , _ bool ) error {
2020-07-03 10:03:46 +02:00
groupName , containerName := getGroupAndContainerName ( containerID )
if groupName != containerID {
return errors . New ( fmt . Sprintf ( ` cannot delete service "%s" from compose app "%s", you must delete the entire compose app with docker compose down ` , containerName , groupName ) )
}
cg , err := deleteACIContainerGroup ( ctx , cs . ctx , groupName )
2020-05-10 22:37:28 +02:00
if err != nil {
return err
}
if cg . StatusCode == http . StatusNoContent {
return ErrNoSuchContainer
}
return err
}
2020-06-12 10:53:06 +02:00
func ( cs * aciContainerService ) Inspect ( ctx context . Context , containerID string ) ( containers . Container , error ) {
2020-06-15 10:38:37 +02:00
groupName , containerName := getGroupAndContainerName ( containerID )
cg , err := getACIContainerGroup ( ctx , cs . ctx , groupName )
2020-06-12 10:53:06 +02:00
if err != nil {
return containers . Container { } , err
}
if cg . StatusCode == http . StatusNoContent {
return containers . Container { } , ErrNoSuchContainer
}
2020-06-15 10:38:37 +02:00
var cc containerinstance . Container
var found = false
for _ , c := range * cg . Containers {
if to . String ( c . Name ) == containerName {
cc = c
found = true
break
}
2020-06-12 10:53:06 +02:00
}
2020-06-15 10:38:37 +02:00
if ! found {
return containers . Container { } , ErrNoSuchContainer
2020-06-12 10:53:06 +02:00
}
2020-06-15 10:38:37 +02:00
return convert . ContainerGroupToContainer ( containerID , cg , cc )
2020-06-12 10:53:06 +02:00
}
2020-05-05 15:37:12 +02:00
type aciComposeService struct {
2020-05-12 23:00:58 +02:00
ctx store . AciContext
2020-05-05 15:37:12 +02:00
}
2020-07-02 16:05:45 +02:00
func ( cs * aciComposeService ) Up ( ctx context . Context , opts cli . ProjectOptions ) error {
project , err := cli . ProjectFromOptions ( & opts )
2020-05-05 10:58:24 +02:00
if err != nil {
return err
}
logrus . Debugf ( "Up on project with name %q\n" , project . Name )
groupDefinition , err := convert . ToContainerGroup ( cs . ctx , * project )
2020-05-06 17:14:53 +02:00
2020-05-05 10:58:24 +02:00
if err != nil {
return err
}
2020-06-18 10:03:28 +02:00
return createOrUpdateACIContainers ( ctx , cs . ctx , groupDefinition )
2020-05-05 10:58:24 +02:00
}
2020-07-02 16:05:45 +02:00
func ( cs * aciComposeService ) Down ( ctx context . Context , opts cli . ProjectOptions ) error {
2020-07-03 10:24:06 +02:00
var project types . Project
if opts . Name != "" {
2020-07-03 10:53:50 +02:00
project = types . Project { Name : opts . Name }
2020-07-03 10:24:06 +02:00
} else {
fullProject , err := cli . ProjectFromOptions ( & opts )
if err != nil {
return err
}
project = * fullProject
2020-05-05 10:58:24 +02:00
}
logrus . Debugf ( "Down on project with name %q\n" , project . Name )
2020-05-10 22:37:28 +02:00
cg , err := deleteACIContainerGroup ( ctx , cs . ctx , project . Name )
if err != nil {
return err
}
if cg . StatusCode == http . StatusNoContent {
return ErrNoSuchContainer
}
2020-05-05 10:58:24 +02:00
return err
}
2020-05-12 13:37:28 +02:00
type aciCloudService struct {
2020-05-12 17:26:11 +02:00
loginService login . AzureLoginService
2020-05-12 13:37:28 +02:00
}
func ( cs * aciCloudService ) Login ( ctx context . Context , params map [ string ] string ) error {
2020-07-01 12:25:18 +02:00
return cs . loginService . Login ( ctx , params [ login . TenantIDLoginParam ] )
2020-05-12 13:37:28 +02:00
}
2020-06-02 09:49:30 +02:00
func ( cs * aciCloudService ) CreateContextData ( ctx context . Context , params map [ string ] string ) ( interface { } , string , error ) {
2020-06-02 23:33:41 +02:00
contextHelper := newContextCreateHelper ( )
return contextHelper . createContextData ( ctx , params )
2020-06-02 09:49:30 +02:00
}