| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059 |
- 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/api/types/image"
- 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"
- "time"
- 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"
- // "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
- // Cross-platform path handling for tar entries
- filenamesplit := strings.Split(filepath.ToSlash(file), "/")
- filename := fmt.Sprintf("%s%s/", extra, filenamesplit[len(filenamesplit)-1])
- tmpExtra := fmt.Sprintf(filename)
- 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(filepath.ToSlash(file), "/")
- filename := fmt.Sprintf("%s%s", extra, filenamesplit[len(filenamesplit)-1])
- 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()
- client, _, err := shuffle.GetDockerClient()
- defer client.Close()
- 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 := image.PullOptions{}
- 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, dockerfileLocation 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 := filepath.Join("/app/", filepath.Dir(dockerfileLocation))
- log.Print("contextDir: ", contextDir)
- 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",
- "--log-format=text",
- "--dockerfile=Dockerfile",
- "--context=dir://" + contextDir,
- "--skip-tls-verify",
- "--destination=" + registryName + "/" + tags[1],
- },
- VolumeMounts: []corev1.VolumeMount{
- {
- Name: "kaniko-workspace",
- MountPath: "/app/generated",
- },
- {
- Name: "docker-config",
- MountPath: "/kaniko/.docker/",
- },
- },
- },
- },
- NodeName: backendNodeName,
- RestartPolicy: corev1.RestartPolicyNever,
- Volumes: []corev1.Volume{
- {
- Name: "kaniko-workspace",
- VolumeSource: corev1.VolumeSource{
- PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{
- ClaimName: "backend-apps-claim",
- },
- },
- },
- {
- Name: "docker-config",
- VolumeSource: corev1.VolumeSource{
- Secret: &corev1.SecretVolumeSource{
- SecretName: os.Getenv("SHUFFLE_REGISTRY_SECRET"),
- Items: []corev1.KeyToPath{
- {
- Key: ".dockerconfigjson",
- Path: "config.json",
- },
- },
- },
- },
- },
- },
- },
- },
- },
- }
- 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)
- }
- }
- }
- }
- return nil
- }
- ctx := context.Background()
- // client, err := client.NewEnvClient()
- client, _, err := shuffle.GetDockerClient()
- defer client.Close()
- if err != nil {
- log.Printf("Unable to create docker client: %s", err)
- return err
- }
- log.Printf("[INFO] Docker Tags: %s", tags)
- log.Printf("[DEBUG] Dockerfile location: %s", dockerfileLocation)
- // Convert to forward slashes for consistent handling across OS
- normalizedPath := filepath.ToSlash(dockerfileLocation)
- dockerfileSplit := strings.Split(normalizedPath, "/")
- // Create a buffer
- buf := new(bytes.Buffer)
- tw := tar.NewWriter(buf)
- defer tw.Close()
- // Use the directory part of the dockerfile path
- baseDir := strings.Join(dockerfileSplit[0:len(dockerfileSplit)-1], "/")
- // Builds the entire folder into buf using OS-specific path
- err = getParsedTar(tw, filepath.FromSlash(baseDir), "")
- if err != nil {
- log.Printf("[ERROR] Tar issue during app build: %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
- }
- // Print the actual file content from dockerFileTarReader
- /*
- data, err := ioutil.ReadAll(dockerFileTarReader)
- if err != nil {
- log.Printf("[ERROR] Failed reading Dockerfile TAR reader: %s", err)
- } else {
- log.Printf("[DEBUG] Dockerfile TAR reader content: %s", string(data))
- }
- */
- // 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()
- client, _, err := shuffle.GetDockerClient()
- if err != nil {
- log.Printf("Unable to create docker client: %s", err)
- return err
- }
- allImages, err := client.ImageList(ctx, image.ListOptions{
- All: true,
- })
- if err != nil {
- log.Printf("[ERROR] Failed creating imagelist: %s", err)
- return err
- }
- filteredImages := []image.Summary{}
- 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
- //}
- var err error
- body := []byte{}
- //log.Printf("IMAGE REQUEST BODY: %#v", request.Body)
- if request.Body == nil || request.Body == http.NoBody {
- // Check for the image query, otherwise we skip everything
- imageQuery := request.URL.Query().Get("image")
- if len(imageQuery) == 0 {
- resp.WriteHeader(400)
- resp.Write([]byte(`{"success": false, "reason": "No image query found"}`))
- return
- }
- body = []byte(fmt.Sprintf(`{"name": "%s"}`, imageQuery))
- } else {
- body, err = ioutil.ReadAll(request.Body)
- if err != nil {
- resp.WriteHeader(400)
- 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 := shuffle.GetDockerClient()
- 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
- }
- img := image.Summary{}
- img2 := image.Summary{}
- tagFound := ""
- tagFound2 := ""
- // Old way of doing it
- //alternativeNameSplit := strings.Split(version.Name, "/")
- //alternativeName := version.Name
- //if len(alternativeNameSplit) == 3 {
- // alternativeName = strings.Join(alternativeNameSplit[1:3], "/")
- //}
- appname, baseAppname, appnameSplit2, err := shuffle.GetAppNameSplit(version)
- if err != nil {
- log.Printf("[ERROR] Failed getting appname split: %s", err)
- resp.WriteHeader(500)
- resp.Write([]byte(fmt.Sprintf(`{"success": false, "message": "Couldn't get the right docker image name"}`)))
- return
- }
- if len(version.Name) == 0 {
- log.Printf("[ERROR] No image name provided for download: %s", version.Name)
- resp.WriteHeader(401)
- resp.Write([]byte(fmt.Sprintf(`{"success": false, "message": "No image name"}`)))
- return
- }
- log.Printf("[INFO] Trying to download image: '%s'. Appname: '%s'. BaseAppname: '%s', Split2: %s", version.Name, appname, baseAppname, appnameSplit2)
- alternativeName := appname
- ctx := context.Background()
- images, err := dockercli.ImageList(ctx, image.ListOptions{
- All: true,
- })
- for _, image := range images {
- for _, tag := range image.RepoTags {
- if strings.Contains(tag, "<none>") {
- continue
- }
- 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 := image.PullOptions{}
- 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("[DEBUG] Downloading API from URL %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 := shuffle.GetExternalClient(url)
- 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
- }
- defer newresp.Body.Close()
- 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 string `json:"app"`
- }
- 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)
- if len(app.App) > 0 {
- resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Not an OpenAPI app, but a Python app. Please download the app using the Remote Download system: https://shuffler.io/docs/apps#importing-remote-apps"}`)))
- } else {
- resp.Write([]byte(`{"success": false, "reason": "App doesn't exist"}`))
- }
- 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)
- }
|