Skip to content
GitLab
Menu
Projects
Groups
Snippets
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in
Toggle navigation
Menu
Open sidebar
feudal
feudalSSH
Commits
8f291ae1
Commit
8f291ae1
authored
Feb 26, 2019
by
Lukas Burgey
Browse files
Initialize with a working prototype
parents
Changes
2
Hide whitespace changes
Inline
Side-by-side
api/api.go
0 → 100644
View file @
8f291ae1
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"`
}
main.go
0 → 100644
View file @
8f291ae1
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
)
}
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment