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 (
ResourceType string `json:"resourcetype"`
}
// Dep describes a deployment action
// Dep now relates to a DeploymentState at the backend
Dep struct {
ID int `json:"id"`
StateTarget scripts.State `json:"state_target"`
User scripts.User `json:"user"`
Credentials map[string][]scripts.Credential `json:"credentials"`
Questionnaire map[string]string `json:"questionnaire"`
// ID is the db primary key of th DeploymentState instance at the backend.
ID int `json:"id"`
// this relates to the polymorphic type added by the polymorphic serializer
ResourceType string `json:"resourcetype"`
// The StateTarget of the DeploymentState parent Deployment.
StateTarget scripts.State `json:"state_target"`
// The user for which we are supposed to deploy access.
User scripts.User `json:"user"`
// VO is only set if this is a VODeployment
VO VO `json:"vo,omitempty"`
// The Service which this Dep is related to.
Service config.Service `json:"service"`
// Service may be filled by scheduleDep
Service config.Service `json:"service,omitempty"`
// Optional Answers by the users. Must have the same keys as the related Questionnaire
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 {
// ID of the according Dep (at the backend: DeploymentState)
ID int `json:"id"`
Output scripts.Output `json:"output"`
// ID is the db primary key of the DeploymentState instance at the backend.
ID int `json:"id"`
// Service is the service for which we performed the deploy action
Service config.Service `json:"service"`
// State of the deployment, after the script execution
// 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{}) {
}
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
......
......@@ -33,46 +33,22 @@ type (
}
)
var (
httpClient = &http.Client{}
)
func (sink *Sink) depServices(dep deps.Dep) (services []config.Service, err error) {
// Option 1: VODeployment
if dep.ResourceType == "VODeployment" && dep.VO != (deps.VO{}) {
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{}) {
// find the matching service
if dep.Service != (config.Service{}) {
for _, s := range sink.Config.Services {
if s.Name == dep.Service.Name {
services = []config.Service{s}
return
}
}
} else {
err = fmt.Errorf("Dep is neither a VODeployment or a ServiceDeployment")
return
}
if len(services) == 0 {
err = fmt.Errorf("Dep has no services")
}
err = fmt.Errorf("Dep has no service")
return
}
......@@ -222,11 +198,20 @@ func (sink *Sink) depHandler() {
}
go func() {
sink.Replies <- deps.Reply{
ID: dep.ID,
Service: dep.Service,
Output: output,
reply := deps.Reply{
ID: dep.ID,
State: output.State,
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() {
}
}
func (sink *Sink) sendReply(te deps.Reply) (err error) {
taskResponse, err := json.MarshalIndent(te, "", " ")
func (sink *Sink) sendReply(reply deps.Reply) (err error) {
taskResponse, err := json.MarshalIndent(reply, "", " ")
if err != nil {
return
}
if sink.Config.Debug.Scripts {
if sink.Config.Debug.Backend {
log.Printf("Dep Response:\n%s", taskResponse)
}
url := fmt.Sprintf("https://%s/backend/clientapi/response", sink.Config.Hostname)
req, err := http.NewRequest("POST", url, bytes.NewReader(taskResponse))
url := fmt.Sprintf("https://%s/backend/clientapi/dep-state", sink.Config.Hostname)
req, err := http.NewRequest("PATCH", url, bytes.NewReader(taskResponse))
if err != nil {
return
}
......@@ -259,32 +244,24 @@ func (sink *Sink) sendReply(te deps.Reply) (err error) {
req.Header.Set("Content-Type", "application/json")
// execute request
resp, err := http.DefaultClient.Do(req)
resp, err := httpClient.Do(req)
if err != nil {
return
}
defer resp.Body.Close()
if resp.StatusCode == 200 {
if sink.Config.Debug.Scripts {
te.Log("Successful response")
}
} else {
te.Log("Response Status: %v", resp.StatusCode)
var (
ackResponseBytes []byte
ackResponse ackResponse
)
ackResponseBytes, err = ioutil.ReadAll(resp.Body)
if err != nil {
return
}
err = json.Unmarshal(ackResponseBytes, &ackResponse)
if err != nil {
return
}
te.Log("Response to our response: '%s'", ackResponse.Error)
body, readErr := ioutil.ReadAll(resp.Body)
if readErr != nil {
return readErr
}
if resp.StatusCode != 200 {
err = fmt.Errorf("sendReply failed: (status: %v, body: %s)", resp.StatusCode, body)
return
}
if sink.Config.Debug.Backend {
reply.Log("Successful response")
}
return
}
......
......@@ -21,6 +21,10 @@ type (
}
)
var (
httpClient = &http.Client{}
)
func (src Source) fetchDeps(sink chan<- deps.Dep) (err error) {
if len(src.Config.EntitlementToServiceIDs) == 0 && len(src.Config.GroupToServiceIDs) == 0 {
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) {
}
// 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)
if err != nil {
err = fmt.Errorf("Request creation failed: %s", err)
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)
// execute the request
resp, err := (&http.Client{}).Do(req)
resp, err := httpClient.Do(req)
if err != nil {
err = fmt.Errorf("Request failed: %s", err)
return
}
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 {
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
}
var newDeps []deps.Dep
body, err := ioutil.ReadAll(resp.Body)
err = json.Unmarshal(body, &newDeps)
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
}
if src.Config.Debug.Backend {
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)
} else {
log.Printf("[HTTP] Fetched:\n%s", indented)
}
}
for _, newDep := range newDeps {
sink <- newDep
}
go func() {
for _, newDep := range newDeps {
sink <- newDep
}
}()
return
}
......@@ -91,9 +93,9 @@ func (src *Source) Init(conf *config.Config) {
}
// 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)
sink = sinkPipe
sink := sinkPipe
// start ticker
src.Ticker = time.NewTicker(src.Config.FetchInterval)
......@@ -110,7 +112,7 @@ func (src *Source) Connect() (sink <-chan deps.Dep, err error) {
}
err = src.fetchDeps(sinkPipe)
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) {
// start one fetch right now
go func() {
if src.fetchDeps(sinkPipe) != nil {
log.Printf("[HTTP] error fetching: %s", err)
err := src.fetchDeps(sinkPipe)
if err != nil {
log.Printf("[HTTP] Error fetching: %s", err)
}
}()
log.Printf("[HTTP] Source connected")
return
return sink, nil
}
// 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