Просмотр исходного кода

Merge branch 'master' of github.com:porter-dev/porter into nico/por-211-provide-render-style-options-upon

jnfrati 4 лет назад
Родитель
Сommit
e0278cd4c8

+ 7 - 2
api/server/handlers/gitinstallation/get_buildpack.go

@@ -2,6 +2,7 @@ package gitinstallation
 
 import (
 	"context"
+	"fmt"
 	"net/http"
 	"sync"
 
@@ -21,8 +22,6 @@ func initBuilderInfo() map[string]*buildpacks.BuilderInfo {
 		Name: "Paketo",
 		Builders: []string{
 			"paketobuildpacks/builder:full",
-			"paketobuildpacks/builder:tiny",
-			"paketobuildpacks/builder:base",
 		},
 	}
 	builders[buildpacks.HerokuBuilder] = &buildpacks.BuilderInfo{
@@ -98,6 +97,12 @@ func (c *GithubGetBuildpackHandler) ServeHTTP(w http.ResponseWriter, r *http.Req
 	wg.Add(len(buildpacks.Runtimes))
 	for i := range buildpacks.Runtimes {
 		go func(idx int) {
+			defer func() {
+				if rec := recover(); rec != nil {
+					c.HandleAPIError(w, r, apierrors.NewErrInternal(fmt.Errorf("panic detected in runtime detection")))
+					return
+				}
+			}()
 			buildpacks.Runtimes[idx].Detect(
 				client, directoryContents, owner, name, request.Dir, repoContentOptions,
 				builderInfoMap[buildpacks.PaketoBuilder], builderInfoMap[buildpacks.HerokuBuilder],

+ 73 - 0
api/server/handlers/namespace/get_pod.go

@@ -0,0 +1,73 @@
+package namespace
+
+import (
+	"errors"
+	"fmt"
+	"net/http"
+
+	"github.com/porter-dev/porter/api/server/authz"
+	"github.com/porter-dev/porter/api/server/handlers"
+	"github.com/porter-dev/porter/api/server/shared"
+	"github.com/porter-dev/porter/api/server/shared/apierrors"
+	"github.com/porter-dev/porter/api/server/shared/config"
+	"github.com/porter-dev/porter/api/server/shared/requestutils"
+	"github.com/porter-dev/porter/api/types"
+	"github.com/porter-dev/porter/internal/kubernetes"
+	"github.com/porter-dev/porter/internal/models"
+)
+
+type GetPodHandler struct {
+	handlers.PorterHandlerReadWriter
+	authz.KubernetesAgentGetter
+}
+
+func NewGetPodHandler(
+	config *config.Config,
+	writer shared.ResultWriter,
+) *GetPodHandler {
+	return &GetPodHandler{
+		PorterHandlerReadWriter: handlers.NewDefaultPorterHandler(config, nil, writer),
+		KubernetesAgentGetter:   authz.NewOutOfClusterAgentGetter(config),
+	}
+}
+
+func (c *GetPodHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+	cluster, _ := r.Context().Value(types.ClusterScope).(*models.Cluster)
+
+	agent, err := c.GetAgent(r, cluster, "")
+
+	if err != nil {
+		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
+		return
+	}
+
+	name, err := requestutils.GetURLParamString(r, types.URLParamPodName)
+
+	if err != nil {
+		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
+		return
+	}
+
+	namespace, err := requestutils.GetURLParamString(r, types.URLParamNamespace)
+
+	if err != nil {
+		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
+		return
+	}
+
+	pod, err := agent.GetPodByName(name, namespace)
+
+	if errors.Is(err, kubernetes.IsNotFoundError) {
+		c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(
+			fmt.Errorf("pod %s/%s was not found", namespace, name),
+			http.StatusNotFound,
+		))
+
+		return
+	} else if err != nil {
+		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
+		return
+	}
+
+	c.WriteResult(w, r, pod)
+}

+ 14 - 3
api/server/handlers/release/get.go

@@ -52,6 +52,17 @@ func (c *ReleaseGetHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 		if release.GitActionConfig != nil {
 			res.GitActionConfig = release.GitActionConfig.ToGitActionConfigType()
 		}
+
+		if release.BuildConfig != 0 {
+			bc, err := c.Repo().BuildConfig().GetBuildConfig(release.BuildConfig)
+
+			if err != nil {
+				c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
+				return
+			}
+
+			res.BuildConfig = bc.ToBuildConfigType()
+		}
 	} else if err != gorm.ErrRecordNotFound {
 		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return
@@ -136,7 +147,7 @@ tabs:
   label: Certificates
   sections:
   - name: section_one
-    contents: 
+    contents:
     - type: heading
       label: Certificates
     - type: resource-list
@@ -156,9 +167,9 @@ tabs:
                     version: v1
                     resource: certificates
       value: |
-        .items[] | { 
+        .items[] | {
           metadata: .metadata,
-          name: "\(.spec.dnsNames | join(","))", 
+          name: "\(.spec.dnsNames | join(","))",
           label: "\(.metadata.namespace)/\(.metadata.name)",
           status: (
             ([.status.conditions[].type] | index("Ready")) as $index | (

+ 33 - 0
api/server/router/namespace.go

@@ -425,6 +425,39 @@ func getNamespaceRoutes(
 		Router:   r,
 	})
 
+	// GET /api/projects/{project_id}/clusters/{cluster_id}/namespaces/{namespace}/pods/{name} -> namespace.NewGetPodHandler
+	getPodEndpoint := factory.NewAPIEndpoint(
+		&types.APIRequestMetadata{
+			Verb:   types.APIVerbGet,
+			Method: types.HTTPVerbGet,
+			Path: &types.Path{
+				Parent: basePath,
+				RelativePath: fmt.Sprintf(
+					"%s/pods/{%s}",
+					relPath,
+					types.URLParamPodName,
+				),
+			},
+			Scopes: []types.PermissionScope{
+				types.UserScope,
+				types.ProjectScope,
+				types.ClusterScope,
+				types.NamespaceScope,
+			},
+		},
+	)
+
+	getPodHandler := namespace.NewGetPodHandler(
+		config,
+		factory.GetResultWriter(),
+	)
+
+	routes = append(routes, &Route{
+		Endpoint: getPodEndpoint,
+		Handler:  getPodHandler,
+		Router:   r,
+	})
+
 	// DELETE /api/projects/{project_id}/clusters/{cluster_id}/namespaces/{namespace}/pods/{name} -> namespace.NewDeletePodHandler
 	deletePodEndpoint := factory.NewAPIEndpoint(
 		&types.APIRequestMetadata{

+ 1 - 1
api/types/build_config.go

@@ -4,7 +4,7 @@ package types
 type BuildConfig struct {
 	Builder    string   `json:"builder"`
 	Buildpacks []string `json:"buildpacks"`
-	Config     []byte   `json:"data"`
+	Config     []byte   `json:"config"`
 }
 
 type CreateBuildConfigRequest struct {

+ 2 - 2
api/types/release.go

@@ -20,7 +20,7 @@ type PorterRelease struct {
 	LatestVersion   string           `json:"latest_version"`
 	GitActionConfig *GitActionConfig `json:"git_action_config,omitempty"`
 	ImageRepoURI    string           `json:"image_repo_uri"`
-	BuildConfig     *BuildConfig     `json:"build_config"`
+	BuildConfig     *BuildConfig     `json:"build_config,omitempty"`
 }
 
 type GetReleaseResponse Release
@@ -46,7 +46,7 @@ type CreateReleaseRequest struct {
 
 	ImageURL           string                        `json:"image_url" form:"required"`
 	GithubActionConfig *CreateGitActionConfigRequest `json:"github_action_config,omitempty"`
-	BuildConfig        *CreateBuildConfigRequest     `json:"build_config" form:"required"`
+	BuildConfig        *CreateBuildConfigRequest     `json:"build_config,omitempty"`
 }
 
 type CreateAddonRequest struct {

+ 3 - 1
cli/cmd/pack/pack.go

@@ -60,7 +60,9 @@ func (a *Agent) Build(opts *docker.BuildOpts, buildConfig *types.BuildConfig) er
 
 	if buildConfig != nil {
 		buildOpts.Builder = buildConfig.Builder
-		buildOpts.Buildpacks = buildConfig.Buildpacks
+		if len(buildConfig.Buildpacks) > 0 {
+			buildOpts.Buildpacks = buildConfig.Buildpacks
+		}
 		// FIXME: use all the config vars
 	}
 

+ 106 - 47
dashboard/src/components/events/SubEventsList.tsx

@@ -1,11 +1,11 @@
 import React, { useContext, useEffect, useMemo, useState } from "react";
 import styled from "styled-components";
-import backArrow from "assets/back_arrow.png";
 import api from "shared/api";
 import { Context } from "shared/Context";
 import SubEventCard from "./sub-events/SubEventCard";
 import Loading from "components/Loading";
 import LogBucketCard from "./sub-events/LogBucketCard";
+import useLastSeenPodStatus from "./useLastSeenPodStatus";
 
 const getReadableDate = (s: number) => {
   let ts = new Date(s);
@@ -20,8 +20,18 @@ const getReadableDate = (s: number) => {
 const SubEventsList: React.FC<{
   clearSelectedEvent: () => void;
   event: any;
-}> = ({ event, clearSelectedEvent }) => {
+  enableTopMargin?: boolean;
+}> = ({ event, clearSelectedEvent, enableTopMargin }) => {
   const { currentProject, currentCluster } = useContext(Context);
+  const {
+    status,
+    hasError: hasPodStatusErrored,
+    isLoading: isPodStatusLoading,
+  } = useLastSeenPodStatus({
+    podName: event.name,
+    namespace: event.namespace,
+    resource_type: event.resource_type,
+  });
   const [isLoading, setIsLoading] = useState(true);
   const [subEvents, setSubEvents] = useState(null);
 
@@ -104,28 +114,64 @@ const SubEventsList: React.FC<{
   }, [subEvents]);
 
   return (
-    <Timeline>
-      <ControlRow>
-        <BackButton onClick={clearSelectedEvent}>
-          <i className="material-icons">close</i>
-        </BackButton>
-        <Icon
-          status={event.event_type.toLowerCase() as any}
-          className="material-icons-outlined"
-        >
-          {event.event_type === "critical" ? "report_problem" : "info"}
-        </Icon>
-        Pod {event.name} crashed
-      </ControlRow>
-      {isLoading ? (
-        <Placeholder>
-          <Loading />
-        </Placeholder>
-      ) : sortedSubEvents?.length ? (
-        <EventsGrid>
-          <Rail />
-          {sortedSubEvents.map((subEvent: any, i: number) => {
-            if (subEvent?.event_type === "log_bucket") {
+    <>
+      <Timeline enableTopMargin={enableTopMargin}>
+        <ControlRow>
+          <BackButton onClick={clearSelectedEvent}>
+            <i className="material-icons">close</i>
+          </BackButton>
+          <Icon
+            status={event.event_type.toLowerCase() as any}
+            className="material-icons-outlined"
+          >
+            {event.event_type === "critical" ? "report_problem" : "info"}
+          </Icon>
+          <div>
+            Pod {event.name} crashed
+            {event?.resource_type?.toLowerCase() === "pod" && (
+              <StyledHelper>
+                {hasPodStatusErrored ? (
+                  "We couldn't retrieve last pod status, please try again later"
+                ) : (
+                  <>
+                    {isPodStatusLoading ? (
+                      "Loading last seen pod status"
+                    ) : (
+                      <>
+                        Last seen pod status: {status}{" "}
+                        <StatusColor
+                          status={status?.toLowerCase()}
+                        ></StatusColor>
+                      </>
+                    )}
+                  </>
+                )}
+              </StyledHelper>
+            )}
+          </div>
+        </ControlRow>
+        {isLoading ? (
+          <Placeholder>
+            <Loading />
+          </Placeholder>
+        ) : sortedSubEvents?.length ? (
+          <EventsGrid>
+            <Rail />
+            {sortedSubEvents.map((subEvent: any, i: number) => {
+              if (subEvent?.event_type === "log_bucket") {
+                return (
+                  <Wrapper>
+                    <TimelineNode>
+                      <Penumbra>
+                        <Circle />
+                      </Penumbra>
+                      {getReadableDate(subEvent.timestamp)}
+                    </TimelineNode>
+                    <LogBucketCard logEvent={subEvent} />
+                    {i === sortedSubEvents.length - 1 && <RailCover />}
+                  </Wrapper>
+                );
+              }
               return (
                 <Wrapper>
                   <TimelineNode>
@@ -134,37 +180,31 @@ const SubEventsList: React.FC<{
                     </Penumbra>
                     {getReadableDate(subEvent.timestamp)}
                   </TimelineNode>
-                  <LogBucketCard logEvent={subEvent} />
+                  <SubEventCard subEvent={subEvent} />
                   {i === sortedSubEvents.length - 1 && <RailCover />}
                 </Wrapper>
               );
-            }
-            return (
-              <Wrapper>
-                <TimelineNode>
-                  <Penumbra>
-                    <Circle />
-                  </Penumbra>
-                  {getReadableDate(subEvent.timestamp)}
-                </TimelineNode>
-                <SubEventCard subEvent={subEvent} />
-                {i === sortedSubEvents.length - 1 && <RailCover />}
-              </Wrapper>
-            );
-          })}
-        </EventsGrid>
-      ) : (
-        <Placeholder>
-          <i className="material-icons">search</i>
-          No sub-events were found.
-        </Placeholder>
-      )}
-    </Timeline>
+            })}
+          </EventsGrid>
+        ) : (
+          <Placeholder>
+            <i className="material-icons">search</i>
+            No sub-events were found.
+          </Placeholder>
+        )}
+      </Timeline>
+    </>
   );
 };
 
 export default SubEventsList;
 
+const StyledHelper = styled.div`
+  color: #aaaabb;
+  line-height: 1.6em;
+  font-size: 13px;
+`;
+
 const Placeholder = styled.div`
   padding: 30px;
   padding-bottom: 40px;
@@ -240,6 +280,8 @@ const Rail = styled.div`
 `;
 
 const Timeline = styled.div`
+  margin-top: ${(props: { enableTopMargin: boolean }) =>
+    props.enableTopMargin ? "30px" : "unset"};
   animation: floatIn 0.3s;
   animation-timing-function: ease-out;
   animation-fill-mode: forwards;
@@ -299,3 +341,20 @@ const EventsGrid = styled.div`
   position: relative;
   padding-top: 9px;
 `;
+
+const StatusColor = styled.div`
+  display: inline-block;
+  margin-right: 7px;
+  width: 7px;
+  min-width: 7px;
+  height: 7px;
+  background: ${(props: { status: string }) =>
+    props.status === "running"
+      ? "#4797ff"
+      : props.status === "failed" || props.status === "deleted"
+      ? "#ed5f85"
+      : props.status === "completed"
+      ? "#00d12a"
+      : "#f5cb42"};
+  border-radius: 20px;
+`;

+ 1 - 1
dashboard/src/components/events/sub-events/SubEventCard.tsx

@@ -35,7 +35,7 @@ const StyledCard = styled.div<{ status: string }>`
   padding: 14px;
   padding-left: 13px;
   overflow: hidden;
-  height: 55px;
+  min-height: 55px;
   font-size: 13px;
   color: #aaaabb;
   animation: fadeIn 0.5s;

+ 89 - 0
dashboard/src/components/events/useLastSeenPodStatus.ts

@@ -0,0 +1,89 @@
+import { useContext, useEffect, useState } from "react";
+import api from "shared/api";
+import { Context } from "shared/Context";
+
+const useLastSeenPodStatus = ({
+  podName,
+  namespace,
+  resource_type,
+}: {
+  podName: string;
+  namespace: string;
+  resource_type: string;
+}) => {
+  const [status, setCurrentStatus] = useState(null);
+  const [isLoading, setIsLoading] = useState(true);
+  const [hasError, setHasError] = useState(false);
+  const { currentProject, currentCluster } = useContext(Context);
+
+  const getPodStatus = (status: any) => {
+    if (
+      status?.phase === "Pending" &&
+      status?.containerStatuses !== undefined
+    ) {
+      return status.containerStatuses[0].state.waiting.reason;
+    } else if (status?.phase === "Pending") {
+      return "Pending";
+    }
+
+    if (status?.phase === "Failed") {
+      return "failed";
+    }
+
+    if (status?.phase === "Running") {
+      let collatedStatus = "running";
+
+      status?.containerStatuses?.forEach((s: any) => {
+        if (s.state?.waiting) {
+          collatedStatus =
+            s.state?.waiting.reason === "CrashLoopBackOff"
+              ? "failed"
+              : "waiting";
+        } else if (s.state?.terminated) {
+          collatedStatus = "failed";
+        }
+      });
+      return collatedStatus;
+    }
+  };
+
+  const updatePods = async () => {
+    try {
+      const res = await api.getPodByName(
+        "<token>",
+        {},
+        {
+          project_id: currentProject.id,
+          cluster_id: currentCluster.id,
+          namespace: "default",
+          name: podName,
+        }
+      );
+      console.log(getPodStatus(res.data.status));
+
+      setCurrentStatus(getPodStatus(res.data.status));
+    } catch (error) {
+      if (error?.response?.status === 404) {
+        setCurrentStatus("Deleted");
+      } else {
+        setHasError(true);
+      }
+    } finally {
+      setIsLoading(false);
+    }
+  };
+
+  useEffect(() => {
+    if (resource_type?.toLowerCase() === "pod") {
+      updatePods();
+    }
+  }, [podName, namespace, resource_type]);
+
+  return {
+    status,
+    isLoading,
+    hasError,
+  };
+};
+
+export default useLastSeenPodStatus;

+ 1 - 0
dashboard/src/main/home/cluster-dashboard/dashboard/events/EventsTab.tsx

@@ -54,6 +54,7 @@ const EventsTab = () => {
       <SubEventsList
         event={currentEvent}
         clearSelectedEvent={() => setCurrentEvent(null)}
+        enableTopMargin
       />
     );
   }

+ 3 - 1
dashboard/src/main/home/cluster-dashboard/expanded-chart/status/StatusSection.tsx

@@ -65,7 +65,9 @@ const StatusSectionFC: React.FunctionComponent<Props> = ({
         setControllers([]);
         setIsLoading(false);
       });
-    return () => (isSubscribed = false);
+    return () => {
+      isSubscribed = false;
+    };
   }, [currentProject, currentCluster, setCurrentError, currentChart]);
 
   const renderLogs = () => {

+ 15 - 0
dashboard/src/shared/api.tsx

@@ -636,6 +636,20 @@ const getJobPods = baseApi<
   return `/api/projects/${id}/clusters/${cluster_id}/namespaces/${namespace}/jobs/${name}/pods`;
 });
 
+const getPodByName = baseApi<
+  {},
+  {
+    project_id: number;
+    cluster_id: number;
+    namespace: string;
+    name: string;
+  }
+>(
+  "GET",
+  ({ project_id, cluster_id, namespace, name }) =>
+    `/api/projects/${project_id}/clusters/${cluster_id}/namespaces/${namespace}/pods/${name}`
+);
+
 const getMatchingPods = baseApi<
   {
     namespace: string;
@@ -1255,6 +1269,7 @@ export default {
   getJobs,
   getJobStatus,
   getJobPods,
+  getPodByName,
   getMatchingPods,
   getMetrics,
   getNamespaces,

+ 1 - 10
internal/integrations/buildpacks/go.go

@@ -1,7 +1,6 @@
 package buildpacks
 
 import (
-	"fmt"
 	"sync"
 
 	"github.com/google/go-github/github"
@@ -24,6 +23,7 @@ func (runtime *goRuntime) detectMod(results chan struct {
 		name := directoryContent[i].GetName()
 		if name == "go.mod" {
 			goModFound = true
+			break
 		}
 	}
 	if goModFound {
@@ -73,11 +73,8 @@ func (runtime *goRuntime) Detect(
 		bool
 	}, 2)
 
-	fmt.Printf("Starting detection for a Go runtime for %s/%s\n", owner, name)
 	runtime.wg.Add(2)
-	fmt.Println("Checking for go-mod")
 	go runtime.detectMod(results, directoryContent)
-	fmt.Println("Checking for dep")
 	go runtime.detectDep(results, directoryContent)
 	runtime.wg.Wait()
 	close(results)
@@ -92,17 +89,11 @@ func (runtime *goRuntime) Detect(
 	}
 
 	if len(results) == 0 {
-		fmt.Printf("No Go runtime detected for %s/%s\n", owner, name)
 		paketo.Others = append(paketo.Others, paketoBuildpackInfo)
 		heroku.Others = append(heroku.Others, herokuBuildpackInfo)
 		return nil
 	}
 
-	detected := make(map[string]bool)
-	for result := range results {
-		detected[result.string] = result.bool
-	}
-
 	paketo.Detected = append(paketo.Detected, paketoBuildpackInfo)
 	heroku.Detected = append(heroku.Detected, herokuBuildpackInfo)
 

+ 3 - 7
internal/integrations/buildpacks/nodejs.go

@@ -167,15 +167,11 @@ func (runtime *nodejsRuntime) Detect(
 	results := make(chan struct {
 		string
 		bool
-	})
+	}, 3)
 
-	fmt.Printf("Starting detection for a NodeJS runtime for %s/%s\n", owner, name)
 	runtime.wg.Add(3)
-	fmt.Println("Checking for yarn")
 	go runtime.detectYarn(results, directoryContent)
-	fmt.Println("Checking for NPM")
 	go runtime.detectNPM(results, directoryContent)
-	fmt.Println("Checking for NodeJS standalone")
 	go runtime.detectStandalone(results, directoryContent)
 	runtime.wg.Wait()
 	close(results)
@@ -190,7 +186,6 @@ func (runtime *nodejsRuntime) Detect(
 	}
 
 	if len(results) == 0 {
-		fmt.Printf("No NodeJS runtime detected for %s/%s\n", owner, name)
 		paketo.Others = append(paketo.Others, paketoBuildpackInfo)
 		heroku.Others = append(heroku.Others, herokuBuildpackInfo)
 		return nil
@@ -211,7 +206,6 @@ func (runtime *nodejsRuntime) Detect(
 
 	if foundYarn || foundNPM {
 		// it is safe to assume that the project contains a package.json
-		fmt.Println("package.json file detected")
 		fileContent, _, _, err := client.Repositories.GetContents(
 			context.Background(),
 			owner,
@@ -327,10 +321,12 @@ func (runtime *nodejsRuntime) Detect(
 			packageJSON.Engines.Node = "16.*.*"
 		}
 
+		paketoBuildpackInfo.Config = make(map[string]interface{})
 		paketoBuildpackInfo.Config["scripts"] = packageJSON.Scripts
 		paketoBuildpackInfo.Config["node_engine"] = packageJSON.Engines.Node
 		paketo.Detected = append(paketo.Detected, paketoBuildpackInfo)
 
+		herokuBuildpackInfo.Config = make(map[string]interface{})
 		herokuBuildpackInfo.Config["scripts"] = packageJSON.Scripts
 		herokuBuildpackInfo.Config["node_engine"] = packageJSON.Engines.Node
 		heroku.Detected = append(heroku.Detected, herokuBuildpackInfo)

+ 1 - 8
internal/integrations/buildpacks/python.go

@@ -1,7 +1,6 @@
 package buildpacks
 
 import (
-	"fmt"
 	"strings"
 	"sync"
 
@@ -118,17 +117,12 @@ func (runtime *pythonRuntime) Detect(
 	results := make(chan struct {
 		string
 		bool
-	})
+	}, 4)
 
-	fmt.Printf("Starting detection for a Python runtime for %s/%s\n", owner, name)
 	runtime.wg.Add(4)
-	fmt.Println("Checking for pipenv")
 	go runtime.detectPipenv(results, directoryContent)
-	fmt.Println("Checking for pip")
 	go runtime.detectPip(results, directoryContent)
-	fmt.Println("Checking for conda")
 	go runtime.detectConda(results, directoryContent)
-	fmt.Println("Checking for Python standalone")
 	go runtime.detectStandalone(results, directoryContent)
 	runtime.wg.Wait()
 	close(results)
@@ -143,7 +137,6 @@ func (runtime *pythonRuntime) Detect(
 	}
 
 	if len(results) == 0 {
-		fmt.Printf("No Python runtime detected for %s/%s\n", owner, name)
 		paketo.Others = append(paketo.Others, paketoBuildpackInfo)
 		heroku.Others = append(heroku.Others, herokuBuildpackInfo)
 		return nil

+ 1 - 12
internal/integrations/buildpacks/ruby.go

@@ -125,13 +125,11 @@ func (runtime *rubyRuntime) detectRackup(
 	fileContent, _, _, err := client.Repositories.GetContents(context.Background(),
 		owner, name, "Gemfile.lock", &repoContentOptions)
 	if err != nil {
-		fmt.Printf("Error fetching contents of Gemfile.lock for %s/%s: %v\n", owner, name, err)
 		runtime.wg.Done()
 		return
 	}
 	gemfileLockContent, err := fileContent.GetContent()
 	if err != nil {
-		fmt.Printf("Error calling GetContent() on Gemfile.lock for %s/%s: %v\n", owner, name, err)
 		runtime.wg.Done()
 		return
 	}
@@ -215,7 +213,6 @@ func (runtime *rubyRuntime) Detect(
 	}
 
 	if !gemfileFound {
-		fmt.Printf("No Ruby runtime detected for %s/%s\n", owner, name)
 		paketo.Others = append(paketo.Others, paketoBuildpackInfo)
 		heroku.Others = append(heroku.Others, herokuBuildpackInfo)
 		return nil
@@ -249,18 +246,14 @@ func (runtime *rubyRuntime) Detect(
 	results := make(chan struct {
 		string
 		bool
-	})
+	}, count)
 
-	fmt.Printf("Starting detection for a Ruby runtime for %s/%s\n", owner, name)
 	runtime.wg.Add(count)
-	fmt.Println("Checking for puma")
 	go runtime.detectPuma(gemfileContent, results)
-	fmt.Println("Checking for thin")
 	go runtime.detectThin(gemfileContent, results)
 	if configRuFound {
 		{
 			// FIXME: find a better, more readable way of doing this
-			fmt.Printf("Ruby rackup runtime detected for %s/%s\n", owner, name)
 			results <- struct {
 				string
 				bool
@@ -268,17 +261,13 @@ func (runtime *rubyRuntime) Detect(
 			runtime.wg.Done()
 		}
 
-		fmt.Println("Checking for unicorn")
 		go runtime.detectUnicorn(gemfileContent, results)
 	}
-	fmt.Println("Checking for passenger")
 	go runtime.detectPassenger(gemfileContent, results)
 	if !configRuFound && gemfileLockFound {
-		fmt.Println("Checking for rackup")
 		go runtime.detectRackup(client, owner, name, repoContentOptions, results)
 	}
 	if rakefileFound {
-		fmt.Println("Checking for rake")
 		go runtime.detectRake(gemfileContent, results)
 	}
 	runtime.wg.Wait()

+ 20 - 0
internal/kubernetes/agent.go

@@ -518,6 +518,26 @@ func (a *Agent) GetPodsByLabel(selector string, namespace string) (*v1.PodList,
 	)
 }
 
+// GetPodByName retrieves a single instance of pod with given name
+func (a *Agent) GetPodByName(name string, namespace string) (*v1.Pod, error) {
+	// Get pod by name
+	pod, err := a.Clientset.CoreV1().Pods(namespace).Get(
+		context.TODO(),
+		name,
+		metav1.GetOptions{},
+	)
+
+	if err != nil && errors.IsNotFound(err) {
+		return nil, IsNotFoundError
+	}
+
+	if err != nil {
+		return nil, err
+	}
+
+	return pod, nil
+}
+
 // DeletePod deletes a pod by name and namespace
 func (a *Agent) DeletePod(namespace string, name string) error {
 	err := a.Clientset.CoreV1().Pods(namespace).Delete(

+ 1 - 1
internal/models/build_config.go

@@ -11,7 +11,7 @@ type BuildConfig struct {
 	gorm.Model
 
 	Name       string `json:"name"`
-	Builder    string `json:"runtime"`
+	Builder    string `json:"builder"`
 	Buildpacks string `json:"buildpacks"`
 	Config     []byte `json:"config"`
 }

+ 1 - 0
internal/repository/build_config.go

@@ -6,4 +6,5 @@ import "github.com/porter-dev/porter/internal/models"
 type BuildConfigRepository interface {
 	CreateBuildConfig(*models.BuildConfig) (*models.BuildConfig, error)
 	UpdateBuildConfig(*models.BuildConfig) (*models.BuildConfig, error)
+	GetBuildConfig(uint) (*models.BuildConfig, error)
 }

+ 11 - 0
internal/repository/gorm/build_config.go

@@ -35,3 +35,14 @@ func (repo *BuildConfigRepository) UpdateBuildConfig(bc *models.BuildConfig) (*m
 
 	return bc, nil
 }
+
+// GetBuildConfig returns a BuildConfig with the specified id
+func (repo *BuildConfigRepository) GetBuildConfig(id uint) (*models.BuildConfig, error) {
+	bc := &models.BuildConfig{}
+
+	if err := repo.db.First(bc, id).Error; err != nil {
+		return nil, err
+	}
+
+	return bc, nil
+}

+ 17 - 4
internal/repository/test/build_config.go

@@ -17,19 +17,32 @@ func NewBuildConfigRepository(canQuery bool) repository.BuildConfigRepository {
 }
 
 func (repo *BuildConfigRepository) CreateBuildConfig(
-	a *models.BuildConfig,
+	bc *models.BuildConfig,
 ) (*models.BuildConfig, error) {
 	if !repo.canQuery {
 		return nil, errors.New("cannot write database")
 	}
 
-	repo.buildConfigs = append(repo.buildConfigs, a)
-	a.ID = uint(len(repo.buildConfigs))
+	repo.buildConfigs = append(repo.buildConfigs, bc)
+	bc.ID = uint(len(repo.buildConfigs))
 
-	return a, nil
+	return bc, nil
 }
 
 func (repo *BuildConfigRepository) UpdateBuildConfig(bc *models.BuildConfig) (*models.BuildConfig, error) {
+	if !repo.canQuery {
+		return nil, errors.New("cannot write database")
+	}
+
 	// TODO
 	return bc, nil
 }
+
+func (repo *BuildConfigRepository) GetBuildConfig(id uint) (*models.BuildConfig, error) {
+	if !repo.canQuery {
+		return nil, errors.New("cannot write database")
+	}
+
+	// TODO
+	return nil, nil
+}