Commit 190c0c95 authored by Lukas Burgey's avatar Lukas Burgey
Browse files

Adapt to changes to the deployment flow

We deploy using the DeploymentState model now.
Also we now respond by patching the DeploymentState which is much nicer.
parent 8c2fc383
...@@ -16,32 +16,49 @@ type ( ...@@ -16,32 +16,49 @@ type (
ResourceType string `json:"resourcetype"` ResourceType string `json:"resourcetype"`
} }
// Dep describes a deployment action // Dep now relates to a DeploymentState at the backend
Dep struct { Dep struct {
// ID is the db primary key of th DeploymentState instance at the backend.
ID int `json:"id"` ID int `json:"id"`
// The StateTarget of the DeploymentState parent Deployment.
StateTarget scripts.State `json:"state_target"` StateTarget scripts.State `json:"state_target"`
User scripts.User `json:"user"`
Credentials map[string][]scripts.Credential `json:"credentials"`
Questionnaire map[string]string `json:"questionnaire"`
// this relates to the polymorphic type added by the polymorphic serializer // The user for which we are supposed to deploy access.
ResourceType string `json:"resourcetype"` User scripts.User `json:"user"`
// VO is only set if this is a VODeployment // The Service which this Dep is related to.
VO VO `json:"vo,omitempty"` Service config.Service `json:"service"`
// Service may be filled by scheduleDep // Optional Answers by the users. Must have the same keys as the related Questionnaire
Service config.Service `json:"service,omitempty"` Answers map[string]string `json:"answers,omitempty"`
} }
// Reply is sent back to the backend to inform it about executed deployment actions // Reply is used to patch the fields of the DeploymentState
Reply struct { Reply struct {
// ID of the according Dep (at the backend: DeploymentState) // ID is the db primary key of the DeploymentState instance at the backend.
ID int `json:"id"` ID int `json:"id"`
Output scripts.Output `json:"output"`
// Service is the service for which we performed the deploy action // State of the deployment, after the script execution
Service config.Service `json:"service"` // when State == Questionnaire then Output.Questionnaire *must* be set
// when State == Deployed then Output.Credentials *can* be set
State scripts.State `json:"state"`
// Message for the user
Msg string `json:"message"`
// Questionnaire requested by the script
// Ignored when State is not Questionnaire
// Maps a question name to a description of the question
Questionnaire map[string]string `json:"questionnaire,omitempty"`
// Credentials for the user
// Ignored when State is not Deployed
// Maps a credential name to a credential value
Credentials map[string]string `json:"credentials,omitempty"`
// UserCredentialStates are the State s of the credentials found in Input.User.Credentials.
UserCredentialStates scripts.UserCredentialStates `json:"credential_states,omitempty"`
} }
) )
...@@ -58,7 +75,7 @@ func (dep Dep) Log(formatString string, params ...interface{}) { ...@@ -58,7 +75,7 @@ func (dep Dep) Log(formatString string, params ...interface{}) {
} }
func (rep Reply) String() string { func (rep Reply) String() string {
return fmt.Sprintf("Rep[%v]#%v]", rep.Service.Name, rep.ID) return fmt.Sprintf("Rep[%s]#%v]", rep.State, rep.ID)
} }
// Log logs a message for a Reply // Log logs a message for a Reply
......
...@@ -33,46 +33,22 @@ type ( ...@@ -33,46 +33,22 @@ type (
} }
) )
var (
httpClient = &http.Client{}
)
func (sink *Sink) depServices(dep deps.Dep) (services []config.Service, err error) { func (sink *Sink) depServices(dep deps.Dep) (services []config.Service, err error) {
// Option 1: VODeployment // find the matching service
if dep.ResourceType == "VODeployment" && dep.VO != (deps.VO{}) { if dep.Service != (config.Service{}) {
var ok bool
// find sids
var sids []config.ServiceID
if dep.VO.ResourceType == "Group" {
sids, ok = sink.Config.GroupToServiceIDs[dep.VO.Name]
if !ok {
err = fmt.Errorf("%s: Group %s does not exist", dep, dep.VO.Name)
return
}
} else if dep.VO.ResourceType == "Entitlement" {
sids, ok = sink.Config.EntitlementToServiceIDs[dep.VO.Name]
if !ok {
err = fmt.Errorf("%s: Entitlement %s does not exist", dep, dep.VO.Name)
return
}
}
services, err = sink.Config.GetServices(sids)
if err != nil {
return
}
} else if dep.ResourceType == "ServiceDeployment" && dep.Service != (config.Service{}) {
for _, s := range sink.Config.Services { for _, s := range sink.Config.Services {
if s.Name == dep.Service.Name { if s.Name == dep.Service.Name {
services = []config.Service{s} services = []config.Service{s}
return return
} }
} }
} else {
err = fmt.Errorf("Dep is neither a VODeployment or a ServiceDeployment")
return
} }
if len(services) == 0 { err = fmt.Errorf("Dep has no service")
err = fmt.Errorf("Dep has no services")
}
return return
} }
...@@ -222,11 +198,20 @@ func (sink *Sink) depHandler() { ...@@ -222,11 +198,20 @@ func (sink *Sink) depHandler() {
} }
go func() { go func() {
sink.Replies <- deps.Reply{ reply := deps.Reply{
ID: dep.ID, ID: dep.ID,
Service: dep.Service, State: output.State,
Output: output, Msg: output.Msg,
UserCredentialStates: output.UserCredentialStates,
} }
if output.Questionnaire != nil && output.State == scripts.Questionnaire {
reply.Questionnaire = output.Questionnaire
}
if output.Credentials != nil && output.State == scripts.Deployed {
reply.Credentials = output.Credentials
}
sink.Replies <- reply
}() }()
} }
...@@ -240,18 +225,18 @@ func (sink *Sink) depHandler() { ...@@ -240,18 +225,18 @@ func (sink *Sink) depHandler() {
} }
} }
func (sink *Sink) sendReply(te deps.Reply) (err error) { func (sink *Sink) sendReply(reply deps.Reply) (err error) {
taskResponse, err := json.MarshalIndent(te, "", " ") taskResponse, err := json.MarshalIndent(reply, "", " ")
if err != nil { if err != nil {
return return
} }
if sink.Config.Debug.Scripts { if sink.Config.Debug.Backend {
log.Printf("Dep Response:\n%s", taskResponse) log.Printf("Dep Response:\n%s", taskResponse)
} }
url := fmt.Sprintf("https://%s/backend/clientapi/response", sink.Config.Hostname) url := fmt.Sprintf("https://%s/backend/clientapi/dep-state", sink.Config.Hostname)
req, err := http.NewRequest("POST", url, bytes.NewReader(taskResponse)) req, err := http.NewRequest("PATCH", url, bytes.NewReader(taskResponse))
if err != nil { if err != nil {
return return
} }
...@@ -259,32 +244,24 @@ func (sink *Sink) sendReply(te deps.Reply) (err error) { ...@@ -259,32 +244,24 @@ func (sink *Sink) sendReply(te deps.Reply) (err error) {
req.Header.Set("Content-Type", "application/json") req.Header.Set("Content-Type", "application/json")
// execute request // execute request
resp, err := http.DefaultClient.Do(req) resp, err := httpClient.Do(req)
if err != nil { if err != nil {
return return
} }
defer resp.Body.Close() defer resp.Body.Close()
if resp.StatusCode == 200 { body, readErr := ioutil.ReadAll(resp.Body)
if sink.Config.Debug.Scripts { if readErr != nil {
te.Log("Successful response") return readErr
} }
} else {
te.Log("Response Status: %v", resp.StatusCode)
var ( if resp.StatusCode != 200 {
ackResponseBytes []byte err = fmt.Errorf("sendReply failed: (status: %v, body: %s)", resp.StatusCode, body)
ackResponse ackResponse
)
ackResponseBytes, err = ioutil.ReadAll(resp.Body)
if err != nil {
return return
} }
err = json.Unmarshal(ackResponseBytes, &ackResponse)
if err != nil { if sink.Config.Debug.Backend {
return reply.Log("Successful response")
}
te.Log("Response to our response: '%s'", ackResponse.Error)
} }
return return
} }
......
...@@ -21,6 +21,10 @@ type ( ...@@ -21,6 +21,10 @@ type (
} }
) )
var (
httpClient = &http.Client{}
)
func (src Source) fetchDeps(sink chan<- deps.Dep) (err error) { func (src Source) fetchDeps(sink chan<- deps.Dep) (err error) {
if len(src.Config.EntitlementToServiceIDs) == 0 && len(src.Config.GroupToServiceIDs) == 0 { if len(src.Config.EntitlementToServiceIDs) == 0 && len(src.Config.GroupToServiceIDs) == 0 {
log.Printf("[HTTP] Not fetching because the are no services to fetch") log.Printf("[HTTP] Not fetching because the are no services to fetch")
...@@ -31,57 +35,55 @@ func (src Source) fetchDeps(sink chan<- deps.Dep) (err error) { ...@@ -31,57 +35,55 @@ func (src Source) fetchDeps(sink chan<- deps.Dep) (err error) {
} }
// construct a request // construct a request
uri := "https://" + src.Config.Hostname + "/backend/clientapi/deployments" uri := "https://" + src.Config.Hostname + "/backend/clientapi/dep-states"
req, err := http.NewRequest("GET", uri, nil) req, err := http.NewRequest("GET", uri, nil)
if err != nil { if err != nil {
err = fmt.Errorf("Request creation failed: %s", err)
return return
} }
// add query params for the services
q := req.URL.Query()
for groupName := range src.Config.GroupToServiceIDs {
q.Add("g", groupName)
}
for entitlementName := range src.Config.EntitlementToServiceIDs {
q.Add("e", entitlementName)
}
req.URL.RawQuery = q.Encode()
req.SetBasicAuth(src.Config.Username, src.Config.Password) req.SetBasicAuth(src.Config.Username, src.Config.Password)
// execute the request // execute the request
resp, err := (&http.Client{}).Do(req) resp, err := httpClient.Do(req)
if err != nil { if err != nil {
err = fmt.Errorf("Request failed: %s", err)
return return
} }
defer resp.Body.Close() defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
err = fmt.Errorf("Reading response body: %s", err)
return
}
if resp.StatusCode != 200 { if resp.StatusCode != 200 {
err = fmt.Errorf("Unable to fetch tasks (response: %v)", resp.Status) err = fmt.Errorf("Unable to fetch tasks (response: %v, body: %s)", resp.Status, body)
return return
} }
var newDeps []deps.Dep var newDeps []deps.Dep
body, err := ioutil.ReadAll(resp.Body)
err = json.Unmarshal(body, &newDeps) err = json.Unmarshal(body, &newDeps)
if err != nil { if err != nil {
err = fmt.Errorf("Error unmarshaling: %s\n%s", err, body) err = fmt.Errorf("Unmarshaling response body: %s\n%s", err, body)
return return
} }
if src.Config.Debug.Backend { if src.Config.Debug.Backend {
indented := new(bytes.Buffer) indented := new(bytes.Buffer)
if err := json.Indent(indented, body, "", " "); err != nil { indentErr := json.Indent(indented, body, "", " ")
if indentErr != nil {
log.Printf("[HTTP] Fetched (unable to indent: %v):\n%s", err, body) log.Printf("[HTTP] Fetched (unable to indent: %v):\n%s", err, body)
} else { } else {
log.Printf("[HTTP] Fetched:\n%s", indented) log.Printf("[HTTP] Fetched:\n%s", indented)
} }
} }
go func() {
for _, newDep := range newDeps { for _, newDep := range newDeps {
sink <- newDep sink <- newDep
} }
}()
return return
} }
...@@ -91,9 +93,9 @@ func (src *Source) Init(conf *config.Config) { ...@@ -91,9 +93,9 @@ func (src *Source) Init(conf *config.Config) {
} }
// Connect starts fetching using the rest interface. // Connect starts fetching using the rest interface.
func (src *Source) Connect() (sink <-chan deps.Dep, err error) { func (src *Source) Connect() (<-chan deps.Dep, error) {
sinkPipe := make(chan deps.Dep) sinkPipe := make(chan deps.Dep)
sink = sinkPipe sink := sinkPipe
// start ticker // start ticker
src.Ticker = time.NewTicker(src.Config.FetchInterval) src.Ticker = time.NewTicker(src.Config.FetchInterval)
...@@ -110,7 +112,7 @@ func (src *Source) Connect() (sink <-chan deps.Dep, err error) { ...@@ -110,7 +112,7 @@ func (src *Source) Connect() (sink <-chan deps.Dep, err error) {
} }
err = src.fetchDeps(sinkPipe) err = src.fetchDeps(sinkPipe)
if err != nil { if err != nil {
log.Printf("[HTTP] error fetching: %s", err) log.Printf("[HTTP] Error fetching: %s", err)
} }
} }
} }
...@@ -118,13 +120,14 @@ func (src *Source) Connect() (sink <-chan deps.Dep, err error) { ...@@ -118,13 +120,14 @@ func (src *Source) Connect() (sink <-chan deps.Dep, err error) {
// start one fetch right now // start one fetch right now
go func() { go func() {
if src.fetchDeps(sinkPipe) != nil { err := src.fetchDeps(sinkPipe)
log.Printf("[HTTP] error fetching: %s", err) if err != nil {
log.Printf("[HTTP] Error fetching: %s", err)
} }
}() }()
log.Printf("[HTTP] Source connected") log.Printf("[HTTP] Source connected")
return return sink, nil
} }
// Close stop the fetching // Close stop the fetching
......
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