blob: 311521797d9e29274ddea84086384c67dfd6787f [file] [log] [blame]
package apiGatewayDeploy
import (
"encoding/json"
"fmt"
"github.com/30x/apidGatewayDeploy/github"
"io"
"net/http"
"net/url"
"os"
"encoding/base64"
"path"
)
var (
bundlePath string
gitHubAccessToken string // todo: temporary - should come from Manifest
)
type systemBundle struct {
URI string `json:"uri"`
}
type dependantBundle struct {
URI string `json:"uri"`
Scope string `json:"scope"`
Org string `json:"org"`
Env string `json:"env"`
}
type bundleManifest struct {
SysBun systemBundle `json:"system"`
DepBun []dependantBundle `json:"bundles"`
}
type bundle struct {
BundleId string `json:"bundleId"`
URI string `json:"uri"`
AuthCode string `json:"authCode,omitempty"`
}
type deployment struct {
DeploymentId string `json:"deploymentId"`
Bundles []bundle `json:"bundles"`
System bundle `json:"system"`
}
type deploymentErrorDetail struct {
ErrorCode int `json:"errorCode"`
Reason string `json:"reason"`
BundleId string `json:"bundleId"`
}
type deploymentErrorResponse struct {
ErrorCode int `json:"errorCode"`
Reason string `json:"reason"`
ErrorDetails []deploymentErrorDetail `json:"bundleErrors"`
}
type deploymentResponse struct {
Status string `json:"status"`
GWbunRsp deploymentErrorResponse `json:"error"`
}
// retrieveBundle retrieves bundle data from a URI
func retrieveBundle(uriString string) (io.ReadCloser, error) {
uri, err := url.Parse(uriString)
if err != nil {
return nil, fmt.Errorf("DownloadFileUrl: Failed to parse urlStr: %s", uriString)
}
// todo: temporary - if not a github url, just open it or call GET on it
if uri.Host != "github.com" {
// assume it's a file if no scheme
if uri.Scheme == "" || uri.Scheme == "file" {
f, err := os.Open(uri.Path)
if err != nil {
return nil, err
}
return f, nil
}
// some random uri, try to GET it
res, err := http.Get(uriString)
if err != nil {
return nil, err
}
return res.Body, nil
}
// go get it from github using access token
return github.GetUrlData(uri, gitHubAccessToken)
}
// todo: retry on error?
// check if already exists and skip
func prepareBundle(depID string, bun dependantBundle) error {
bundleFile := getBundleFilePath(depID, bun.URI)
out, err := os.Create(bundleFile)
if err != nil {
log.Errorf("Unable to create bundle file %s, Err: %s", bundleFile, err)
return err
}
defer out.Close()
bundleData, err := retrieveBundle(bun.URI)
if err != nil {
log.Errorf("Unable to retrieve bundle %s, Err: %s", bun.URI, err)
return err
}
defer bundleData.Close()
_, err = io.Copy(out, bundleData)
if err != nil {
log.Errorf("Unable to write bundle %s, Err: %s", bundleFile, err)
return err
}
return nil
}
func getDeploymentFilesPath(depID string) string {
return bundlePath + "/" + depID
}
func getBundleFilePath(depID string, bundleURI string) string {
return path.Join(getDeploymentFilesPath(depID), base64.StdEncoding.EncodeToString([]byte(bundleURI)))
}
// returns first bundle download error
// all bundles will be attempted regardless of errors, in the future we could retry
func prepareDeployment(depID string, manifest bundleManifest) error {
deploymentPath := getDeploymentFilesPath(depID)
err := os.Mkdir(deploymentPath, 0700)
if err != nil {
log.Errorf("Deployment dir creation failed: %v", err)
return err
}
// todo: any reason to put all this in a single transaction?
err = insertDeployment(depID, manifest)
if err != nil {
log.Errorf("Prepare deployment failed: %v", err)
return err
}
// download bundles and store them locally
errors := make(chan error, len(manifest.DepBun))
for i, bun := range manifest.DepBun {
go func() {
err := prepareBundle(depID, bun)
errors <- err
if err != nil {
id := string(i)
err = updateBundleStatus(db, depID, id, DEPLOYMENT_STATE_ERR_APID, ERROR_CODE_TODO, err.Error())
if err != nil {
log.Errorf("Update bundle %s:%s status failed: %v", depID, id, err)
}
}
}()
}
// fail fast on first error, otherwise wait for completion
for range manifest.DepBun {
err := <- errors
if err != nil {
updateDeploymentStatus(db, depID, DEPLOYMENT_STATE_ERR_APID, ERROR_CODE_TODO)
return err
}
}
return updateDeploymentStatus(db, depID, DEPLOYMENT_STATE_READY, 0)
}
func serviceDeploymentQueue() {
log.Debug("Checking for new deployments")
depID, manifestString := getQueuedDeployment()
if depID == "" {
return
}
var manifest bundleManifest
err := json.Unmarshal([]byte(manifestString), &manifest)
if err != nil {
log.Errorf("JSON decoding Manifest failed Err: %v", err)
return
}
err = prepareDeployment(depID, manifest)
if err != nil {
log.Errorf("Prepare deployment failed: %v", depID)
return
}
err = dequeueDeployment(depID)
if err != nil {
log.Warnf("Dequeue deployment failed: %v", depID)
}
log.Debugf("Signaling new deployment ready: %s", depID)
incoming <- depID
}