blob: 5c1b6b2b3db53decc172033ee98cce320d9139bc [file]
package apiGatewayDeploy
import (
"encoding/json"
"fmt"
"github.com/30x/apidGatewayDeploy/github"
"io"
"net/http"
"net/url"
"os"
"encoding/base64"
"path"
"errors"
)
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"`
}
// event bundle
type bundle struct {
BundleID string `json:"bundleId"`
URI string `json:"uri"`
Scope string `json:"scope"`
Org string `json:"org"`
Env string `json:"env"`
}
// event deployment
type deployment struct {
DeploymentID string `json:"deploymentId"`
System bundle `json:"system"`
Bundles []bundle `json:"bundles"`
}
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"`
Error 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 bundle) 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, dep deployment) error {
log.Debugf("preparing deployment: %s", depID)
err := insertDeployment(depID, dep)
if err != nil {
log.Errorf("insert deployment failed: %v", err)
return err
}
deploymentPath := getDeploymentFilesPath(depID)
err = os.Mkdir(deploymentPath, 0700)
if err != nil {
log.Errorf("Deployment dir creation failed: %v", err)
return err
}
// download bundles and store them locally
errorsChan := make(chan error, len(dep.Bundles))
for i, bun := range dep.Bundles {
go func() {
err := prepareBundle(depID, bun)
errorsChan <- 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 dep.Bundles {
err := <-errorsChan
if err != nil {
updateDeploymentStatus(db, depID, DEPLOYMENT_STATE_ERR_APID, ERROR_CODE_TODO)
return err
}
}
return updateDeploymentStatus(db, depID, DEPLOYMENT_STATE_READY, 0)
}
func parseManifest(manifestString string) (dep deployment, err error) {
err = json.Unmarshal([]byte(manifestString), &dep)
if err != nil {
log.Errorf("JSON decoding Manifest failed Err: %v", err)
return
}
// validate manifest
if dep.System.URI == "" {
err = errors.New("system bundle 'uri' is required")
return
}
for _, bun := range dep.Bundles {
if bun.BundleID == "" {
err = errors.New("bundle 'bundleId' is required")
return
}
if bun.URI == "" {
err = errors.New("bundle 'uri' is required")
return
}
if bun.Scope == "" {
err = errors.New("bundle 'scope' is required")
return
}
}
return
}