Commit 8f291ae1 authored by Lukas Burgey's avatar Lukas Burgey
Browse files

Initialize with a working prototype

parents
package api
import "fmt"
type SSHKey struct {
Name string `json:"name"`
Key string `json:"key"`
}
type Service struct {
ID int `json:"id,omitempty"`
Name string `json:"name"`
Description string `json:"description"`
}
func (s Service) String() string {
return fmt.Sprintf("%s: %s", s.Name, s.Description)
}
// from the user
type Credential struct {
ID int `json:"id,omitempty"`
Name string `json:"name"`
}
type CredentialState struct {
State string `json:"state"`
Target string `json:"state_target"`
Credential Credential `json:"credential"`
}
// credentials from the client
type Credentials map[string]string
type DepState struct {
ID int `json:"id,omitempty"`
State string `json:"state"`
Target string `json:"state_target"`
Credentials Credentials `json:"credentials"`
CredentialStates []CredentialState `json:"credential_states"`
}
type Deployment struct {
ID int `json:"id,omitempty"`
State string `json:"state"`
Target string `json:"state_target"`
States []DepState `json:"states"`
}
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"log"
"net/http"
"os"
"os/exec"
"path/filepath"
"strings"
"time"
api "git.scc.kit.edu/feudal/feudalSSH/api"
sshclient "github.com/helloyi/go-sshclient"
kingpin "gopkg.in/alecthomas/kingpin.v2"
)
var (
app = kingpin.New(
"FEUDAL SSH",
"SSH intergration for FEUDAL",
).Author(
"Lukas Burgey",
).Version(
"v0.1.0",
)
httpClient = &http.Client{}
serviceName = app.Arg("serviceName", "Service name to ssh to").String()
accessToken = app.Flag("at", "Access Token").Short('a').Required().String()
issuerURI = app.Flag("issuer", "Issuer URI of your access token").Short('i').Default("https://unity.helmholtz-data-federation.de/oauth2").String()
feudalURI = app.Flag("uri", "Feudal URI").Short('u').Default("https://hdf-portal-dev.data.kit.edu").String()
pubKey = app.Flag("pubkey", "SSH public key file path").Short('k').Required().String()
)
func restCall(method string, path string, body io.Reader) (responseBytes []byte, err error) {
request, err := http.NewRequest(method, fmt.Sprintf("%s/rest/%s", *feudalURI, path), body)
if err != nil {
return
}
request.Header.Add("X-Issuer", *issuerURI)
request.Header.Add("Authorization", fmt.Sprintf("Bearer %s", *accessToken))
if body != nil {
request.Header.Add("Content-Type", "application/json")
}
response, err := httpClient.Do(request)
if err != nil {
return
}
responseBytes, err = ioutil.ReadAll(response.Body)
if err != nil {
return
}
return
}
func fetchDepState(id int) (state api.DepState) {
response, err := restCall("GET", fmt.Sprintf("state/%d", id), nil)
if err != nil {
log.Fatal(err)
}
if err := json.Unmarshal(
response,
&state,
); err != nil {
panic(err)
}
return
}
func readPubKey() (name, key string) {
// determine which file to use
path := *pubKey
switch filepath.Ext(path) {
case ".pub":
// all nice and good
case "":
// add extension (probably a private key file was provided)
path += ".pub"
default:
// unusable extension
log.Fatalf("Unusable file: %s", path)
}
if _, err := os.Stat(path); err != nil {
if os.IsNotExist(err) {
log.Fatalf("File does not exist: %s", path)
}
log.Fatal(err)
}
keyBytes, err := ioutil.ReadFile(path)
if err != nil {
log.Fatal(err)
}
name = filepath.Base(path)
key = string(keyBytes)
return
}
// keyExists check if the given key / name combination was already uploaded to feudal
func keyExists(name, key string) bool {
// fetch key list
keyListResponse, err := restCall("GET", "ssh-keys", nil)
if err != nil {
return false
}
var sshKeys = []api.SSHKey{}
err = json.Unmarshal(keyListResponse, &sshKeys)
if err != nil {
return false
}
for _, k := range sshKeys {
// the backend strips comments so we adjust our check
if k.Name == name && strings.HasPrefix(key, k.Key) {
return true
}
}
return false
}
func uploadPubKey() (state api.DepState) {
name, key := readPubKey()
if keyExists(name, key) {
log.Printf("Key already uploaded: %s", name)
return
}
log.Printf("Uploading key %s", name)
body := map[string]string{"name": name, "key": key}
bodyBytes, err := json.Marshal(body)
if err != nil {
log.Fatal(err)
}
response, err := restCall("POST", "ssh-keys", bytes.NewReader(bodyBytes))
if err != nil {
log.Fatalf("Uploading key: %s", err)
}
if responseIdented, err := json.MarshalIndent(response, "", " "); err == nil {
log.Printf("Response: %s", responseIdented)
} else {
log.Printf("Response: %s", response)
}
return
}
func findServiceID() (serviceID int) {
serviceBytes, err := restCall("GET", "services", nil)
if err != nil {
log.Fatal("Retrieving services: %s", err)
}
var services = []api.Service{}
json.Unmarshal(serviceBytes, &services)
if *serviceName == "" {
fmt.Println("Available services:")
for _, s := range services {
fmt.Println(s)
}
log.Fatal("Specify service name to use. See --help")
}
// find the service id
for _, s := range services {
if s.Name == *serviceName {
serviceID = s.ID
log.Printf("Selected service '%s' (id: %d)", *serviceName, serviceID)
return
}
}
if serviceID == 0 {
log.Fatalf("Service with name '%s' does not exist", *serviceName)
}
return
}
// pollDepState polls the api until the state reaches "deployed", "failed", or "questionnare"
func pollDepState(state api.DepState) api.DepState {
for state.State != "deployed" && state.State != "questionnaire" && state.State != "failed" {
// log old
log.Println("Deployment has state:", state.State)
// poll new
time.Sleep(time.Second)
state = fetchDepState(state.ID)
}
log.Println("Deployment has state:", state.State)
return state
}
func sshConnect(creds api.Credentials) {
// use the credentials to access the service
if credsBytes, err := json.MarshalIndent(creds, "", " "); err == nil {
log.Printf("Received credentials: %s", credsBytes)
} else {
log.Printf("Received credentials: %s", creds)
}
var (
userOk, hostOk bool
sshUser, sshHost string
)
sshUser, userOk = creds["ssh_user"]
sshHost, hostOk = creds["ssh_host"]
if !(userOk && hostOk) {
log.Fatal("Invalid credentials for ssh. The service may not be suitable for ssh.")
}
var native = false
// use go native ssh?
if native {
var err error
var client *sshclient.Client
client, err = sshclient.DialWithKey(
"room.hadiko:22",
"burgey",
"/home/burgey/.ssh/id_room.hadiko",
)
if err != nil {
log.Fatalf("Error dialing: %s", err)
}
defer client.Close()
/*
log.Printf("Opening remote shell")
err = client.Shell().Start()
if err != nil {
log.Fatal(err)
}
*/
// default terminal
/*
log.Printf("Opening remote terminal")
if err := client.Terminal(nil).Start(); err != nil {
log.Fatal(err)
}
*/
cmd := exec.Command("ssh", "-i", "/home/burgey/.ssh/id_room.hadiko", "room.hadiko")
cmd.Stdout = os.Stdout
err = cmd.Run()
} else {
log.Printf("Done. SSH using: 'ssh -i %s %s@%s'", *pubKey, sshUser, sshHost)
}
}
func main() {
// get arguments
kingpin.MustParse(app.Parse(os.Args[1:]))
var serviceID = findServiceID()
uploadPubKey()
// start the deployment
var deployment api.Deployment
body := "{\"state_target\":\"deployed\"}"
response, err := restCall("PATCH", fmt.Sprintf("deployment/service/%d", serviceID), strings.NewReader(body))
if err != nil {
log.Fatalf("Requesting deployment: %s", err)
}
json.Unmarshal(response, &deployment)
if count := len(deployment.States); count != 1 {
fmt.Printf("%s", response)
log.Fatalf("Got %d states, want 1", count)
}
// wait for the deployment to be executed
var state = deployment.States[0]
state = pollDepState(state)
switch state.State {
case "deployed":
sshConnect(state.Credentials)
case "failed":
log.Fatal("Deployment failed. Please contact an administrator.")
default:
log.Fatalf("State %s: Not implemented", state.State)
}
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment