This commit is contained in:
984
shuffle/backend/go-app/docker.go
Normal file
984
shuffle/backend/go-app/docker.go
Normal file
@ -0,0 +1,984 @@
|
||||
package main
|
||||
|
||||
// Docker
|
||||
import (
|
||||
"archive/tar"
|
||||
|
||||
"github.com/shuffle/shuffle-shared"
|
||||
|
||||
//"bufio"
|
||||
"path/filepath"
|
||||
//"strconv"
|
||||
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
//"github.com/docker/docker"
|
||||
"github.com/docker/docker/api/types"
|
||||
//"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/client"
|
||||
newdockerclient "github.com/fsouza/go-dockerclient"
|
||||
"github.com/go-git/go-billy/v5"
|
||||
|
||||
//network "github.com/docker/docker/api/types/network"
|
||||
//natting "github.com/docker/go-connections/nat"
|
||||
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
batchv1 "k8s.io/api/batch/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/rest"
|
||||
"time"
|
||||
// "k8s.io/client-go/tools/clientcmd"
|
||||
// "k8s.io/client-go/util/homedir"
|
||||
)
|
||||
|
||||
// Parses a directory with a Dockerfile into a tar for Docker images..
|
||||
func getParsedTar(tw *tar.Writer, baseDir, extra string) error {
|
||||
return filepath.Walk(baseDir, func(file string, fi os.FileInfo, err error) error {
|
||||
if file == baseDir {
|
||||
return nil
|
||||
}
|
||||
|
||||
//log.Printf("File: %s", file)
|
||||
//log.Printf("Fileinfo: %#v", fi)
|
||||
switch mode := fi.Mode(); {
|
||||
case mode.IsDir():
|
||||
// do directory recursion
|
||||
//log.Printf("DIR: %s", file)
|
||||
|
||||
// Append "src" as extra here
|
||||
filenamesplit := strings.Split(file, "/")
|
||||
filename := fmt.Sprintf("%s%s/", extra, filenamesplit[len(filenamesplit)-1])
|
||||
|
||||
tmpExtra := fmt.Sprintf(filename)
|
||||
//log.Printf("TmpExtra: %s", tmpExtra)
|
||||
err = getParsedTar(tw, file, tmpExtra)
|
||||
if err != nil {
|
||||
log.Printf("Directory parse issue: %s", err)
|
||||
return err
|
||||
}
|
||||
case mode.IsRegular():
|
||||
// do file stuff
|
||||
//log.Printf("FILE: %s", file)
|
||||
|
||||
fileReader, err := os.Open(file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Read the actual Dockerfile
|
||||
readFile, err := ioutil.ReadAll(fileReader)
|
||||
if err != nil {
|
||||
log.Printf("Not file: %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
filenamesplit := strings.Split(file, "/")
|
||||
filename := fmt.Sprintf("%s%s", extra, filenamesplit[len(filenamesplit)-1])
|
||||
//log.Printf("Filename: %s", filename)
|
||||
tarHeader := &tar.Header{
|
||||
Name: filename,
|
||||
Size: int64(len(readFile)),
|
||||
}
|
||||
|
||||
//Writes the header described for the TAR file
|
||||
err = tw.WriteHeader(tarHeader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Writes the dockerfile data to the TAR file
|
||||
_, err = tw.Write(readFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// Custom TAR builder in memory for Docker images
|
||||
func getParsedTarMemory(fs billy.Filesystem, tw *tar.Writer, baseDir, extra string) error {
|
||||
// This one has to use baseDir + Extra
|
||||
newBase := fmt.Sprintf("%s%s", baseDir, extra)
|
||||
dir, err := fs.ReadDir(newBase)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, file := range dir {
|
||||
// Folder?
|
||||
switch mode := file.Mode(); {
|
||||
case mode.IsDir():
|
||||
filename := file.Name()
|
||||
filenamesplit := strings.Split(filename, "/")
|
||||
|
||||
tmpExtra := fmt.Sprintf("%s%s/", extra, filenamesplit[len(filenamesplit)-1])
|
||||
//log.Printf("EXTRA: %s", tmpExtra)
|
||||
err = getParsedTarMemory(fs, tw, baseDir, tmpExtra)
|
||||
if err != nil {
|
||||
log.Printf("Directory parse issue: %s", err)
|
||||
return err
|
||||
}
|
||||
case mode.IsRegular():
|
||||
filenamesplit := strings.Split(file.Name(), "/")
|
||||
filename := fmt.Sprintf("%s%s", extra, filenamesplit[len(filenamesplit)-1])
|
||||
// Newbase
|
||||
path := fmt.Sprintf("%s%s", newBase, file.Name())
|
||||
|
||||
fileReader, err := fs.Open(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
//log.Printf("FILENAME: %s", filename)
|
||||
readFile, err := ioutil.ReadAll(fileReader)
|
||||
if err != nil {
|
||||
log.Printf("Not file: %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
// Fixes issues with older versions of Docker and reference formats
|
||||
// Specific to Shuffle rn. Could expand.
|
||||
// FIXME: Seems like the issue was with multi-stage builds
|
||||
/*
|
||||
if filename == "Dockerfile" {
|
||||
log.Printf("Should search and replace in readfile.")
|
||||
|
||||
referenceCheck := "FROM frikky/shuffle:"
|
||||
if strings.Contains(string(readFile), referenceCheck) {
|
||||
log.Printf("SHOULD SEARCH & REPLACE!")
|
||||
newReference := fmt.Sprintf("FROM registry.hub.docker.com/frikky/shuffle:")
|
||||
readFile = []byte(strings.Replace(string(readFile), referenceCheck, newReference, -1))
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
//log.Printf("Filename: %s", filename)
|
||||
// FIXME - might need the folder from EXTRA here
|
||||
// Name has to be e.g. just "requirements.txt"
|
||||
tarHeader := &tar.Header{
|
||||
Name: filename,
|
||||
Size: int64(len(readFile)),
|
||||
}
|
||||
|
||||
//Writes the header described for the TAR file
|
||||
err = tw.WriteHeader(tarHeader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Writes the dockerfile data to the TAR file
|
||||
_, err = tw.Write(readFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
/*
|
||||
// Fixes App SDK issues.. meh
|
||||
func fixTags(tags []string) []string {
|
||||
checkTag := "frikky/shuffle"
|
||||
newTags := []string{}
|
||||
for _, tag := range tags {
|
||||
if strings.HasPrefix(tag, checkTags) {
|
||||
newTags.append(newTags, fmt.Sprintf("registry.hub.docker.com/%s", tag))
|
||||
}
|
||||
|
||||
newTags.append(tag)
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
// Custom Docker image builder wrapper in memory
|
||||
func buildImageMemory(fs billy.Filesystem, tags []string, dockerfileFolder string, downloadIfFail bool) error {
|
||||
ctx := context.Background()
|
||||
client, err := client.NewEnvClient()
|
||||
if err != nil {
|
||||
log.Printf("Unable to create docker client: %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
tw := tar.NewWriter(buf)
|
||||
defer tw.Close()
|
||||
|
||||
log.Printf("[INFO] Setting up memory build structure for folder: %s", dockerfileFolder)
|
||||
err = getParsedTarMemory(fs, tw, dockerfileFolder, "")
|
||||
if err != nil {
|
||||
log.Printf("Tar issue: %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
dockerFileTarReader := bytes.NewReader(buf.Bytes())
|
||||
|
||||
// Dockerfile is inside the TAR itself. Not local context
|
||||
// docker build --build-arg http_proxy=http://my.proxy.url
|
||||
// Attempt at setting name according to #359: https://github.com/frikky/Shuffle/issues/359
|
||||
labels := map[string]string{}
|
||||
//target := ""
|
||||
//if len(tags) > 0 {
|
||||
// if strings.Contains(tags[0], ":") {
|
||||
// version := strings.Split(tags[0], ":")
|
||||
// if len(version) == 2 {
|
||||
// target = fmt.Sprintf("shuffle-build-%s", version[1])
|
||||
// tags = append(tags, target)
|
||||
// labels["name"] = target
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
|
||||
buildOptions := types.ImageBuildOptions{
|
||||
Remove: true,
|
||||
Tags: tags,
|
||||
BuildArgs: map[string]*string{},
|
||||
Labels: labels,
|
||||
}
|
||||
// NetworkMode: "host",
|
||||
|
||||
httpProxy := os.Getenv("HTTP_PROXY")
|
||||
if len(httpProxy) > 0 {
|
||||
buildOptions.BuildArgs["HTTP_PROXY"] = &httpProxy
|
||||
}
|
||||
httpsProxy := os.Getenv("HTTPS_PROXY")
|
||||
if len(httpProxy) > 0 {
|
||||
buildOptions.BuildArgs["HTTPS_PROXY"] = &httpsProxy
|
||||
}
|
||||
|
||||
// Build the actual image
|
||||
log.Printf(`[INFO] Building %s with proxy "%s". Tags: "%s". This may take up to a few minutes.`, dockerfileFolder, httpsProxy, strings.Join(tags, ","))
|
||||
imageBuildResponse, err := client.ImageBuild(
|
||||
ctx,
|
||||
dockerFileTarReader,
|
||||
buildOptions,
|
||||
)
|
||||
|
||||
//log.Printf("RESPONSE: %#v", imageBuildResponse)
|
||||
//log.Printf("Response: %#v", imageBuildResponse.Body)
|
||||
//log.Printf("[DEBUG] IMAGERESPONSE: %#v", imageBuildResponse.Body)
|
||||
|
||||
if imageBuildResponse.Body != nil {
|
||||
defer imageBuildResponse.Body.Close()
|
||||
buildBuf := new(strings.Builder)
|
||||
_, newerr := io.Copy(buildBuf, imageBuildResponse.Body)
|
||||
if newerr != nil {
|
||||
log.Printf("[WARNING] Failed reading Docker build STDOUT: %s", newerr)
|
||||
} else {
|
||||
log.Printf("[INFO] STRING: %s", buildBuf.String())
|
||||
if strings.Contains(buildBuf.String(), "errorDetail") {
|
||||
log.Printf("[ERROR] Docker build:\n%s\nERROR ABOVE: Trying to pull tags from: %s", buildBuf.String(), strings.Join(tags, "\n"))
|
||||
|
||||
// Handles pulling of the same image if applicable
|
||||
// This fixes some issues with older versions of Docker which can't build
|
||||
// on their own ( <17.05 )
|
||||
pullOptions := types.ImagePullOptions{}
|
||||
downloaded := false
|
||||
for _, image := range tags {
|
||||
// Is this ok? Not sure. Tags shouldn't be controlled here prolly.
|
||||
image = strings.ToLower(image)
|
||||
|
||||
newImage := fmt.Sprintf("%s/%s", registryName, image)
|
||||
log.Printf("[INFO] Pulling image %s", newImage)
|
||||
reader, err := client.ImagePull(ctx, newImage, pullOptions)
|
||||
if err != nil {
|
||||
log.Printf("[ERROR] Failed getting image %s: %s", newImage, err)
|
||||
continue
|
||||
}
|
||||
|
||||
// Attempt to retag the image to not contain registry...
|
||||
|
||||
//newBuf := buildBuf
|
||||
downloaded = true
|
||||
io.Copy(os.Stdout, reader)
|
||||
log.Printf("[INFO] Successfully downloaded and built %s", newImage)
|
||||
}
|
||||
|
||||
if !downloaded {
|
||||
|
||||
return errors.New(fmt.Sprintf("Failed to build / download images %s", strings.Join(tags, ",")))
|
||||
}
|
||||
//baseDockerName
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
// Read the STDOUT from the build process
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getK8sClient() (*kubernetes.Clientset, error) {
|
||||
config, err := rest.InClusterConfig()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("[ERROR] failed to get in-cluster config: %v", err)
|
||||
}
|
||||
|
||||
clientset, err := kubernetes.NewForConfig(config)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("[ERROR] failed to create Kubernetes client: %v", err)
|
||||
}
|
||||
|
||||
return clientset, nil
|
||||
}
|
||||
|
||||
func deleteJob(client *kubernetes.Clientset, jobName, namespace string) error {
|
||||
deletePolicy := metav1.DeletePropagationForeground
|
||||
return client.BatchV1().Jobs(namespace).Delete(context.TODO(), jobName, metav1.DeleteOptions{
|
||||
PropagationPolicy: &deletePolicy,
|
||||
})
|
||||
}
|
||||
|
||||
func buildImage(tags []string, dockerfileFolder string) error {
|
||||
|
||||
isKubernetes := false
|
||||
if os.Getenv("IS_KUBERNETES") == "true" {
|
||||
isKubernetes = true
|
||||
}
|
||||
|
||||
if isKubernetes {
|
||||
// log.Printf("K8S ###################")
|
||||
// log.Print("dockerfileFolder: ", dockerfileFolder)
|
||||
// log.Print("tags: ", tags)
|
||||
// log.Print("only tag: ", tags[1])
|
||||
|
||||
registryName := ""
|
||||
if len(os.Getenv("REGISTRY_URL")) > 0 {
|
||||
registryName = os.Getenv("REGISTRY_URL")
|
||||
}
|
||||
|
||||
log.Printf("[INFO] registry name: %s", registryName)
|
||||
|
||||
contextDir := strings.Replace(dockerfileFolder, "Dockerfile", "", -1)
|
||||
contextDir = "/app/" + contextDir
|
||||
log.Print("contextDir: ", contextDir)
|
||||
dockerFile := "./Dockerfile"
|
||||
|
||||
client, err := getK8sClient()
|
||||
if err != nil {
|
||||
fmt.Printf("Unable to authencticate : %v\n", err)
|
||||
return err
|
||||
}
|
||||
|
||||
BackendPodLabel := "io.kompose.service=backend"
|
||||
|
||||
backendPodList, podListErr := client.CoreV1().Pods("shuffle").List(context.TODO(), metav1.ListOptions{
|
||||
LabelSelector: BackendPodLabel,
|
||||
})
|
||||
|
||||
if podListErr != nil || len(backendPodList.Items) == 0 {
|
||||
fmt.Println("Error getting backend pod or no pod found:", podListErr)
|
||||
return podListErr
|
||||
}
|
||||
|
||||
backendNodeName := backendPodList.Items[0].Spec.NodeName
|
||||
log.Printf("[INFO] Backend running on: %s", backendNodeName)
|
||||
|
||||
job := &batchv1.Job{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "shuffle-app-builder",
|
||||
},
|
||||
Spec: batchv1.JobSpec{
|
||||
Template: corev1.PodTemplateSpec{
|
||||
Spec: corev1.PodSpec{
|
||||
Containers: []corev1.Container{
|
||||
{
|
||||
Name: "kaniko",
|
||||
Image: "gcr.io/kaniko-project/executor:latest",
|
||||
Args: []string{
|
||||
"--verbosity=debug",
|
||||
"--dockerfile=" + dockerFile,
|
||||
"--context=dir://" + contextDir,
|
||||
"--skip-tls-verify",
|
||||
"--destination=" + registryName + "/" + tags[1],
|
||||
},
|
||||
VolumeMounts: []corev1.VolumeMount{
|
||||
{
|
||||
Name: "kaniko-workspace",
|
||||
MountPath: "/app/generated",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
NodeSelector: map[string]string{
|
||||
"node": backendNodeName,
|
||||
},
|
||||
RestartPolicy: corev1.RestartPolicyNever,
|
||||
Volumes: []corev1.Volume{
|
||||
{
|
||||
Name: "kaniko-workspace",
|
||||
VolumeSource: corev1.VolumeSource{
|
||||
PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{
|
||||
ClaimName: "backend-apps-claim",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
createdJob, err := client.BatchV1().Jobs("shuffle").Create(context.TODO(), job, metav1.CreateOptions{})
|
||||
if err != nil {
|
||||
log.Printf("Failed to start image builder job: %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
timeout := time.After(5 * time.Minute)
|
||||
tick := time.Tick(5 * time.Second)
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-timeout:
|
||||
return fmt.Errorf("job didn't complete within the expected time")
|
||||
case <-tick:
|
||||
currentJob, err := client.BatchV1().Jobs("shuffle").Get(context.TODO(), createdJob.Name, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("[ERROR] failed to fetch %s status: %v", createdJob.Name, err)
|
||||
}
|
||||
|
||||
if currentJob.Status.Succeeded > 0 {
|
||||
log.Printf("[INFO] Job %s completed successfully!", createdJob.Name)
|
||||
log.Printf("[INFO] Cleaning up the job %s", createdJob.Name)
|
||||
err := deleteJob(client, createdJob.Name, "shuffle")
|
||||
if err != nil {
|
||||
return fmt.Errorf("[ERROR] failed deleting job %s with error: %s", createdJob.Name, err)
|
||||
}
|
||||
log.Println("Job deleted successfully!")
|
||||
return nil
|
||||
} else if currentJob.Status.Failed > 0 {
|
||||
log.Printf("[ERROR] %s job failed with error: %s", createdJob.Name, err)
|
||||
err := deleteJob(client, createdJob.Name, "shuffle")
|
||||
if err != nil {
|
||||
return fmt.Errorf("[ERROR] failed deleting job %s with error: %s", createdJob.Name, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
ctx := context.Background()
|
||||
client, err := client.NewEnvClient()
|
||||
if err != nil {
|
||||
log.Printf("Unable to create docker client: %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
log.Printf("[INFO] Docker Tags: %s", tags)
|
||||
dockerfileSplit := strings.Split(dockerfileFolder, "/")
|
||||
|
||||
// Create a buffer
|
||||
buf := new(bytes.Buffer)
|
||||
tw := tar.NewWriter(buf)
|
||||
defer tw.Close()
|
||||
baseDir := strings.Join(dockerfileSplit[0:len(dockerfileSplit)-1], "/")
|
||||
|
||||
// Builds the entire folder into buf
|
||||
err = getParsedTar(tw, baseDir, "")
|
||||
if err != nil {
|
||||
log.Printf("Tar issue: %s", err)
|
||||
}
|
||||
|
||||
dockerFileTarReader := bytes.NewReader(buf.Bytes())
|
||||
buildOptions := types.ImageBuildOptions{
|
||||
Remove: true,
|
||||
Tags: tags,
|
||||
BuildArgs: map[string]*string{},
|
||||
}
|
||||
//NetworkMode: "host",
|
||||
|
||||
httpProxy := os.Getenv("HTTP_PROXY")
|
||||
if len(httpProxy) > 0 {
|
||||
buildOptions.BuildArgs["HTTP_PROXY"] = &httpProxy
|
||||
}
|
||||
httpsProxy := os.Getenv("HTTPS_PROXY")
|
||||
if len(httpProxy) > 0 {
|
||||
buildOptions.BuildArgs["https_proxy"] = &httpsProxy
|
||||
}
|
||||
|
||||
// Build the actual image
|
||||
imageBuildResponse, err := client.ImageBuild(
|
||||
ctx,
|
||||
dockerFileTarReader,
|
||||
buildOptions,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Read the STDOUT from the build process
|
||||
defer imageBuildResponse.Body.Close()
|
||||
buildBuf := new(strings.Builder)
|
||||
_, err = io.Copy(buildBuf, imageBuildResponse.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
} else {
|
||||
if strings.Contains(buildBuf.String(), "errorDetail") {
|
||||
log.Printf("[ERROR] Docker build:\n%s\nERROR ABOVE: Trying to pull tags from: %s", buildBuf.String(), strings.Join(tags, "\n"))
|
||||
return errors.New(fmt.Sprintf("Failed building %s. Check backend logs for details. Most likely means you have an old version of Docker.", strings.Join(tags, ",")))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Checks if an image exists
|
||||
func imageCheckBuilder(images []string) error {
|
||||
//log.Printf("[FIXME] ImageNames to check: %#v", images)
|
||||
return nil
|
||||
|
||||
ctx := context.Background()
|
||||
client, err := client.NewEnvClient()
|
||||
if err != nil {
|
||||
log.Printf("Unable to create docker client: %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
allImages, err := client.ImageList(ctx, types.ImageListOptions{
|
||||
All: true,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
log.Printf("[ERROR] Failed creating imagelist: %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
filteredImages := []types.ImageSummary{}
|
||||
for _, image := range allImages {
|
||||
found := false
|
||||
for _, repoTag := range image.RepoTags {
|
||||
if strings.Contains(repoTag, baseDockerName) {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if found {
|
||||
filteredImages = append(filteredImages, image)
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: Continue fixing apps here
|
||||
// https://github.com/frikky/Shuffle/issues/135
|
||||
// 1. Find if app exists
|
||||
// 2. Create app if it doesn't
|
||||
//log.Printf("Apps: %#v", filteredImages)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// https://stackoverflow.com/questions/23935141/how-to-copy-docker-images-from-one-host-to-another-without-using-a-repository
|
||||
func getDockerImage(resp http.ResponseWriter, request *http.Request) {
|
||||
cors := shuffle.HandleCors(resp, request)
|
||||
if cors {
|
||||
return
|
||||
}
|
||||
|
||||
// Just here to verify that the user is logged in
|
||||
//_, err := shuffle.HandleApiAuthentication(resp, request)
|
||||
//if err != nil {
|
||||
// log.Printf("[WARNING] Api authentication failed in DOWNLOAD IMAGE: %s", err)
|
||||
// resp.WriteHeader(401)
|
||||
// resp.Write([]byte(`{"success": false}`))
|
||||
// return
|
||||
//}
|
||||
|
||||
body, err := ioutil.ReadAll(request.Body)
|
||||
if err != nil {
|
||||
resp.WriteHeader(401)
|
||||
resp.Write([]byte(`{"success": false, "reason": "Failed reading body"}`))
|
||||
return
|
||||
}
|
||||
|
||||
// This has to be done in a weird way because Datastore doesn't
|
||||
// support map[string]interface and similar (openapi3.Swagger)
|
||||
var version shuffle.DockerRequestCheck
|
||||
err = json.Unmarshal(body, &version)
|
||||
if err != nil {
|
||||
resp.WriteHeader(422)
|
||||
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Failed JSON marshalling: %s"}`, err)))
|
||||
return
|
||||
}
|
||||
|
||||
//log.Printf("[DEBUG] Image to load: %s", version.Name)
|
||||
dockercli, err := client.NewEnvClient()
|
||||
if err != nil {
|
||||
log.Printf("[WARNING] Unable to create docker client: %s", err)
|
||||
resp.WriteHeader(422)
|
||||
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Failed JSON marshalling: %s"}`, err)))
|
||||
return
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
images, err := dockercli.ImageList(ctx, types.ImageListOptions{
|
||||
All: true,
|
||||
})
|
||||
|
||||
img := types.ImageSummary{}
|
||||
tagFound := ""
|
||||
|
||||
img2 := types.ImageSummary{}
|
||||
tagFound2 := ""
|
||||
|
||||
alternativeNameSplit := strings.Split(version.Name, "/")
|
||||
alternativeName := version.Name
|
||||
if len(alternativeNameSplit) == 3 {
|
||||
alternativeName = strings.Join(alternativeNameSplit[1:3], "/")
|
||||
}
|
||||
|
||||
log.Printf("[INFO] Trying to download image: %s. Alt: %s", version.Name, alternativeName)
|
||||
|
||||
for _, image := range images {
|
||||
for _, tag := range image.RepoTags {
|
||||
//log.Printf("[DEBUG] Tag: %s", tag)
|
||||
if strings.ToLower(tag) == strings.ToLower(version.Name) {
|
||||
img = image
|
||||
tagFound = tag
|
||||
break
|
||||
}
|
||||
|
||||
if strings.ToLower(tag) == strings.ToLower(alternativeName) {
|
||||
img2 = image
|
||||
tagFound2 = tag
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pullOptions := types.ImagePullOptions{}
|
||||
if len(img.ID) == 0 {
|
||||
_, err := dockercli.ImagePull(context.Background(), version.Name, pullOptions)
|
||||
if err == nil {
|
||||
tagFound = version.Name
|
||||
img.ID = version.Name
|
||||
img2.ID = version.Name
|
||||
|
||||
dockercli.ImageTag(ctx, version.Name, alternativeName)
|
||||
}
|
||||
}
|
||||
|
||||
if len(img2.ID) == 0 {
|
||||
_, err := dockercli.ImagePull(context.Background(), alternativeName, pullOptions)
|
||||
if err == nil {
|
||||
tagFound = alternativeName
|
||||
img.ID = alternativeName
|
||||
img2.ID = alternativeName
|
||||
|
||||
dockercli.ImageTag(ctx, alternativeName, version.Name)
|
||||
}
|
||||
}
|
||||
|
||||
// REBUILDS THE APP
|
||||
if len(img.ID) == 0 {
|
||||
if len(img2.ID) == 0 {
|
||||
workflowapps, err := shuffle.GetAllWorkflowApps(ctx, 0, 0)
|
||||
log.Printf("[INFO] Getting workflowapps for a rebuild. Got %d with err %#v", len(workflowapps), err)
|
||||
if err == nil {
|
||||
imageName := ""
|
||||
imageVersion := ""
|
||||
newNameSplit := strings.Split(version.Name, ":")
|
||||
if len(newNameSplit) == 2 {
|
||||
//log.Printf("[DEBUG] Found name %#v", newNameSplit)
|
||||
|
||||
findVersionSplit := strings.Split(newNameSplit[1], "_")
|
||||
//log.Printf("[DEBUG] Found another split %#v", findVersionSplit)
|
||||
if len(findVersionSplit) == 2 {
|
||||
imageVersion = findVersionSplit[len(findVersionSplit)-1]
|
||||
imageName = findVersionSplit[0]
|
||||
} else if len(findVersionSplit) >= 2 {
|
||||
imageVersion = findVersionSplit[len(findVersionSplit)-1]
|
||||
imageName = strings.Join(findVersionSplit[0:len(findVersionSplit)-1], "_")
|
||||
} else {
|
||||
log.Printf("[DEBUG] Couldn't parse appname & version for %#v", findVersionSplit)
|
||||
}
|
||||
}
|
||||
|
||||
if len(imageName) > 0 && len(imageVersion) > 0 {
|
||||
foundApp := shuffle.WorkflowApp{}
|
||||
imageName = strings.ToLower(imageName)
|
||||
imageVersion = strings.ToLower(imageVersion)
|
||||
log.Printf("[DEBUG] Docker Looking for appname %s with version %s", imageName, imageVersion)
|
||||
|
||||
for _, app := range workflowapps {
|
||||
if strings.ToLower(strings.Replace(app.Name, " ", "_", -1)) == imageName && app.AppVersion == imageVersion {
|
||||
if app.Generated {
|
||||
log.Printf("[DEBUG] Found matching app %s:%s - %s", imageName, imageVersion, app.ID)
|
||||
foundApp = app
|
||||
break
|
||||
} else {
|
||||
log.Printf("[WARNING] Trying to rebuild app that isn't generated - not allowed. Looking further.")
|
||||
}
|
||||
|
||||
//break
|
||||
}
|
||||
}
|
||||
|
||||
if len(foundApp.ID) > 0 {
|
||||
openApiApp, err := shuffle.GetOpenApiDatastore(ctx, foundApp.ID)
|
||||
if err != nil {
|
||||
log.Printf("[ERROR] Failed getting OpenAPI app %s to database: %s", foundApp.ID, err)
|
||||
} else {
|
||||
log.Printf("[DEBUG] Found OpenAPI app for %s as generated - now building!", version.Name)
|
||||
user := shuffle.User{}
|
||||
|
||||
//img = version.Name
|
||||
if len(alternativeName) > 0 {
|
||||
tagFound = alternativeName
|
||||
} else {
|
||||
tagFound = version.Name
|
||||
}
|
||||
|
||||
buildSwaggerApp(resp, []byte(openApiApp.Body), user, false)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log.Printf("[WARNING] Couldn't find an image with registry name %s and %s", version.Name, alternativeName)
|
||||
resp.WriteHeader(401)
|
||||
resp.Write([]byte(fmt.Sprintf(`{"success": false, "message": "Couldn't find image %s"}`, version.Name)))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if len(tagFound) == 0 && len(tagFound2) > 0 {
|
||||
img = img2
|
||||
tagFound = tagFound2
|
||||
}
|
||||
}
|
||||
|
||||
//log.Printf("[INFO] Img found (%s): %#v", tagFound, img)
|
||||
//log.Printf("[INFO] Img found to be downloaded by client: %s", tagFound)
|
||||
|
||||
newClient, err := newdockerclient.NewClientFromEnv()
|
||||
if err != nil {
|
||||
log.Printf("[ERROR] Failed setting up docker env: %#v", newClient)
|
||||
resp.WriteHeader(401)
|
||||
resp.Write([]byte(fmt.Sprintf(`{"success": false, "message": "Couldn't make docker client"}`)))
|
||||
return
|
||||
}
|
||||
|
||||
////https://github.com/fsouza/go-dockerclient/issues/600
|
||||
//defer fileReader.Close()
|
||||
opts := newdockerclient.ExportImageOptions{
|
||||
Name: tagFound,
|
||||
OutputStream: resp,
|
||||
}
|
||||
|
||||
if err := newClient.ExportImage(opts); err != nil {
|
||||
log.Printf("[ERROR] FAILED to save image to file: %s", err)
|
||||
resp.WriteHeader(401)
|
||||
resp.Write([]byte(fmt.Sprintf(`{"success": false, "message": "Couldn't export image"}`)))
|
||||
return
|
||||
}
|
||||
|
||||
//resp.WriteHeader(200)
|
||||
}
|
||||
|
||||
// Downloads and activates an app from shuffler.io if possible
|
||||
func handleRemoteDownloadApp(resp http.ResponseWriter, ctx context.Context, user shuffle.User, appId string) {
|
||||
url := fmt.Sprintf("https://shuffler.io/api/v1/apps/%s/config", appId)
|
||||
log.Printf("Downloading API from %s", url)
|
||||
req, err := http.NewRequest(
|
||||
"GET",
|
||||
url,
|
||||
nil,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
log.Printf("[ERROR] Failed auto-downloading app %s: %s", appId, err)
|
||||
resp.WriteHeader(401)
|
||||
resp.Write([]byte(`{"success": false, "reason": "App doesn't exist"}`))
|
||||
return
|
||||
}
|
||||
|
||||
httpClient := &http.Client{}
|
||||
newresp, err := httpClient.Do(req)
|
||||
if err != nil {
|
||||
log.Printf("[ERROR] Failed running auto-download request for %s: %s", appId, err)
|
||||
resp.WriteHeader(401)
|
||||
resp.Write([]byte(`{"success": false, "reason": "App doesn't exist"}`))
|
||||
return
|
||||
}
|
||||
|
||||
respBody, err := ioutil.ReadAll(newresp.Body)
|
||||
if err != nil {
|
||||
log.Printf("[ERROR] Failed setting respbody for workflow download: %s", err)
|
||||
resp.WriteHeader(401)
|
||||
resp.Write([]byte(`{"success": false, "reason": "App doesn't exist"}`))
|
||||
return
|
||||
}
|
||||
|
||||
if len(respBody) > 0 {
|
||||
type tmpapp struct {
|
||||
Success bool `json:"success"`
|
||||
OpenAPI string `json:"openapi"`
|
||||
}
|
||||
|
||||
app := tmpapp{}
|
||||
err := json.Unmarshal(respBody, &app)
|
||||
if err != nil || app.Success == false || len(app.OpenAPI) == 0 {
|
||||
log.Printf("[ERROR] Failed app unmarshal during auto-download. Success: %#v. Applength: %d: %s", app.Success, len(app.OpenAPI), err)
|
||||
resp.WriteHeader(401)
|
||||
resp.Write([]byte(`{"success": false, "reason": "App doesn't exist"}`))
|
||||
return
|
||||
}
|
||||
|
||||
key, err := base64.StdEncoding.DecodeString(app.OpenAPI)
|
||||
if err != nil {
|
||||
log.Printf("[ERROR] Failed auto-setting OpenAPI app: %s", err)
|
||||
resp.WriteHeader(401)
|
||||
resp.Write([]byte(`{"success": false, "reason": "App doesn't exist"}`))
|
||||
return
|
||||
}
|
||||
|
||||
cacheKey := fmt.Sprintf("workflowapps-sorted-100")
|
||||
shuffle.DeleteCache(ctx, cacheKey)
|
||||
cacheKey = fmt.Sprintf("workflowapps-sorted-500")
|
||||
shuffle.DeleteCache(ctx, cacheKey)
|
||||
cacheKey = fmt.Sprintf("workflowapps-sorted-1000")
|
||||
shuffle.DeleteCache(ctx, cacheKey)
|
||||
|
||||
newapp := shuffle.ParsedOpenApi{}
|
||||
err = json.Unmarshal(key, &newapp)
|
||||
if err != nil {
|
||||
log.Printf("[ERROR] Failed openapi unmarshal during auto-download: %s", app.Success, len(app.OpenAPI), err)
|
||||
resp.WriteHeader(401)
|
||||
resp.Write([]byte(`{"success": false, "reason": "App doesn't exist"}`))
|
||||
return
|
||||
}
|
||||
|
||||
err = json.Unmarshal(key, &newapp)
|
||||
if err != nil {
|
||||
log.Printf("[ERROR] Failed openapi unmarshal during auto-download: %s", app.Success, len(app.OpenAPI), err)
|
||||
resp.WriteHeader(401)
|
||||
resp.Write([]byte(`{"success": false, "reason": "App doesn't exist"}`))
|
||||
return
|
||||
}
|
||||
|
||||
buildSwaggerApp(resp, []byte(newapp.Body), user, true)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func activateWorkflowAppDocker(resp http.ResponseWriter, request *http.Request) {
|
||||
cors := shuffle.HandleCors(resp, request)
|
||||
if cors {
|
||||
return
|
||||
}
|
||||
|
||||
user, err := shuffle.HandleApiAuthentication(resp, request)
|
||||
if err != nil {
|
||||
log.Printf("[WARNING] Api authentication failed in get active apps: %s", err)
|
||||
resp.WriteHeader(401)
|
||||
resp.Write([]byte(`{"success": false}`))
|
||||
return
|
||||
}
|
||||
|
||||
if user.Role == "org-reader" {
|
||||
log.Printf("[WARNING] Org-reader doesn't have access to activate workflow app (shared): %s (%s)", user.Username, user.Id)
|
||||
resp.WriteHeader(401)
|
||||
resp.Write([]byte(`{"success": false, "reason": "Read only user"}`))
|
||||
return
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
location := strings.Split(request.URL.String(), "/")
|
||||
var fileId string
|
||||
if location[1] == "api" {
|
||||
if len(location) <= 4 {
|
||||
resp.WriteHeader(401)
|
||||
resp.Write([]byte(`{"success": false}`))
|
||||
return
|
||||
}
|
||||
|
||||
fileId = location[4]
|
||||
}
|
||||
|
||||
app, err := shuffle.GetApp(ctx, fileId, user, false)
|
||||
if err != nil {
|
||||
appName := request.URL.Query().Get("app_name")
|
||||
appVersion := request.URL.Query().Get("app_version")
|
||||
|
||||
if len(appName) > 0 && len(appVersion) > 0 {
|
||||
apps, err := shuffle.FindWorkflowAppByName(ctx, appName)
|
||||
//log.Printf("[INFO] Found %d apps for %s", len(apps), appName)
|
||||
if err != nil || len(apps) == 0 {
|
||||
log.Printf("[WARNING] Error getting app %s (app config). Starting remote download.: %s", appName, err)
|
||||
|
||||
handleRemoteDownloadApp(resp, ctx, user, fileId)
|
||||
return
|
||||
}
|
||||
|
||||
selectedApp := shuffle.WorkflowApp{}
|
||||
for _, app := range apps {
|
||||
if !app.Sharing && !app.Public {
|
||||
continue
|
||||
}
|
||||
|
||||
if app.Name == appName {
|
||||
selectedApp = app
|
||||
}
|
||||
|
||||
if app.Name == appName && app.AppVersion == appVersion {
|
||||
selectedApp = app
|
||||
}
|
||||
}
|
||||
|
||||
app = &selectedApp
|
||||
} else {
|
||||
log.Printf("[WARNING] Error getting app with ID %s (app config): %s. Starting remote download(2)", fileId, err)
|
||||
handleRemoteDownloadApp(resp, ctx, user, fileId)
|
||||
return
|
||||
//resp.WriteHeader(401)
|
||||
//resp.Write([]byte(`{"success": false, "reason": "App doesn't exist"}`))
|
||||
//return
|
||||
}
|
||||
}
|
||||
|
||||
// Just making sure it's being built properly
|
||||
if app == nil {
|
||||
log.Printf("[WARNING] App is nil. This shouldn't happen. Starting remote download(3)")
|
||||
handleRemoteDownloadApp(resp, ctx, user, fileId)
|
||||
return
|
||||
}
|
||||
|
||||
// Check the app.. hmm
|
||||
openApiApp, err := shuffle.GetOpenApiDatastore(ctx, app.ID)
|
||||
if err != nil {
|
||||
log.Printf("[WARNING] Error getting app %s (openapi config): %s", app.ID, err)
|
||||
resp.WriteHeader(401)
|
||||
resp.Write([]byte(`{"success": false, "reason": "Couldn't find app OpenAPI"}`))
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("[INFO] User %s (%s) is activating %s. Public: %t, Shared: %t", user.Username, user.Id, app.Name, app.Public, app.Sharing)
|
||||
buildSwaggerApp(resp, []byte(openApiApp.Body), user, true)
|
||||
|
||||
//app.Active = true
|
||||
//app.Generated = true
|
||||
//app, err := shuffle.SetApp(ctx, app)
|
||||
|
||||
//resp.WriteHeader(200)
|
||||
//resp.Write([]byte(`{"success": true}`))
|
||||
}
|
Reference in New Issue
Block a user