Skip to content

Commit

Permalink
feat: start/stop A Cloud Guru sandboxes and configure local credentials
Browse files Browse the repository at this point in the history
  • Loading branch information
nicerloop committed Feb 10, 2024
0 parents commit a16811b
Show file tree
Hide file tree
Showing 3 changed files with 354 additions and 0 deletions.
17 changes: 17 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
module acloudguru-sandbox

go 1.21.6

require (
github.com/go-ini/ini v1.67.0
github.com/go-rod/rod v0.114.6
)

require (
github.com/stretchr/testify v1.8.4 // indirect
github.com/ysmood/fetchup v0.2.3 // indirect
github.com/ysmood/goob v0.4.0 // indirect
github.com/ysmood/got v0.34.1 // indirect
github.com/ysmood/gson v0.7.3 // indirect
github.com/ysmood/leakless v0.8.0 // indirect
)
26 changes: 26 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A=
github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
github.com/go-rod/rod v0.114.6 h1:NrutWvLGn6Vea+0ZpLSHQ2cT5UMTqk9DeO+V6xeJBxw=
github.com/go-rod/rod v0.114.6/go.mod h1:aiedSEFg5DwG/fnNbUOTPMTTWX3MRj6vIs/a684Mthw=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/ysmood/fetchup v0.2.3 h1:ulX+SonA0Vma5zUFXtv52Kzip/xe7aj4vqT5AJwQ+ZQ=
github.com/ysmood/fetchup v0.2.3/go.mod h1:xhibcRKziSvol0H1/pj33dnKrYyI2ebIvz5cOOkYGns=
github.com/ysmood/goob v0.4.0 h1:HsxXhyLBeGzWXnqVKtmT9qM7EuVs/XOgkX7T6r1o1AQ=
github.com/ysmood/goob v0.4.0/go.mod h1:u6yx7ZhS4Exf2MwciFr6nIM8knHQIE22lFpWHnfql18=
github.com/ysmood/gop v0.0.2 h1:VuWweTmXK+zedLqYufJdh3PlxDNBOfFHjIZlPT2T5nw=
github.com/ysmood/gop v0.0.2/go.mod h1:rr5z2z27oGEbyB787hpEcx4ab8cCiPnKxn0SUHt6xzk=
github.com/ysmood/got v0.34.1 h1:IrV2uWLs45VXNvZqhJ6g2nIhY+pgIG1CUoOcqfXFl1s=
github.com/ysmood/got v0.34.1/go.mod h1:yddyjq/PmAf08RMLSwDjPyCvHvYed+WjHnQxpH851LM=
github.com/ysmood/gotrace v0.6.0 h1:SyI1d4jclswLhg7SWTL6os3L1WOKeNn/ZtzVQF8QmdY=
github.com/ysmood/gotrace v0.6.0/go.mod h1:TzhIG7nHDry5//eYZDYcTzuJLYQIkykJzCRIo4/dzQM=
github.com/ysmood/gson v0.7.3 h1:QFkWbTH8MxyUTKPkVWAENJhxqdBa4lYTQWqZCiLG6kE=
github.com/ysmood/gson v0.7.3/go.mod h1:3Kzs5zDl21g5F/BlLTNcuAGAYLKt2lV5G8D1zF3RNmg=
github.com/ysmood/leakless v0.8.0 h1:BzLrVoiwxikpgEQR0Lk8NyBN5Cit2b1z+u0mgL4ZJak=
github.com/ysmood/leakless v0.8.0/go.mod h1:R8iAXPRaG97QJwqxs74RdwzcRHT1SWCGTNqY8q0JvMQ=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
311 changes: 311 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,311 @@
package main

import (
"fmt"
"log"
"os"
"os/exec"
"path/filepath"
"strings"

"github.com/go-rod/rod"
"github.com/go-rod/rod/lib/defaults"
"github.com/go-rod/rod/lib/input"

"github.com/go-ini/ini"
)

const acgSandbox = "acloudguru-sandbox"
const acgSandboxesUrl = "https://learn.acloud.guru/cloud-playground/cloud-sandboxes"

func main() {
log.SetPrefix(fmt.Sprintf("%s: ", acgSandbox))
command := CheckSubCommand(acgSandbox, os.Args)
page := Login(GetGitCredentials(acgSandboxesUrl))
switch command {
case "current":
command = DetectSandbox(page)
case "stop":
page = StopSandbox(page)
case "aws":
page = StartSandbox(page, "AWS")
case "azure":
page = StartSandbox(page, "Azure")
case "gcloud":
page = StartSandbox(page, "Google Cloud")
}
switch command {
case "stop":
log.Printf("Stopped\n")
case "aws":
ConfigureAwsSandbox(page)
case "azure":
ConfigureAzureSandbox(page)
case "gcloud":
ConfigureGcloudSandbox(page)
}
Logout(page)
}

func CheckSubCommand(command string, args []string) string {
paramsSyntax := "<current|stop|aws|azure|gcloud> [-rod=...]"
if len(args) < 2 {
log.Fatalf("missing sandbox command:\n\t%s %s\n", command, paramsSyntax)
}
if len(args) > 3 {
log.Fatalf("unexpected arguments: %s\n\t%s %s\n", strings.Join(args[1:], " "), command, paramsSyntax)
}
if len(args) == 3 && !strings.HasPrefix(args[2], "-rod=") {
log.Fatalln("2")
}
switch args[1] {
case "current", "stop", "aws", "azure", "gcloud":
return args[1]
default:
log.Fatalf("unknown sandbox command: %s\n\t%s %s\n", args[1], command, paramsSyntax)
// unreached
return "unknown"
}
}

// https://pkg.go.dev/golang.org/x/tools/cmd/auth/gitauth
func GetGitCredentials(url string) (username string, password string) {
log.Printf("git credentials for url: %s\n", url)
cmd := exec.Command("git", "credential", "fill")
cmd.Stdin = strings.NewReader(fmt.Sprintf("url=%s\n", url))
out := RunCmd(cmd, 3)
lines := strings.Split(string(out), "\n")
for _, line := range lines {
frags := strings.SplitN(line, "=", 2)
if len(frags) != 2 {
continue // Ignore unrecognized response lines.
}
switch strings.TrimSpace(frags[0]) {
case "username":
username = frags[1]
case "password":
password = frags[1]
}
}
log.Printf("git username: %s\n", username)
return username, password
}

func RunCmd(cmd *exec.Cmd, logArgsCount int) []byte {
userHome, err := os.UserHomeDir()
if err != nil {
log.Fatalf("cannot get user HOME: %v\n", err)
}
cmd.Dir = userHome
cmd.Stderr = os.Stderr
var out []byte = nil

if cmd.Stdout != nil {
err = cmd.Run()
} else {
out, err = cmd.Output()
}

if err != nil {
logCmd := strings.Join(cmd.Args[:logArgsCount], " ")
log.Fatalf("'%s' failed: %v\n", logCmd, err)
}
return out
}

func Login(username string, password string) *rod.Page {
browser := rod.New().MustConnect().NoDefaultDevice()
page := browser.MustPage(acgSandboxesUrl)
page.MustElement("input[name='email']").MustInput(username).MustType(input.Enter)
page.MustElement("input[name='password']").MustInput(password).MustType(input.Enter)
if page.MustHas("input[name='captcha']") {
if defaults.Show {
page.MustElement("input[name='captcha']").MustFocus()
} else {
log.Fatalf("Warning: CAPTCHA in login form, use -rod=show option")
}
}
return page
}

func Logout(page *rod.Page) *rod.Page {
page.MustNavigate("https://learn.acloud.guru/logout")
return page
}

func DetectSandbox(page *rod.Page) string {
current := "stop"
page.Race().
ElementR("button", "Start").MustHandle(func(e *rod.Element) {
current = "stop"
}).
ElementR("h3", "AWS Sandbox").MustHandle(func(e *rod.Element) {
current = "aws"
}).
ElementR("h3", "Azure Sandbox").MustHandle(func(e *rod.Element) {
current = "azure"
}).
ElementR("h3", "Google Cloud Sandbox").MustHandle(func(e *rod.Element) {
current = "gcloud"
}).
MustDo()
return current
}

func StartSandbox(page *rod.Page, target string) *rod.Page {
sandboxHeading := fmt.Sprintf("%s Sandbox", target)
startButtonText := fmt.Sprintf("Start %s Sandbox", target)
for targetReached := false; !targetReached; {
page.Race().
ElementR("h3", sandboxHeading).MustHandle(func(e *rod.Element) {
targetReached = true
}).
ElementR("button", startButtonText).MustHandle(func(e *rod.Element) {
e.MustClick()
}).
ElementR("button", "Delete Sandbox").MustHandle(func(e *rod.Element) {
DeleteSandbox(e, page)
}).
MustDo()
}
return page
}

func DeleteSandbox(e *rod.Element, page *rod.Page) {
e.MustClick()
buttons := page.MustElements("button")
buttons.Last().MustClick()
page.MustWaitStable()
}

func StopSandbox(page *rod.Page) *rod.Page {
for targetReached := false; !targetReached; {
page.Race().
ElementR("button", "Start").MustHandle(func(e *rod.Element) {
targetReached = true
}).
ElementR("button", "Delete Sandbox").MustHandle(func(e *rod.Element) {
DeleteSandbox(e, page)
}).
MustDo()
}
return page
}

func ConfigureAwsSandbox(page *rod.Page) {
awsAccessKeyId, awsSecretAccessKey := GetAwsSandboxCredentials(page)
WriteAwsCredentialsFile(acgSandbox, awsAccessKeyId, awsSecretAccessKey)
CheckAwsCredentials(acgSandbox)
}

func GetAwsSandboxCredentials(page *rod.Page) (string, string) {
elements := page.MustElements("input[aria-label='Copy to clipboard']")
aws_username := elements[0].MustText()
aws_password := elements[1].MustText()
aws_url := elements[2].MustText()
aws_access_key_id := elements[3].MustText()
aws_secret_access_key := elements[4].MustText()
log.Printf("Aws username: %s\n", aws_username)
log.Printf("Aws password: %s\n", aws_password)
log.Printf("Aws URL: %s\n", aws_url)
log.Printf("Aws access key ID: %s\n", aws_access_key_id)
log.Printf("Aws secret access key: %s\n", aws_secret_access_key)
return aws_access_key_id, aws_secret_access_key
}

func WriteAwsCredentialsFile(profile string, awsAccessKeyId string, awsSecretAccessKey string) {
userHome, err := os.UserHomeDir()
if err != nil {
log.Fatalf("cannot get user HOME: %v\n", err)
}

awsFolder := filepath.Join(userHome, ".aws")
err = os.MkdirAll(awsFolder, os.ModePerm)
if err != nil {
log.Fatalf("Cannot access AWS config folder: %v\n", err)
} else {
log.Printf("Aws config folder: %s\n", awsFolder)
}

awsCredentialsFile := filepath.Join(awsFolder, "credentials")
awsCredentials, err := ini.LooseLoad(awsCredentialsFile)
if err != nil {
log.Fatalf("Cannot read AWS credentials file: %v\n", err)
}
log.Printf("Aws proflie: %s\n", profile)
awsCredentials.Section(profile).Key("aws_access_key_id").SetValue(awsAccessKeyId)
awsCredentials.Section(profile).Key("aws_secret_access_key").SetValue(awsSecretAccessKey)
awsCredentials.SaveTo(awsCredentialsFile)
}

func CheckAwsCredentials(profile string) {
cmd := exec.Command("aws", "iam", "get-user", "--profile", profile)
cmd.Stdout = os.Stdout
_ = RunCmd(cmd, 3)
}

func ConfigureAzureSandbox(page *rod.Page) {
azure_username, azure_password := GetAzureSandboxCredentials(page)
LoginAzureCli(azure_username, azure_password)
}

func GetAzureSandboxCredentials(page *rod.Page) (string, string) {
elements := page.MustElements("input[aria-label='Copy to clipboard']")
azure_username := elements[0].MustText()
azure_password := elements[1].MustText()
azure_url := elements[2].MustText()
azure_application_client_id := elements[3].MustText()
azure_secret := elements[4].MustText()

if true {
log.Printf("Azure username: %s\n", azure_username)
log.Printf("Azure password: %s\n", azure_password)
log.Printf("Azure URL: %s\n", azure_url)
log.Printf("Azure application client ID: %s\n", azure_application_client_id)
log.Printf("Azure secret: %s\n", azure_secret)
}
return azure_username, azure_password
}

func LoginAzureCli(azure_username string, azure_password string) {
cmd := exec.Command("az", "login", "--user", azure_username, "--password", azure_password)
cmd.Stdout = os.Stdout
_ = RunCmd(cmd, 2)
}

func ConfigureGcloudSandbox(page *rod.Page) {
gcloud_service_account_credentials := GetGoogleCloudSandboxCredentials(page)
LoginGoogleCloudCli(gcloud_service_account_credentials)
}

func GetGoogleCloudSandboxCredentials(page *rod.Page) string {
elements := page.MustElements("input[aria-label='Copy to clipboard']")
gcloud_username := elements[0].MustText()
gcloud_password := elements[1].MustText()
gcloud_url := elements[2].MustText()
gcloud_service_account_credentials := elements[3].MustText()

if true {
log.Printf("Google Cloud username: %s\n", gcloud_username)
log.Printf("Google Cloud password: %s\n", gcloud_password)
log.Printf("Google Cloud URL: %s\n", gcloud_url)
log.Printf("Google Cloud service account credentials: %s\n", gcloud_service_account_credentials)
}
return gcloud_service_account_credentials
}

func LoginGoogleCloudCli(gcloud_service_account_credentials string) {
tempFile, err := os.CreateTemp("", "sample")
if err != nil {
log.Fatalf("cannot create temporary file: %v\n", err)
}
defer os.Remove(tempFile.Name())
_, err = tempFile.WriteString(gcloud_service_account_credentials)
if err != nil {
log.Fatalf("cannot write service accout credentials: %v\n", err)
}
keyArgument := fmt.Sprintf("--key=%s", tempFile.Name())
cmd := exec.Command("gcloud", "auth", "activate-service-account", keyArgument)
cmd.Stdout = os.Stdout
_ = RunCmd(cmd, 3)
}

0 comments on commit a16811b

Please # to comment.