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"
2020-05-05 16:01:31 +02:00
"strings"
2020-05-05 16:51:41 +02:00
"time"
2020-05-01 15:28:44 +02:00
2020-05-05 17:55:53 +02:00
"github.com/Azure/azure-sdk-for-go/profiles/2019-03-01/resources/mgmt/resources"
"github.com/Azure/azure-sdk-for-go/profiles/preview/preview/subscription/mgmt/subscription"
2020-05-01 15:28:44 +02:00
"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"
2020-05-05 16:51:41 +02:00
tm "github.com/buger/goterm"
2020-05-04 20:38:10 +02:00
"github.com/gobwas/ws"
"github.com/gobwas/ws/wsutil"
"github.com/pkg/errors"
"github.com/docker/api/context/store"
2020-05-01 15:28:44 +02:00
)
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-05 16:27:22 +02:00
func createACIContainers ( ctx context . Context , aciContext store . AciContext , groupDefinition containerinstance . ContainerGroup ) error {
2020-05-04 23:50:00 +02:00
containerGroupsClient , err := getContainerGroupsClient ( aciContext . SubscriptionID )
if err != nil {
2020-05-05 16:27:22 +02:00
return errors . Wrapf ( err , "cannot get container group client" )
2020-05-04 23:50:00 +02:00
}
2020-05-01 15:28:44 +02:00
// 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 {
2020-05-05 16:27:22 +02:00
return err
2020-05-01 15:28:44 +02:00
}
} else {
2020-05-05 16:27:22 +02:00
return err
2020-05-01 15:28:44 +02:00
}
} else {
2020-05-05 16:27:22 +02:00
return 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 {
2020-05-05 16:27:22 +02:00
return err
2020-05-01 15:28:44 +02:00
}
err = future . WaitForCompletionRef ( ctx , containerGroupsClient . Client )
if err != nil {
2020-05-05 16:27:22 +02:00
return err
2020-05-01 15:28:44 +02:00
}
containerGroup , err := future . Result ( containerGroupsClient )
if err != nil {
2020-05-05 16:27:22 +02:00
return err
2020-05-01 15:28:44 +02:00
}
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 {
2020-05-05 16:27:22 +02:00
return err
2020-05-01 15:28:44 +02:00
}
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-05 16:27:22 +02:00
return err
2020-05-01 15:28:44 +02:00
}
}
2020-05-05 16:27:22 +02:00
return err
2020-05-01 15:28:44 +02:00
}
2020-05-05 10:58:24 +02:00
func deleteACIContainerGroup ( ctx context . Context , aciContext store . AciContext , containerGroupName string ) ( c containerinstance . ContainerGroup , err error ) {
containerGroupsClient , err := getContainerGroupsClient ( aciContext . SubscriptionID )
if err != nil {
return c , fmt . Errorf ( "cannot get container group client: %v" , err )
}
return containerGroupsClient . Delete ( ctx , aciContext . ResourceGroup , containerGroupName )
}
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-04 23:50:00 +02:00
containerClient , err := getContainerClient ( aciContext . SubscriptionID )
if err != nil {
return c , errors . Wrapf ( err , "cannot get container client" )
}
2020-05-01 15:28:44 +02:00
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 {
2020-05-05 16:01:31 +02:00
commands string
2020-05-01 15:28:44 +02:00
}
2020-05-05 16:01:31 +02:00
func ( cs * commandSender ) Read ( p [ ] byte ) ( int , error ) {
2020-05-03 13:35:25 +02:00
if len ( cs . commands ) == 0 {
return 0 , io . EOF
}
2020-05-05 16:01:31 +02:00
var command string
if len ( p ) >= len ( cs . commands ) {
command = cs . commands
cs . commands = ""
} else {
command = cs . commands [ : len ( p ) ]
cs . commands = cs . commands [ len ( p ) : ]
}
2020-05-03 13:35:25 +02:00
copy ( p , command )
2020-05-05 16:01:31 +02:00
2020-05-03 13:35:25 +02:00
return len ( command ) , nil
}
func execCommands ( ctx context . Context , address string , password string , commands [ ] string ) error {
writer := ioutil . Discard
2020-05-05 16:01:31 +02:00
reader := & commandSender {
commands : strings . Join ( commands , "\n" ) ,
2020-05-03 13:35:25 +02:00
}
return exec ( ctx , address , password , reader , writer )
}
func exec ( ctx context . Context , address string , password string , reader io . Reader , writer io . Writer ) error {
conn , _ , _ , err := ws . DefaultDialer . Dial ( ctx , address )
2020-05-01 15:28:44 +02:00
if err != nil {
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 {
return err
}
2020-05-03 13:35:25 +02:00
2020-05-04 20:38:10 +02:00
downstreamChannel := make ( chan error , 10 )
upstreamChannel := make ( chan error , 10 )
2020-05-03 13:35:25 +02:00
2020-05-01 15:28:44 +02:00
go func ( ) {
for {
msg , _ , err := wsutil . ReadServerData ( conn )
if err != nil {
2020-05-04 20:38:10 +02:00
if err == io . EOF {
downstreamChannel <- nil
return
}
downstreamChannel <- err
2020-05-01 15:28:44 +02:00
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
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-05 16:01:31 +02:00
if err == io . EOF {
upstreamChannel <- nil
return
}
2020-05-04 20:38:10 +02:00
upstreamChannel <- err
return
2020-05-01 15:28:44 +02:00
}
2020-05-03 13:35:25 +02:00
if n > 0 {
2020-05-04 20:38:10 +02:00
err := wsutil . WriteClientMessage ( conn , ws . OpText , buffer )
if err != nil {
upstreamChannel <- err
2020-05-05 16:01:31 +02:00
return
2020-05-04 20:38:10 +02:00
}
2020-05-03 13:35:25 +02:00
}
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 {
2020-05-04 20:38:10 +02:00
case err := <- downstreamChannel :
return errors . Wrap ( err , "failed to read input from container" )
case err := <- upstreamChannel :
return errors . Wrap ( err , "failed to send input to container" )
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 ) {
2020-05-04 23:50:00 +02:00
containerClient , err := getContainerClient ( aciContext . SubscriptionID )
if err != nil {
return "" , errors . Wrapf ( err , "cannot get container client" )
}
2020-05-03 13:41:45 +02:00
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-04 23:50:00 +02:00
func getContainerGroupsClient ( subscriptionID string ) ( containerinstance . ContainerGroupsClient , error ) {
auth , err := auth . NewAuthorizerFromCLI ( )
if err != nil {
return containerinstance . ContainerGroupsClient { } , err
}
2020-05-01 15:28:44 +02:00
containerGroupsClient := containerinstance . NewContainerGroupsClient ( subscriptionID )
containerGroupsClient . Authorizer = auth
2020-05-05 16:51:41 +02:00
containerGroupsClient . PollingDelay = 5 * time . Second
containerGroupsClient . RetryAttempts = 30
containerGroupsClient . RetryDuration = 1 * time . Second
2020-05-04 23:50:00 +02:00
return containerGroupsClient , nil
2020-05-01 15:28:44 +02:00
}
2020-05-04 23:50:00 +02:00
func getContainerClient ( subscriptionID string ) ( containerinstance . ContainerClient , error ) {
auth , err := auth . NewAuthorizerFromCLI ( )
if err != nil {
return containerinstance . ContainerClient { } , err
}
2020-05-01 15:28:44 +02:00
containerClient := containerinstance . NewContainerClient ( subscriptionID )
containerClient . Authorizer = auth
2020-05-04 23:50:00 +02:00
return containerClient , nil
2020-05-01 15:28:44 +02:00
}
2020-05-05 17:55:53 +02:00
func getSubscriptionsClient ( ) subscription . SubscriptionsClient {
subc := subscription . NewSubscriptionsClient ( )
authorizer , _ := auth . NewAuthorizerFromCLI ( )
subc . Authorizer = authorizer
return subc
}
//GetGroupsClient ...
func GetGroupsClient ( subscriptionID string ) resources . GroupsClient {
groupsClient := resources . NewGroupsClient ( subscriptionID )
authorizer , _ := auth . NewAuthorizerFromCLI ( )
groupsClient . Authorizer = authorizer
return groupsClient
}
//GetSubscriptionID ...
func GetSubscriptionID ( ctx context . Context ) ( string , error ) {
c := getSubscriptionsClient ( )
res , err := c . List ( ctx )
if err != nil {
return "" , err
}
subs := res . Values ( )
if len ( subs ) == 0 {
return "" , errors . New ( "no subscriptions found" )
}
sub := subs [ 0 ]
return * sub . SubscriptionID , nil
}