2020-05-01 15:28:44 +02:00
package azure
import (
"context"
"fmt"
"io"
2020-05-03 13:35:25 +02:00
"io/ioutil"
2020-05-01 15:28:44 +02:00
"net/http"
"os"
"github.com/docker/api/context/store"
"github.com/gobwas/ws"
"github.com/gobwas/ws/wsutil"
"github.com/Azure/azure-sdk-for-go/services/containerinstance/mgmt/2018-10-01/containerinstance"
"github.com/Azure/azure-sdk-for-go/services/keyvault/auth"
"github.com/Azure/go-autorest/autorest"
"github.com/Azure/go-autorest/autorest/to"
tm "github.com/buger/goterm"
)
2020-05-01 15:48:20 +02:00
func init ( ) {
2020-05-04 11:27:16 +02:00
// required to get auth.NewAuthorizerFromCLI() to work, otherwise getting "The access token has been obtained for wrong audience or resource 'https://vault.azure.net'."
2020-05-04 11:31:44 +02:00
err := os . Setenv ( "AZURE_KEYVAULT_RESOURCE" , "https://management.azure.com" )
if err != nil {
panic ( "unable to set environment variable AZURE_KEYVAULT_RESOURCE" )
}
2020-05-01 15:48:20 +02:00
}
2020-05-01 15:28:44 +02:00
2020-05-04 11:27:31 +02:00
func createACIContainers ( ctx context . Context , aciContext store . AciContext , groupDefinition containerinstance . ContainerGroup ) ( c containerinstance . ContainerGroup , err error ) {
2020-05-01 15:28:44 +02:00
containerGroupsClient , err := getContainerGroupsClient ( aciContext . SubscriptionID )
if err != nil {
return c , fmt . Errorf ( "cannot get container group client: %v" , err )
}
// Check if the container group already exists
_ , err = containerGroupsClient . Get ( ctx , aciContext . ResourceGroup , * groupDefinition . Name )
if err != nil {
if err , ok := err . ( autorest . DetailedError ) ; ok {
if err . StatusCode != http . StatusNotFound {
return c , err
}
} else {
return c , err
}
} else {
2020-05-04 11:27:16 +02:00
return c , fmt . Errorf ( "container group %q already exists" , * groupDefinition . Name )
2020-05-01 15:28:44 +02:00
}
future , err := containerGroupsClient . CreateOrUpdate (
ctx ,
aciContext . ResourceGroup ,
* groupDefinition . Name ,
groupDefinition ,
)
if err != nil {
return c , err
}
err = future . WaitForCompletionRef ( ctx , containerGroupsClient . Client )
if err != nil {
return c , err
}
containerGroup , err := future . Result ( containerGroupsClient )
if err != nil {
return c , err
}
2020-05-01 15:48:20 +02:00
if len ( * containerGroup . Containers ) > 1 {
2020-05-01 15:28:44 +02:00
var commands [ ] string
2020-05-01 15:48:20 +02:00
for _ , container := range * containerGroup . Containers {
commands = append ( commands , fmt . Sprintf ( "echo 127.0.0.1 %s >> /etc/hosts" , * container . Name ) )
2020-05-01 15:28:44 +02:00
}
commands = append ( commands , "exit" )
2020-05-01 15:48:20 +02:00
containers := * containerGroup . Containers
container := containers [ 0 ]
2020-05-03 13:35:25 +02:00
response , err := execACIContainer ( ctx , aciContext , "/bin/sh" , * containerGroup . Name , * container . Name )
2020-05-01 15:28:44 +02:00
if err != nil {
return c , err
}
2020-05-03 13:35:25 +02:00
if err = execCommands (
2020-05-01 15:28:44 +02:00
ctx ,
* response . WebSocketURI ,
* response . Password ,
commands ,
2020-05-03 13:35:25 +02:00
) ; err != nil {
2020-05-01 15:28:44 +02:00
return containerinstance . ContainerGroup { } , err
}
}
return containerGroup , err
}
2020-05-04 11:22:24 +02:00
func listACIContainers ( aciContext store . AciContext ) ( c [ ] containerinstance . ContainerGroup , err error ) {
2020-05-01 15:28:44 +02:00
ctx := context . TODO ( )
containerGroupsClient , err := getContainerGroupsClient ( aciContext . SubscriptionID )
if err != nil {
return c , fmt . Errorf ( "cannot get container group client: %v" , err )
}
var containers [ ] containerinstance . ContainerGroup
result , err := containerGroupsClient . ListByResourceGroup ( ctx , aciContext . ResourceGroup )
if err != nil {
return [ ] containerinstance . ContainerGroup { } , err
}
for result . NotDone ( ) {
containers = append ( containers , result . Values ( ) ... )
if err := result . NextWithContext ( ctx ) ; err != nil {
return [ ] containerinstance . ContainerGroup { } , err
}
}
return containers , err
}
2020-05-03 13:35:25 +02:00
func execACIContainer ( ctx context . Context , aciContext store . AciContext , command , containerGroup string , containerName string ) ( c containerinstance . ContainerExecResponse , err error ) {
2020-05-01 15:28:44 +02:00
containerClient := getContainerClient ( aciContext . SubscriptionID )
rows , cols := getTermSize ( )
containerExecRequest := containerinstance . ContainerExecRequest {
Command : to . StringPtr ( command ) ,
TerminalSize : & containerinstance . ContainerExecRequestTerminalSize {
Rows : rows ,
Cols : cols ,
} ,
}
2020-05-03 13:35:25 +02:00
2020-05-01 15:28:44 +02:00
return containerClient . ExecuteCommand (
ctx ,
aciContext . ResourceGroup ,
containerGroup ,
containerName ,
containerExecRequest )
}
func getTermSize ( ) ( * int32 , * int32 ) {
rows := tm . Height ( )
cols := tm . Width ( )
return to . Int32Ptr ( int32 ( rows ) ) , to . Int32Ptr ( int32 ( cols ) )
}
2020-05-03 13:35:25 +02:00
type commandSender struct {
commands [ ] string
2020-05-01 15:28:44 +02:00
}
2020-05-03 13:35:25 +02:00
func ( cs commandSender ) Read ( p [ ] byte ) ( int , error ) {
if len ( cs . commands ) == 0 {
return 0 , io . EOF
}
command := cs . commands [ 0 ]
cs . commands = cs . commands [ 1 : ]
copy ( p , command )
return len ( command ) , nil
}
func execCommands ( ctx context . Context , address string , password string , commands [ ] string ) error {
writer := ioutil . Discard
reader := commandSender {
commands : commands ,
}
return exec ( ctx , address , password , reader , writer )
}
func exec ( ctx context . Context , address string , password string , reader io . Reader , writer io . Writer ) error {
2020-05-01 15:28:44 +02:00
ctx , cancel := context . WithCancel ( ctx )
2020-05-03 13:35:25 +02:00
conn , _ , _ , err := ws . DefaultDialer . Dial ( ctx , address )
2020-05-01 15:28:44 +02:00
if err != nil {
cancel ( )
return err
}
2020-05-03 13:35:25 +02:00
err = wsutil . WriteClientMessage ( conn , ws . OpText , [ ] byte ( password ) )
2020-05-01 15:28:44 +02:00
if err != nil {
cancel ( )
return err
}
2020-05-03 13:35:25 +02:00
2020-05-01 15:28:44 +02:00
done := make ( chan struct { } )
2020-05-03 13:35:25 +02:00
2020-05-01 15:28:44 +02:00
go func ( ) {
defer close ( done )
for {
msg , _ , err := wsutil . ReadServerData ( conn )
if err != nil {
return
}
2020-05-03 13:35:25 +02:00
fmt . Fprint ( writer , string ( msg ) )
2020-05-01 15:28:44 +02:00
}
} ( )
2020-05-03 13:35:25 +02:00
readChannel := make ( chan [ ] byte , 10 )
2020-05-01 15:28:44 +02:00
go func ( ) {
for {
2020-05-03 13:35:25 +02:00
// We send each byte, byte-per-byte over the
// websocket because the console is in raw mode
buffer := make ( [ ] byte , 1 )
n , err := reader . Read ( buffer )
if err != nil {
2020-05-01 15:28:44 +02:00
close ( done )
cancel ( )
break
}
2020-05-03 13:35:25 +02:00
if n > 0 {
readChannel <- buffer
}
2020-05-01 15:28:44 +02:00
}
} ( )
2020-05-03 13:35:25 +02:00
2020-05-01 15:28:44 +02:00
for {
select {
case <- done :
return nil
2020-05-03 13:35:25 +02:00
case bytes := <- readChannel :
err := wsutil . WriteClientMessage ( conn , ws . OpText , bytes )
2020-05-01 15:28:44 +02:00
if err != nil {
2020-05-03 13:35:25 +02:00
return err
2020-05-01 15:28:44 +02:00
}
}
}
}
2020-05-03 13:41:45 +02:00
func getACIContainerLogs ( ctx context . Context , aciContext store . AciContext , containerGroupName , containerName string ) ( string , error ) {
containerClient := getContainerClient ( aciContext . SubscriptionID )
logs , err := containerClient . ListLogs ( ctx , aciContext . ResourceGroup , containerGroupName , containerName , nil )
if err != nil {
return "" , fmt . Errorf ( "cannot get container logs: %v" , err )
}
return * logs . Content , err
}
2020-05-01 15:28:44 +02:00
func getContainerGroupsClient ( subscriptionID string ) ( containerinstance . ContainerGroupsClient , error ) {
auth , _ := auth . NewAuthorizerFromCLI ( )
containerGroupsClient := containerinstance . NewContainerGroupsClient ( subscriptionID )
containerGroupsClient . Authorizer = auth
return containerGroupsClient , nil
}
func getContainerClient ( subscriptionID string ) containerinstance . ContainerClient {
auth , _ := auth . NewAuthorizerFromCLI ( )
containerClient := containerinstance . NewContainerClient ( subscriptionID )
containerClient . Authorizer = auth
return containerClient
}